Procházet zdrojové kódy

feat(设备管理): 修改设备管理新增表单;提公用UploadImage组件

xieyonghong před 3 roky
rodič
revize
01bf3451a9

+ 82 - 0
src/components/Upload/Image/index.less

@@ -0,0 +1,82 @@
+@import '../../../../node_modules/antd/lib/style/themes/variable';
+
+@border: 1px dashed @border-color-base;
+@mask-color: rgba(#000, 0.35);
+@with: 160px;
+@height: 150px;
+
+.flex-center() {
+  align-items: center;
+  justify-content: center;
+}
+
+.upload-image-warp {
+  display: flex;
+  justify-content: flex-start;
+
+  .upload-image-border {
+    position: relative;
+    width: @with;
+    height: @height;
+    overflow: hidden;
+    //border-radius: 50%;
+    border: @border;
+    transition: all 0.3s;
+
+    &:hover {
+      border-color: @primary-color-hover;
+    }
+
+    .upload-image-content {
+      .flex-center();
+      position: relative;
+
+      display: flex;
+      flex-direction: column;
+      width: @with;
+      height: @height;
+      background-color: rgba(#000, 0.06);
+      cursor: pointer;
+
+      .upload-image-mask {
+        .flex-center();
+        position: absolute;
+        top: 0;
+        left: 0;
+
+        display: none;
+        width: 100%;
+        height: 100%;
+        color: #fff;
+        font-size: 16px;
+        background-color: @mask-color;
+      }
+
+      .upload-image {
+        width: 144px;
+        height: 138px;
+        //border-radius: 50%;
+        background-repeat: no-repeat;
+        background-position: center;
+        background-size: cover;
+      }
+
+      &:hover .upload-image-mask {
+        display: flex;
+      }
+    }
+  }
+
+  .upload-loading-mask {
+    .flex-center();
+
+    position: absolute;
+    top: 0;
+    left: 0;
+    display: flex;
+    width: 100%;
+    height: 100%;
+    color: #fff;
+    background-color: @mask-color;
+  }
+}

+ 100 - 0
src/components/Upload/Image/index.tsx

@@ -0,0 +1,100 @@
+import { message, Upload } from 'antd';
+import { useEffect, useState } from 'react';
+import { LoadingOutlined, PlusOutlined } from '@ant-design/icons';
+import SystemConst from '@/utils/const';
+import Token from '@/utils/token';
+import type { UploadChangeParam } from 'antd/lib/upload/interface';
+import type { RcFile } from 'antd/es/upload';
+import './index.less';
+
+interface UploadImageProps {
+  onChange?: (url: string) => void;
+  value?: string;
+  disabled?: boolean;
+  /**
+   * 图片格式类型,默认 'image/jpeg', 'image/png'
+   */
+  types?: string[];
+  /**
+   * 图片大小限制, 单位 M,默认 4 M
+   */
+  size?: number;
+}
+
+export default ({ onChange, value, ...extraProps }: UploadImageProps) => {
+  const [values, setValues] = useState(value || '');
+  const [loading, setLoading] = useState<boolean>(false);
+  const imageTypes = extraProps.types ? extraProps.types : ['image/jpeg', 'image/png'];
+
+  useEffect(() => {
+    setValues(value || '');
+  }, [value]);
+
+  const handleChange = (info: UploadChangeParam) => {
+    if (info.file.status === 'uploading') {
+      setLoading(true);
+    }
+    if (info.file.status === 'done') {
+      info.file.url = info.file.response?.result;
+      setLoading(false);
+      setValues(info.file.response?.result);
+      if (onChange) {
+        onChange(info.file.response?.result);
+      }
+    }
+  };
+
+  const beforeUpload = (file: RcFile) => {
+    const isType = imageTypes.includes(file.type);
+    if (!isType) {
+      message.error(`图片格式错误,必须是${imageTypes.toString()}格式`);
+      return false;
+    }
+    const isSize = file.size / 1024 / 1024 < (extraProps.size || 4);
+    if (!isSize) {
+      message.error(`图片大小必须小于${extraProps.size || 4}M`);
+    }
+    return isType && isSize;
+  };
+
+  return (
+    <div className={'upload-image-warp'}>
+      <div className={'upload-image-border'}>
+        <Upload
+          action={`/${SystemConst.API_BASE}/file/static`}
+          headers={{
+            'X-Access-Token': Token.get(),
+          }}
+          showUploadList={false}
+          onChange={handleChange}
+          beforeUpload={beforeUpload}
+          {...extraProps}
+        >
+          <div className={'upload-image-content'}>
+            {values ? (
+              <>
+                {/*<img width={120} height={120} src={values} />*/}
+                <div className={'upload-image'} style={{ backgroundImage: `url(${values})` }} />
+                <div className={'upload-image-mask'}>点击修改</div>
+              </>
+            ) : (
+              <>
+                {loading ? (
+                  <LoadingOutlined style={{ fontSize: 28 }} />
+                ) : (
+                  <PlusOutlined style={{ fontSize: 28 }} />
+                )}
+                <div>点击上传图片</div>
+              </>
+            )}
+          </div>
+        </Upload>
+        {values && loading ? (
+          <div className={'upload-loading-mask'}>
+            {loading ? <LoadingOutlined style={{ fontSize: 28 }} /> : null}
+          </div>
+        ) : null}
+      </div>
+    </div>
+  );
+};

+ 1 - 0
src/components/index.ts

@@ -1 +1,2 @@
 export { default as RadioCard } from './RadioCard';
 export { default as RadioCard } from './RadioCard';
+export { default as UploadImage } from './Upload/Image';

+ 14 - 0
src/locales/zh-CN/pages.ts

@@ -46,6 +46,14 @@ export default {
   'pages.table.type': '类型',
   'pages.table.type': '类型',
   'pages.table.deviceId': '设备ID',
   'pages.table.deviceId': '设备ID',
   'pages.table.createTime': '创建时间',
   'pages.table.createTime': '创建时间',
+  'pages.form.tip.select': '请选择',
+  'pages.form.tip.input': '请输入',
+  'pages.form.tip.max64': '最多输入64个字符',
+  'pages.form.tip.id': '请输入英文或者数字或者-或者_',
+  'pages.form.tooltip.id': '若不填写,系统将自动生成唯一ID',
+  'pages.form.tip.existsID': 'ID重复',
+  'pages.form.tip.input.props': '请输入{name}',
+  'pages.form.tip.select.props': '请选择{name}',
 
 
   // 统计分析
   // 统计分析
   'pages.analysis.volume': '今日设备消息量',
   'pages.analysis.volume': '今日设备消息量',
@@ -222,6 +230,12 @@ export default {
   'pages.device.productDetail.setting': '应用配置',
   'pages.device.productDetail.setting': '应用配置',
   'pages.device.productDetail.disable': '停用',
   'pages.device.productDetail.disable': '停用',
   'pages.device.productDetail.enabled': '启用',
   'pages.device.productDetail.enabled': '启用',
+
+  // 设备管理-设备分类
+  'pages.device.type.device': '直连设备',
+  'pages.device.type.childrenDevice': '网关子设备',
+  'pages.device.type.gateway': '网关设备',
+
   // 设备管理-产品分类
   // 设备管理-产品分类
   'pages.device.category': '产品分类',
   'pages.device.category': '产品分类',
   'pages.device.category.id': '分类ID',
   'pages.device.category.id': '分类ID',

+ 168 - 102
src/pages/device/Instance/Save/index.tsx

@@ -1,14 +1,11 @@
-import { message, Modal } from 'antd';
-import { createForm } from '@formily/core';
-import { Form, FormItem, FormLayout, Input, Radio, Select } from '@formily/antd';
-import { createSchemaField } from '@formily/react';
-import type { ISchema } from '@formily/json-schema';
-import FUpload from '@/components/Upload';
+import { Col, message, Modal } from 'antd';
+import { Form, Input, Row, Select } from 'antd';
 import { service } from '@/pages/device/Instance';
 import { service } from '@/pages/device/Instance';
-import 'antd/lib/tree-select/style/index.less';
 import type { DeviceInstance } from '../typings';
 import type { DeviceInstance } from '../typings';
 import { useEffect, useState } from 'react';
 import { useEffect, useState } from 'react';
 import { useIntl } from '@@/plugin-locale/localeExports';
 import { useIntl } from '@@/plugin-locale/localeExports';
+import { UploadImage } from '@/components';
+import { debounce } from 'lodash';
 
 
 interface Props {
 interface Props {
   visible: boolean;
   visible: boolean;
@@ -21,13 +18,11 @@ interface Props {
 const Save = (props: Props) => {
 const Save = (props: Props) => {
   const { visible, close, data } = props;
   const { visible, close, data } = props;
   const [productList, setProductList] = useState<any[]>([]);
   const [productList, setProductList] = useState<any[]>([]);
-  const form = createForm({
-    initialValues: {},
-  });
+  const [form] = Form.useForm();
 
 
   useEffect(() => {
   useEffect(() => {
     if (visible && data) {
     if (visible && data) {
-      form.setValues({
+      form.setFieldsValue({
         id: data.id,
         id: data.id,
         name: data.name,
         name: data.name,
         productId: data.productId,
         productId: data.productId,
@@ -49,105 +44,67 @@ const Save = (props: Props) => {
       }
       }
     });
     });
   }, []);
   }, []);
-  const SchemaField = createSchemaField({
-    components: {
-      FormItem,
-      Input,
-      Select,
-      Radio,
-      FUpload,
-      FormLayout,
-    },
-  });
 
 
-  const handleSave = async () => {
-    const values = (await form.submit()) as any;
-    console.log(values);
-    const productId = values.productId;
-    if (productId) {
-      const product = productList.find((i) => i.value === productId);
-      values.productName = product.label;
+  const intlFormat = (
+    id: string,
+    defaultMessage: string,
+    paramsID?: string,
+    paramsMessage?: string,
+  ) => {
+    const paramsObj: Record<string, string> = {};
+    if (paramsID) {
+      const paramsMsg = intl.formatMessage({
+        id: paramsID,
+        defaultMessage: paramsMessage,
+      });
+      paramsObj.name = paramsMsg;
     }
     }
-    const resp = (await service.update(values)) as any;
-    if (resp.status === 200) {
-      message.success('保存成功');
-      if (props.reload) {
-        props.reload();
+    const msg = intl.formatMessage(
+      {
+        id,
+        defaultMessage,
+      },
+      paramsObj,
+    );
+    return msg;
+  };
+
+  const handleSave = async () => {
+    const values = await form.validateFields();
+    if (values) {
+      const resp = (await service.update(values)) as any;
+      if (resp.status === 200) {
+        message.success('保存成功');
+        if (props.reload) {
+          props.reload();
+        }
+        props.close();
       }
       }
-      props.close();
     }
     }
   };
   };
 
 
-  const schema: ISchema = {
-    type: 'object',
-    properties: {
-      layout: {
-        type: 'void',
-        'x-component': 'FormLayout',
-        'x-component-props': {
-          labelCol: 4,
-          wrapperCol: 18,
-        },
-        properties: {
-          id: {
-            title: 'ID',
-            'x-disabled': !!data?.id,
-            'x-component': 'Input',
-            'x-decorator': 'FormItem',
-            'x-decorator-props': {
-              tooltip: <div>若不填写,系统将自动生成唯一ID</div>,
-            },
-            // 'x-validator': `{{(value) => {
-            //     return service.isExists(value).then(resp => {
-            //         if(resp.status === 200){
-            //             if(!resp.result){
-            //                 resolve('已存在该设备ID')
-            //             }
-            //         } else {
-            //             resolve('')
-            //         }
-            //     })
-            //   }}}`
-          },
-          name: {
-            title: '名称',
-            'x-component': 'Input',
-            'x-decorator': 'FormItem',
-            'x-validator': [
-              {
-                required: true,
-                message: '请输入名称',
-              },
-              {
-                max: 64,
-                message: '最多可输入64个字符',
-              },
-            ],
-          },
-          productId: {
-            title: '所属产品',
-            required: true,
-            'x-component': 'Select',
-            'x-decorator': 'FormItem',
-            enum: [...productList],
-          },
-          describe: {
-            title: '说明',
-            'x-component': 'Input.TextArea',
-            'x-decorator': 'FormItem',
-            'x-component-props': {
-              showCount: true,
-              maxLength: 200,
-            },
-          },
-        },
-      },
-    },
+  const vailId = (_: any, value: any, callback: Function) => {
+    service.isExists(value).then((resp) => {
+      if (resp.status === 200 && resp.result) {
+        callback(
+          intl.formatMessage({
+            id: 'pages.form.tip.existsID',
+            defaultMessage: 'ID重复',
+          }),
+        );
+      } else {
+        callback();
+      }
+    });
   };
   };
+
   return (
   return (
     <Modal
     <Modal
       visible={visible}
       visible={visible}
-      onCancel={() => close()}
+      onCancel={() => {
+        form.resetFields();
+        close();
+      }}
       width="30vw"
       width="30vw"
       title={intl.formatMessage({
       title={intl.formatMessage({
         id: `pages.data.option.${props.model || 'add'}`,
         id: `pages.data.option.${props.model || 'add'}`,
@@ -155,8 +112,117 @@ const Save = (props: Props) => {
       })}
       })}
       onOk={handleSave}
       onOk={handleSave}
     >
     >
-      <Form form={form}>
-        <SchemaField schema={schema} />
+      <Form
+        form={form}
+        layout={'vertical'}
+        labelAlign={'right'}
+        labelCol={{
+          style: { width: 100 },
+        }}
+      >
+        <Row>
+          <Col span={8}>
+            <Form.Item name={'photoUrl'}>
+              <UploadImage />
+            </Form.Item>
+          </Col>
+          <Col span={16}>
+            <Form.Item
+              label={'ID'}
+              name={'id'}
+              tooltip={intlFormat('pages.form.tooltip.id', '若不填写,系统将自动生成唯一ID')}
+              rules={[
+                {
+                  pattern: /^[a-zA-Z0-9_\-]+$/,
+                  message: intlFormat('pages.form.tip.id', '请输入英文或者数字或者-或者_'),
+                },
+                {
+                  max: 64,
+                  message: intlFormat('pages.form.tip.max64', '最多输入64个字符'),
+                },
+                {
+                  validator: debounce(vailId, 300),
+                },
+              ]}
+            >
+              <Input
+                disabled={props.model === 'edit'}
+                placeholder={intlFormat('pages.form.tip.input', '请输入')}
+              />
+            </Form.Item>
+            <Form.Item
+              label={intlFormat('pages.table.name', '名称')}
+              name={'name'}
+              rules={[
+                {
+                  required: true,
+                  message: intlFormat(
+                    'pages.form.tip.input.props',
+                    '请输入名称',
+                    'pages.table.name',
+                    '名称',
+                  ),
+                },
+                {
+                  max: 64,
+                  message: intl.formatMessage({
+                    id: 'pages.form.tip.max64',
+                    defaultMessage: '最多输入64个字符',
+                  }),
+                },
+              ]}
+              required
+            >
+              <Input placeholder={intlFormat('pages.form.tip.input', '请输入')} />
+            </Form.Item>
+          </Col>
+        </Row>
+        <Row>
+          <Col span={24}>
+            <Form.Item
+              label={'所属产品'}
+              name={'productId'}
+              rules={[
+                {
+                  required: true,
+                  message: intlFormat(
+                    'pages.form.tip.select.props',
+                    '请选择所属产品',
+                    'pages.device.instanceDetail.deviceType',
+                    '设备类型',
+                  ),
+                },
+              ]}
+              required
+            >
+              <Select
+                showSearch
+                options={productList}
+                onSelect={(_: any, node: any) => {
+                  form.setFieldsValue({
+                    productName: node.name,
+                  });
+                }}
+              />
+            </Form.Item>
+            <Form.Item hidden={true} name={'productName'}>
+              <Input />
+            </Form.Item>
+          </Col>
+        </Row>
+        <Row>
+          <Col span={24}>
+            <Form.Item label={intlFormat('pages.table.description', '说明')} name={'describe'}>
+              <Input.TextArea
+                placeholder={intlFormat('pages.form.tip.input', '请输入')}
+                rows={4}
+                style={{ width: '100%' }}
+                maxLength={200}
+                showCount={true}
+              />
+            </Form.Item>
+          </Col>
+        </Row>
       </Form>
       </Form>
     </Modal>
     </Modal>
   );
   );

+ 217 - 205
src/pages/device/Product/Save/index.tsx

@@ -1,15 +1,11 @@
-import { message, Modal } from 'antd';
-import type { Field } from '@formily/core';
-import { createForm, onFieldValueChange } from '@formily/core';
-import { Form, FormItem, FormLayout, Input, Radio, Select, TreeSelect } from '@formily/antd';
-import { createSchemaField } from '@formily/react';
-import type { ISchema } from '@formily/json-schema';
-import FUpload from '@/components/Upload';
+import { useEffect } from 'react';
 import { service } from '@/pages/device/Product';
 import { service } from '@/pages/device/Product';
-import { action } from '@formily/reactive';
-import 'antd/lib/tree-select/style/index.less';
 import type { ProductItem } from '@/pages/device/Product/typings';
 import type { ProductItem } from '@/pages/device/Product/typings';
 import { useIntl } from '@@/plugin-locale/localeExports';
 import { useIntl } from '@@/plugin-locale/localeExports';
+import { RadioCard, UploadImage } from '@/components';
+import { Form, Input, Modal, TreeSelect, Row, Col, message } from 'antd';
+import { useRequest } from 'umi';
+import { debounce } from 'lodash';
 
 
 interface Props {
 interface Props {
   visible: boolean;
   visible: boolean;
@@ -19,27 +15,16 @@ interface Props {
   model: 'add' | 'edit';
   model: 'add' | 'edit';
 }
 }
 
 
-/**
- * 处理品类数据
- * @param tree
- */
-const treeToArray = (tree: any) => {
-  const arr: any[] = [];
-  const expanded = (datas: any[]) => {
-    if (datas && datas.length > 0) {
-      datas.forEach((e) => {
-        arr.push(e);
-        expanded(e.children);
-      });
-    }
-  };
-  expanded(tree);
-  return arr;
-};
-
 const Save = (props: Props) => {
 const Save = (props: Props) => {
   const { visible, close, data } = props;
   const { visible, close, data } = props;
   const intl = useIntl();
   const intl = useIntl();
+  const [form] = Form.useForm();
+  const { data: classOptions, run: classRequest } = useRequest(service.category, {
+    manual: true,
+    formatResult: (response) => {
+      return response.result;
+    },
+  });
 
 
   const handleData = () => {
   const handleData = () => {
     // 特殊处理deviceType字段
     // 特殊处理deviceType字段
@@ -50,202 +35,229 @@ const Save = (props: Props) => {
     }
     }
     return data;
     return data;
   };
   };
-  const form = createForm({
-    initialValues: handleData(),
-    effects() {
-      onFieldValueChange('messageProtocol', (field, f) => {
-        const protocol = (field as Field).value;
-        f.setFieldState('transportProtocol', async (state) => {
-          state.loading = true;
-          const resp = await service.getTransport(protocol);
-          state.dataSource = resp.result.map((item: { name: string; id: string }) => ({
-            label: item.name,
-            value: item.id,
-          }));
-          state.loading = false;
-        });
-      });
-    },
-  });
-
-  const SchemaField = createSchemaField({
-    components: {
-      FormItem,
-      Input,
-      Select,
-      Radio,
-      FUpload,
-      FormLayout,
-      TreeSelect,
-    },
-  });
-  const serviceMap = new Map<string, Promise<any>>();
-  serviceMap.set('classifiedId', service.category());
-  serviceMap.set('protocol', service.getProtocol());
-  serviceMap.set('storePolicy', service.getStorage());
-  serviceMap.set('org', service.getOrg());
 
 
-  let classifiedList: any[] = [];
-  const useAsyncDataSource = (type: string) => (field: Field) => {
-    field.loading = true;
-    serviceMap.get(type)!.then(
-      action.bound!((resp) => {
-        if (type === 'classifiedId') {
-          // 处理key错误
-          field.dataSource = resp.result.map((item: Record<string, unknown>) => ({
-            ...item,
-            key: item.id,
-          }));
-          // 考虑冗余分类字段
-          classifiedList = resp.result;
-        } else {
-          field.dataSource = resp.result.map((item: { name: any; id: any }) => ({
-            label: item.name,
-            value: item.id,
-          }));
-        }
-        field.loading = false;
-      }),
+  const intlFormat = (
+    id: string,
+    defaultMessage: string,
+    paramsID?: string,
+    paramsMessage?: string,
+  ) => {
+    const paramsObj: Record<string, string> = {};
+    if (paramsID) {
+      const paramsMsg = intl.formatMessage({
+        id: paramsID,
+        defaultMessage: paramsMessage,
+      });
+      paramsObj.name = paramsMsg;
+    }
+    const msg = intl.formatMessage(
+      {
+        id,
+        defaultMessage,
+      },
+      paramsObj,
     );
     );
+    return msg;
   };
   };
 
 
-  const handleSave = async () => {
-    const values = (await form.submit()) as any;
-    // 冗余classifiedName 字段;
-    // 如果只存储string。 可考虑字段解构方式处理
-    // 可能存在数据反显问题,此处考虑与后台协商处理
-    const classifiedId = values.classifiedId;
-    if (classifiedId) {
-      const tempClassifiedList = treeToArray(classifiedList);
-      const classified = tempClassifiedList.find((i) => i.id === classifiedId);
-      // values.classifiedId = classifiedId[classifiedId.length - 1];
-      values.classfiedName = classified.name;
+  useEffect(() => {
+    if (visible) {
+      // 获取产品分类
+      classRequest();
+      form.setFieldsValue(handleData());
     }
     }
-    const resp = (await service.update(values)) as any;
-    if (resp.status === 200) {
-      message.success('保存成功');
-      if (props.reload) {
-        props.reload();
+  }, [visible]);
+
+  const handleSave = async () => {
+    const formData = await form.validateFields();
+    if (formData) {
+      const res = await service.update(formData);
+      if (res.status === 200) {
+        message.success('保存成功');
+        if (props.reload) {
+          props.reload();
+        }
+        props.close();
       }
       }
-      props.close();
     }
     }
   };
   };
-  const schema: ISchema = {
-    type: 'object',
-    properties: {
-      layout: {
-        type: 'void',
-        'x-component': 'FormLayout',
-        'x-component-props': {
-          labelCol: 4,
-          wrapperCol: 18,
-        },
-        properties: {
-          photoUrl: {
-            title: '图标',
-            'x-component': 'FUpload',
-            'x-decorator': 'FormItem',
-          },
-          id: {
-            title: 'ID',
-            'x-component': 'Input',
-            'x-decorator': 'FormItem',
-            'x-decorator-props': {
-              tooltip: <div>若不填写,系统将自动生成唯一ID</div>,
-            },
-            'x-component-props': {
-              disabled: props.model === 'edit',
-            },
-          },
-          name: {
-            title: '名称',
-            'x-component': 'Input',
-            'x-decorator': 'FormItem',
-            'x-validator': [
-              {
-                required: true,
-                message: '请输入名称',
-              },
-              {
-                max: 64,
-                message: '最多可输入64个字符',
-              },
-            ],
-          },
-          classifiedId: {
-            title: '所属品类',
-            'x-component': 'TreeSelect',
-            'x-decorator': 'FormItem',
-            'x-component-props': {
-              fieldNames: { label: 'name', value: 'id', children: 'children', key: 'id' },
-            },
-            'x-reactions': ['{{useAsyncDataSource("classifiedId")}}'],
-          },
-          // orgId: {
-          //   title: '所属机构',
-          //   'x-component': 'Select',
-          //   'x-decorator': 'FormItem',
-          //   'x-reactions': ['{{useAsyncDataSource("org")}}'],
-          // },
-          // messageProtocol: {
-          //   title: '消息协议',
-          //   'x-component': 'Select',
-          //   'x-decorator': 'FormItem',
-          //   'x-reactions': ['{{useAsyncDataSource("protocol")}}'],
-          // },
-          // transportProtocol: {
-          //   title: '传输协议',
-          //   'x-component': 'Select',
-          //   'x-decorator': 'FormItem',
-          // },
-          // storePolicy: {
-          //   title: '存储策略',
-          //   'x-component': 'Select',
-          //   'x-decorator': 'FormItem',
-          //   'x-reactions': ['{{useAsyncDataSource("storePolicy")}}'],
-          // },
-          deviceType: {
-            title: '设备类型',
-            'x-component': 'Radio.Group',
-            'x-decorator': 'FormItem',
-            enum: [
-              { label: '直连设备', value: 'device' },
-              { label: '网关子设备', value: 'childrenDevice' },
-              { label: '网关设备', value: 'gateway' },
-            ],
-            'x-validator': [
-              {
-                required: true,
-                message: '请选择设备类型',
-              },
-            ],
-          },
-          describe: {
-            title: '描述',
-            'x-component': 'Input.TextArea',
-            'x-decorator': 'FormItem',
-            'x-component-props': {
-              showCount: true,
-              maxLength: 200,
-            },
-          },
-        },
-      },
-    },
+
+  const vailId = (_: any, value: any, callback: Function) => {
+    service.existsID(value).then((res) => {
+      if (res.status === 200 && res.result) {
+        callback(
+          intl.formatMessage({
+            id: 'pages.form.tip.existsID',
+            defaultMessage: 'ID重复',
+          }),
+        );
+      }
+      callback();
+    });
   };
   };
+
   return (
   return (
     <Modal
     <Modal
       visible={visible}
       visible={visible}
-      onCancel={() => close()}
-      width="30vw"
+      onCancel={() => {
+        form.resetFields();
+        close();
+      }}
+      width={610}
       title={intl.formatMessage({
       title={intl.formatMessage({
         id: `pages.data.option.${props.model || 'add'}`,
         id: `pages.data.option.${props.model || 'add'}`,
         defaultMessage: '新增',
         defaultMessage: '新增',
       })}
       })}
       onOk={handleSave}
       onOk={handleSave}
     >
     >
-      <Form form={form}>
-        <SchemaField schema={schema} scope={{ useAsyncDataSource }} />
+      <Form
+        form={form}
+        layout={'vertical'}
+        labelAlign={'right'}
+        labelCol={{
+          style: { width: 100 },
+        }}
+      >
+        <Row>
+          <Col span={8}>
+            <Form.Item name={'photoUrl'}>
+              <UploadImage />
+            </Form.Item>
+          </Col>
+          <Col span={16}>
+            <Form.Item
+              label={'ID'}
+              name={'id'}
+              tooltip={intl.formatMessage({
+                id: 'pages.form.tooltip.id',
+                defaultMessage: '若不填写,系统将自动生成唯一ID',
+              })}
+              rules={[
+                {
+                  pattern: /^[a-zA-Z0-9_\-]+$/,
+                  message: intl.formatMessage({
+                    id: 'pages.form.tip.id',
+                    defaultMessage: '请输入英文或者数字或者-或者_',
+                  }),
+                },
+                {
+                  max: 64,
+                  message: intl.formatMessage({
+                    id: 'pages.form.tip.max64',
+                    defaultMessage: '最多输入64个字符',
+                  }),
+                },
+                {
+                  validator: debounce(vailId, 300),
+                },
+              ]}
+            >
+              <Input
+                disabled={props.model === 'edit'}
+                placeholder={intlFormat('pages.form.tip.input', '请输入')}
+              />
+            </Form.Item>
+            <Form.Item
+              label={intlFormat('pages.table.name', '名称')}
+              name={'name'}
+              rules={[
+                {
+                  required: true,
+                  message: intlFormat(
+                    'pages.form.tip.input.props',
+                    '请输入名称',
+                    'pages.table.name',
+                    '名称',
+                  ),
+                },
+                {
+                  max: 64,
+                  message: intl.formatMessage({
+                    id: 'pages.form.tip.max64',
+                    defaultMessage: '最多输入64个字符',
+                  }),
+                },
+              ]}
+              required
+            >
+              <Input placeholder={intlFormat('pages.form.tip.input', '请输入')} />
+            </Form.Item>
+          </Col>
+        </Row>
+        <Row>
+          <Col span={24}>
+            <Form.Item label={'分类'} name={'classifiedId'}>
+              <TreeSelect
+                showSearch
+                onSelect={(_: any, node: any) => {
+                  form.setFieldsValue({
+                    classifiedName: node.name,
+                  });
+                }}
+                filterTreeNode={(input, treeNode) => treeNode.name.includes(input)}
+                placeholder={intlFormat('pages.form.tip.select', '请选择')}
+                fieldNames={{
+                  label: 'name',
+                  value: 'id',
+                }}
+                treeData={classOptions}
+              />
+            </Form.Item>
+            <Form.Item hidden={true} name={'classifiedName'}>
+              <Input />
+            </Form.Item>
+          </Col>
+          <Col span={24}>
+            <Form.Item
+              label={intlFormat('pages.device.instanceDetail.deviceType', '设备类型')}
+              name={'deviceType'}
+              rules={[
+                {
+                  required: true,
+                  message: intlFormat(
+                    'pages.form.tip.select.props',
+                    '请选择设备类型',
+                    'pages.device.instanceDetail.deviceType',
+                    '设备类型',
+                  ),
+                },
+              ]}
+              required
+            >
+              <RadioCard
+                model={'singular'}
+                options={[
+                  {
+                    label: intlFormat('pages.device.type.device', '直连设备'),
+                    value: 'device',
+                    imgUrl: require('/public/images/device-type-1.png'),
+                  },
+                  {
+                    label: intlFormat('pages.device.type.childrenDevice', '网关子设备'),
+                    value: 'childrenDevice',
+                    imgUrl: require('/public/images/device-type-2.png'),
+                  },
+                  {
+                    label: intlFormat('pages.device.type.gateway', '网关设备'),
+                    value: 'gateway',
+                    imgUrl: require('/public/images/device-type-3.png'),
+                  },
+                ]}
+              />
+            </Form.Item>
+          </Col>
+          <Col span={24}>
+            <Form.Item label={intlFormat('pages.table.description', '说明')} name={'describe'}>
+              <Input.TextArea
+                placeholder={intlFormat('pages.form.tip.input', '请输入')}
+                rows={4}
+                style={{ width: '100%' }}
+                maxLength={200}
+                showCount={true}
+              />
+            </Form.Item>
+          </Col>
+        </Row>
       </Form>
       </Form>
     </Modal>
     </Modal>
   );
   );

+ 2 - 0
src/pages/device/Product/service.ts

@@ -142,6 +142,8 @@ class Service extends BaseService<ProductItem> {
         method: 'POST',
         method: 'POST',
       },
       },
     );
     );
+
+  public existsID = (id: string) => request(`${this.uri}/${id}/exists`, { method: 'GET' });
 }
 }
 
 
 export default Service;
 export default Service;