index.tsx 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  1. import { Modal } from 'antd';
  2. import type { FirmwareItem } from '@/pages/device/Firmware/typings';
  3. import { createSchemaField } from '@formily/react';
  4. import { Form, FormGrid, FormItem, Input, Select, ArrayTable, NumberPicker } from '@formily/antd';
  5. import type { Field } from '@formily/core';
  6. import { onFieldValueChange, onFormInit } from '@formily/core';
  7. import { createForm } from '@formily/core';
  8. import type { ISchema } from '@formily/json-schema';
  9. import FUpload from '@/components/FUpload';
  10. import { action } from '@formily/reactive';
  11. import { service } from '@/pages/device/Firmware';
  12. import { useRef } from 'react';
  13. import type { ProductItem } from '@/pages/device/Product/typings';
  14. import { onlyMessage } from '@/utils/util';
  15. import RemoveData from './RemoveData';
  16. import _ from 'lodash';
  17. interface Props {
  18. data?: FirmwareItem;
  19. close: () => void;
  20. visible: boolean;
  21. }
  22. const Save = (props: Props) => {
  23. const { data, close, visible } = props;
  24. const fileInfo = useRef<any>({});
  25. const signMethod = useRef<'md5' | 'sha256'>('md5');
  26. const form = createForm({
  27. validateFirst: true,
  28. initialValues: data,
  29. effects: () => {
  30. onFormInit(async (form1) => {
  31. if (!data?.id) return;
  32. form1.setInitialValues({ ...data, upload: { url: data?.url } });
  33. });
  34. onFieldValueChange('signMethod', (field) => {
  35. const value = (field as Field).value;
  36. signMethod.current = value;
  37. });
  38. onFieldValueChange('upload', (field) => {
  39. const value = (field as Field).value;
  40. fileInfo.current = value;
  41. });
  42. onFieldValueChange('productId', (field, form1) => {
  43. if (field.modified) {
  44. form1.setFieldState('versionOrder', (state) => {
  45. state.value = undefined;
  46. });
  47. }
  48. });
  49. onFieldValueChange('versionOrder', async (field, f1) => {
  50. const value = (field as Field).value;
  51. const productId = (field.query('.productId').take() as Field).value;
  52. if (field.modified && productId && value) {
  53. const resp = await service.validateVersion(productId, value);
  54. if (resp.status === 200) {
  55. f1.setFieldState('versionOrder', (state) => {
  56. state.selfErrors = resp.result ? ['版本序号已存在'] : undefined;
  57. });
  58. }
  59. }
  60. });
  61. },
  62. });
  63. const products = useRef<ProductItem[]>([]);
  64. const useAsyncDataSource = (services: (arg0: Field) => Promise<any>) => (field: Field) => {
  65. field.loading = true;
  66. services(field).then(
  67. action.bound!((list: any) => {
  68. const _data = list.result.filter((it: any) => {
  69. return _.map(it?.features || [], 'id').includes('supportFirmware');
  70. });
  71. field.dataSource = _data.map((item: any) => ({ label: item.name, value: item.id }));
  72. products.current = list.result;
  73. field.loading = false;
  74. }),
  75. );
  76. };
  77. const loadData = async () =>
  78. service.queryProduct({
  79. paging: false,
  80. terms: [
  81. {
  82. column: 'state',
  83. value: 1,
  84. },
  85. ],
  86. sorts: [{ name: 'createTime', order: 'desc' }],
  87. });
  88. const SchemaField = createSchemaField({
  89. components: {
  90. FormItem,
  91. FormGrid,
  92. Input,
  93. FUpload,
  94. Select,
  95. ArrayTable,
  96. NumberPicker,
  97. RemoveData,
  98. },
  99. });
  100. const save = async () => {
  101. const values: any = await form.submit();
  102. const product = products.current?.find((item) => item.id === values.productId);
  103. values.productName = product?.name || '';
  104. const { upload, ...extra } = values;
  105. const params = {
  106. ...extra,
  107. url: upload.url || data?.url,
  108. size: upload.length || data?.size,
  109. };
  110. const resp = (await service.update(params)) as any;
  111. if (resp.status === 200) {
  112. onlyMessage('保存成功!');
  113. close();
  114. }
  115. };
  116. const schema: ISchema = {
  117. type: 'object',
  118. properties: {
  119. grid: {
  120. type: 'void',
  121. 'x-component': 'FormGrid',
  122. 'x-component-props': {
  123. minColumns: 2,
  124. maxColumns: 2,
  125. },
  126. properties: {
  127. name: {
  128. title: '名称',
  129. 'x-decorator': 'FormItem',
  130. 'x-component': 'Input',
  131. 'x-component-props': {
  132. placeholder: '请输入固件名称',
  133. },
  134. required: true,
  135. 'x-decorator-props': {
  136. gridSpan: 2,
  137. },
  138. 'x-validator': [
  139. {
  140. required: true,
  141. message: '请输入固件名称',
  142. },
  143. {
  144. max: 64,
  145. message: '最多可输入64个字符',
  146. },
  147. ],
  148. },
  149. productId: {
  150. title: '所属产品',
  151. 'x-decorator': 'FormItem',
  152. 'x-component': 'Select',
  153. 'x-reactions': ['{{useAsyncDataSource(loadData)}}'],
  154. 'x-decorator-props': {
  155. gridSpan: 2,
  156. },
  157. required: true,
  158. 'x-validator': [
  159. {
  160. required: true,
  161. message: '请选择所属产品',
  162. },
  163. ],
  164. 'x-component-props': {
  165. placeholder: '请选择所属产品',
  166. showSearch: true,
  167. allowClear: true,
  168. filterOption: (input: string, option: any) =>
  169. option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0,
  170. },
  171. },
  172. version: {
  173. title: '版本号',
  174. 'x-decorator': 'FormItem',
  175. 'x-component': 'Input',
  176. 'x-component-props': {
  177. placeholder: '请输入版本号',
  178. },
  179. 'x-decorator-props': {
  180. gridSpan: 1,
  181. },
  182. required: true,
  183. 'x-validator': [
  184. {
  185. required: true,
  186. message: '请输入版本号',
  187. },
  188. {
  189. max: 64,
  190. message: '最多可输入64个字符',
  191. },
  192. ],
  193. },
  194. versionOrder: {
  195. title: '版本序号',
  196. 'x-decorator': 'FormItem',
  197. 'x-component': 'NumberPicker',
  198. 'x-component-props': {
  199. placeholder: '请输入版本序号',
  200. },
  201. 'x-decorator-props': {
  202. gridSpan: 1,
  203. },
  204. required: true,
  205. 'x-validator': [
  206. {
  207. required: true,
  208. message: '请输入版本号',
  209. },
  210. {
  211. maximum: 99999,
  212. minimum: 1,
  213. },
  214. ],
  215. },
  216. signMethod: {
  217. title: '签名方式',
  218. 'x-decorator': 'FormItem',
  219. 'x-component': 'Select',
  220. enum: [
  221. { label: 'MD5', value: 'md5' },
  222. { label: 'SHA256', value: 'sha256' },
  223. ],
  224. 'x-component-props': {
  225. placeholder: '请选择签名方式',
  226. },
  227. 'x-decorator-props': {
  228. gridSpan: 1,
  229. },
  230. required: true,
  231. 'x-validator': [
  232. {
  233. required: true,
  234. message: '请选择签名方式',
  235. },
  236. ],
  237. },
  238. sign: {
  239. title: '签名',
  240. 'x-decorator': 'FormItem',
  241. 'x-component': 'Input',
  242. 'x-component-props': {
  243. placeholder: '请输入签名',
  244. },
  245. 'x-decorator-props': {
  246. tooltip: '请输入本地文件进行签名加密后的值',
  247. gridSpan: 1,
  248. },
  249. required: true,
  250. 'x-validator': [
  251. {
  252. required: true,
  253. message: '请输入签名',
  254. },
  255. // {
  256. // validator: (value: string) => {
  257. // return new Promise((resolve, reject) => {
  258. // if (value !== '' && signMethod.current && fileInfo.current[signMethod.current]) {
  259. // if (value !== fileInfo.current[signMethod.current]) {
  260. // return reject(new Error('签名不一致,请检查文件是否上传正确'));
  261. // }
  262. // }
  263. // return resolve('');
  264. // });
  265. // },
  266. // },
  267. ],
  268. 'x-reactions': [
  269. {
  270. dependencies: ['.upload', 'signMethod'],
  271. fulfill: {
  272. state: {
  273. selfErrors:
  274. '{{$deps[0] && $deps[1] && $deps[0][$deps[1]] && $self.value && $self.value !== $deps[0][$deps[1]] ? "签名不一致,请检查文件是否上传正确" : ""}}',
  275. },
  276. },
  277. },
  278. ],
  279. },
  280. upload: {
  281. title: '固件上传',
  282. 'x-decorator': 'FormItem',
  283. 'x-component': 'FUpload',
  284. 'x-component-props': {
  285. type: 'file',
  286. placeholder: '请上传文件',
  287. },
  288. 'x-decorator-props': {
  289. gridSpan: 2,
  290. },
  291. required: true,
  292. 'x-validator': [
  293. {
  294. required: true,
  295. message: '请上传文件',
  296. },
  297. ],
  298. },
  299. properties: {
  300. type: 'array',
  301. 'x-decorator': 'FormItem',
  302. 'x-component': 'ArrayTable',
  303. title: '其他配置',
  304. 'x-component-props': {
  305. pagination: { pageSize: 10 },
  306. scroll: { x: '100%' },
  307. },
  308. 'x-decorator-props': {
  309. gridSpan: 2,
  310. },
  311. items: {
  312. type: 'object',
  313. properties: {
  314. column1: {
  315. type: 'void',
  316. 'x-component': 'ArrayTable.Column',
  317. 'x-component-props': { title: 'KEY' },
  318. properties: {
  319. id: {
  320. type: 'string',
  321. 'x-decorator': 'FormItem',
  322. 'x-component': 'Input',
  323. 'x-validator': [
  324. {
  325. required: true,
  326. message: '请输入KEY',
  327. },
  328. ],
  329. },
  330. },
  331. },
  332. column2: {
  333. type: 'void',
  334. 'x-component': 'ArrayTable.Column',
  335. 'x-component-props': { title: 'VALUE' },
  336. properties: {
  337. value: {
  338. type: 'string',
  339. 'x-decorator': 'FormItem',
  340. 'x-component': 'Input',
  341. 'x-validator': [
  342. {
  343. required: true,
  344. message: '请输入VALUE',
  345. },
  346. ],
  347. },
  348. },
  349. },
  350. column3: {
  351. type: 'void',
  352. 'x-component': 'ArrayTable.Column',
  353. 'x-component-props': {
  354. title: '操作',
  355. dataIndex: 'operations',
  356. },
  357. properties: {
  358. item: {
  359. type: 'void',
  360. 'x-component': 'FormItem',
  361. properties: {
  362. remove: {
  363. type: 'void',
  364. 'x-component': 'RemoveData',
  365. },
  366. },
  367. },
  368. },
  369. },
  370. },
  371. },
  372. properties: {
  373. add: {
  374. type: 'void',
  375. 'x-component': 'ArrayTable.Addition',
  376. title: '添加',
  377. },
  378. },
  379. },
  380. description: {
  381. title: '说明',
  382. 'x-decorator': 'FormItem',
  383. 'x-component': 'Input.TextArea',
  384. 'x-decorator-props': {
  385. gridSpan: 2,
  386. },
  387. 'x-component-props': {
  388. rows: 3,
  389. showCount: true,
  390. maxLength: 200,
  391. placeholder: '请输入说明',
  392. },
  393. },
  394. },
  395. },
  396. },
  397. };
  398. return (
  399. <Modal
  400. maskClosable={false}
  401. width="50vw"
  402. title={data?.id ? '编辑' : '新增'}
  403. onCancel={() => close()}
  404. onOk={() => save()}
  405. visible={visible}
  406. >
  407. <Form form={form} labelCol={5} wrapperCol={16} layout="vertical">
  408. <SchemaField schema={schema} scope={{ useAsyncDataSource, loadData }} />
  409. </Form>
  410. </Modal>
  411. );
  412. };
  413. export default Save;