Step1Basic.backup.tsx 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. import * as React from 'react';
  2. import { Form, Input, Select, Cascader, Tag, InputNumber, ColorPicker, Button, Space, message } from 'antd';
  3. import { PlusCircleOutlined, MinusCircleOutlined, CloseCircleOutlined, LinkOutlined } from '@ant-design/icons';
  4. import * as AllIcons from '@ant-design/icons';
  5. import IconPicker from './IconPicker';
  6. import VipSelector from './VipSelector';
  7. import './style.less';
  8. interface Step1BasicProps {
  9. form: any;
  10. appTypeList: any[];
  11. appVisibleList: any[];
  12. appProjectList: any[];
  13. isAppPro: boolean;
  14. visibleFlag: string | number;
  15. vipList: any[];
  16. userInfo: any;
  17. onAppChange: (typeId: number) => void;
  18. onVisibleChange: (value: any) => void;
  19. onRemoveVip: (userId: string) => void;
  20. onVipConfirm: (users: any[]) => void;
  21. onNext: () => void;
  22. onBack: () => void;
  23. }
  24. interface InputItem {
  25. id: number;
  26. value: string;
  27. }
  28. const Step1Basic: React.FC<Step1BasicProps> = (props) => {
  29. const {
  30. form,
  31. appTypeList,
  32. appVisibleList,
  33. appProjectList,
  34. isAppPro,
  35. visibleFlag,
  36. vipList,
  37. userInfo,
  38. onAppChange,
  39. onVisibleChange,
  40. onAddVip,
  41. onRemoveVip,
  42. onNext,
  43. onBack,
  44. } = props;
  45. const [inputs, setInputs] = React.useState<InputItem[]>([{ id: 1, value: '' }]);
  46. const [iconPickerVisible, setIconPickerVisible] = React.useState(false);
  47. const [vipSelectorVisible, setVipSelectorVisible] = React.useState(false);
  48. const [selectedIcon, setSelectedIcon] = React.useState<string | null>(null);
  49. const [previewBg, setPreviewBg] = React.useState<string>('#1D4ED8');
  50. const getContrastColor = (hex: string) => {
  51. const c = hex.replace('#', '');
  52. const r = parseInt(c.substring(0, 2), 16);
  53. const g = parseInt(c.substring(2, 4), 16);
  54. const b = parseInt(c.substring(4, 6), 16);
  55. const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
  56. return luminance > 0.6 ? '#000' : '#fff';
  57. };
  58. const presetColors = ['#1677ff', '#52c41a', '#fa8c16', '#f5222d', '#722ed1', '#ffffff', '#f0f0f0'];
  59. const presetItems = [{ label: '', colors: presetColors }];
  60. const addInput = () => {
  61. const newId = inputs.length + 1;
  62. setInputs([...inputs, { id: newId, value: '' }]);
  63. };
  64. const delInput = (id: number) => {
  65. if (inputs.length <= 1) {
  66. message.warning("至少保留 1 个预设问题");
  67. return;
  68. }
  69. setInputs(inputs.filter(input => input.id !== id));
  70. };
  71. const handleChange = (id: number, value: string) => {
  72. setInputs(inputs.map(input => (input.id === id ? { ...input, value } : input)));
  73. form.setFieldValue('questionList', inputs.map(input => input.value));
  74. };
  75. const handleNext = () => {
  76. form.validateFields(['name', 'desc', 'appProId', 'iconType']).then((values) => {
  77. form.setFieldValue('questionList', inputs.map(input => input.value));
  78. onNext();
  79. }).catch((error) => {
  80. console.error(error);
  81. });
  82. };
  83. return (
  84. <div className='create-step1'>
  85. <Form.Item
  86. label='请选择应用图标'
  87. tooltip='用于在应用广场展示'
  88. name='iconType'
  89. rules={[{ required: true, message: '请选择图标' }]}
  90. >
  91. <div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
  92. <div style={{ width: 84, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
  93. <div style={{
  94. width: 64,
  95. height: 64,
  96. borderRadius: 8,
  97. display: 'flex',
  98. alignItems: 'center',
  99. justifyContent: 'center',
  100. background: previewBg,
  101. border: '1px solid #e8e8e8'
  102. }}>
  103. {selectedIcon ? (() => {
  104. const C = (AllIcons as any)[selectedIcon];
  105. const iconColor = getContrastColor(previewBg);
  106. return C ? <C style={{ fontSize: 28, color: iconColor }} /> : <span style={{ fontSize: 12 }}>{selectedIcon}</span>;
  107. })() : <span style={{ color: '#999', fontSize: 12 }}>预览</span>}
  108. </div>
  109. </div>
  110. <div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
  111. <a onClick={() => setIconPickerVisible(true)} style={{ fontSize: 13, color: '#1677ff', cursor: 'pointer' }}>
  112. 选择图标
  113. </a>
  114. <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
  115. <div style={{ fontSize: 12, color: '#666' }}>背景色:</div>
  116. <ColorPicker
  117. presets={presetItems}
  118. value={previewBg}
  119. onChange={(color) => {
  120. const hex = color.toHexString?.() || color?.toString?.() || previewBg;
  121. setPreviewBg(hex);
  122. form.setFieldValue('iconColor', hex);
  123. }}
  124. />
  125. </div>
  126. </div>
  127. </div>
  128. </Form.Item>
  129. <Form.Item
  130. label='问答应用名称'
  131. tooltip='尽量概括应用的主要功能'
  132. name='name'
  133. rules={[{ required: true, message: '问答应用名称不能为空' }]}
  134. >
  135. <Input placeholder="请输入问答应用名称" className='form-input' />
  136. </Form.Item>
  137. <Form.Item
  138. label='应用类型'
  139. tooltip='应用的实际分类'
  140. name='typeId'
  141. >
  142. <Select
  143. className='form-input'
  144. placeholder='请选择问答应用类型'
  145. onChange={onAppChange}
  146. allowClear
  147. >
  148. {appTypeList.map((item) => (
  149. <Select.Option key={item.value} value={item.value}>
  150. {item.label}
  151. </Select.Option>
  152. ))}
  153. </Select>
  154. </Form.Item>
  155. {isAppPro && (
  156. <Form.Item
  157. label='项目'
  158. tooltip='应用所属项目'
  159. name='appProId'
  160. rules={[{ required: true, message: '项目不能为空' }]}
  161. >
  162. <Cascader
  163. options={appProjectList}
  164. placeholder="请选择项目"
  165. showSearch
  166. className='form-input'
  167. />
  168. </Form.Item>
  169. )}
  170. <Form.Item
  171. label='是否公开'
  172. tooltip='公开应用后,所有用户均可使用该应用,私有应用仅限自己和指定用户使用'
  173. name='visible'
  174. >
  175. <Select
  176. className='form-input'
  177. placeholder='请选择是否公开'
  178. allowClear
  179. onChange={(value) => {
  180. onVisibleChange(value);
  181. }}
  182. >
  183. {appVisibleList.map((item) => (
  184. <Select.Option key={item.value} value={item.value}>
  185. {item.label}
  186. </Select.Option>
  187. ))}
  188. </Select>
  189. </Form.Item>
  190. {userInfo?.tenantId === '000000' && (visibleFlag === '0' || visibleFlag === 0) && (
  191. <Form.Item
  192. label='集团公开'
  193. tooltip='集团下所有用户均可使用该应用'
  194. name='groupVisible'
  195. layout='horizontal'
  196. valuePropName='checked'
  197. >
  198. <Select />
  199. </Form.Item>
  200. )}
  201. <Form.Item
  202. label='显示顺序'
  203. name='sort'
  204. tooltip='用于应用广场的显示顺序'
  205. >
  206. <InputNumber placeholder="请输入显示顺序" className='form-input' style={{ height: '36px' }} />
  207. </Form.Item>
  208. {(visibleFlag === '1' || visibleFlag === 1) && (
  209. <Form.Item
  210. label='指定用户'
  211. tooltip='私有应用的指定用户'
  212. >
  213. <div className='tags-info'>
  214. <div className='tags-list'>
  215. {vipList.map((item: any) => (
  216. <Tag
  217. key={item.userId}
  218. color="blue"
  219. closable
  220. onClose={(e) => {
  221. e?.preventDefault();
  222. onRemoveVip(item.userId);
  223. }}
  224. >
  225. {item.userName}
  226. </Tag>
  227. ))}
  228. </div>
  229. <Space>
  230. {vipList.length > 0 && (
  231. <CloseCircleOutlined
  232. className='cup'
  233. onClick={() => onRemoveVip('all')}
  234. />
  235. )}
  236. <Button type="primary" variant="outlined" onClick={() => setVipSelectorVisible(true)}>
  237. 选择
  238. </Button>
  239. </Space>
  240. </div>
  241. </Form.Item>
  242. )}
  243. {/* IconPicker 弹窗 */}
  244. <IconPicker
  245. open={iconPickerVisible}
  246. onClose={() => setIconPickerVisible(false)}
  247. onSelect={(iconName) => {
  248. setSelectedIcon(iconName);
  249. form.setFieldValue('iconType', iconName);
  250. }}
  251. value={selectedIcon}
  252. />
  253. {/* VIP 用户选择弹窗 */}
  254. <VipSelector
  255. open={vipSelectorVisible}
  256. onClose={() => setVipSelectorVisible(false)}
  257. onConfirm={(users) => {
  258. props.onVipConfirm(users);
  259. setVipSelectorVisible(false);
  260. }}
  261. existingUsers={vipList}
  262. />
  263. <Form.Item
  264. label='问答应用描述'
  265. tooltip='对当前应用功能的描述使用户更了解应用的使用范围'
  266. name='desc'
  267. rules={[{ required: true, message: '问答应用描述不能为空' }]}
  268. >
  269. <Input.TextArea
  270. showCount
  271. maxLength={500}
  272. placeholder="请输入当前应用的描述"
  273. className='form-textarea'
  274. />
  275. </Form.Item>
  276. <div className='preset-questions'>
  277. <h4>添加引导问题</h4>
  278. <div>
  279. {inputs.map(input => (
  280. <div key={input.id} className='question-item'>
  281. <label>引导问题 {input.id}</label>
  282. <Input
  283. className='question-input'
  284. type="text"
  285. value={input.value}
  286. onChange={e => handleChange(input.id, e.target.value)}
  287. />
  288. <div className='question-actions'>
  289. <PlusCircleOutlined className='question-icon' onClick={addInput} />
  290. <MinusCircleOutlined className='question-icon' onClick={() => delInput(input.id)} />
  291. </div>
  292. </div>
  293. ))}
  294. </div>
  295. </div>
  296. <div className='step-actions'>
  297. <Button onClick={onBack}>
  298. 返回
  299. </Button>
  300. <Button type='primary' onClick={handleNext}>
  301. 下一步
  302. </Button>
  303. </div>
  304. </div>
  305. );
  306. };
  307. export default Step1Basic;