Browse Source

feat: merge

xieyonghong 3 years ago
parent
commit
a8060feae2
46 changed files with 915 additions and 592 deletions
  1. 91 42
      public/logo.svg
  2. 3 0
      src/app.tsx
  3. 0 1
      src/components/BaseCrud/save/index.tsx
  4. 7 1
      src/components/Metadata/ArrayParam/index.tsx
  5. 30 8
      src/components/Metadata/JsonParam/index.tsx
  6. 27 3
      src/global.less
  7. 3 6
      src/pages/device/Category/index.tsx
  8. 0 5
      src/pages/device/Instance/Detail/ChildDevice/BindChildDevice/index.tsx
  9. 1 5
      src/pages/device/Instance/Detail/ChildDevice/index.tsx
  10. 12 5
      src/pages/device/Instance/Detail/Config/index.tsx
  11. 5 11
      src/pages/device/Instance/Detail/Log/index.tsx
  12. 29 3
      src/pages/device/Instance/Detail/Running/Event/index.tsx
  13. 0 1
      src/pages/device/Instance/Detail/Running/Property/index.tsx
  14. 28 26
      src/pages/device/Instance/Detail/index.tsx
  15. 4 0
      src/pages/device/Instance/Export/index.tsx
  16. 6 0
      src/pages/device/Instance/Import/index.tsx
  17. 0 1
      src/pages/device/Instance/index.tsx
  18. 1 1
      src/pages/device/Instance/service.ts
  19. 48 27
      src/pages/device/Product/Detail/Access/AccessConfig/index.tsx
  20. 22 0
      src/pages/device/Product/Detail/Access/index.less
  21. 31 29
      src/pages/device/Product/Detail/Access/index.tsx
  22. 6 6
      src/pages/device/Product/Detail/index.tsx
  23. 0 1
      src/pages/device/Product/index.tsx
  24. 7 1
      src/pages/device/components/Metadata/Base/Edit/index.tsx
  25. 5 5
      src/pages/device/components/Metadata/Base/columns.ts
  26. 7 3
      src/pages/link/AccessConfig/Detail/Access/index.less
  27. 107 89
      src/pages/link/AccessConfig/Detail/Access/index.tsx
  28. 23 2
      src/pages/link/AccessConfig/index.tsx
  29. 30 25
      src/pages/link/Protocol/FileUpload/index.tsx
  30. 23 7
      src/pages/link/Protocol/index.tsx
  31. 3 0
      src/pages/link/Protocol/service.ts
  32. 2 2
      src/pages/link/Type/Save/index.tsx
  33. 0 2
      src/pages/link/Type/index.tsx
  34. 0 1
      src/pages/system/Department/Member/index.tsx
  35. 0 1
      src/pages/system/Department/index.tsx
  36. 1 1
      src/pages/system/Menu/components/permission.tsx
  37. 0 1
      src/pages/system/Menu/index.tsx
  38. 260 0
      src/pages/system/Permission/Save/index.tsx
  39. 64 243
      src/pages/system/Permission/index.tsx
  40. 4 3
      src/pages/system/Permission/service.ts
  41. 1 1
      src/pages/system/Role/Edit/Permission/index.tsx
  42. 0 6
      src/pages/system/Role/Edit/UserManage/BindUser.tsx
  43. 0 5
      src/pages/system/Role/Edit/UserManage/index.tsx
  44. 17 1
      src/pages/system/Role/index.tsx
  45. 5 9
      src/pages/system/User/index.tsx
  46. 2 2
      src/pages/user/Login/index.tsx

File diff suppressed because it is too large
+ 91 - 42
public/logo.svg


+ 3 - 0
src/app.tsx

@@ -177,6 +177,8 @@ export const request: RequestConfig = {
 // ProLayout 支持的api https://procomponents.ant.design/components/layout
 export const layout: RunTimeLayoutConfig = ({ initialState }) => {
   return {
+    navTheme: 'light',
+    headerTheme: 'light',
     rightContentRender: () => <RightContent />,
     disableContentMargin: false,
     waterMarkProps: {
@@ -209,6 +211,7 @@ export const layout: RunTimeLayoutConfig = ({ initialState }) => {
     // 自定义 403 页面
     // unAccessible: <div>unAccessible</div>,
     ...initialState?.settings,
+    title: '',
   };
 };
 

+ 0 - 1
src/components/BaseCrud/save/index.tsx

@@ -107,7 +107,6 @@ const Save = <T extends Record<string, any>>(props: Props<T>) => {
 
   const save = async () => {
     const values: T = await (customForm || form).submit();
-    console.log(form.values, 'value', values);
     // 特殊处理通知模版
 
     if (service?.getUri().includes('/notifier/template')) {

+ 7 - 1
src/components/Metadata/ArrayParam/index.tsx

@@ -1,5 +1,5 @@
 import { createSchemaField } from '@formily/react';
-import { FormLayout, Editable, Select, FormItem, Input, NumberPicker } from '@formily/antd';
+import { Editable, FormItem, FormLayout, Input, NumberPicker, Select } from '@formily/antd';
 import type { ISchema } from '@formily/json-schema';
 import './index.less';
 import { DataTypeList, DateTypeList, FileTypeList } from '@/pages/device/data';
@@ -67,6 +67,12 @@ const ArrayParam = () => {
             'x-decorator': 'FormItem',
             'x-component': 'Select',
             'x-visible': false,
+            'x-component-props': {
+              showSearch: true,
+              showArrow: true,
+              filterOption: (input: string, option: any) =>
+                option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0,
+            },
             enum: Store.get('units'),
             'x-reactions': {
               dependencies: ['.type'],

+ 30 - 8
src/components/Metadata/JsonParam/index.tsx

@@ -11,6 +11,8 @@ import { createSchemaField } from '@formily/react';
 import type { ISchema } from '@formily/json-schema';
 import { DataTypeList, DateTypeList } from '@/pages/device/data';
 import { Store } from 'jetlinks-store';
+import { useAsyncDataSource } from '@/utils/util';
+import { service } from '@/pages/device/components/Metadata';
 
 // 不算是自定义组件。只是抽离了JSONSchema
 interface Props {
@@ -30,6 +32,17 @@ const JsonParam = (props: Props) => {
       NumberPicker,
     },
   });
+  const getUnit = () =>
+    service.getUnit().then((resp) => {
+      const _data = resp.result.map((item: any) => ({
+        label: item.description,
+        value: item.id,
+      }));
+      // 缓存单位数据
+      Store.set('units', _data);
+      return _data;
+    });
+
   const schema: ISchema = {
     type: 'object',
     properties: {
@@ -101,15 +114,24 @@ const JsonParam = (props: Props) => {
                       'x-decorator': 'FormItem',
                       'x-component': 'Select',
                       'x-visible': false,
-                      enum: Store.get('units'), // 理论上首层已经就缓存了单位数据,此处可直接获取
-                      'x-reactions': {
-                        dependencies: ['..valueType.type'],
-                        fulfill: {
-                          state: {
-                            visible: "{{['int','float','long','double'].includes($deps[0])}}",
+                      'x-component-props': {
+                        showSearch: true,
+                        showArrow: true,
+                        filterOption: (input: string, option: any) =>
+                          option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0,
+                      },
+                      enum: Store.get('units'),
+                      'x-reactions': [
+                        {
+                          dependencies: ['..valueType.type'],
+                          fulfill: {
+                            state: {
+                              visible: "{{['int','float','long','double'].includes($deps[0])}}",
+                            },
                           },
                         },
-                      },
+                        '{{useAsyncDataSource(getUnit)}}',
+                      ],
                     },
                     format: {
                       title: '时间格式',
@@ -196,6 +218,6 @@ const JsonParam = (props: Props) => {
       },
     },
   };
-  return <SchemaField schema={schema} />;
+  return <SchemaField schema={schema} scope={{ useAsyncDataSource, getUnit }} />;
 };
 export default JsonParam;

+ 27 - 3
src/global.less

@@ -71,6 +71,30 @@ ol {
   }
 }
 
-// .ant-formily-item-colon {
-//   display: none;
-// }
+.ant-pro-top-nav-header-logo {
+  display: flex;
+  justify-content: center;
+}
+
+.ant-form-item-required {
+  &::before {
+    position: absolute;
+    right: -12px;
+  }
+}
+
+.ant-formily-item-label-content {
+  position: relative;
+  overflow: unset;
+
+  .ant-formily-item-asterisk {
+    float: right;
+    margin-right: 0;
+    //position: absolute;
+    //right: -8px;
+  }
+}
+
+.ant-formily-item-colon {
+  display: none;
+}

+ 3 - 6
src/pages/device/Category/index.tsx

@@ -1,12 +1,11 @@
 import { PageContainer } from '@ant-design/pro-layout';
 import Service from '@/pages/device/Category/service';
-import type { ProColumns } from '@jetlinks/pro-table';
+import type { ActionType, ProColumns } from '@jetlinks/pro-table';
+import ProTable from '@jetlinks/pro-table';
 import { DeleteOutlined, EditOutlined, PlusOutlined } from '@ant-design/icons';
 import { Button, message, Popconfirm, Tooltip } from 'antd';
 import { useRef, useState } from 'react';
-import type { ActionType } from '@jetlinks/pro-table';
 import { useIntl } from '@@/plugin-locale/localeExports';
-import ProTable from '@jetlinks/pro-table';
 import Save from '@/pages/device/Category/Save';
 import { model } from '@formily/reactive';
 import { observer } from '@formily/react';
@@ -41,7 +40,6 @@ const Category = observer(() => {
     {
       title: '分类排序',
       dataIndex: 'sortIndex',
-      align: 'center',
       // render: (text) => (
       //   <Space>{text}<EditOutlined onClick={() => {
 
@@ -55,7 +53,6 @@ const Category = observer(() => {
       }),
       dataIndex: 'description',
       width: 300,
-      align: 'center',
       ellipsis: true,
     },
     {
@@ -64,7 +61,7 @@ const Category = observer(() => {
         defaultMessage: '操作',
       }),
       valueType: 'option',
-      align: 'center',
+      width: 200,
       render: (text, record) => [
         <a
           key={'edit'}

+ 0 - 5
src/pages/device/Instance/Detail/ChildDevice/BindChildDevice/index.tsx

@@ -24,11 +24,6 @@ const BindChildDevice = (props: Props) => {
 
   const columns: ProColumns<DeviceInstance>[] = [
     {
-      dataIndex: 'index',
-      valueType: 'indexBorder',
-      width: 48,
-    },
-    {
       title: 'ID',
       dataIndex: 'id',
     },

+ 1 - 5
src/pages/device/Instance/Detail/ChildDevice/index.tsx

@@ -29,11 +29,6 @@ const ChildDevice = () => {
 
   const columns: ProColumns<LogItem>[] = [
     {
-      dataIndex: 'index',
-      valueType: 'indexBorder',
-      width: 48,
-    },
-    {
       title: 'ID',
       dataIndex: 'id',
     },
@@ -184,6 +179,7 @@ const ChildDevice = () => {
         data={{}}
         onCancel={() => {
           setVisible(false);
+          actionRef.current?.reload?.();
         }}
       />
     </Card>

+ 12 - 5
src/pages/device/Instance/Detail/Config/index.tsx

@@ -37,7 +37,15 @@ const Config = () => {
     initialValues: InstanceModel.detail?.configuration,
   });
 
-  const id = InstanceModel.detail?.id;
+  const id = InstanceModel.detail?.id || params?.id;
+
+  const getDetail = () => {
+    service.detail(id || '').then((resp) => {
+      if (resp.status === 200) {
+        InstanceModel.detail = { id, ...resp.result };
+      }
+    });
+  };
 
   useEffect(() => {
     if (id) {
@@ -115,10 +123,7 @@ const Config = () => {
                           configuration: { ...values },
                         });
                         if (resp.status === 200) {
-                          InstanceModel.detail = {
-                            ...InstanceModel.detail,
-                            configuration: { ...values },
-                          };
+                          getDetail();
                         }
                       }
                       setState(!state);
@@ -133,6 +138,7 @@ const Config = () => {
                         const resp = await service.deployDevice(id || '');
                         if (resp.status === 200) {
                           message.success('操作成功');
+                          getDetail();
                         }
                       }}
                     >
@@ -149,6 +155,7 @@ const Config = () => {
                         const resp = await service.configurationReset(id || '');
                         if (resp.status === 200) {
                           message.success('恢复默认配置成功');
+                          getDetail();
                         }
                       }}
                     >

+ 5 - 11
src/pages/device/Instance/Detail/Log/index.tsx

@@ -16,32 +16,27 @@ const Log = () => {
   const [searchParams, setSearchParams] = useState<any>({});
 
   useEffect(() => {
-    service.getLogType().then((resp) => {
+    service.queryLogsType().then((resp) => {
       if (resp.status === 200) {
-        const list = (resp.result as { text: string; type: string }[]).reduce(
+        const list = (resp.result as { text: string; value: string }[]).reduce(
           (previousValue, currentValue) => {
-            previousValue[currentValue.type] = currentValue;
+            previousValue[currentValue.value] = currentValue;
             return previousValue;
           },
           {},
         );
-        setType(list);
+        setType({ ...list });
       }
     });
   }, []);
 
   const columns: ProColumns<LogItem>[] = [
     {
-      dataIndex: 'index',
-      valueType: 'indexBorder',
-      width: 48,
-    },
-    {
       title: '类型',
       dataIndex: 'type',
       renderText: (text) => text.text,
       valueType: 'select',
-      valueEnum: type,
+      valueEnum: { ...type },
     },
     {
       title: '时间',
@@ -49,7 +44,6 @@ const Log = () => {
       defaultSortOrder: 'descend',
       valueType: 'dateTime',
       sorter: true,
-      hideInSearch: true,
     },
     {
       title: '内容',

+ 29 - 3
src/pages/device/Instance/Detail/Running/Event/index.tsx

@@ -1,14 +1,16 @@
 import type { ActionType, ProColumns } from '@jetlinks/pro-table';
 import ProTable from '@jetlinks/pro-table';
-import { service } from '@/pages/device/Instance';
+import { InstanceModel, service } from '@/pages/device/Instance';
 import { useParams } from 'umi';
 import type { EventMetadata } from '@/pages/device/Product/typings';
 import SearchComponent from '@/components/SearchComponent';
 import moment from 'moment';
 import { Form, Modal } from 'antd';
 import { SearchOutlined } from '@ant-design/icons';
-import { useRef, useState } from 'react';
+import { useEffect, useRef, useState } from 'react';
 import MonacoEditor from 'react-monaco-editor';
+import useSendWebsocketMessage from '@/hooks/websocket/useSendWebsocketMessage';
+import { map } from 'rxjs/operators';
 
 interface Props {
   data: Partial<EventMetadata>;
@@ -18,7 +20,9 @@ const EventLog = (props: Props) => {
   const params = useParams<{ id: string }>();
   const { data } = props;
   const actionRef = useRef<ActionType>();
-  const [searchParams, setSearchParams] = useState<any>({});
+  const [searchParams, setSearchParams] = useState<any>({ pageSize: 10 });
+  const device = InstanceModel.detail;
+  const [subscribeTopic] = useSendWebsocketMessage();
 
   const columns: ProColumns<MetadataLogData>[] = [
     {
@@ -63,6 +67,28 @@ const EventLog = (props: Props) => {
     },
   ];
 
+  /**
+   * 订阅事件数据
+   */
+  const subscribeEvent = () => {
+    const id = `instance-info-event-${device.id}-${device.productId}`;
+    const topic = `/dashboard/device/${device.productId}/events/realTime`;
+    subscribeTopic!(id, topic, {
+      deviceId: device.id,
+    })
+      ?.pipe(map((res) => res.payload))
+      .subscribe((payload: any) => {
+        const { value } = payload;
+        if (value) {
+          actionRef.current?.reload?.();
+        }
+      });
+  };
+
+  useEffect(() => {
+    subscribeEvent();
+  }, []);
+
   const createColumn = (): ProColumns[] =>
     data.valueType?.type === 'object'
       ? data.valueType.properties.map(

+ 0 - 1
src/pages/device/Instance/Detail/Running/Property/index.tsx

@@ -28,7 +28,6 @@ const ColResponsiveProps = {
 
 const Property = (props: Props) => {
   const { data } = props;
-  console.log(data);
   const device = InstanceModel.detail;
   const params = useParams<{ id: string }>();
   const [subscribeTopic] = useSendWebsocketMessage();

+ 28 - 26
src/pages/device/Instance/Detail/index.tsx

@@ -5,7 +5,7 @@ import { Badge, Button, Card, Descriptions, Divider, message, Tooltip } from 'an
 import { useEffect, useState } from 'react';
 import { observer } from '@formily/react';
 import Log from '@/pages/device/Instance/Detail/Log';
-import Alarm from '@/pages/device/components/Alarm';
+// import Alarm from '@/pages/device/components/Alarm';
 import Info from '@/pages/device/Instance/Detail/Info';
 import Functions from '@/pages/device/Instance/Detail/Functions';
 import Running from '@/pages/device/Instance/Detail/Running';
@@ -113,32 +113,33 @@ const InstanceDetail = observer(() => {
       tab: '子设备',
       component: <ChildDevice />,
     },
-    {
-      key: 'alarm',
-      tab: intl.formatMessage({
-        id: 'pages.device.instanceDetail.alarm',
-        defaultMessage: '告警设置',
-      }),
-      component: (
-        <Card>
-          <Alarm type="device" />
-        </Card>
-      ),
-    },
-    {
-      key: 'visualization',
-      tab: intl.formatMessage({
-        id: 'pages.device.instanceDetail.visualization',
-        defaultMessage: '可视化',
-      }),
-      component: <div>开发中...</div>,
-    },
+    // {
+    //   key: 'alarm',
+    //   tab: intl.formatMessage({
+    //     id: 'pages.device.instanceDetail.alarm',
+    //     defaultMessage: '告警设置',
+    //   }),
+    //   component: (
+    //     <Card>
+    //       <Alarm type="device" />
+    //     </Card>
+    //   ),
+    // },
+    // {
+    //   key: 'visualization',
+    //   tab: intl.formatMessage({
+    //     id: 'pages.device.instanceDetail.visualization',
+    //     defaultMessage: '可视化',
+    //   }),
+    //   component: <div>开发中...</div>,
+    // },
   ];
 
   useEffect(() => {
     if (!InstanceModel.current && !params.id) {
       history.goBack();
     } else {
+      setTab('detail');
       getDetail(InstanceModel.current?.id || params.id);
     }
     return () => {
@@ -151,9 +152,10 @@ const InstanceDetail = observer(() => {
       onBack={history.goBack}
       onTabChange={setTab}
       tabList={list}
+      tabActiveKey={tab}
       content={
         <Descriptions size="small" column={4}>
-          <Descriptions.Item label={'ID'}>{InstanceModel.detail.id}</Descriptions.Item>
+          <Descriptions.Item label={'ID'}>{InstanceModel.detail?.id}</Descriptions.Item>
           <Descriptions.Item label={'所属产品'}>
             <Button
               type={'link'}
@@ -161,21 +163,21 @@ const InstanceDetail = observer(() => {
               onClick={() => {
                 const url = getMenuPathByParams(
                   MENUS_CODE['device/Product/Detail'],
-                  InstanceModel.detail.productId,
+                  InstanceModel.detail?.productId,
                 );
                 history.replace(url);
               }}
             >
-              {InstanceModel.detail.productName}
+              {InstanceModel.detail?.productName}
             </Button>
           </Descriptions.Item>
         </Descriptions>
       }
       title={
         <>
-          {InstanceModel.detail.name}
+          {InstanceModel.detail?.name}
           <Divider type="vertical" />
-          {deviceStatus.get(InstanceModel.detail.state?.value)}
+          {deviceStatus.get(InstanceModel.detail?.state?.value)}
         </>
       }
       // extra={[

+ 4 - 0
src/pages/device/Instance/Export/index.tsx

@@ -62,6 +62,10 @@ const Export = (props: Props) => {
             enum: [...productList],
             'x-component-props': {
               allowClear: true,
+              showSearch: true,
+              showArrow: true,
+              filterOption: (input: string, option: any) =>
+                option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0,
             },
           },
           fileType: {

+ 6 - 0
src/pages/device/Instance/Import/index.tsx

@@ -233,6 +233,12 @@ const Import = (props: Props) => {
             'x-decorator': 'FormItem',
             'x-component': 'Select',
             enum: [...productList],
+            'x-component-props': {
+              showSearch: true,
+              showArrow: true,
+              filterOption: (input: string, option: any) =>
+                option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0,
+            },
           },
           fileType: {
             title: '文件格式',

+ 0 - 1
src/pages/device/Instance/index.tsx

@@ -237,7 +237,6 @@ const Instance = () => {
         defaultMessage: '操作',
       }),
       valueType: 'option',
-      align: 'center',
       width: 200,
       render: (text, record) => tools(record),
     },

+ 1 - 1
src/pages/device/Instance/service.ts

@@ -130,7 +130,7 @@ class Service extends BaseService<DeviceInstance> {
       data: params,
     });
 
-  public getLogType = () =>
+  public queryLogsType = () =>
     request(`/${SystemConst.API_BASE}/dictionary/device-log-type/items`, {
       method: 'GET',
     });

+ 48 - 27
src/pages/device/Product/Detail/Access/AccessConfig/index.tsx

@@ -48,6 +48,25 @@ const AccessConfig = (props: Props) => {
       title: '名称',
       dataIndex: 'name',
     },
+    {
+      title: '状态',
+      dataIndex: 'state',
+      valueType: 'select',
+      valueEnum: {
+        disabled: {
+          text: '已停止',
+          status: 'disabled',
+        },
+        enabled: {
+          text: '已启动',
+          status: 'enabled',
+        },
+      },
+    },
+    {
+      title: '说明',
+      dataIndex: 'description',
+    },
   ];
 
   useEffect(() => {
@@ -89,36 +108,38 @@ const AccessConfig = (props: Props) => {
         }
       }}
     >
-      <SearchComponent
-        field={columns}
-        pattern={'simple'}
-        onSearch={(data: any) => {
-          const dt = {
-            pageSize: 4,
-            terms: [...data.terms],
-          };
-          handleSearch(dt);
-        }}
-        onReset={() => {
-          handleSearch({ pageSize: 4 });
-        }}
-      />
-      <div style={{ display: 'flex', justifyContent: 'flex-end' }}>
-        <Button
-          type="primary"
-          onClick={() => {
-            const tab: any = window.open(`${origin}/#/link/AccessConfig/Detail`);
-            tab!.onTabSaveSuccess = (value: any) => {
-              if (value.status === 200) {
-                handleSearch(param);
-              }
+      <div className={styles.search}>
+        <SearchComponent
+          field={columns}
+          pattern={'simple'}
+          onSearch={(data: any) => {
+            const dt = {
+              pageSize: 4,
+              terms: [...data.terms],
             };
+            handleSearch(dt);
+          }}
+          onReset={() => {
+            handleSearch({ pageSize: 4 });
           }}
-        >
-          新增
-        </Button>
+        />
+        <div style={{ display: 'flex', justifyContent: 'flex-end' }}>
+          <Button
+            type="primary"
+            onClick={() => {
+              const tab: any = window.open(`${origin}/#/link/AccessConfig/Detail`);
+              tab!.onTabSaveSuccess = (value: any) => {
+                if (value.status === 200) {
+                  handleSearch(param);
+                }
+              };
+            }}
+          >
+            新增
+          </Button>
+        </div>
       </div>
-      <Row gutter={[16, 16]} style={{ marginTop: 10 }}>
+      <Row gutter={[16, 16]}>
         {dataSource.data.map((item: any) => (
           <Col key={item.name} span={12}>
             <Card

+ 22 - 0
src/pages/device/Product/Detail/Access/index.less

@@ -89,3 +89,25 @@
     content: '|';
   }
 }
+
+.search {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-bottom: 24px;
+
+  :global {
+    .ant-card {
+      margin-bottom: 0 !important;
+      border: none !important;
+    }
+
+    .ant-formily-item-feedback-layout-loose {
+      margin-bottom: 0 !important;
+    }
+
+    .ant-card-body {
+      padding: 24px !important;
+    }
+  }
+}

+ 31 - 29
src/pages/device/Product/Detail/Access/index.tsx

@@ -62,16 +62,9 @@ const Access = () => {
       ),
     },
     {
-      title: 'qos',
-      dataIndex: 'qos',
-      key: 'qos',
-      ellipsis: true,
-      align: 'center',
-    },
-    {
-      title: '地址',
-      dataIndex: 'address',
-      key: 'address',
+      title: 'topic',
+      dataIndex: 'topic',
+      key: 'topic',
       ellipsis: true,
       align: 'center',
       render: (text: any) => (
@@ -81,15 +74,15 @@ const Access = () => {
       ),
     },
     {
-      title: 'topic',
-      dataIndex: 'topic',
-      key: 'topic',
+      title: '上下行',
+      dataIndex: 'stream',
+      key: 'stream',
       ellipsis: true,
       align: 'center',
-      render: (text: any) => (
-        <Tooltip placement="top" title={text}>
-          {text}
-        </Tooltip>
+      render: (text: any, record: any) => (
+        <span>
+          上行: {String(record?.upstream)}, 下行: {String(record?.downstream)}
+        </span>
       ),
     },
     {
@@ -108,9 +101,9 @@ const Access = () => {
 
   const columnsHTTP: any[] = [
     {
-      title: '地址',
-      dataIndex: 'address',
-      key: 'address',
+      title: '分组',
+      dataIndex: 'group',
+      key: 'group',
       ellipsis: true,
       align: 'center',
       render: (text: any) => (
@@ -120,9 +113,9 @@ const Access = () => {
       ),
     },
     {
-      title: '分组',
-      dataIndex: 'group',
-      key: 'group',
+      title: '地址',
+      dataIndex: 'address',
+      key: 'address',
       ellipsis: true,
       align: 'center',
       render: (text: any) => (
@@ -214,13 +207,23 @@ const Access = () => {
             <Descriptions.Item label="接入方式">
               {providers.find((i) => i.id === access?.provider)?.name || ''}
             </Descriptions.Item>
-            <Descriptions.Item>
-              {providers.find((i) => i.id === access?.provider)?.description || ''}
-            </Descriptions.Item>
+            {providers.find((i) => i.id === access?.provider)?.description && (
+              <Descriptions.Item label="">
+                <span style={{ color: 'rgba(0,0,0,0.55)' }}>
+                  {providers.find((i) => i.id === access?.provider)?.description || ''}
+                </span>
+              </Descriptions.Item>
+            )}
             <Descriptions.Item label="消息协议">
               {access?.protocolDetail?.name || ''}
             </Descriptions.Item>
-            <Descriptions.Item>{access?.protocolDetail?.description || ''}</Descriptions.Item>
+            {access?.protocolDetail?.description && (
+              <Descriptions.Item label="">
+                <span style={{ color: 'rgba(0,0,0,0.55)' }}>
+                  {access?.protocolDetail?.description || ''}
+                </span>
+              </Descriptions.Item>
+            )}
             <Descriptions.Item label="网络组件">
               {(networkList.find((i) => i.id === access?.channelId)?.addresses || []).map(
                 (item: any) => (
@@ -237,12 +240,11 @@ const Access = () => {
           </Descriptions>
           {config?.routes && config?.routes?.length > 0 && (
             <div>
-              <div>路由信息:</div>
               <Table
                 dataSource={config?.routes || []}
                 columns={config.id === 'MQTT' ? columnsMQTT : columnsHTTP}
                 pagination={false}
-                scroll={{ x: 500 }}
+                scroll={{ y: 240 }}
               />
             </div>
           )}

+ 6 - 6
src/pages/device/Product/Detail/index.tsx

@@ -56,11 +56,16 @@ const ProductDetail = observer(() => {
     service.getProductDetail(param?.id).subscribe((data) => {
       if (data.metadata) {
         const metadata: DeviceMetadata = JSON.parse(data.metadata);
-        productModel.current = data;
         MetadataAction.insert(metadata);
       }
+      service.instanceCount(encodeQuery({ terms: { productId: param?.id } })).then((res: any) => {
+        if (res.status === 200) {
+          productModel.current = { ...data, count: res.result };
+        }
+      });
     });
   };
+
   useEffect(() => {
     const subscription = Store.subscribe(SystemConst.GET_METADATA, () => {
       MetadataAction.clean();
@@ -73,11 +78,6 @@ const ProductDetail = observer(() => {
       history.goBack();
     } else {
       initMetadata();
-      service.instanceCount(encodeQuery({ terms: { productId: param?.id } })).then((res: any) => {
-        if (res.status === 200) {
-          productModel.current!.count = res.result;
-        }
-      });
     }
     return () => {
       MetadataAction.clean();

+ 0 - 1
src/pages/device/Product/index.tsx

@@ -251,7 +251,6 @@ const Product = observer(() => {
         defaultMessage: '操作',
       }),
       valueType: 'option',
-      align: 'center',
       width: 200,
       render: (_, record) => tools(record),
     },

+ 7 - 1
src/pages/device/components/Metadata/Base/Edit/index.tsx

@@ -414,6 +414,13 @@ const Edit = observer((props: Props) => {
             'x-component': 'Select',
             enum: PropertySource,
           },
+          'virtualRule.type': {
+            type: 'string',
+            'x-value': 'script',
+            'x-component': 'Input',
+            'x-decorator': 'FormItem',
+            'x-hidden': true,
+          },
           'virtualRule.script': {
             type: 'string',
             'x-component': 'FRuleEditor',
@@ -770,7 +777,6 @@ const Edit = observer((props: Props) => {
       }
     };
 
-    console.log(typeMap.get(props.type), 'log');
     const _data = updateMetadata(type, [params], typeMap.get(props.type), updateDB);
     // console.log(params, JSON.parse(_data.metadata));
     // if (props.type === 'product') {

+ 5 - 5
src/pages/device/components/Metadata/Base/columns.ts

@@ -2,11 +2,11 @@ import type { ProColumns } from '@jetlinks/pro-table';
 import type { MetadataItem } from '@/pages/device/Product/typings';
 
 const BaseColumns: ProColumns<MetadataItem>[] = [
-  {
-    dataIndex: 'index',
-    valueType: 'indexBorder',
-    width: 48,
-  },
+  // {
+  //   dataIndex: 'index',
+  //   valueType: 'indexBorder',
+  //   width: 48,
+  // },
   {
     title: '标识',
     dataIndex: 'id',

+ 7 - 3
src/pages/link/AccessConfig/Detail/Access/index.less

@@ -19,7 +19,11 @@
 }
 
 .title {
-  font-weight: 600;
+  width: '100%';
+  overflow: hidden;
+  font-weight: 800;
+  white-space: nowrap;
+  text-overflow: ellipsis;
 }
 
 .desc {
@@ -35,12 +39,12 @@
 
 .cardContent {
   display: flex;
-  margin-top: 10px;
+  flex-direction: column;
+  margin-top: 5px;
   color: rgba(0, 0, 0, 0.55);
 
   .item {
     width: 100%;
-    margin: 5px 0;
     overflow: hidden;
     white-space: nowrap;
     text-overflow: ellipsis;

+ 107 - 89
src/pages/link/AccessConfig/Detail/Access/index.tsx

@@ -168,16 +168,9 @@ const Access = (props: Props) => {
       ),
     },
     {
-      title: 'qos',
-      dataIndex: 'qos',
-      key: 'qos',
-      ellipsis: true,
-      align: 'center',
-    },
-    {
-      title: '地址',
-      dataIndex: 'address',
-      key: 'address',
+      title: 'topic',
+      dataIndex: 'topic',
+      key: 'topic',
       ellipsis: true,
       align: 'center',
       render: (text: any) => (
@@ -187,15 +180,15 @@ const Access = (props: Props) => {
       ),
     },
     {
-      title: 'topic',
-      dataIndex: 'topic',
-      key: 'topic',
+      title: '上下行',
+      dataIndex: 'stream',
+      key: 'stream',
       ellipsis: true,
       align: 'center',
-      render: (text: any) => (
-        <Tooltip placement="top" title={text}>
-          {text}
-        </Tooltip>
+      render: (text: any, record: any) => (
+        <span>
+          上行: {String(record?.upstream)}, 下行: {String(record?.downstream)}
+        </span>
       ),
     },
     {
@@ -314,13 +307,23 @@ const Access = (props: Props) => {
                     >
                       <div className={styles.title}>{item.name}</div>
                       <div className={styles.cardContent}>
-                        <div style={{ width: '100%', height: '50px' }}>
+                        <div
+                          style={{
+                            width: '100%',
+                            height: '40px',
+                            display: 'flex',
+                            flexDirection: 'column',
+                            alignItems: 'center',
+                            justifyContent: 'center',
+                          }}
+                        >
                           {item.addresses.slice(0, 2).map((i: any) => (
                             <div className={styles.item} key={i.address}>
                               <Badge color={i.health === -1 ? 'red' : 'green'} text={i.address} />
                             </div>
                           ))}
                         </div>
+                        <div className={styles.desc}>{item?.description || ''}</div>
                       </div>
                     </Card>
                   </Col>
@@ -330,7 +333,7 @@ const Access = (props: Props) => {
               <Empty
                 description={
                   <span>
-                    暂无数据{' '}
+                    暂无数据
                     <a
                       onClick={() => {
                         const tab: any = window.open(`${origin}/#/link/Type/Save/:id`);
@@ -400,8 +403,10 @@ const Access = (props: Props) => {
                         setProcotolCurrent(item.id);
                       }}
                     >
-                      <div className={styles.title}>{item.name}</div>
-                      <div className={styles.desc}>{item.description}</div>
+                      <div style={{ height: '45px' }}>
+                        <div className={styles.title}>{item.name}</div>
+                        <div className={styles.desc}>{item.description}</div>
+                      </div>
                     </Card>
                   </Col>
                 ))}
@@ -446,6 +451,68 @@ const Access = (props: Props) => {
                   <Input.TextArea showCount maxLength={200} />
                 </Form.Item>
               </Form>
+              <div className={styles.action}>
+                <Button style={{ margin: '0 8px' }} onClick={() => prev()}>
+                  上一步
+                </Button>
+                <Button
+                  type="primary"
+                  onClick={async () => {
+                    try {
+                      const values = await form.validateFields();
+                      // 编辑还是保存
+                      if (!params.get('id')) {
+                        service
+                          .save({
+                            name: values.name,
+                            description: values.description,
+                            provider: props.data.id,
+                            protocol: procotolCurrent,
+                            transport: ProcotoleMapping.get(props.data.id),
+                            channel: 'network', // 网络组件
+                            channelId: networkCurrent,
+                          })
+                          .then((resp: any) => {
+                            if (resp.status === 200) {
+                              message.success('操作成功!');
+                              history.goBack();
+                              if ((window as any).onTabSaveSuccess) {
+                                (window as any).onTabSaveSuccess(resp);
+                                setTimeout(() => window.close(), 300);
+                              }
+                            }
+                          });
+                      } else {
+                        service
+                          .update({
+                            id: access?.id,
+                            name: values.name,
+                            description: values.description,
+                            provider: access?.provider,
+                            protocol: procotolCurrent,
+                            transport: access?.transport,
+                            channel: 'network', // 网络组件
+                            channelId: networkCurrent,
+                          })
+                          .then((resp: any) => {
+                            if (resp.status === 200) {
+                              message.success('操作成功!');
+                              history.goBack();
+                              if ((window as any).onTabSaveSuccess) {
+                                (window as any).onTabSaveSuccess(resp);
+                                setTimeout(() => window.close(), 300);
+                              }
+                            }
+                          });
+                      }
+                    } catch (errorInfo) {
+                      console.error('Failed:', errorInfo);
+                    }
+                  }}
+                >
+                  保存
+                </Button>
+              </div>
             </div>
             <div className={styles.config}>
               <div className={styles.title}>配置概览</div>
@@ -453,16 +520,25 @@ const Access = (props: Props) => {
                 <Descriptions.Item label="接入方式">
                   {props.data?.name || providers.find((i) => i.id === access?.provider)?.name}
                 </Descriptions.Item>
-                <Descriptions.Item>
-                  {props.data?.description ||
-                    providers.find((i) => i.id === access?.provider)?.description}
-                </Descriptions.Item>
+                {(props.data?.description ||
+                  providers.find((i) => i.id === access?.provider)?.description) && (
+                  <Descriptions.Item>
+                    <span style={{ color: 'rgba(0,0,0,0.55)' }}>
+                      {props.data?.description ||
+                        providers.find((i) => i.id === access?.provider)?.description}
+                    </span>
+                  </Descriptions.Item>
+                )}
                 <Descriptions.Item label="消息协议">
                   {procotolList.find((i) => i.id === procotolCurrent)?.name || ''}
                 </Descriptions.Item>
-                <Descriptions.Item>
-                  {procotolList.find((i) => i.id === procotolCurrent)?.description || ''}
-                </Descriptions.Item>
+                {procotolList.find((i) => i.id === procotolCurrent)?.description && (
+                  <Descriptions.Item style={{ color: 'rgba(0,0,0,0.55)' }}>
+                    <span style={{ color: 'rgba(0,0,0,0.55)' }}>
+                      {procotolList.find((i) => i.id === procotolCurrent)?.description || ''}
+                    </span>
+                  </Descriptions.Item>
+                )}
                 <Descriptions.Item label="网络组件">
                   {(networkList.find((i) => i.id === networkCurrent)?.addresses || []).map(
                     (item: any) => (
@@ -479,12 +555,11 @@ const Access = (props: Props) => {
               </Descriptions>
               {config?.routes && config?.routes?.length > 0 && (
                 <div>
-                  <div>路由信息:</div>
                   <Table
                     dataSource={config?.routes || []}
                     columns={config.id === 'MQTT' ? columnsMQTT : columnsHTTP}
                     pagination={false}
-                    scroll={{ x: 500 }}
+                    scroll={{ y: 240 }}
                   />
                 </div>
               )}
@@ -520,73 +595,16 @@ const Access = (props: Props) => {
         </div>
         <div className={styles.content}>{renderSteps(current)}</div>
         <div className={styles.action}>
-          {current > 0 && (
+          {current === 1 && (
             <Button style={{ margin: '0 8px' }} onClick={() => prev()}>
               上一步
             </Button>
           )}
-          {current < steps.length - 1 && (
+          {(current === 0 || current === 1) && (
             <Button type="primary" onClick={() => next()}>
               下一步
             </Button>
           )}
-          {current === steps.length - 1 && (
-            <Button
-              type="primary"
-              onClick={async () => {
-                try {
-                  const values = await form.validateFields();
-                  // 编辑还是保存
-                  if (!params.get('id')) {
-                    const param = {
-                      name: values.name,
-                      description: values.description,
-                      provider: props.data.id,
-                      protocol: procotolCurrent,
-                      transport: ProcotoleMapping.get(props.data.id),
-                      channel: 'network', // 网络组件
-                      channelId: networkCurrent,
-                    };
-                    service.save(param).then((resp: any) => {
-                      if (resp.status === 200) {
-                        message.success('操作成功!');
-                        history.goBack();
-                        if ((window as any).onTabSaveSuccess) {
-                          (window as any).onTabSaveSuccess(resp);
-                          setTimeout(() => window.close(), 300);
-                        }
-                      }
-                    });
-                  } else {
-                    const param = {
-                      id: access?.id,
-                      name: values.name,
-                      description: values.description,
-                      provider: access?.provider,
-                      protocol: procotolCurrent,
-                      transport: access?.transport,
-                      channel: 'network', // 网络组件
-                      channelId: networkCurrent,
-                    };
-                    service.update(param).then((resp: any) => {
-                      if (resp.status === 200) {
-                        message.success('操作成功!');
-                        history.goBack();
-                        if ((window as any).onTabSaveSuccess) {
-                          (window as any).onTabSaveSuccess(resp);
-                          setTimeout(() => window.close(), 300);
-                        }
-                      }
-                    });
-                  }
-                } catch (errorInfo) {
-                  console.error('Failed:', errorInfo);
-                }
-              }}
-            >
-              保存
-            </Button>
-          )}
         </div>
       </div>
     </Card>

+ 23 - 2
src/pages/link/AccessConfig/index.tsx

@@ -20,6 +20,25 @@ const AccessConfig = () => {
       title: '名称',
       dataIndex: 'name',
     },
+    {
+      title: '状态',
+      dataIndex: 'state',
+      valueType: 'select',
+      valueEnum: {
+        disabled: {
+          text: '已停止',
+          status: 'disabled',
+        },
+        enabled: {
+          text: '已启动',
+          status: 'enabled',
+        },
+      },
+    },
+    {
+      title: '说明',
+      dataIndex: 'description',
+    },
   ];
 
   const [dataSource, setDataSource] = useState<any>({
@@ -154,7 +173,7 @@ const AccessConfig = () => {
                           </a>
                         </div>
                       </div>
-                      <div className={styles.desc}>这里是接入方式的解释说明</div>
+                      <div className={styles.desc}>{item.description}</div>
                     </div>
                     <div className={styles.container}>
                       <div className={styles.server}>
@@ -169,7 +188,9 @@ const AccessConfig = () => {
                       </div>
                       <div className={styles.procotol}>
                         <div className={styles.title}>{item?.protocolDetail?.name}</div>
-                        <p style={{ color: 'rgba(0, 0, 0, .55)' }}>{item.description}</p>
+                        <p style={{ color: 'rgba(0, 0, 0, .55)' }}>
+                          {item.protocolDetail?.description}
+                        </p>
                       </div>
                     </div>
                   </div>

+ 30 - 25
src/pages/link/Protocol/FileUpload/index.tsx

@@ -2,7 +2,7 @@ import SystemConst from '@/utils/const';
 import Token from '@/utils/token';
 import { useState } from 'react';
 import { connect } from '@formily/react';
-import { Button, Input, message, Upload } from 'antd';
+import { Button, Input, message, Spin, Upload } from 'antd';
 import type { UploadChangeParam } from 'antd/lib/upload/interface';
 
 interface Props {
@@ -13,41 +13,46 @@ interface Props {
 
 const FileUpload = connect((props: Props) => {
   const [url, setUrl] = useState<string>(props?.value);
+  const [loading, setLoading] = useState<boolean>(false);
 
   const handleChange = (info: UploadChangeParam) => {
+    setLoading(true);
     if (info.file.status === 'done') {
       message.success('上传成功!');
       info.file.url = info.file.response?.result;
       setUrl(info.file.response?.result);
+      setLoading(false);
       props.onChange(info.file.response?.result);
     }
   };
 
   return (
-    <Upload
-      accept={props?.accept || '*'}
-      listType={'text'}
-      action={`/${SystemConst.API_BASE}/file/static`}
-      headers={{
-        'X-Access-Token': Token.get(),
-      }}
-      onChange={handleChange}
-      showUploadList={false}
-    >
-      <Input.Group compact>
-        <Input
-          style={{ width: 'calc(100% - 100px)' }}
-          value={url}
-          onClick={(e) => {
-            e.preventDefault();
-            e.stopPropagation();
-          }}
-        />
-        <Button shape="round" style={{ width: '100px', textAlign: 'center' }} type="primary">
-          上传jar包
-        </Button>
-      </Input.Group>
-    </Upload>
+    <Spin spinning={loading}>
+      <Upload
+        accept={props?.accept || '*'}
+        listType={'text'}
+        action={`/${SystemConst.API_BASE}/file/static`}
+        headers={{
+          'X-Access-Token': Token.get(),
+        }}
+        onChange={handleChange}
+        showUploadList={false}
+      >
+        <Input.Group compact>
+          <Input
+            style={{ width: 'calc(100% - 100px)' }}
+            value={url}
+            onClick={(e) => {
+              e.preventDefault();
+              e.stopPropagation();
+            }}
+          />
+          <Button shape="round" style={{ width: '100px', textAlign: 'center' }} type="primary">
+            上传jar包
+          </Button>
+        </Input.Group>
+      </Upload>
+    </Spin>
   );
 });
 export default FileUpload;

+ 23 - 7
src/pages/link/Protocol/index.tsx

@@ -31,18 +31,15 @@ const Protocol = () => {
 
   const columns: ProColumns<ProtocolItem>[] = [
     {
-      dataIndex: 'index',
-      valueType: 'indexBorder',
-      width: 48,
-    },
-    {
       dataIndex: 'id',
       title: 'ID',
       sorter: true,
+      ellipsis: true,
       defaultSortOrder: 'ascend',
     },
     {
       dataIndex: 'name',
+      ellipsis: true,
       title: intl.formatMessage({
         id: 'pages.table.name',
         defaultMessage: '名称',
@@ -51,6 +48,7 @@ const Protocol = () => {
     {
       dataIndex: 'type',
       title: '类型',
+      ellipsis: true,
     },
     {
       dataIndex: 'state',
@@ -61,6 +59,7 @@ const Protocol = () => {
     },
     {
       dataIndex: 'description',
+      ellipsis: true,
       title: '说明',
     },
     {
@@ -69,7 +68,6 @@ const Protocol = () => {
         defaultMessage: '操作',
       }),
       valueType: 'option',
-      align: 'center',
       width: 200,
       render: (text, record) => [
         <a
@@ -117,7 +115,7 @@ const Protocol = () => {
               : '请先禁用该组件,再删除。'
           }
         >
-          <Button style={{ padding: 0 }} key="delete" type="link" disabled={record.state !== 1}>
+          <Button style={{ padding: 0 }} key="delete" type="link" disabled={record.state === 1}>
             <Popconfirm
               title={intl.formatMessage({
                 id: 'pages.data.option.remove.tips',
@@ -188,6 +186,24 @@ const Protocol = () => {
                 validateId: true,
                 message: 'ID只能由数字、26个英文字母或者下划线组成',
               },
+              {
+                triggerType: 'onBlur',
+                validator: (value: string) => {
+                  if (!value) return;
+                  return new Promise((resolve) => {
+                    service
+                      .validator(value)
+                      .then((resp) => {
+                        if (!!resp?.result) {
+                          resolve('ID已存在');
+                        } else {
+                          resolve('');
+                        }
+                      })
+                      .catch(() => '验证失败!');
+                  });
+                },
+              },
             ],
           },
           name: {

+ 3 - 0
src/pages/link/Protocol/service.ts

@@ -1,6 +1,7 @@
 import type { ProtocolItem } from '@/pages/link/Protocol/typings';
 import { request } from 'umi';
 import BaseService from '@/utils/BaseService';
+import SystemConst from '@/utils/const';
 
 class Service extends BaseService<ProtocolItem> {
   public modifyState = (id: string, action: 'deploy' | 'un-deploy') =>
@@ -13,6 +14,8 @@ class Service extends BaseService<ProtocolItem> {
 
   public debug = (type: 'encode' | 'decode', data: Record<string, unknown>) =>
     request(`${this.uri}/${type}`, { method: 'POST', data });
+
+  public validator = (id: string) => request(`${SystemConst.API_BASE}/protocol/${id}/exists`);
 }
 
 export default Service;

+ 2 - 2
src/pages/link/Type/Save/index.tsx

@@ -247,7 +247,7 @@ const Save = observer(() => {
           gridSpan: 1,
           labelAlign: 'left',
           layout: 'vertical',
-          tooltip: '绑定到服务器上的网卡地址,绑定到所有网卡:0.0.0.0 /',
+          tooltip: '绑定到服务器上的网卡地址,绑定到所有网卡:0.0.0.0',
         },
         required: true,
         'x-reactions': {
@@ -267,7 +267,7 @@ const Save = observer(() => {
         'x-decorator-props': {
           gridSpan: 1,
           labelAlign: 'left',
-          tooltip: '监听指定端口的UDP请求',
+          tooltip: '监听指定端口的请求',
           layout: 'vertical',
         },
         required: true,

+ 0 - 2
src/pages/link/Type/index.tsx

@@ -57,7 +57,6 @@ const Network = () => {
       dataIndex: 'configuration',
       title: '详情',
       renderText: (text, record) => {
-        console.log(record, '详情');
         if (record.shareCluster) {
           const publicHost = record.configuration.publicHost;
           const publicPort = record.configuration.publicPort;
@@ -103,7 +102,6 @@ const Network = () => {
         defaultMessage: '操作',
       }),
       valueType: 'option',
-      align: 'center',
       width: 200,
       render: (text, record) => [
         <a

+ 0 - 1
src/pages/system/Department/Member/index.tsx

@@ -119,7 +119,6 @@ const Member = observer(() => {
         defaultMessage: '操作',
       }),
       valueType: 'option',
-      align: 'center',
       width: 200,
       render: (text, record) => [
         <Popconfirm

+ 0 - 1
src/pages/system/Department/index.tsx

@@ -79,7 +79,6 @@ export default observer(() => {
         defaultMessage: '操作',
       }),
       valueType: 'option',
-      align: 'center',
       width: 240,
       render: (text, record) => [
         <a

+ 1 - 1
src/pages/system/Menu/components/permission.tsx

@@ -1,4 +1,4 @@
-import React, { useEffect, useState, useRef } from 'react';
+import React, { useEffect, useRef, useState } from 'react';
 import { Checkbox } from 'antd';
 import './permission.less';
 import type { CheckboxChangeEvent } from 'antd/es/checkbox';

+ 0 - 1
src/pages/system/Menu/index.tsx

@@ -124,7 +124,6 @@ export default observer(() => {
         defaultMessage: '操作',
       }),
       valueType: 'option',
-      align: 'center',
       width: 240,
       render: (_, record) => [
         <a

+ 260 - 0
src/pages/system/Permission/Save/index.tsx

@@ -0,0 +1,260 @@
+import { message, Modal } from 'antd';
+import { useIntl } from 'umi';
+import { createForm, onFormSubmitStart } from '@formily/core';
+import { createSchemaField } from '@formily/react';
+import React, { useEffect, useState } from 'react';
+import * as ICONS from '@ant-design/icons';
+import { ArrayTable, Form, FormItem, Input, Editable } from '@formily/antd';
+import type { ISchema } from '@formily/json-schema';
+import type { PermissionItem } from '@/pages/system/Permission/typings';
+import { service } from '@/pages/system/Permission';
+
+interface Props {
+  model: 'add' | 'edit' | 'query';
+  data: Partial<PermissionItem>;
+  close: () => void;
+}
+
+const defaultAction = [
+  { action: 'query', name: '查询', describe: '查询' },
+  { action: 'save', name: '保存', describe: '保存' },
+  { action: 'delete', name: '删除', describe: '删除' },
+];
+
+const Save = (props: Props) => {
+  const { model } = props;
+  const intl = useIntl();
+
+  const [data, setData] = useState<Partial<PermissionItem>>(props.data);
+
+  useEffect(() => {
+    if (model === 'edit') {
+      setData(props.data);
+    } else {
+      setData({});
+    }
+  }, [props.data, props.model]);
+
+  const form = createForm({
+    validateFirst: true,
+    initialValues: data,
+    effects: () => {
+      onFormSubmitStart((formEffect) => {
+        formEffect.values.actions = formEffect.values.actions?.filter(
+          (item: any) => Object.keys(item).length > 0,
+        );
+      });
+    },
+  });
+
+  const SchemaField = createSchemaField({
+    components: {
+      FormItem,
+      Input,
+      ArrayTable,
+      Editable,
+    },
+    scope: {
+      icon(name: any) {
+        return React.createElement(ICONS[name]);
+      },
+    },
+  });
+
+  const schema: ISchema = {
+    type: 'object',
+    properties: {
+      id: {
+        title: intl.formatMessage({
+          id: 'pages.system.permission.id',
+          defaultMessage: '标识(ID)',
+        }),
+        type: 'string',
+        'x-decorator': 'FormItem',
+        'x-component': 'Input',
+        name: 'id',
+        'x-decorator-props': {
+          tooltip: <div>标识ID需与代码中的标识ID一致</div>,
+        },
+        'x-validator': [
+          {
+            max: 64,
+            message: '最多可输入64个字符',
+          },
+          {
+            required: true,
+            message: '请输入标识(ID)',
+          },
+        ],
+      },
+      name: {
+        title: intl.formatMessage({
+          id: 'pages.table.name',
+          defaultMessage: '名称',
+        }),
+        type: 'string',
+        'x-decorator': 'FormItem',
+        'x-component': 'Input',
+        name: 'name',
+        'x-validator': [
+          {
+            max: 64,
+            message: '最多可输入64个字符',
+          },
+          {
+            required: true,
+            message: '请输入名称',
+          },
+        ],
+      },
+      actions: {
+        type: 'array',
+        'x-decorator': 'FormItem',
+        'x-component': 'ArrayTable',
+        default: defaultAction,
+        title: '操作类型',
+        items: {
+          type: 'object',
+          properties: {
+            column1: {
+              type: 'void',
+              'x-component': 'ArrayTable.Column',
+              'x-component-props': { width: 80, title: '-', align: 'center' },
+              properties: {
+                index: {
+                  type: 'void',
+                  'x-component': 'ArrayTable.Index',
+                },
+              },
+            },
+            column2: {
+              type: 'void',
+              'x-component': 'ArrayTable.Column',
+              'x-component-props': {
+                width: 200,
+                title: intl.formatMessage({
+                  id: 'pages.system.permission.addConfigurationType',
+                  defaultMessage: '操作类型',
+                }),
+              },
+              properties: {
+                action: {
+                  type: 'string',
+                  'x-decorator': 'Editable',
+                  'x-component': 'Input',
+                },
+              },
+            },
+            column3: {
+              type: 'void',
+              'x-component': 'ArrayTable.Column',
+              'x-component-props': {
+                width: 200,
+                title: intl.formatMessage({
+                  id: 'pages.table.name',
+                  defaultMessage: '名称',
+                }),
+              },
+              properties: {
+                name: {
+                  type: 'string',
+
+                  'x-decorator': 'Editable',
+                  'x-component': 'Input',
+                },
+              },
+            },
+            column4: {
+              type: 'void',
+              'x-component': 'ArrayTable.Column',
+              'x-component-props': {
+                width: 200,
+                title: intl.formatMessage({
+                  id: 'pages.table.describe',
+                  defaultMessage: '描述',
+                }),
+              },
+              properties: {
+                describe: {
+                  type: 'string',
+                  'x-decorator': 'Editable',
+                  'x-component': 'Input',
+                },
+              },
+            },
+            column5: {
+              type: 'void',
+              'x-component': 'ArrayTable.Column',
+              'x-component-props': {
+                title: intl.formatMessage({
+                  id: 'pages.data.option',
+                  defaultMessage: '操作',
+                }),
+                dataIndex: 'operations',
+                width: 200,
+                fixed: 'right',
+              },
+              properties: {
+                item: {
+                  type: 'void',
+                  'x-component': 'FormItem',
+                  properties: {
+                    remove: {
+                      type: 'void',
+                      'x-component': 'ArrayTable.Remove',
+                    },
+                  },
+                },
+              },
+            },
+          },
+        },
+        properties: {
+          add: {
+            type: 'void',
+            'x-component': 'ArrayTable.Addition',
+            title: intl.formatMessage({
+              id: 'pages.system.permission.add',
+              defaultMessage: '添加条目',
+            }),
+          },
+        },
+      },
+    },
+  };
+
+  const save = async () => {
+    const value = await form.submit<UserItem>();
+    const response = await service.update(value);
+    if (response.status === 200) {
+      message.success(
+        intl.formatMessage({
+          id: 'pages.data.option.success',
+          defaultMessage: '操作成功',
+        }),
+      );
+      props.close();
+    } else {
+      message.error('操作失败!');
+    }
+  };
+
+  return (
+    <Modal
+      title={intl.formatMessage({
+        id: `pages.data.option.${model}`,
+        defaultMessage: '编辑',
+      })}
+      maskClosable={false}
+      visible={model !== 'query'}
+      onCancel={props.close}
+      onOk={save}
+      width="1000px"
+    >
+      <Form form={form} layout="vertical">
+        <SchemaField schema={schema} />
+      </Form>
+    </Modal>
+  );
+};
+export default Save;

+ 64 - 243
src/pages/system/Permission/index.tsx

@@ -1,55 +1,39 @@
 import { PageContainer } from '@ant-design/pro-layout';
-import React, { useEffect, useRef } from 'react';
+import React, { useRef, useState } from 'react';
 import {
   CloseCircleOutlined,
   DeleteOutlined,
   EditOutlined,
   PlayCircleOutlined,
+  PlusOutlined,
 } from '@ant-design/icons';
-import { Badge, Button, Menu, message, Popconfirm, Tooltip, Upload } from 'antd';
+import { Badge, Button, Dropdown, Menu, message, Popconfirm, Tooltip, Upload } from 'antd';
 import type { ActionType, ProColumns } from '@jetlinks/pro-table';
 import { useIntl } from '@@/plugin-locale/localeExports';
-import BaseCrud from '@/components/BaseCrud';
-import { CurdModel } from '@/components/BaseCrud/model';
 import type { PermissionItem } from '@/pages/system/Permission/typings';
-import { FormTab } from '@formily/antd';
-import type { ISchema } from '@formily/json-schema';
 import Service from '@/pages/system/Permission/service';
-import { model } from '@formily/reactive';
 import { observer } from '@formily/react';
+import SearchComponent from '@/components/SearchComponent';
+import ProTable from '@jetlinks/pro-table';
+import Save from './Save';
 import SystemConst from '@/utils/const';
-import Token from '@/utils/token';
 import { downloadObject } from '@/utils/util';
+import Token from '@/utils/token';
 
 export const service = new Service('permission');
-
-const defaultAction = [
-  { action: 'query', name: '查询', describe: '查询' },
-  { action: 'save', name: '保存', describe: '保存' },
-  { action: 'delete', name: '删除', describe: '删除' },
-];
-
-const PermissionModel = model<{
-  assetsTypesList: { label: string; value: string }[];
-}>({
-  assetsTypesList: [],
-});
 const Permission: React.FC = observer(() => {
-  useEffect(() => {
-    service.getAssetTypes().subscribe((resp) => {
-      if (resp.status === 200) {
-        PermissionModel.assetsTypesList = resp.result.map((item: { name: string; id: string }) => {
-          return {
-            label: item.name,
-            value: item.id,
-          };
-        });
-      }
-    });
-  }, []);
   const intl = useIntl();
   const actionRef = useRef<ActionType>();
 
+  const [param, setParam] = useState({});
+  const [model, setMode] = useState<'add' | 'edit' | 'query'>('query');
+  const [current, setCurrent] = useState<Partial<PermissionItem>>({});
+
+  const edit = async (record: PermissionItem) => {
+    setMode('edit');
+    setCurrent(record);
+  };
+
   const menu = (
     <Menu>
       <Menu.Item key="import">
@@ -85,7 +69,7 @@ const Permission: React.FC = observer(() => {
         <Popconfirm
           title={'确认导出?'}
           onConfirm={() => {
-            service.getPermission().subscribe((resp) => {
+            service.getPermission({ ...param, paging: false }).subscribe((resp) => {
               if (resp.status === 200) {
                 downloadObject(resp.result, '权限数据');
                 message.success('导出成功');
@@ -110,7 +94,6 @@ const Permission: React.FC = observer(() => {
       dataIndex: 'id',
       // copyable: true,
       ellipsis: true,
-      align: 'center',
       // sorter: true,
       defaultSortOrder: 'ascend',
     },
@@ -122,7 +105,6 @@ const Permission: React.FC = observer(() => {
       dataIndex: 'name',
       // copyable: true,
       ellipsis: true,
-      align: 'center',
     },
     {
       title: intl.formatMessage({
@@ -131,7 +113,6 @@ const Permission: React.FC = observer(() => {
       }),
       dataIndex: 'status',
       // filters: true,
-      align: 'center',
       valueType: 'select',
       valueEnum: {
         1: {
@@ -159,13 +140,12 @@ const Permission: React.FC = observer(() => {
         defaultMessage: '操作',
       }),
       valueType: 'option',
-      align: 'center',
       width: 200,
       render: (text, record) => [
         <a
           key="editable"
           onClick={() => {
-            CurdModel.update(record);
+            edit(record);
           }}
         >
           <Tooltip
@@ -244,216 +224,57 @@ const Permission: React.FC = observer(() => {
     },
   ];
 
-  const formTab = FormTab.createFormTab!();
-
-  const schema: ISchema = {
-    type: 'object',
-    properties: {
-      id: {
-        title: intl.formatMessage({
-          id: 'pages.system.permission.id',
-          defaultMessage: '标识(ID)',
-        }),
-        type: 'string',
-        'x-decorator': 'FormItem',
-        'x-component': 'Input',
-        name: 'id',
-        'x-decorator-props': {
-          tooltip: <div>标识ID需与代码中的标识ID一致</div>,
-        },
-        'x-validator': [
-          {
-            max: 64,
-            message: '最多可输入64个字符',
-          },
-          {
-            required: true,
-            message: '请输入标识(ID)',
-          },
-        ],
-      },
-      name: {
-        title: intl.formatMessage({
-          id: 'pages.table.name',
-          defaultMessage: '名称',
-        }),
-        type: 'string',
-        'x-decorator': 'FormItem',
-        'x-component': 'Input',
-        name: 'name',
-        'x-validator': [
-          {
-            max: 64,
-            message: '最多可输入64个字符',
-          },
-          {
-            required: true,
-            message: '请输入名称',
-          },
-        ],
-      },
-      status: {
-        title: '状态',
-        'x-decorator': 'FormItem',
-        'x-component': 'Switch',
-        required: true,
-        default: 1,
-        enum: [
-          { label: '1', value: 1 },
-          { label: '0', value: 0 },
-        ],
-      },
-      'properties.assetTypes': {
-        type: 'string',
-        title: '关联资产',
-        'x-decorator': 'FormItem',
-        'x-component': 'Select',
-        name: 'properties.assetTypes',
-        required: false,
-        enum: PermissionModel.assetsTypesList,
-        'x-decorator-props': {
-          tooltip: <div>关联资产为角色权限中的权限分配提供数据支持</div>,
-        },
-        'x-component-props': {
-          showSearch: true,
-          mode: 'multiple',
-        },
-      },
-      actions: {
-        type: 'array',
-        'x-decorator': 'FormItem',
-        'x-component': 'ArrayTable',
-        default: defaultAction,
-        title: '操作类型',
-        items: {
-          type: 'object',
-          properties: {
-            column1: {
-              type: 'void',
-              'x-component': 'ArrayTable.Column',
-              'x-component-props': { width: 80, title: '-', align: 'center' },
-              properties: {
-                index: {
-                  type: 'void',
-                  'x-component': 'ArrayTable.Index',
-                },
-              },
-            },
-            column2: {
-              type: 'void',
-              'x-component': 'ArrayTable.Column',
-              'x-component-props': {
-                width: 200,
-                title: intl.formatMessage({
-                  id: 'pages.system.permission.addConfigurationType',
-                  defaultMessage: '操作类型',
-                }),
-              },
-              properties: {
-                action: {
-                  type: 'string',
-                  'x-decorator': 'Editable',
-                  'x-component': 'Input',
-                },
-              },
-            },
-            column3: {
-              type: 'void',
-              'x-component': 'ArrayTable.Column',
-              'x-component-props': {
-                width: 200,
-                title: intl.formatMessage({
-                  id: 'pages.table.name',
-                  defaultMessage: '名称',
-                }),
-              },
-              properties: {
-                name: {
-                  type: 'string',
-                  'x-decorator': 'Editable',
-                  'x-component': 'Input',
-                },
-              },
-            },
-            column4: {
-              type: 'void',
-              'x-component': 'ArrayTable.Column',
-              'x-component-props': {
-                width: 200,
-                title: intl.formatMessage({
-                  id: 'pages.table.describe',
-                  defaultMessage: '描述',
-                }),
-              },
-              properties: {
-                describe: {
-                  type: 'string',
-                  'x-decorator': 'Editable',
-                  'x-component': 'Input',
-                },
-              },
-            },
-            column5: {
-              type: 'void',
-              'x-component': 'ArrayTable.Column',
-              'x-component-props': {
-                title: intl.formatMessage({
-                  id: 'pages.data.option',
-                  defaultMessage: '操作',
-                }),
-                dataIndex: 'operations',
-                width: 200,
-                fixed: 'right',
-              },
-              properties: {
-                item: {
-                  type: 'void',
-                  'x-component': 'FormItem',
-                  properties: {
-                    remove: {
-                      type: 'void',
-                      'x-component': 'ArrayTable.Remove',
-                    },
-                  },
-                },
-              },
-            },
-          },
-        },
-        properties: {
-          add: {
-            type: 'void',
-            'x-component': 'ArrayTable.Addition',
-            title: intl.formatMessage({
-              id: 'pages.system.permission.add',
-              defaultMessage: '添加条目',
-            }),
-          },
-        },
-      },
-    },
-  };
   return (
     <PageContainer>
-      <BaseCrud<PermissionItem>
-        moduleName="permission"
-        actionRef={actionRef}
-        columns={columns}
-        service={service}
-        defaultParams={{ sorts: [{ name: 'modifyTime', order: 'desc' }] }}
-        title={intl.formatMessage({
-          id: 'pages.system.permission',
-          defaultMessage: '',
-        })}
-        schemaConfig={{
-          scope: { formTab },
+      <SearchComponent<PermissionItem>
+        field={columns}
+        target="permission"
+        onSearch={(data) => {
+          // 重置分页数据
+          actionRef.current?.reset?.();
+          setParam(data);
         }}
-        modelConfig={{
-          width: 1000,
+        onReset={() => {
+          // 重置分页及搜索参数
+          actionRef.current?.reset?.();
+          setParam({});
         }}
+      />
+      <ProTable<PermissionItem>
+        actionRef={actionRef}
+        params={param}
+        columns={columns}
         search={false}
-        menu={menu}
-        schema={schema}
+        headerTitle={'权限列表'}
+        request={async (params) =>
+          service.query({ ...params, sorts: [{ name: 'id', order: 'asc' }] })
+        }
+        toolBarRender={() => [
+          <Button
+            onClick={() => {
+              setMode('add');
+            }}
+            key="button"
+            icon={<PlusOutlined />}
+            type="primary"
+          >
+            {intl.formatMessage({
+              id: 'pages.data.option.add',
+              defaultMessage: '新增',
+            })}
+          </Button>,
+          <Dropdown key={'more'} overlay={menu} placement="bottom">
+            <Button>批量操作</Button>
+          </Dropdown>,
+        ]}
+      />
+      <Save
+        model={model}
+        close={() => {
+          setMode('query');
+          actionRef.current?.reload();
+        }}
+        data={current}
       />
     </PageContainer>
   );

+ 4 - 3
src/pages/system/Permission/service.ts

@@ -6,11 +6,12 @@ import SystemConst from '@/utils/const';
 import { map } from 'rxjs/operators';
 
 class Service extends BaseService<PermissionItem> {
-  public getPermission = () =>
+  public getPermission = (data: any) =>
     defer(() =>
       from(
-        request(`/${SystemConst.API_BASE}/permission/_query/for-grant`, {
-          method: 'GET',
+        request(`/${SystemConst.API_BASE}/permission/_query/no-paging`, {
+          method: 'POST',
+          data,
         }),
       ),
     ).pipe(map((item) => item));

+ 1 - 1
src/pages/system/Role/Edit/Permission/index.tsx

@@ -95,7 +95,7 @@ const Permission = () => {
       </Card>
       <Card style={{ marginTop: 20 }}>
         <div className={styles.title}>权限分配</div>
-        <Form.Item label="权限" name="permission" rules={[{ required: true }]}>
+        <Form.Item name="permission" rules={[{ required: true }]}>
           <Allocate />
         </Form.Item>
         <Form.Item>

+ 0 - 6
src/pages/system/Role/Edit/UserManage/BindUser.tsx

@@ -22,17 +22,11 @@ const BindUser = (props: Props) => {
 
   const columns: ProColumns<UserItem>[] = [
     {
-      dataIndex: 'index',
-      valueType: 'indexBorder',
-      width: 48,
-    },
-    {
       title: intl.formatMessage({
         id: 'pages.table.name',
         defaultMessage: '名称',
       }),
       dataIndex: 'name',
-      // copyable: true,
       ellipsis: true,
       tip: intl.formatMessage({
         id: 'pages.system.userName.tips',

+ 0 - 5
src/pages/system/Role/Edit/UserManage/index.tsx

@@ -30,11 +30,6 @@ const UserManage = () => {
   };
   const columns: ProColumns<UserItem>[] = [
     {
-      dataIndex: 'index',
-      valueType: 'indexBorder',
-      width: 48,
-    },
-    {
       title: '姓名',
       dataIndex: 'name',
       ellipsis: true,

+ 17 - 1
src/pages/system/Role/index.tsx

@@ -69,6 +69,7 @@ const Role: React.FC = observer(() => {
         id: 'pages.table.describe',
         defaultMessage: '描述',
       }),
+      ellipsis: true,
       dataIndex: 'description',
       filters: true,
       onFilter: true,
@@ -79,7 +80,6 @@ const Role: React.FC = observer(() => {
         defaultMessage: '操作',
       }),
       valueType: 'option',
-      align: 'center',
       width: 200,
       render: (text, record) => [
         <Link to={`/system/role/edit/${record.id}`} key="link">
@@ -139,6 +139,16 @@ const Role: React.FC = observer(() => {
         'x-decorator-props': {},
         name: 'name',
         required: true,
+        'x-validator': [
+          {
+            max: 64,
+            message: '最多可输入64个字符',
+          },
+          {
+            required: true,
+            message: '请输入名称',
+          },
+        ],
       },
       description: {
         type: 'string',
@@ -154,6 +164,12 @@ const Role: React.FC = observer(() => {
         'x-decorator-props': {},
         name: 'password',
         required: false,
+        'x-validator': [
+          {
+            max: 200,
+            message: '最多可输入200个字符',
+          },
+        ],
       },
     },
   };

+ 5 - 9
src/pages/system/User/index.tsx

@@ -38,7 +38,6 @@ const User = observer(() => {
       dataIndex: 'name',
       // copyable: true,
       ellipsis: true,
-      align: 'center',
       // tip: intl.formatMessage({
       //   id: 'pages.system.name.tips',
       //   defaultMessage: '姓名过长会自动收缩',
@@ -62,7 +61,6 @@ const User = observer(() => {
       dataIndex: 'username',
       // copyable: true,
       ellipsis: true,
-      align: 'center',
       // tip: intl.formatMessage({
       //   id: 'pages.system.userName.tips',
       //   defaultMessage: '用户名过长会自动收缩',
@@ -84,7 +82,6 @@ const User = observer(() => {
       }),
       dataIndex: 'status',
       // filters: true,
-      align: 'center',
       // onFilter: true,
       valueType: 'select',
       valueEnum: {
@@ -113,7 +110,6 @@ const User = observer(() => {
         defaultMessage: '操作',
       }),
       valueType: 'option',
-      align: 'center',
       width: 200,
       render: (text, record) => [
         <a key="editable" onClick={() => edit(record)}>
@@ -185,11 +181,11 @@ const User = observer(() => {
           actionRef.current?.reset?.();
           setParam(data);
         }}
-        // onReset={() => {
-        //   // 重置分页及搜索参数
-        //   actionRef.current?.reset?.();
-        //   setParam({});
-        // }}
+        onReset={() => {
+          // 重置分页及搜索参数
+          actionRef.current?.reset?.();
+          setParam({});
+        }}
       />
       <ProTable<UserItem>
         actionRef={actionRef}

+ 2 - 2
src/pages/user/Login/index.tsx

@@ -10,7 +10,7 @@ import { Form, FormItem, Input, Password, Submit } from '@formily/antd';
 import { catchError, filter, mergeMap } from 'rxjs/operators';
 import * as ICONS from '@ant-design/icons';
 import { useModel } from '@@/plugin-model/useModel';
-import SystemConst from '@/utils/const';
+// import SystemConst from '@/utils/const';
 import { useIntl } from '@@/plugin-locale/localeExports';
 import { SelectLang } from '@@/plugin-locale/SelectLang';
 import Footer from '@/components/Footer';
@@ -166,7 +166,7 @@ const Login: React.FC = () => {
               <div className={styles.header}>
                 <Link to="/">
                   <img alt="logo" className={styles.logo} src="/logo.svg" />
-                  <span className={styles.title}>{SystemConst.SYSTEM_NAME}</span>
+                  {/*<span className={styles.title}>{SystemConst.SYSTEM_NAME}</span>*/}
                 </Link>
               </div>
               <div className={styles.desc}>