Przeglądaj źródła

feat(产品详情): 新增产品详情

xieyonghong 3 lat temu
rodzic
commit
a631a5da1b

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

@@ -181,6 +181,7 @@ export default {
   'pages.device.productDetail.protocolName': '消息协议',
   'pages.device.productDetail.transportProtocol': '链接协议',
   'pages.device.productDetail.createTime': '创建时间',
+  'pages.device.productDetail.updateTime': '创建时间',
   'pages.device.productDetail.base': '配置信息',
   'pages.device.productDetail.base.save': '保存',
   'pages.device.productDetail.metadata': '物模型',

+ 199 - 106
src/pages/device/Product/Detail/BaseInfo/index.tsx

@@ -1,124 +1,217 @@
-import { createSchemaField } from '@formily/react';
 import { productModel, service } from '@/pages/device/Product';
-import { Form, FormItem, FormGrid, Password, FormLayout, PreviewText, Input } from '@formily/antd';
-import { createForm } from '@formily/core';
-import { Card, Empty } from 'antd';
-import type { ISchema } from '@formily/json-schema';
-import type { SetStateAction } from 'react';
-import { useEffect, useState } from 'react';
-import type { ConfigMetadata, ConfigProperty } from '@/pages/device/Product/typings';
+import { Descriptions, Button } from 'antd';
+import { useState } from 'react';
 import { useParams } from 'umi';
 import { useIntl } from '@@/plugin-locale/localeExports';
+import { EditOutlined } from '@ant-design/icons';
+import { getDateFormat } from '@/utils/util';
+import Save from '@/pages/device/Product/Save';
 
-const componentMap = {
-  string: 'Input',
-  password: 'Password',
-};
+// const componentMap = {
+//   string: 'Input',
+//   password: 'Password',
+// };
 const BaseInfo = () => {
   const intl = useIntl();
   const param = useParams<{ id: string }>();
-  const [metadata, setMetadata] = useState<ConfigMetadata[]>([]);
-  const [state, setState] = useState<boolean>(false);
+  // const [metadata, setMetadata] = useState<ConfigMetadata[]>([]);
+  // const [state, setState] = useState<boolean>(false);
+  const [visible, setVisible] = useState(false);
 
-  const form = createForm({
-    validateFirst: true,
-    readPretty: state,
-    initialValues: productModel.current?.configuration,
-  });
+  // const form = createForm({
+  //   validateFirst: true,
+  //   readPretty: state,
+  //   initialValues: productModel.current?.configuration,
+  // });
 
-  useEffect(() => {
-    if (param.id) {
-      service
-        .getConfigMetadata(param.id)
-        .then((config: { result: SetStateAction<ConfigMetadata[]> }) => {
-          setMetadata(config.result);
-        });
-    }
-  }, [param.id]);
+  // useEffect(() => {
+  //   if (param.id) {
+  //     service
+  //       .getConfigMetadata(param.id)
+  //       .then((config: { result: SetStateAction<ConfigMetadata[]> }) => {
+  //         setMetadata(config.result);
+  //       });
+  //   }
+  // }, [param.id]);
 
-  const SchemaField = createSchemaField({
-    components: {
-      Password,
-      FormGrid,
-      PreviewText,
-      FormItem,
-      Input,
-    },
-  });
+  // const SchemaField = createSchemaField({
+  //   components: {
+  //     Password,
+  //     FormGrid,
+  //     PreviewText,
+  //     FormItem,
+  //     Input,
+  //   },
+  // });
+  //
+  // const configToSchema = (data: ConfigProperty[]) => {
+  //   const config = {};
+  //   data.forEach((item) => {
+  //     config[item.property] = {
+  //       type: 'string',
+  //       title: item.name,
+  //       'x-decorator': 'FormItem',
+  //       'x-component': componentMap[item.type.type],
+  //       'x-decorator-props': {
+  //         tooltip: item.description,
+  //       },
+  //     };
+  //   });
+  //   return config;
+  // };
 
-  const configToSchema = (data: ConfigProperty[]) => {
-    const config = {};
-    data.forEach((item) => {
-      config[item.property] = {
-        type: 'string',
-        title: item.name,
-        'x-decorator': 'FormItem',
-        'x-component': componentMap[item.type.type],
-        'x-decorator-props': {
-          tooltip: item.description,
-        },
-      };
+  const getDetailInfo = () => {
+    service.getProductDetail(param?.id).subscribe((data) => {
+      if (data) {
+        productModel.current = data;
+      }
     });
-    return config;
   };
 
-  const renderConfigCard = () => {
-    return metadata && metadata.length > 0 ? (
-      metadata?.map((item) => {
-        const itemSchema: ISchema = {
-          type: 'object',
-          properties: {
-            grid: {
-              type: 'void',
-              'x-component': 'FormGrid',
-              'x-component-props': {
-                minColumns: [2],
-                maxColumns: [2],
-              },
-              properties: configToSchema(item.properties),
-            },
-          },
-        };
+  // const renderConfigCard = () => {
+  //   return metadata && metadata.length > 0 ? (
+  //     metadata?.map((item) => {
+  //       const itemSchema: ISchema = {
+  //         type: 'object',
+  //         properties: {
+  //           grid: {
+  //             type: 'void',
+  //             'x-component': 'FormGrid',
+  //             'x-component-props': {
+  //               minColumns: [2],
+  //               maxColumns: [2],
+  //             },
+  //             properties: configToSchema(item.properties),
+  //           },
+  //         },
+  //       };
+  //
+  //       return (
+  //         <Card
+  //           key={item.name}
+  //           title={item.name}
+  //           extra={
+  //             <a onClick={() => setState(!state)}>
+  //               {state ? (
+  //                 <>
+  //                   {intl.formatMessage({
+  //                     id: 'pages.data.option.edit',
+  //                     defaultMessage: '编辑',
+  //                   })}
+  //                 </>
+  //               ) : (
+  //                 <>
+  //                   {intl.formatMessage({
+  //                     id: 'pages.device.productDetail.base.save',
+  //                     defaultMessage: '保存',
+  //                   })}
+  //                 </>
+  //               )}
+  //             </a>
+  //           }
+  //         >
+  //           <PreviewText.Placeholder value='-'>
+  //             <Form form={form}>
+  //               <FormLayout labelCol={6} wrapperCol={16}>
+  //                 <SchemaField schema={itemSchema} />
+  //               </FormLayout>
+  //             </Form>
+  //           </PreviewText.Placeholder>
+  //         </Card>
+  //       );
+  //     })
+  //   ) : (
+  //     <Empty description={'暂无配置'} />
+  //   );
+  // };
 
-        return (
-          <Card
-            key={item.name}
-            title={item.name}
-            extra={
-              <a onClick={() => setState(!state)}>
-                {state ? (
-                  <>
-                    {intl.formatMessage({
-                      id: 'pages.data.option.edit',
-                      defaultMessage: '编辑',
-                    })}
-                  </>
-                ) : (
-                  <>
-                    {intl.formatMessage({
-                      id: 'pages.device.productDetail.base.save',
-                      defaultMessage: '保存',
-                    })}
-                  </>
-                )}
-              </a>
-            }
+  return (
+    <>
+      <Descriptions
+        size="small"
+        column={3}
+        title={[
+          <span key={1}>产品信息</span>,
+          <Button
+            key={2}
+            type={'link'}
+            onClick={() => {
+              setVisible(true);
+            }}
           >
-            <PreviewText.Placeholder value="-">
-              <Form form={form}>
-                <FormLayout labelCol={6} wrapperCol={16}>
-                  <SchemaField schema={itemSchema} />
-                </FormLayout>
-              </Form>
-            </PreviewText.Placeholder>
-          </Card>
-        );
-      })
-    ) : (
-      <Empty description={'暂无配置'} />
-    );
-  };
-
-  return <>{renderConfigCard()}</>;
+            <EditOutlined />
+          </Button>,
+        ]}
+        bordered
+      >
+        <Descriptions.Item
+          label={intl.formatMessage({
+            id: 'pages.device.category',
+            defaultMessage: '产品ID',
+          })}
+        >
+          {productModel.current?.id}
+        </Descriptions.Item>
+        <Descriptions.Item
+          label={intl.formatMessage({
+            id: 'pages.device.productDetail.classifiedName',
+            defaultMessage: '所属品类',
+          })}
+        >
+          {productModel.current?.classifiedName}
+        </Descriptions.Item>
+        <Descriptions.Item
+          label={intl.formatMessage({
+            id: 'pages.device.productDetail.protocolName',
+            defaultMessage: '消息协议',
+          })}
+        >
+          {productModel.current?.protocolName}
+        </Descriptions.Item>
+        <Descriptions.Item
+          label={intl.formatMessage({
+            id: 'pages.device.productDetail.transportProtocol',
+            defaultMessage: '链接协议',
+          })}
+        >
+          {productModel.current?.transportProtocol}
+        </Descriptions.Item>
+        <Descriptions.Item
+          label={intl.formatMessage({
+            id: 'pages.device.productDetail.updateTime',
+            defaultMessage: '更新时间',
+          })}
+        >
+          {getDateFormat(productModel.current?.updateTime)}
+        </Descriptions.Item>
+        <Descriptions.Item
+          label={intl.formatMessage({
+            id: 'pages.device.productDetail.createTime',
+            defaultMessage: '创建时间',
+          })}
+        >
+          {getDateFormat(productModel.current?.createTime)}
+        </Descriptions.Item>
+        <Descriptions.Item
+          span={3}
+          label={intl.formatMessage({
+            id: 'pages.device.productDetail.metadata.describe',
+            defaultMessage: '描述',
+          })}
+        >
+          {productModel.current?.describe}
+        </Descriptions.Item>
+      </Descriptions>
+      <Save
+        model={'edit'}
+        data={productModel.current}
+        close={() => {
+          setVisible(false);
+        }}
+        reload={getDetailInfo}
+        visible={visible}
+      />
+    </>
+  );
 };
 export default BaseInfo;

+ 35 - 64
src/pages/device/Product/Detail/index.tsx

@@ -1,6 +1,17 @@
 import { PageContainer } from '@ant-design/pro-layout';
-import { history, useParams } from 'umi';
-import { Button, Card, Descriptions, Space, Tabs, Badge, message, Spin, Tooltip } from 'antd';
+import { history, useParams, Link } from 'umi';
+import {
+  Button,
+  Card,
+  Descriptions,
+  Space,
+  Tabs,
+  Badge,
+  message,
+  Spin,
+  Tooltip,
+  Switch,
+} from 'antd';
 import BaseInfo from '@/pages/device/Product/Detail/BaseInfo';
 import { observer } from '@formily/react';
 import { productModel, service } from '@/pages/device/Product';
@@ -9,10 +20,11 @@ import { useIntl } from '@@/plugin-locale/localeExports';
 import Metadata from '@/pages/device/components/Metadata';
 import Alarm from '@/pages/device/components/Alarm';
 import type { DeviceMetadata } from '@/pages/device/Product/typings';
-import { Link } from 'umi';
 import { Store } from 'jetlinks-store';
 import MetadataAction from '@/pages/device/components/Metadata/DataBaseAction';
 import { QuestionCircleOutlined } from '@ant-design/icons';
+import { getMenuPathByCode, MENUS_CODE } from '@/utils/menu';
+import encodeQuery from '@/utils/encodeQuery';
 
 const ProductDetail = observer(() => {
   const intl = useIntl();
@@ -59,6 +71,11 @@ const ProductDetail = observer(() => {
           MetadataAction.insert(metadata);
         }
       });
+      service.instanceCount(encodeQuery({ terms: { productId: param?.id } })).then((res: any) => {
+        if (res.status === 200) {
+          productModel.current!.count = res.result;
+        }
+      });
     }
 
     return () => {
@@ -109,73 +126,27 @@ const ProductDetail = observer(() => {
       content={
         <Spin spinning={loading}>
           <Descriptions size="small" column={2}>
-            <Descriptions.Item
-              label={intl.formatMessage({
-                id: 'pages.device.category',
-                defaultMessage: '产品ID',
-              })}
-            >
-              {productModel.current?.id}
-            </Descriptions.Item>
-            <Descriptions.Item
-              label={intl.formatMessage({
-                id: 'pages.table.productName',
-                defaultMessage: '产品名称',
-              })}
-            >
-              {productModel.current?.name}
-            </Descriptions.Item>
-            <Descriptions.Item
-              label={intl.formatMessage({
-                id: 'pages.device.productDetail.classifiedName',
-                defaultMessage: '所属品类',
-              })}
-            >
-              {productModel.current?.classifiedName}
-            </Descriptions.Item>
-            <Descriptions.Item
-              label={intl.formatMessage({
-                id: 'pages.device.productDetail.protocolName',
-                defaultMessage: '消息协议',
-              })}
-            >
-              {productModel.current?.protocolName}
-            </Descriptions.Item>
-            <Descriptions.Item
-              label={intl.formatMessage({
-                id: 'pages.device.productDetail.transportProtocol',
-                defaultMessage: '链接协议',
-              })}
-            >
-              {productModel.current?.transportProtocol}
-            </Descriptions.Item>
             <Descriptions.Item label={'设备数量'}>
-              <Link to={'/device/instance'}> {productModel.current?.count}</Link>
-            </Descriptions.Item>
-            <Descriptions.Item
-              label={intl.formatMessage({
-                id: 'pages.device.productDetail.createTime',
-                defaultMessage: '创建时间',
-              })}
-            >
-              {productModel.current?.createTime}
+              <Link to={getMenuPathByCode(MENUS_CODE['device/Instance'])}>
+                {' '}
+                {productModel.current?.count || 0}
+              </Link>
             </Descriptions.Item>
           </Descriptions>
         </Spin>
       }
-      extra={[
-        statusMap[productModel.current?.state || 0].component,
-        <Button
-          key="2"
-          onClick={() => {
+      title={productModel.current?.name}
+      subTitle={
+        <Switch
+          key={2}
+          checkedChildren="启用"
+          unCheckedChildren="停用"
+          onChange={() => {
             changeDeploy(statusMap[productModel.current?.state || 0].action);
           }}
-        >
-          {intl.formatMessage({
-            id: `pages.device.productDetail.${statusMap[productModel.current?.state || 0].key}`,
-            defaultMessage: statusMap[productModel.current?.state || 1].name,
-          })}
-        </Button>,
+        />
+      }
+      extra={[
         <Button key="1" type="primary" onClick={() => changeDeploy('deploy')}>
           {intl.formatMessage({
             id: 'pages.device.productDetail.setting',
@@ -185,7 +156,7 @@ const ProductDetail = observer(() => {
       ]}
     >
       <Card>
-        <Tabs tabPosition="left" defaultActiveKey="base">
+        <Tabs defaultActiveKey="base">
           <Tabs.TabPane
             tab={intl.formatMessage({
               id: 'pages.device.productDetail.base',

+ 19 - 1
src/pages/device/Product/Save/index.tsx

@@ -9,11 +9,14 @@ 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 { useIntl } from '@@/plugin-locale/localeExports';
 
 interface Props {
   visible: boolean;
   close: () => void;
+  reload: () => void;
   data?: ProductItem;
+  model: 'add' | 'edit';
 }
 
 /**
@@ -36,6 +39,8 @@ const treeToArray = (tree: any) => {
 
 const Save = (props: Props) => {
   const { visible, close, data } = props;
+  const intl = useIntl();
+
   const handleData = () => {
     // 特殊处理deviceType字段
     if (data) {
@@ -119,6 +124,7 @@ const Save = (props: Props) => {
     const resp = (await service.update(values)) as any;
     if (resp.status === 200) {
       message.success('保存成功');
+      props.reload();
       props.close();
     }
   };
@@ -145,6 +151,9 @@ const Save = (props: Props) => {
             'x-decorator-props': {
               tooltip: <div>若不填写,系统将自动生成唯一ID</div>,
             },
+            'x-component-props': {
+              disabled: props.model === 'edit',
+            },
           },
           name: {
             title: '名称',
@@ -223,7 +232,16 @@ const Save = (props: Props) => {
     },
   };
   return (
-    <Modal visible={visible} onCancel={() => close()} width="30vw" title="新增" onOk={handleSave}>
+    <Modal
+      visible={visible}
+      onCancel={() => close()}
+      width="30vw"
+      title={intl.formatMessage({
+        id: `pages.data.option.${props.model}`,
+        defaultMessage: '新增',
+      })}
+      onOk={handleSave}
+    >
       <Form form={form}>
         <SchemaField schema={schema} scope={{ useAsyncDataSource }} />
       </Form>

+ 98 - 39
src/pages/device/Product/index.tsx

@@ -1,12 +1,12 @@
 import { PageContainer } from '@ant-design/pro-layout';
-import { Badge, Button, message, Space, Tooltip } from 'antd';
+import { Badge, Button, message, Popconfirm, Space, Tooltip } from 'antd';
 import type { ProductItem } from '@/pages/device/Product/typings';
 import {
-  CloseCircleOutlined,
+  StopOutlined,
   DownloadOutlined,
   EditOutlined,
   EyeOutlined,
-  MinusOutlined,
+  DeleteOutlined,
   PlayCircleOutlined,
   PlusOutlined,
 } from '@ant-design/icons';
@@ -18,9 +18,10 @@ import { useIntl } from '@@/plugin-locale/localeExports';
 import type { ActionType, ProColumns } from '@jetlinks/pro-table';
 import { useRef, useState } from 'react';
 import ProTable from '@jetlinks/pro-table';
-import { lastValueFrom } from 'rxjs';
 import encodeQuery from '@/utils/encodeQuery';
 import Save from '@/pages/device/Product/Save';
+import SearchComponent from '@/components/SearchComponent';
+import { getMenuPathByParams, MENUS_CODE } from '@/utils/menu';
 
 export const service = new Service('device-product');
 export const statusMap = {
@@ -38,6 +39,8 @@ const Product = observer(() => {
   const [current, setCurrent] = useState<ProductItem>();
   const actionRef = useRef<ActionType>();
   const intl = useIntl();
+  const [param, setParam] = useState({});
+
   const status = {
     1: (
       <Badge
@@ -58,31 +61,54 @@ const Product = observer(() => {
       />
     ),
   };
+
+  const deleteItem = async (id: string) => {
+    const response: any = await service.remove(id);
+    if (response.status === 200) {
+      message.success(
+        intl.formatMessage({
+          id: 'pages.data.option.success',
+          defaultMessage: '操作成功!',
+        }),
+      );
+    }
+    actionRef.current?.reload();
+  };
+
+  /**
+   * table 查询参数
+   * @param data
+   */
+  const searchFn = (data: any) => {
+    setParam({
+      terms: data,
+    });
+  };
+
+  const changeDeploy = (id: string, state: 'deploy' | 'undeploy') => {
+    service.changeDeploy(id, state).subscribe((res) => {
+      if (res) {
+        actionRef?.current?.reload();
+      }
+    });
+  };
+
   const columns: ProColumns<ProductItem>[] = [
     {
-      dataIndex: 'index',
-      valueType: 'indexBorder',
-      width: 48,
-    },
-    {
       title: 'ID',
       dataIndex: 'id',
     },
     {
-      title: '产品名称',
+      title: '名称',
       dataIndex: 'name',
     },
     {
-      title: '状态',
-      render: (_, row) => <Space size={0}>{status[row.state]}</Space>,
-    },
-    {
-      title: '设备数量',
-      dataIndex: 'count',
+      title: '设备类型',
+      dataIndex: 'classifiedName',
     },
     {
-      title: '设备分类',
-      dataIndex: 'classifiedName',
+      title: '状态',
+      render: (_, row) => <Space size={0}>{status[row.state]}</Space>,
     },
     {
       title: intl.formatMessage({
@@ -104,7 +130,7 @@ const Product = observer(() => {
             onClick={() => {
               productModel.current = record;
             }}
-            to={`/device/product/detail/${record.id}`}
+            to={`${getMenuPathByParams(MENUS_CODE['device/Product/Detail'], record.id)}`}
             key="link"
           >
             <EyeOutlined />
@@ -147,46 +173,76 @@ const Product = observer(() => {
             />
           </a>
         </Tooltip>,
-        <Tooltip
+        <Popconfirm
+          key={'state'}
           title={intl.formatMessage({
-            id: `pages.data.option.${record.state ? 'disabled' : 'enabled'}`,
-            defaultMessage: record.state ? '禁用' : '启用',
+            id: `pages.data.option.${record.state ? 'disabled' : 'enabled'}.tips`,
+            defaultMessage: '是否删除该菜单',
           })}
-          key={'state'}
+          onConfirm={() => {
+            changeDeploy(record.id, record.state ? 'undeploy' : 'deploy');
+          }}
         >
-          <a key="state">{record.state ? <CloseCircleOutlined /> : <PlayCircleOutlined />}</a>
-        </Tooltip>,
-        <Tooltip
+          <Tooltip
+            title={intl.formatMessage({
+              id: `pages.data.option.${record.state ? 'disabled' : 'enabled'}`,
+              defaultMessage: record.state ? '禁用' : '启用',
+            })}
+          >
+            <a key="state">{record.state ? <StopOutlined /> : <PlayCircleOutlined />}</a>
+          </Tooltip>
+        </Popconfirm>,
+        <Popconfirm
+          key="unBindUser"
           title={intl.formatMessage({
-            id: 'pages.data.option.remove',
-            defaultMessage: '删除',
+            id: 'page.system.menu.table.delete',
+            defaultMessage: '是否删除该菜单',
           })}
-          key={'remove'}
+          onConfirm={() => {
+            deleteItem(record.id);
+          }}
         >
-          <a key="delete">
-            <MinusOutlined />
-          </a>
-        </Tooltip>,
+          <Tooltip
+            title={intl.formatMessage({
+              id: 'pages.data.option.remove.tips',
+              defaultMessage: '删除',
+            })}
+            key={'remove'}
+          >
+            <a key="delete">
+              <DeleteOutlined />
+            </a>
+          </Tooltip>
+        </Popconfirm>,
       ],
     },
   ];
 
   return (
     <PageContainer>
+      <SearchComponent field={columns} onSearch={searchFn} />
       <ProTable<ProductItem>
         columns={columns}
         actionRef={actionRef}
         options={{ fullScreen: true }}
-        request={async (params = {}) => {
-          return await lastValueFrom(
-            service.queryZipCount(encodeQuery({ ...params, sorts: { id: 'ascend' } })),
-          );
-        }}
+        // request={async (params = {}) => {
+        //   return await lastValueFrom(
+        //     service.queryZipCount(encodeQuery({ ...params, sorts: { id: 'ascend' } })),
+        //   );
+        // }}
+        params={param}
+        request={(params = {}) =>
+          service.query(encodeQuery({ ...params, sorts: { createTime: 'ascend' } }))
+        }
         rowKey="id"
+        search={false}
         pagination={{ pageSize: 10 }}
         toolBarRender={() => [
           <Button
-            onClick={() => setVisible(true)}
+            onClick={() => {
+              setCurrent(undefined);
+              setVisible(true);
+            }}
             key="button"
             icon={<PlusOutlined />}
             type="primary"
@@ -199,9 +255,12 @@ const Product = observer(() => {
         ]}
       />
       <Save
+        model={!current ? 'add' : 'edit'}
         data={current}
         close={() => {
           setVisible(false);
+        }}
+        reload={() => {
           actionRef.current?.reload();
         }}
         visible={visible}

+ 7 - 7
src/pages/device/Product/service.ts

@@ -18,7 +18,7 @@ class Service extends BaseService<ProductItem> {
         from((i.result as PageResult)?.data).pipe(
           concatMap((t: ProductItem) =>
             from(this.instanceCount(encodeQuery({ terms: { productId: t.id } }))).pipe(
-              map((count) => ({ ...t, count: count.result })),
+              map((count: any) => ({ ...t, count: count.result })),
             ),
           ),
           toArray(),
@@ -74,8 +74,8 @@ class Service extends BaseService<ProductItem> {
         }),
       ),
     ).pipe(
-      filter((resp) => resp.status === 200),
-      map((resp) => resp.result),
+      filter((resp: any) => resp.status === 200),
+      map((resp: any) => resp.result),
     );
 
   public codecs = () =>
@@ -86,8 +86,8 @@ class Service extends BaseService<ProductItem> {
         }),
       ),
     ).pipe(
-      filter((resp) => resp.status === 200),
-      map((resp) => resp.result),
+      filter((resp: any) => resp.status === 200),
+      map((resp: any) => resp.result),
     );
 
   public convertMetadata = (direction: 'from' | 'to', type: string, data: any) =>
@@ -99,8 +99,8 @@ class Service extends BaseService<ProductItem> {
         }),
       ),
     ).pipe(
-      filter((resp) => resp.status === 200),
-      map((resp) => resp.result),
+      filter((resp: any) => resp.status === 200),
+      map((resp: any) => resp.result),
     );
 
   public productAlarm = (id: string) =>

+ 8 - 6
src/pages/device/Product/typings.d.ts

@@ -1,5 +1,10 @@
 import type { BaseItem, State } from '@/utils/typings';
 
+type DeviceType = {
+  text: string;
+  value: string;
+};
+
 export type ProductItem = {
   id: string;
   name: string;
@@ -7,13 +12,9 @@ export type ProductItem = {
   classifiedName: string;
   configuration: Record<string, any>;
   createTime: number;
+  updateTime: number;
   creatorId: string;
-  deviceType:
-    | {
-        text: string;
-        value: string;
-      }
-    | string;
+  deviceType: string | DeviceType;
   count?: number;
   messageProtocol: string;
   metadata: string;
@@ -21,6 +22,7 @@ export type ProductItem = {
   protocolName: string;
   state: number;
   transportProtocol: string;
+  describe?: string;
 };
 
 export type ConfigProperty = {

+ 0 - 3
src/utils/menu.ts

@@ -82,8 +82,6 @@ export const MENUS_CODE = {
   'system/Menu': 'system/Menu',
   'system/OpenAPI': 'system/OpenAPI',
   'system/Permission': 'system/Permission',
-  'system/Role/Edit/Info': 'system/Role/Edit/Info',
-  'system/Role/Edit/UserManage': 'system/Role/Edit/UserManage',
   'system/Role/Edit': 'system/Role/Edit',
   'system/Role': 'system/Role',
   'system/Tenant/Detail/Assets': 'system/Tenant/Detail/Assets',
@@ -92,7 +90,6 @@ export const MENUS_CODE = {
   'system/Tenant/Detail/Permission': 'system/Tenant/Detail/Permission',
   'system/Tenant/Detail': 'system/Tenant/Detail',
   'system/Tenant': 'system/Tenant',
-  'system/User/Save': 'system/User/Save',
   'system/User': 'system/User',
   'user/Login': 'user/Login',
   'visualization/Category': 'visualization/Category',

+ 7 - 0
src/utils/util.ts

@@ -29,3 +29,10 @@ export const useAsyncDataSource =
       }),
     );
   };
+
+export const getDateFormat = (
+  date: moment.MomentInput,
+  format: string = 'YYYY-MM-DD HH:mm:ss',
+): string => {
+  return date ? moment(date).format(format) : '-';
+};