Просмотр исходного кода

Merge branch 'next' into next-xyh

# Conflicts:
#	src/pages/system/Department/index.tsx
xieyonghong 3 лет назад
Родитель
Сommit
4390a9488b

+ 6 - 2
config/proxy.ts

@@ -9,8 +9,12 @@
 export default {
   dev: {
     '/jetlinks': {
-      target: 'http://192.168.33.222:8844/',
-      ws: 'ws://192.168.33.222:8844/',
+      // target: 'http://192.168.33.222:8844/',
+      // ws: 'ws://192.168.33.222:8844/',
+      target: 'http://120.79.18.123:8844/',
+      ws: 'ws://120.79.18.123:8844/',
+      // target: 'http://192.168.66.5:8844/',
+      // ws: 'ws://192.168.66.5:8844/',
       // ws: 'ws://demo.jetlinks.cn/jetlinks',
       // target: 'http://demo.jetlinks.cn/jetlinks',
       changeOrigin: true,

+ 368 - 360
config/routes.ts

@@ -15,12 +15,12 @@
       },
     ],
   },
-  {
-    path: '/analysis',
-    name: 'analysis',
-    icon: 'smile',
-    component: './Analysis',
-  },
+  // {
+  //   path: '/analysis',
+  //   name: 'analysis',
+  //   icon: 'smile',
+  //   component: './Analysis',
+  // },
   {
     path: '/system',
     name: 'system',
@@ -38,44 +38,6 @@
         component: './system/User',
       },
       {
-        path: '/system/role',
-        name: 'role',
-        icon: 'smile',
-        access: 'role',
-        component: './system/Role',
-      },
-      {
-        path: '/system/permission',
-        name: 'permission',
-        icon: 'smile',
-        component: './system/Permission',
-      },
-      {
-        path: '/system/open-api',
-        name: 'open-api',
-        icon: 'smile',
-        component: './system/OpenAPI',
-      },
-      {
-        path: '/system/tenant',
-        name: 'tenant',
-        icon: 'smile',
-        component: './system/Tenant',
-      },
-      {
-        hideInMenu: true,
-        path: '/system/tenant/detail/:id',
-        name: 'tenant-detail',
-        icon: 'smile',
-        component: './system/Tenant/Detail',
-      },
-      {
-        path: '/system/datasource',
-        name: 'datasource',
-        icon: 'smile',
-        component: './system/DataSource',
-      },
-      {
         path: '/system/department',
         name: 'department',
         icon: 'smile',
@@ -95,340 +57,386 @@
         icon: 'smile',
         component: './system/Department/Member',
       },
-    ],
-  },
-  {
-    path: '/device',
-    name: 'device',
-    icon: 'crown',
-    routes: [
-      {
-        path: '/device',
-        redirect: '/device/product',
-      },
-      {
-        path: '/device/product',
-        name: 'product',
-        icon: 'smile',
-        component: './device/Product',
-      },
       {
-        path: '/device/category',
-        name: 'category',
-        icon: 'smile',
-        component: './device/Category',
-      },
-      {
-        hideInMenu: true,
-        path: '/device/product/detail/:id',
-        name: 'product-detail',
-        icon: 'smile',
-        component: './device/Product/Detail',
-      },
-      {
-        path: '/device/instance',
-        name: 'instance',
+        path: '/system/role',
+        name: 'role',
         icon: 'smile',
-        component: './device/Instance',
+        access: 'role',
+        component: './system/Role',
       },
       {
         hideInMenu: true,
-        path: '/device/instance/detail/:id',
-        name: 'instance-detail',
+        path: '/system/role/edit/:id',
+        name: 'role-edit',
         icon: 'smile',
-        component: './device/Instance/Detail',
+        component: './system/Role/Edit',
       },
       {
-        path: '/device/command',
-        name: 'command',
-        icon: 'smile',
-        component: './device/Command',
-      },
-      {
-        path: '/device/firmware',
-        name: 'firmware',
+        path: '/system/permission',
+        name: 'permission',
         icon: 'smile',
-        component: './device/Firmware',
+        component: './system/Permission',
       },
+      // {
+      //   path: '/system/open-api',
+      //   name: 'open-api',
+      //   icon: 'smile',
+      //   component: './system/OpenAPI',
+      // },
+      // {
+      //   path: '/system/tenant',
+      //   name: 'tenant',
+      //   icon: 'smile',
+      //   component: './system/Tenant',
+      // },
       {
         hideInMenu: true,
-        path: '/device/firmware/detail/:id',
-        name: 'firmware-detail',
-        icon: 'smile',
-        component: './device/Firmware/Detail',
-      },
-      {
-        path: '/device/alarm',
-        name: 'alarm',
-        icon: 'smile',
-        component: './device/Alarm',
-      },
-      {
-        path: '/device/location',
-        name: 'location',
-        icon: 'smile',
-        component: './device/Location',
-      },
-    ],
-  },
-  {
-    path: '/link',
-    name: 'link',
-    icon: 'crown',
-    routes: [
-      {
-        path: '/link',
-        redirect: '/link/certificate',
-      },
-      {
-        path: '/link/certificate',
-        name: 'certificate',
-        icon: 'smile',
-        component: './link/Certificate',
-      },
-      {
-        path: '/link/protocol',
-        name: 'protocol',
-        icon: 'smile',
-        component: './link/Protocol',
-      },
-      {
-        path: 'link/type',
-        name: 'type',
-        icon: 'smile',
-        component: './link/Type',
-      },
-      {
-        path: '/link/gateway',
-        name: 'gateway',
-        icon: 'smile',
-        component: './link/Gateway',
-      },
-      {
-        path: '/link/opcua',
-        name: 'opcua',
-        icon: 'smile',
-        component: './link/Opcua',
-      },
-    ],
-  },
-  {
-    path: '/notice',
-    name: 'notice',
-    icon: 'crown',
-    routes: [
-      {
-        path: '/notice',
-        redirect: '/notice/config',
-      },
-      {
-        path: '/notice/config',
-        name: 'config',
-        icon: 'smile',
-        component: './notice/Config',
-      },
-      {
-        path: '/notice/template',
-        name: 'template',
-        icon: 'smile',
-        component: './notice/Template',
-      },
-    ],
-  },
-  {
-    path: '/rule-engine',
-    name: 'rule-engine',
-    icon: 'crown',
-    routes: [
-      {
-        path: '/rule-engine',
-        redirect: '/rule-engine/instance',
-      },
-      {
-        path: '/rule-engine/instance',
-        name: 'instance',
-        icon: 'smile',
-        component: './rule-engine/Instance',
-      },
-      {
-        path: '/rule-engine/sqlRule',
-        name: 'sqlRule',
-        icon: 'smile',
-        component: './rule-engine/SQLRule',
-      },
-      {
-        path: '/rule-engine/scene',
-        name: 'scene',
-        icon: 'smile',
-        component: './rule-engine/Scene',
-      },
-    ],
-  },
-  {
-    path: '/visualization',
-    name: 'visualization',
-    icon: 'crown',
-    routes: [
-      {
-        path: '/visualization',
-        redirect: '/visualization/category',
-      },
-      {
-        path: '/visualization/category',
-        name: 'category',
-        icon: 'smile',
-        component: './visualization/Category',
-      },
-      {
-        path: '/visualization/screen',
-        name: 'screen',
-        icon: 'smile',
-        component: './visualization/Screen',
-      },
-      {
-        path: '/visualization/configuration',
-        name: 'configuration',
-        icon: 'smile',
-        component: './visualization/Configuration',
-      },
-    ],
-  },
-  {
-    path: '/simulator',
-    name: 'simulator',
-    icon: 'crown',
-    routes: [
-      {
-        path: '/simulator',
-        redirect: '/simulator/device',
-      },
-      {
-        path: '/simulator/device',
-        name: 'device',
-        icon: 'smile',
-        component: './simulator/Device',
-      },
-    ],
-  },
-  {
-    path: '/log',
-    name: 'log',
-    icon: 'crown',
-    routes: [
-      {
-        path: '/log',
-        redirect: '/log/access',
-      },
-      {
-        path: '/log/access',
-        name: 'access',
-        icon: 'smile',
-        component: './log/Access',
-      },
-      {
-        path: '/log/system',
-        name: 'system',
-        icon: 'smile',
-        component: './log/System',
-      },
-    ],
-  },
-  {
-    path: '/cloud',
-    name: 'cloud',
-    icon: 'crown',
-    routes: [
-      {
-        path: '/cloud',
-        redirect: '/cloud/duer',
-      },
-      {
-        path: '/cloud/dueros',
-        name: 'DuerOS',
-        icon: 'smile',
-        component: './cloud/DuerOS',
-      },
-      {
-        path: '/cloud/aliyun',
-        name: 'aliyun',
-        icon: 'smile',
-        component: './cloud/Aliyun',
-      },
-      {
-        path: '/cloud/onenet',
-        name: 'onenet',
-        icon: 'smile',
-        component: './cloud/Onenet',
-      },
-      {
-        path: '/cloud/ctwing',
-        name: 'ctwing',
-        icon: 'smile',
-        component: './cloud/Ctwing',
-      },
-    ],
-  },
-  {
-    path: '/media',
-    name: 'media',
-    icon: 'crown',
-    routes: [
-      {
-        path: '/media',
-        redirect: '/media/config',
-      },
-      {
-        path: '/media/config',
-        name: 'config',
-        icon: 'smile',
-        component: './media/Config',
-      },
-      {
-        path: '/media/device',
-        name: 'device',
-        icon: 'smile',
-        component: './media/Device',
-      },
-      {
-        path: '/media/reveal',
-        name: 'reveal',
-        icon: 'smile',
-        component: './media/Reveal',
-      },
-      {
-        path: '/media/cascade',
-        name: 'cascade',
-        icon: 'smile',
-        component: './media/Cascade',
-      },
-    ],
-  },
-  {
-    path: '/edge',
-    name: 'edge',
-    icon: 'crown',
-    routes: [
-      {
-        path: '/edge',
-        redirect: '/edge/product',
-      },
-      {
-        path: '/edge/product',
-        name: 'product',
-        icon: 'smile',
-        component: './edge/Product',
-      },
-      {
-        path: '/edge/device',
-        name: 'device',
+        path: '/system/tenant/detail/:id',
+        name: 'tenant-detail',
         icon: 'smile',
-        component: './edge/Device',
+        component: './system/Tenant/Detail',
       },
+      // {
+      //   path: '/system/datasource',
+      //   name: 'datasource',
+      //   icon: 'smile',
+      //   component: './system/DataSource',
+      // },
+      //
     ],
   },
+  // {
+  //   path: '/device',
+  //   name: 'device',
+  //   icon: 'crown',
+  //   routes: [
+  //     {
+  //       path: '/device',
+  //       redirect: '/device/product',
+  //     },
+  //     {
+  //       path: '/device/product',
+  //       name: 'product',
+  //       icon: 'smile',
+  //       component: './device/Product',
+  //     },
+  //     {
+  //       path: '/device/category',
+  //       name: 'category',
+  //       icon: 'smile',
+  //       component: './device/Category',
+  //     },
+  //     {
+  //       hideInMenu: true,
+  //       path: '/device/product/detail/:id',
+  //       name: 'product-detail',
+  //       icon: 'smile',
+  //       component: './device/Product/Detail',
+  //     },
+  //     {
+  //       path: '/device/instance',
+  //       name: 'instance',
+  //       icon: 'smile',
+  //       component: './device/Instance',
+  //     },
+  //     {
+  //       hideInMenu: true,
+  //       path: '/device/instance/detail/:id',
+  //       name: 'instance-detail',
+  //       icon: 'smile',
+  //       component: './device/Instance/Detail',
+  //     },
+  //     {
+  //       path: '/device/command',
+  //       name: 'command',
+  //       icon: 'smile',
+  //       component: './device/Command',
+  //     },
+  //     {
+  //       path: '/device/firmware',
+  //       name: 'firmware',
+  //       icon: 'smile',
+  //       component: './device/Firmware',
+  //     },
+  //     {
+  //       hideInMenu: true,
+  //       path: '/device/firmware/detail/:id',
+  //       name: 'firmware-detail',
+  //       icon: 'smile',
+  //       component: './device/Firmware/Detail',
+  //     },
+  //     {
+  //       path: '/device/alarm',
+  //       name: 'alarm',
+  //       icon: 'smile',
+  //       component: './device/Alarm',
+  //     },
+  //     {
+  //       path: '/device/location',
+  //       name: 'location',
+  //       icon: 'smile',
+  //       component: './device/Location',
+  //     },
+  //   ],
+  // },
+  // {
+  //   path: '/link',
+  //   name: 'link',
+  //   icon: 'crown',
+  //   routes: [
+  //     {
+  //       path: '/link',
+  //       redirect: '/link/certificate',
+  //     },
+  //     {
+  //       path: '/link/certificate',
+  //       name: 'certificate',
+  //       icon: 'smile',
+  //       component: './link/Certificate',
+  //     },
+  //     {
+  //       path: '/link/protocol',
+  //       name: 'protocol',
+  //       icon: 'smile',
+  //       component: './link/Protocol',
+  //     },
+  //     {
+  //       path: 'link/type',
+  //       name: 'type',
+  //       icon: 'smile',
+  //       component: './link/Type',
+  //     },
+  //     {
+  //       path: '/link/gateway',
+  //       name: 'gateway',
+  //       icon: 'smile',
+  //       component: './link/Gateway',
+  //     },
+  //     {
+  //       path: '/link/opcua',
+  //       name: 'opcua',
+  //       icon: 'smile',
+  //       component: './link/Opcua',
+  //     },
+  //   ],
+  // },
+  // {
+  //   path: '/notice',
+  //   name: 'notice',
+  //   icon: 'crown',
+  //   routes: [
+  //     {
+  //       path: '/notice',
+  //       redirect: '/notice/config',
+  //     },
+  //     {
+  //       path: '/notice/config',
+  //       name: 'config',
+  //       icon: 'smile',
+  //       component: './notice/Config',
+  //     },
+  //     {
+  //       path: '/notice/template',
+  //       name: 'template',
+  //       icon: 'smile',
+  //       component: './notice/Template',
+  //     },
+  //   ],
+  // },
+  // {
+  //   path: '/rule-engine',
+  //   name: 'rule-engine',
+  //   icon: 'crown',
+  //   routes: [
+  //     {
+  //       path: '/rule-engine',
+  //       redirect: '/rule-engine/instance',
+  //     },
+  //     {
+  //       path: '/rule-engine/instance',
+  //       name: 'instance',
+  //       icon: 'smile',
+  //       component: './rule-engine/Instance',
+  //     },
+  //     {
+  //       path: '/rule-engine/sqlRule',
+  //       name: 'sqlRule',
+  //       icon: 'smile',
+  //       component: './rule-engine/SQLRule',
+  //     },
+  //     {
+  //       path: '/rule-engine/scene',
+  //       name: 'scene',
+  //       icon: 'smile',
+  //       component: './rule-engine/Scene',
+  //     },
+  //   ],
+  // },
+  // {
+  //   path: '/visualization',
+  //   name: 'visualization',
+  //   icon: 'crown',
+  //   routes: [
+  //     {
+  //       path: '/visualization',
+  //       redirect: '/visualization/category',
+  //     },
+  //     {
+  //       path: '/visualization/category',
+  //       name: 'category',
+  //       icon: 'smile',
+  //       component: './visualization/Category',
+  //     },
+  //     {
+  //       path: '/visualization/screen',
+  //       name: 'screen',
+  //       icon: 'smile',
+  //       component: './visualization/Screen',
+  //     },
+  //     {
+  //       path: '/visualization/configuration',
+  //       name: 'configuration',
+  //       icon: 'smile',
+  //       component: './visualization/Configuration',
+  //     },
+  //   ],
+  // },
+  // {
+  //   path: '/simulator',
+  //   name: 'simulator',
+  //   icon: 'crown',
+  //   routes: [
+  //     {
+  //       path: '/simulator',
+  //       redirect: '/simulator/device',
+  //     },
+  //     {
+  //       path: '/simulator/device',
+  //       name: 'device',
+  //       icon: 'smile',
+  //       component: './simulator/Device',
+  //     },
+  //   ],
+  // },
+  // {
+  //   path: '/log',
+  //   name: 'log',
+  //   icon: 'crown',
+  //   routes: [
+  //     {
+  //       path: '/log',
+  //       redirect: '/log/access',
+  //     },
+  //     {
+  //       path: '/log/access',
+  //       name: 'access',
+  //       icon: 'smile',
+  //       component: './log/Access',
+  //     },
+  //     {
+  //       path: '/log/system',
+  //       name: 'system',
+  //       icon: 'smile',
+  //       component: './log/System',
+  //     },
+  //   ],
+  // },
+  // {
+  //   path: '/cloud',
+  //   name: 'cloud',
+  //   icon: 'crown',
+  //   routes: [
+  //     {
+  //       path: '/cloud',
+  //       redirect: '/cloud/duer',
+  //     },
+  //     {
+  //       path: '/cloud/dueros',
+  //       name: 'DuerOS',
+  //       icon: 'smile',
+  //       component: './cloud/DuerOS',
+  //     },
+  //     {
+  //       path: '/cloud/aliyun',
+  //       name: 'aliyun',
+  //       icon: 'smile',
+  //       component: './cloud/Aliyun',
+  //     },
+  //     {
+  //       path: '/cloud/onenet',
+  //       name: 'onenet',
+  //       icon: 'smile',
+  //       component: './cloud/Onenet',
+  //     },
+  //     {
+  //       path: '/cloud/ctwing',
+  //       name: 'ctwing',
+  //       icon: 'smile',
+  //       component: './cloud/Ctwing',
+  //     },
+  //   ],
+  // },
+  // {
+  //   path: '/media',
+  //   name: 'media',
+  //   icon: 'crown',
+  //   routes: [
+  //     {
+  //       path: '/media',
+  //       redirect: '/media/config',
+  //     },
+  //     {
+  //       path: '/media/config',
+  //       name: 'config',
+  //       icon: 'smile',
+  //       component: './media/Config',
+  //     },
+  //     {
+  //       path: '/media/device',
+  //       name: 'device',
+  //       icon: 'smile',
+  //       component: './media/Device',
+  //     },
+  //     {
+  //       path: '/media/reveal',
+  //       name: 'reveal',
+  //       icon: 'smile',
+  //       component: './media/Reveal',
+  //     },
+  //     {
+  //       path: '/media/cascade',
+  //       name: 'cascade',
+  //       icon: 'smile',
+  //       component: './media/Cascade',
+  //     },
+  //   ],
+  // },
+  // {
+  //   path: '/edge',
+  //   name: 'edge',
+  //   icon: 'crown',
+  //   routes: [
+  //     {
+  //       path: '/edge',
+  //       redirect: '/edge/product',
+  //     },
+  //     {
+  //       path: '/edge/product',
+  //       name: 'product',
+  //       icon: 'smile',
+  //       component: './edge/Product',
+  //     },
+  //     {
+  //       path: '/edge/device',
+  //       name: 'device',
+  //       icon: 'smile',
+  //       component: './edge/Device',
+  //     },
+  //   ],
+  // },
   {
     path: '/',
-    redirect: '/analysis',
+    redirect: '/system',
   },
   {
     component: './404',

+ 1 - 1
package.json

@@ -70,7 +70,7 @@
     "@formily/reactive-react": "2.0.0-rc.17",
     "@formily/shared": "2.0.0-rc.17",
     "@jetlinks/pro-list": "^1.10.8",
-    "@jetlinks/pro-table": "^2.63.8",
+    "@jetlinks/pro-table": "^2.63.9",
     "@umijs/route-utils": "^1.0.36",
     "ahooks": "^2.10.9",
     "antd": "^4.17.0-alpha.9",

+ 1 - 0
src/components/BaseCrud/model.ts

@@ -16,6 +16,7 @@ export const CurdModel = model<Option>({
   },
 
   update(current: any) {
+    console.log('触发编辑');
     Store.set(SystemConst.BASE_CURD_MODEL, 'edit');
     Store.set(SystemConst.BASE_CURD_MODAL_VISIBLE, true);
     Store.set(SystemConst.BASE_CURD_CURRENT, current);

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

@@ -104,8 +104,11 @@ const Save = <T extends Record<string, any>>(props: Props<T>) => {
         values.template,
       );
     }
-    await service.update(values);
+    const response = await service.update(values);
 
+    if (response.status === 200) {
+      Store.set(SystemConst.BASE_UPDATE_DATA, response.result);
+    }
     message.success(
       intl.formatMessage({
         id: 'pages.data.option.success',

+ 91 - 35
src/components/SearchComponent/index.tsx

@@ -11,10 +11,11 @@ import {
   PreviewText,
   Select,
 } from '@formily/antd';
-import { createForm } from '@formily/core';
+import type { Field } from '@formily/core';
+import { createForm, onFieldReact } from '@formily/core';
 import GroupNameControl from '@/components/SearchComponent/GroupNameControl';
 import { DeleteOutlined, DoubleRightOutlined } from '@ant-design/icons';
-import { Button, Dropdown, Input as AInput, Menu, message, Popconfirm, Popover } from 'antd';
+import { Button, Dropdown, Empty, Menu, message, Popconfirm, Popover, Typography } from 'antd';
 import { useState } from 'react';
 import type { ProColumns } from '@jetlinks/pro-table';
 import type { EnumData } from '@/utils/typings';
@@ -24,8 +25,8 @@ import _ from 'lodash';
 import { useIntl } from '@@/plugin-locale/localeExports';
 
 const ui2Server = (source: SearchTermsUI): SearchTermsServer => [
-  { terms: source.terms1, type: source.type },
-  { terms: source.terms2 },
+  { terms: source.terms1 },
+  { terms: source.terms2, type: source.type },
 ];
 
 const server2Ui = (source: SearchTermsServer): SearchTermsUI => ({
@@ -38,6 +39,7 @@ interface Props<T> {
   field: ProColumns<T>[];
   onSearch: (params: any) => void;
   target?: string;
+  onReset?: () => void;
 }
 
 const termType = [
@@ -56,12 +58,16 @@ const termType = [
 const service = new Service();
 const defaultTerm = { termType: 'like' };
 
-const SearchComponent = <T extends Record<string, any>>({ field, onSearch, target }: Props<T>) => {
+const SearchComponent = <T extends Record<string, any>>({
+  field,
+  onSearch,
+  target,
+  onReset,
+}: Props<T>) => {
   const intl = useIntl();
   const [expand, setExpand] = useState<boolean>(true);
   const initForm = server2Ui([{ terms: [defaultTerm], type: 'and' }, { terms: [defaultTerm] }]);
   const [logVisible, setLogVisible] = useState<boolean>(false);
-  const [alias, setAlias] = useState<string>('');
   const [aliasVisible, setAliasVisible] = useState<boolean>(false);
   const [initParams, setInitParams] = useState<SearchTermsUI>(initForm);
   const [history, setHistory] = useState([]);
@@ -69,8 +75,32 @@ const SearchComponent = <T extends Record<string, any>>({ field, onSearch, targe
   const form = createForm<SearchTermsUI>({
     validateFirst: true,
     initialValues: initParams,
+    effects() {
+      onFieldReact('*.*.column', (typeFiled, f) => {
+        const _column = (typeFiled as Field).value;
+        const _field = field.find((item) => item.dataIndex === _column);
+        if (_field?.valueType === 'select') {
+          const option = Object.values(_field?.valueEnum || {}).map((item) => ({
+            label: item.text,
+            value: item.status,
+          }));
+
+          f.setFieldState(typeFiled.query('.termType'), async (state) => {
+            state.value = 'eq';
+          });
+          f.setFieldState(typeFiled.query('.value'), async (state) => {
+            state.componentType = 'Select';
+            state.loading = true;
+            state.dataSource = option;
+            state.loading = false;
+          });
+        }
+      });
+    },
   });
 
+  const historyForm = createForm();
+
   const queryHistory = async () => {
     const response = await service.history.query(`${target}-search`);
     if (response.status === 200) {
@@ -211,33 +241,49 @@ const SearchComponent = <T extends Record<string, any>>({ field, onSearch, targe
     setInitParams(log);
   };
   const historyDom = (
-    <Menu>
-      {history.map((item: SearchHistory) => (
-        <Menu.Item onClick={() => handleHistory(item)} key={item.id}>
+    <Menu style={{ width: '176px' }}>
+      {history.length > 0 ? (
+        history.map((item: SearchHistory) => (
+          <Menu.Item onClick={() => handleHistory(item)} key={item.id}>
+            <div
+              style={{
+                display: 'flex',
+                justifyContent: 'space-between',
+                alignItems: 'center',
+                width: '148px',
+              }}
+            >
+              <Typography.Text ellipsis={{ tooltip: item.name }}>{item.name}</Typography.Text>
+              <Popconfirm
+                onConfirm={async () => {
+                  const response = await service.history.remove(`${target}-search`, item.key);
+                  if (response.status === 200) {
+                    message.success('操作成功');
+                    const temp = history.filter((h: any) => h.key !== item.key);
+                    setHistory(temp);
+                  }
+                }}
+                title={'确认删除吗?'}
+              >
+                <DeleteOutlined />
+              </Popconfirm>
+            </div>
+          </Menu.Item>
+        ))
+      ) : (
+        <Menu.Item>
           <div
             style={{
               display: 'flex',
-              justifyContent: 'space-between',
+              justifyContent: 'center',
               alignItems: 'center',
+              width: '148px',
             }}
           >
-            <span style={{ marginRight: '5px' }}>{item.name}</span>
-            <Popconfirm
-              onConfirm={async () => {
-                const response = await service.history.remove(`${target}-search`, item.key);
-                if (response.status === 200) {
-                  message.success('操作成功');
-                  const temp = history.filter((h: any) => h.key !== item.key);
-                  setHistory(temp);
-                }
-              }}
-              title={'确认删除吗?'}
-            >
-              <DeleteOutlined />
-            </Popconfirm>
+            <Empty />
           </div>
         </Menu.Item>
-      ))}
+      )}
     </Menu>
   );
 
@@ -257,7 +303,7 @@ const SearchComponent = <T extends Record<string, any>>({ field, onSearch, targe
     const value = form.values;
     setInitParams(value);
     const filterTerms = (data: Partial<Term>[]) =>
-      data.filter((item) => item.column != null).filter((item) => item.value);
+      data.filter((item) => item.column != null).filter((item) => item.value != null);
     const temp = _.cloneDeep(value);
     temp.terms1 = filterTerms(temp.terms1);
     temp.terms2 = filterTerms(temp.terms2);
@@ -266,8 +312,9 @@ const SearchComponent = <T extends Record<string, any>>({ field, onSearch, targe
 
   const handleSaveLog = async () => {
     const value = await form.submit<SearchTermsUI>();
+    const value2 = await historyForm.submit<{ alias: string }>();
     const response = await service.history.save(`${target}-search`, {
-      name: alias,
+      name: value2.alias,
       content: JSON.stringify(value),
     });
     if (response.status === 200) {
@@ -279,11 +326,12 @@ const SearchComponent = <T extends Record<string, any>>({ field, onSearch, targe
   };
 
   const resetForm = async () => {
-    const temp = initParams;
+    const temp = initForm;
     temp.terms1 = temp.terms1.map(() => defaultTerm);
     temp.terms2 = temp.terms2.map(() => defaultTerm);
     setInitParams(temp);
     await form.reset();
+    onReset?.();
   };
   return (
     <div>
@@ -298,6 +346,8 @@ const SearchComponent = <T extends Record<string, any>>({ field, onSearch, targe
               visible={logVisible}
               onVisibleChange={async (visible) => {
                 setLogVisible(visible);
+                const value = form.values;
+                setInitParams(value);
                 if (visible) {
                   await queryHistory();
                 }
@@ -309,20 +359,26 @@ const SearchComponent = <T extends Record<string, any>>({ field, onSearch, targe
             </Dropdown.Button>
             <Popover
               content={
-                <>
-                  <AInput.TextArea
-                    rows={3}
-                    value={alias}
-                    onChange={(e) => setAlias(e.target.value)}
+                <Form style={{ width: '217px' }} form={historyForm}>
+                  <SchemaField
+                    schema={{
+                      type: 'object',
+                      properties: {
+                        alias: {
+                          'x-decorator': 'FormItem',
+                          'x-component': 'Input.TextArea',
+                          maxLength: 50,
+                        },
+                      },
+                    }}
                   />
                   <Button onClick={handleSaveLog} type="primary" className={styles.saveLog}>
                     保存
                   </Button>
-                </>
+                </Form>
               }
               visible={aliasVisible}
               onVisibleChange={(visible) => {
-                setAlias('');
                 setInitParams(form.values);
                 setAliasVisible(visible);
               }}

+ 1 - 0
src/locales/en-US/menu.ts

@@ -7,6 +7,7 @@ export default {
   'menu.system': 'System',
   'menu.system.user': 'User',
   'menu.system.role': 'Role',
+  'menu.system.role-edit': 'Access Configuration',
   'menu.system.org': 'Organization',
   'menu.system.open-api': 'OpenAPI',
   'menu.system.permission': 'Permission',

+ 3 - 0
src/locales/en-US/pages.ts

@@ -86,6 +86,9 @@ export default {
   'pages.system.role': 'Role',
   'pages.system.role.id': 'ID',
   'pages.system.role.option.bindUser': 'Bind User',
+  'pages.system.role.access.baseInfo': 'Basic Information',
+  'pages.system.role.access.permission': 'Access Configuration',
+  'pages.system.role.access.userManagement': 'User Management',
   // 系统设置-角色管理-绑定用户
   'pages.bindUser.theBoundUser': 'The Bound User',
   'pages.bindUser.theBoundUser.success': 'Unbundling success',

+ 2 - 1
src/locales/zh-CN/menu.ts

@@ -5,7 +5,8 @@ export default {
   'menu.admin': '管理页',
   'menu.system': '系统设置',
   'menu.system.user': '用户管理',
-  'menu.system.role': '角色管理',
+  'menu.system.role': '角色权限',
+  'menu.system.role-edit': '权限配置',
   'menu.system.org': '机构管理',
   'menu.system.open-api': '第三方平台',
   'menu.system.permission': '权限管理',

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

@@ -87,6 +87,9 @@ export default {
   'pages.system.role': '角色管理',
   'pages.system.role.id': '标识',
   'pages.system.role.option.bindUser': '绑定用户',
+  'pages.system.role.access.baseInfo': '基本信息',
+  'pages.system.role.access.permission': '权限分配',
+  'pages.system.role.access.userManagement': '用户管理',
   // 系统设置-角色管理-绑定用户
   'pages.bindUser.theBoundUser': '已绑定用户',
   'pages.bindUser.theBoundUser.success': '解绑成功',

+ 253 - 441
src/pages/system/Permission/index.tsx

@@ -1,7 +1,12 @@
 import { PageContainer } from '@ant-design/pro-layout';
 import React, { useEffect, useRef } from 'react';
-import { EditOutlined, CloseCircleOutlined, PlayCircleOutlined } from '@ant-design/icons';
-import { Menu, Tooltip, Popconfirm, message } from 'antd';
+import {
+  EditOutlined,
+  CloseCircleOutlined,
+  PlayCircleOutlined,
+  MinusOutlined,
+} from '@ant-design/icons';
+import { Menu, Tooltip, Popconfirm, message, Button, Upload } from 'antd';
 import type { ProColumns, ActionType } from '@jetlinks/pro-table';
 import { useIntl } from '@@/plugin-locale/localeExports';
 import BaseCrud from '@/components/BaseCrud';
@@ -12,47 +17,108 @@ 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 { map } from 'rxjs/operators';
-import { toArray } from 'rxjs';
-
-const menu = (
-  <Menu>
-    <Menu.Item key="1">1st item</Menu.Item>
-    <Menu.Item key="2">2nd item</Menu.Item>
-    <Menu.Item key="3">3rd item</Menu.Item>
-  </Menu>
-);
+import moment from 'moment';
+import SystemConst from '@/utils/const';
+import Token from '@/utils/token';
 
 export const service = new Service('permission');
 
 const defaultAction = [
-  { value: 'query', label: '查询' },
-  { value: 'save', label: '保存' },
-  { value: 'delete', label: '删除' },
-  { value: 'import', label: '导入' },
-  { value: 'export', label: '导出' },
+  { action: 'query', name: '查询', describe: '查询' },
+  { action: 'save', name: '保存', describe: '保存' },
+  { action: 'delete', name: '删除', describe: '删除' },
 ];
 
+const downloadObject = (record: any, fileName: string) => {
+  // 创建隐藏的可下载链接
+  const eleLink = document.createElement('a');
+  eleLink.download = `${fileName}-${
+    record.name || moment(new Date()).format('YYYY/MM/DD HH:mm:ss')
+  }.json`;
+  eleLink.style.display = 'none';
+  // 字符内容转变成blob地址
+  const blob = new Blob([JSON.stringify(record)]);
+  eleLink.href = URL.createObjectURL(blob);
+  // 触发点击
+  document.body.appendChild(eleLink);
+  eleLink.click();
+  // 然后移除
+  document.body.removeChild(eleLink);
+};
+
 const PermissionModel = model<{
-  permissionList: { label: string; value: string }[];
+  assetsTypesList: { label: string; value: string }[];
 }>({
-  permissionList: [],
+  assetsTypesList: [],
 });
 const Permission: React.FC = observer(() => {
   useEffect(() => {
-    service
-      .getPermission()
-      .pipe(
-        map((item) => ({ label: item.name, value: item.id })),
-        toArray(),
-      )
-      .subscribe((data) => {
-        PermissionModel.permissionList = data;
-      });
+    service.getAssetTypes().subscribe((resp) => {
+      if (resp.status === 200) {
+        const list = resp.result.map((item: { name: string; id: string }) => {
+          return {
+            label: item.name,
+            value: item.id,
+          };
+        });
+        PermissionModel.assetsTypesList = list;
+      }
+    });
   }, []);
   const intl = useIntl();
   const actionRef = useRef<ActionType>();
 
+  const menu = (
+    <Menu>
+      <Menu.Item key="import">
+        <Upload
+          action={`/${SystemConst.API_BASE}/file/static`}
+          headers={{
+            'X-Access-Token': Token.get(),
+          }}
+          showUploadList={false}
+          accept=".json"
+          beforeUpload={(file) => {
+            const reader = new FileReader();
+            reader.readAsText(file);
+            reader.onload = (result: any) => {
+              try {
+                const data = JSON.parse(result.target.result);
+                service.batchAdd(data).subscribe((resp) => {
+                  if (resp.status === 200) {
+                    message.success('导入成功');
+                    actionRef.current?.reload();
+                  }
+                });
+              } catch (error) {
+                message.error('导入失败,请重试!');
+              }
+            };
+          }}
+        >
+          <Button>导入</Button>
+        </Upload>
+      </Menu.Item>
+      <Menu.Item key="export">
+        <Popconfirm
+          title={'确认导出?'}
+          onConfirm={() => {
+            service.getPermission().subscribe((resp) => {
+              if (resp.status === 200) {
+                downloadObject(resp.result, '权限数据');
+                message.success('导出成功');
+              } else {
+                message.error('导出错误');
+              }
+            });
+          }}
+        >
+          <Button>导出</Button>
+        </Popconfirm>
+      </Menu.Item>
+    </Menu>
+  );
+
   const columns: ProColumns<PermissionItem>[] = [
     {
       dataIndex: 'index',
@@ -173,6 +239,7 @@ const Permission: React.FC = observer(() => {
             })}
             onConfirm={async () => {
               await service.update({
+                ...record,
                 id: record.id,
                 status: record.status ? 0 : 1,
               });
@@ -195,6 +262,33 @@ const Permission: React.FC = observer(() => {
             </Tooltip>
           </Popconfirm>
         </a>,
+        <a key="delete">
+          <Popconfirm
+            title={intl.formatMessage({
+              id: 'pages.data.option.remove.tips',
+              defaultMessage: '确认删除?',
+            })}
+            onConfirm={async () => {
+              await service.remove(record.id);
+              message.success(
+                intl.formatMessage({
+                  id: 'pages.data.option.success',
+                  defaultMessage: '操作成功!',
+                }),
+              );
+              actionRef.current?.reload();
+            }}
+          >
+            <Tooltip
+              title={intl.formatMessage({
+                id: 'pages.data.option.remove',
+                defaultMessage: '删除',
+              })}
+            >
+              <MinusOutlined />
+            </Tooltip>
+          </Popconfirm>
+        </a>,
       ],
     },
   ];
@@ -204,453 +298,170 @@ const Permission: React.FC = observer(() => {
   const schema: ISchema = {
     type: 'object',
     properties: {
-      collapse: {
-        type: 'void',
-        'x-component': 'FormTab',
+      id: {
+        title: intl.formatMessage({
+          id: 'pages.system.permission.id',
+          defaultMessage: '标识(ID)',
+        }),
+        type: 'string',
+        'x-decorator': 'FormItem',
+        'x-component': 'Input',
+        name: 'id',
+        required: true,
+      },
+      name: {
+        title: intl.formatMessage({
+          id: 'pages.table.name',
+          defaultMessage: '名称',
+        }),
+        type: 'string',
+        'x-decorator': 'FormItem',
+        'x-component': 'Input',
+        name: 'name',
+        required: true,
+      },
+      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-component-props': {
-          formTab: '{{formTab}}',
+          showSearch: true,
+          mode: 'multiple',
         },
-        properties: {
-          baseTab: {
-            type: 'void',
-            'x-component': 'FormTab.TabPane',
-            'x-component-props': {
-              tab: intl.formatMessage({
-                id: 'pages.system.permission.addInformation',
-                defaultMessage: '基础信息',
-              }),
+      },
+      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',
+                },
+              },
             },
-            properties: {
-              id: {
+            column2: {
+              type: 'void',
+              'x-component': 'ArrayTable.Column',
+              'x-component-props': {
+                width: 200,
                 title: intl.formatMessage({
-                  id: 'pages.system.permission.id',
-                  defaultMessage: '标识(ID)',
+                  id: 'pages.system.permission.addConfigurationType',
+                  defaultMessage: '操作类型',
                 }),
-                type: 'string',
-                'x-decorator': 'FormItem',
-                'x-component': 'Input',
-                name: 'id',
-                required: true,
               },
-              name: {
+              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: '名称',
                 }),
-                type: 'string',
-                'x-decorator': 'FormItem',
-                'x-component': 'Input',
-                name: 'name',
-                required: true,
               },
-              'properties.type': {
-                type: 'string',
+              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.searchTable.titleStatus',
-                  defaultMessage: '分类',
+                  id: 'pages.table.describe',
+                  defaultMessage: '描述',
                 }),
-                'x-decorator': 'FormItem',
-                'x-component': 'Select',
-                name: 'properties.type',
-                required: false,
               },
-            },
-          },
-          actionsTab: {
-            type: 'void',
-            'x-component': 'FormTab.TabPane',
-            'x-component-props': {
-              tab: intl.formatMessage({
-                id: 'pages.system.permission.addConfiguration',
-                defaultMessage: '操作配置',
-              }),
-            },
-            properties: {
-              actions: {
-                type: 'array',
-                'x-decorator': 'FormItem',
-                'x-component': 'ArrayTable',
-                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',
-                            },
-                            moveDown: {
-                              type: 'void',
-                              'x-component': 'ArrayTable.MoveDown',
-                            },
-                            moveUp: {
-                              type: 'void',
-                              'x-component': 'ArrayTable.MoveUp',
-                            },
-                          },
-                        },
-                      },
-                    },
-                  },
-                },
-                properties: {
-                  add: {
-                    type: 'void',
-                    'x-component': 'ArrayTable.Addition',
-                    title: intl.formatMessage({
-                      id: 'pages.system.permission.add',
-                      defaultMessage: '添加条目',
-                    }),
-                  },
+              properties: {
+                describe: {
+                  type: 'string',
+                  'x-decorator': 'Editable',
+                  'x-component': 'Input',
                 },
               },
             },
-          },
-          relationTab: {
-            type: 'void',
-            'x-component': 'FormTab.TabPane',
-            'x-component-props': {
-              tab: intl.formatMessage({
-                id: 'pages.system.permission.addPermissionOperation',
-                defaultMessage: '关联权限',
-              }),
-            },
-            properties: {
-              parents: {
-                type: 'array',
-                'x-decorator': 'FormItem',
-                'x-component': 'ArrayTable',
-                'x-component-props': {},
-                items: {
-                  type: 'object',
+            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: {
-                    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.addPermissionPreOperation',
-                          defaultMessage: '前置操作',
-                        }),
-                      },
-                      properties: {
-                        preActions: {
-                          type: 'string',
-                          'x-decorator': 'Editable',
-                          'x-component': 'Select',
-                          enum: defaultAction,
-                          'x-component-props': {
-                            mode: 'multiple',
-                            style: { minWidth: 100 },
-                          },
-                        },
-                      },
-                    },
-                    column3: {
-                      type: 'void',
-                      'x-component': 'ArrayTable.Column',
-                      'x-component-props': {
-                        width: 200,
-                        title: intl.formatMessage({
-                          id: 'pages.system.permission.addPermission',
-                          defaultMessage: '关联权限',
-                        }),
-                      },
-                      properties: {
-                        permission: {
-                          type: 'string',
-                          'x-decorator': 'Editable',
-                          'x-component': 'Select',
-                          enum: PermissionModel.permissionList,
-                          'x-component-props': {
-                            style: { minWidth: 200, maxWidth: 300 },
-                          },
-                        },
-                      },
-                    },
-                    column4: {
-                      type: 'void',
-                      'x-component': 'ArrayTable.Column',
-                      'x-component-props': {
-                        width: 200,
-                        title: intl.formatMessage({
-                          id: 'pages.system.permission.addPermissionOperation',
-                          defaultMessage: '关联操作',
-                        }),
-                      },
-                      properties: {
-                        actions: {
-                          type: 'string',
-                          'x-decorator': 'Editable',
-                          'x-component': 'Select',
-                          'x-component-props': {
-                            mode: 'multiple',
-                            style: { minWidth: 100 },
-                          },
-                          enum: defaultAction,
-                        },
-                      },
-                    },
-                    column5: {
+                    remove: {
                       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',
-                            },
-                            moveDown: {
-                              type: 'void',
-                              'x-component': 'ArrayTable.MoveDown',
-                            },
-                            moveUp: {
-                              type: 'void',
-                              'x-component': 'ArrayTable.MoveUp',
-                            },
-                          },
-                        },
-                      },
+                      'x-component': 'ArrayTable.Remove',
                     },
                   },
                 },
-                properties: {
-                  add: {
-                    type: 'void',
-                    'x-component': 'ArrayTable.Addition',
-                    title: intl.formatMessage({
-                      id: 'pages.system.permission.add',
-                      defaultMessage: '添加条目',
-                    }),
-                  },
-                },
               },
             },
           },
-          optionalFieldsTab: {
+        },
+        properties: {
+          add: {
             type: 'void',
-            'x-component': 'FormTab.TabPane',
-            'x-component-props': {
-              tab: intl.formatMessage({
-                id: 'pages.system.permission.addDataView',
-                defaultMessage: '数据视图',
-              }),
-            },
-            properties: {
-              optionalFields: {
-                type: 'array',
-                'x-decorator': 'FormItem',
-                'x-component': 'ArrayTable',
-                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.addDataViewField',
-                          defaultMessage: '字段',
-                        }),
-                      },
-                      properties: {
-                        name: {
-                          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.describe',
-                          defaultMessage: '描述',
-                        }),
-                      },
-                      properties: {
-                        describe: {
-                          type: 'string',
-                          'x-decorator': 'Editable',
-                          'x-component': 'Input',
-                        },
-                      },
-                    },
-                    column4: {
-                      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',
-                            },
-                            moveDown: {
-                              type: 'void',
-                              'x-component': 'ArrayTable.MoveDown',
-                            },
-                            moveUp: {
-                              type: 'void',
-                              'x-component': 'ArrayTable.MoveUp',
-                            },
-                          },
-                        },
-                      },
-                    },
-                  },
-                },
-                properties: {
-                  add: {
-                    type: 'void',
-                    'x-component': 'ArrayTable.Addition',
-                    title: intl.formatMessage({
-                      id: 'pages.system.permission.add',
-                      defaultMessage: '添加条目',
-                    }),
-                  },
-                },
-              },
-            },
+            '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}
@@ -664,6 +475,7 @@ const Permission: React.FC = observer(() => {
         modelConfig={{
           width: 1000,
         }}
+        search={false}
         menu={menu}
         schema={schema}
       />

+ 19 - 5
src/pages/system/Permission/service.ts

@@ -3,7 +3,7 @@ import type { PermissionItem } from '@/pages/system/Permission/typings';
 import { defer, from } from 'rxjs';
 import { request } from '@@/plugin-request/request';
 import SystemConst from '@/utils/const';
-import { filter, mergeMap } from 'rxjs/operators';
+import { map } from 'rxjs/operators';
 
 class Service extends BaseService<PermissionItem> {
   public getPermission = () =>
@@ -13,10 +13,24 @@ class Service extends BaseService<PermissionItem> {
           method: 'GET',
         }),
       ),
-    ).pipe(
-      filter((item) => item.status === 200),
-      mergeMap((item) => item.result as PermissionItem[]),
-    );
+    ).pipe(map((item) => item));
+  public getAssetTypes = () =>
+    defer(() =>
+      from(
+        request(`/${SystemConst.API_BASE}/asset/types`, {
+          method: 'GET',
+        }),
+      ),
+    ).pipe(map((item) => item));
+  public batchAdd = (data: any) =>
+    defer(() =>
+      from(
+        request(`/${SystemConst.API_BASE}/dimension/_batch`, {
+          method: 'POST',
+          data,
+        }),
+      ),
+    ).pipe(map((item) => item));
 }
 
 export default Service;

+ 107 - 0
src/pages/system/Role/Edit/Info/index.tsx

@@ -0,0 +1,107 @@
+import { Form, FormButtonGroup, FormItem, Input, Submit } from '@formily/antd';
+import { createSchemaField } from '@formily/react';
+import { Card, message, Spin } from 'antd';
+import { createForm } from '@formily/core';
+import { useEffect, useState } from 'react';
+import { service } from '@/pages/system/Role';
+import { useParams, history } from 'umi';
+
+const Info = () => {
+  const [loading, setLoading] = useState<boolean>(true);
+  const [type, setType] = useState<'edit' | 'disabled'>('disabled');
+  const [data, setData] = useState<RoleItem>();
+  const params = useParams<{ id: string }>();
+  const getDetail = async (id: string) => {
+    const res = await service.detail(id);
+    if (res.status === 200) {
+      setData(res.result);
+      setLoading(false);
+    }
+  };
+
+  useEffect(() => {
+    const { id } = params;
+    if (id) {
+      getDetail(id);
+    } else {
+      history.goBack();
+    }
+  }, [params, params.id]);
+
+  const SchemaField = createSchemaField({
+    components: {
+      Input,
+      FormItem,
+    },
+  });
+
+  const form = createForm({
+    validateFirst: true,
+    initialValues: {
+      id: data?.id,
+      name: data?.name,
+      description: data?.description,
+    },
+  });
+
+  const schema = {
+    type: 'object',
+    properties: {
+      name: {
+        type: 'string',
+        title: '名称',
+        required: true,
+        'x-disabled': type === 'disabled',
+        'x-decorator': 'FormItem',
+        'x-component': 'Input',
+      },
+      description: {
+        type: 'string',
+        title: '角色描述',
+        required: false,
+        'x-disabled': type === 'disabled',
+        'x-decorator': 'FormItem',
+        'x-component': 'Input.TextArea',
+      },
+    },
+  };
+
+  const save = async () => {
+    const values: RoleItem = await form.submit();
+    const resp = await service.modify(values.id, values);
+    if (resp.status === 200) {
+      message.success('操作成功!');
+      getDetail(values.id);
+      setType('disabled');
+    }
+  };
+
+  return (
+    <Card>
+      <div style={{ width: '500px' }}>
+        <Spin spinning={loading}>
+          <Form form={form} labelCol={5} wrapperCol={16}>
+            <SchemaField schema={schema} />
+            <FormButtonGroup.FormItem>
+              <Submit
+                block
+                size="large"
+                onClick={() => {
+                  if (type === 'edit') {
+                    save();
+                  } else {
+                    setType('edit');
+                  }
+                }}
+              >
+                {type === 'disabled' ? '编辑' : '保存'}
+              </Submit>
+            </FormButtonGroup.FormItem>
+          </Form>
+        </Spin>
+      </div>
+    </Card>
+  );
+};
+
+export default Info;

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

@@ -0,0 +1,114 @@
+import type { ProColumns } from '@jetlinks/pro-table';
+import ProTable from '@jetlinks/pro-table';
+import { message, Modal } from 'antd';
+import { useRef, useState } from 'react';
+import { useIntl } from '@@/plugin-locale/localeExports';
+import { service } from '@/pages/system/User/index';
+import encodeQuery from '@/utils/encodeQuery';
+import Service from '@/pages/system/Role/service';
+interface Props {
+  visible: boolean;
+  data: any;
+  cancel: () => void;
+}
+
+const BindUser = (props: Props) => {
+  const roleService = new Service('role');
+  const intl = useIntl();
+  const actionRef = useRef<any>();
+  const [selectedRowKeys, setSelectedRowKeys] = useState<string[]>([]);
+  const columns: ProColumns<RoleItem>[] = [
+    {
+      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',
+        defaultMessage: '用户名过长会自动收缩',
+      }),
+      formItemProps: {
+        rules: [
+          {
+            required: true,
+            message: '此项为必填项',
+          },
+        ],
+      },
+    },
+    {
+      title: intl.formatMessage({
+        id: 'pages.system.username',
+        defaultMessage: '用户名',
+      }),
+      dataIndex: 'username',
+      filters: true,
+      onFilter: true,
+    },
+  ];
+
+  return (
+    <Modal
+      title="添加"
+      width={900}
+      visible={props.visible}
+      onCancel={() => {
+        props.cancel();
+        setSelectedRowKeys([]);
+      }}
+      onOk={() => {
+        roleService.bindUser(props.data.id, selectedRowKeys).subscribe((resp) => {
+          if (resp.status === 200) {
+            message.success('操作成功!');
+            actionRef.current?.reload();
+          }
+        });
+        setSelectedRowKeys([]);
+        props.cancel();
+      }}
+    >
+      <ProTable
+        actionRef={actionRef}
+        rowSelection={{
+          selectedRowKeys: selectedRowKeys,
+          onChange: (key) => {
+            setSelectedRowKeys(key as string[]);
+          },
+        }}
+        pagination={{
+          pageSize: 10,
+        }}
+        request={async (param: any) => {
+          const response = await service.query(
+            encodeQuery({
+              pageSize: param.pageSize,
+              pageIndex: param.current,
+              terms: {
+                'id$in-dimension$role$not': props.data.id,
+              },
+            }),
+          );
+          return {
+            result: { data: response.result.data },
+            success: true,
+            status: 200,
+            total: response.result.total,
+          } as any;
+        }}
+        columns={columns}
+        rowKey="id"
+        toolBarRender={false}
+      />
+    </Modal>
+  );
+};
+
+export default BindUser;

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

@@ -0,0 +1,158 @@
+import { MinusOutlined, PlusOutlined } from '@ant-design/icons';
+import type { ProColumns, ActionType } from '@jetlinks/pro-table';
+import { Button, Card, message, Popconfirm, Space, Tooltip } from 'antd';
+import { useIntl } from '@@/plugin-locale/localeExports';
+import { useRef, useState } from 'react';
+import ProTable from '@jetlinks/pro-table';
+import BindUser from './BindUser';
+import { service } from '@/pages/system/User/index';
+import encodeQuery from '@/utils/encodeQuery';
+import { useParams } from 'umi';
+import Service from '@/pages/system/Role/service';
+
+const UserManage = () => {
+  const roleService = new Service('role');
+  const params = useParams<{ id: string }>();
+  const intl = useIntl();
+  const actionRef = useRef<ActionType>();
+  const [selectedRowKeys, setSelectedRowKeys] = useState<string[]>([]);
+  const [bindUserVisible, setBindUserVisible] = useState<boolean>(false);
+  const unBindUser = (id: string, ids: string[]) => {
+    roleService.unbindUser(id, ids).subscribe((resp) => {
+      if (resp.status === 200) {
+        message.success('操作成功!');
+        actionRef.current?.reload();
+      }
+    });
+  };
+  const columns: ProColumns<RoleItem>[] = [
+    {
+      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',
+        defaultMessage: '用户名过长会自动收缩',
+      }),
+      formItemProps: {
+        rules: [
+          {
+            required: true,
+            message: '此项为必填项',
+          },
+        ],
+      },
+    },
+    {
+      title: intl.formatMessage({
+        id: 'pages.system.username',
+        defaultMessage: '用户名',
+      }),
+      dataIndex: 'username',
+      filters: true,
+      onFilter: true,
+    },
+    {
+      title: intl.formatMessage({
+        id: 'pages.data.option',
+        defaultMessage: '操作',
+      }),
+      valueType: 'option',
+      align: 'center',
+      width: 200,
+      render: (text, record) => [
+        <a key="delete">
+          <Popconfirm
+            title={'确认解绑'}
+            onConfirm={() => {
+              unBindUser(params.id, [record.id]);
+            }}
+          >
+            <Tooltip title={'解绑'}>
+              <MinusOutlined />
+            </Tooltip>
+          </Popconfirm>
+        </a>,
+      ],
+    },
+  ];
+  return (
+    <Card>
+      <ProTable
+        actionRef={actionRef}
+        tableAlertOptionRender={() => (
+          <Space size={16}>
+            <a
+              onClick={() => {
+                setSelectedRowKeys([]);
+                unBindUser(params.id, [...selectedRowKeys]);
+              }}
+            >
+              批量解绑
+            </a>
+          </Space>
+        )}
+        toolBarRender={() => [
+          <Button
+            onClick={() => {
+              setBindUserVisible(true);
+            }}
+            key="button"
+            icon={<PlusOutlined />}
+            type="primary"
+          >
+            添加
+          </Button>,
+        ]}
+        rowSelection={{
+          selectedRowKeys: selectedRowKeys,
+          onChange: (key) => {
+            setSelectedRowKeys(key as string[]);
+          },
+        }}
+        pagination={{
+          pageSize: 10,
+        }}
+        request={async (param: any) => {
+          const response = await service.query(
+            encodeQuery({
+              pageSize: param.pageSize,
+              pageIndex: param.current,
+              terms: {
+                'id$in-dimension$role': params.id,
+              },
+            }),
+          );
+          return {
+            result: { data: response.result.data },
+            success: true,
+            status: 200,
+            total: response.result.total,
+          } as any;
+        }}
+        columns={columns}
+        rowKey="id"
+      />
+      <BindUser
+        visible={bindUserVisible}
+        data={{
+          id: params.id || '',
+        }}
+        cancel={() => {
+          setBindUserVisible(false);
+          actionRef.current?.reload();
+        }}
+      />
+    </Card>
+  );
+};
+export default UserManage;

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

@@ -0,0 +1,47 @@
+import { observer } from '@formily/react';
+import { PageContainer } from '@ant-design/pro-layout';
+import { useState } from 'react';
+import { history } from 'umi';
+import UserManage from '@/pages/system/Role/Edit/UserManage';
+// import Permission from '@/pages/system/Role/Edit/Permission';
+import Info from '@/pages/system/Role/Edit/Info';
+import { useIntl } from '@@/plugin-locale/localeExports';
+
+const RoleEdit = observer(() => {
+  const intl = useIntl();
+  const [tab, setTab] = useState<string>('baseInfo');
+
+  const list = [
+    {
+      key: 'baseInfo',
+      tab: intl.formatMessage({
+        id: 'pages.system.role.access.baseInfo',
+        defaultMessage: '基本信息',
+      }),
+      component: <Info />,
+    },
+    // {
+    //     key: 'permission',
+    //     tab: intl.formatMessage({
+    //         id: 'pages.system.role.access.permission',
+    //         defaultMessage: '权限分配',
+    //     }),
+    //     component: <Permission />,
+    // },
+    {
+      key: 'userManagement',
+      tab: intl.formatMessage({
+        id: 'pages.system.role.access.userManagement',
+        defaultMessage: '用户管理',
+      }),
+      component: <UserManage />,
+    },
+  ];
+
+  return (
+    <PageContainer onBack={history.goBack} tabList={list} onTabChange={setTab}>
+      {list.find((k) => k.key === tab)?.component}
+    </PageContainer>
+  );
+});
+export default RoleEdit;

+ 38 - 81
src/pages/system/Role/index.tsx

@@ -1,19 +1,22 @@
 import { PageContainer } from '@ant-design/pro-layout';
-import React, { useRef } from 'react';
-import { EditOutlined, KeyOutlined, MinusOutlined, UserAddOutlined } from '@ant-design/icons';
-import { Drawer, message, Modal, Popconfirm, Tooltip } from 'antd';
+import React, { useEffect, useRef } from 'react';
+import { EditOutlined, MinusOutlined } from '@ant-design/icons';
+import { message, Popconfirm, Tooltip } from 'antd';
 import type { ProColumns, ActionType } from '@jetlinks/pro-table';
 import BaseCrud from '@/components/BaseCrud';
-import { CurdModel } from '@/components/BaseCrud/model';
 import BaseService from '@/utils/BaseService';
 import { useIntl } from '@@/plugin-locale/localeExports';
 import { observer } from '@formily/react';
-import autzModel from '@/components/Authorization/autz';
-import Authorization from '@/components/Authorization';
-import { BindModel } from '@/components/BindUser/model';
-import BindUser from '@/components/BindUser';
+// import autzModel from '@/components/Authorization/autz';
+// import Authorization from '@/components/Authorization';
+// import { BindModel } from '@/components/BindUser/model';
+// import BindUser from '@/components/BindUser';
+import { Link, useLocation } from 'umi';
+import { Store } from 'jetlinks-store';
+import SystemConst from '@/utils/const';
+import { CurdModel } from '@/components/BaseCrud/model';
 
-const service = new BaseService<RoleItem>('dimension');
+export const service = new BaseService<RoleItem>('role');
 
 const Role: React.FC = observer(() => {
   const intl = useIntl();
@@ -70,7 +73,7 @@ const Role: React.FC = observer(() => {
         id: 'pages.table.describe',
         defaultMessage: '描述',
       }),
-      dataIndex: 'describe',
+      dataIndex: 'description',
       filters: true,
       onFilter: true,
     },
@@ -83,56 +86,17 @@ const Role: React.FC = observer(() => {
       align: 'center',
       width: 200,
       render: (text, record) => [
-        <a key="editable" onClick={() => CurdModel.update(record)}>
+        <Link to={`/system/role/edit/${record.id}`} key="link">
           <Tooltip
             title={intl.formatMessage({
               id: 'pages.data.option.edit',
               defaultMessage: '编辑',
             })}
+            key={'edit'}
           >
             <EditOutlined />
           </Tooltip>
-        </a>,
-        <a
-          key="autz"
-          onClick={() => {
-            autzModel.autzTarget.id = record.id;
-            autzModel.autzTarget.name = record.name;
-            autzModel.autzTarget.type = 'role';
-            autzModel.visible = true;
-          }}
-        >
-          <Tooltip
-            title={intl.formatMessage({
-              id: 'pages.data.option.authorize',
-              defaultMessage: '授权',
-            })}
-          >
-            <KeyOutlined />
-          </Tooltip>
-        </a>,
-
-        <a
-          key="bind"
-          onClick={() => {
-            BindModel.dimension = {
-              id: record.id,
-              name: record.name,
-              type: 'role',
-            };
-            BindModel.visible = true;
-            actionRef.current?.reload();
-          }}
-        >
-          <Tooltip
-            title={intl.formatMessage({
-              id: 'pages.system.role.option.bindUser',
-              defaultMessage: '绑定用户',
-            })}
-          >
-            <UserAddOutlined />
-          </Tooltip>
-        </a>,
+        </Link>,
         <a key="delete">
           <Popconfirm
             title={intl.formatMessage({
@@ -167,21 +131,6 @@ const Role: React.FC = observer(() => {
   const schema = {
     type: 'object',
     properties: {
-      id: {
-        title: intl.formatMessage({
-          id: 'pages.system.role.id',
-          defaultMessage: '角色标识',
-        }),
-        type: 'string',
-        'x-decorator': 'FormItem',
-        'x-component': 'Input',
-        'x-component-props': {
-          disabled: CurdModel.model === 'edit',
-        },
-        'x-decorator-props': {},
-        name: 'id',
-        required: true,
-      },
       name: {
         title: intl.formatMessage({
           id: 'pages.table.name',
@@ -195,7 +144,7 @@ const Role: React.FC = observer(() => {
         name: 'name',
         required: true,
       },
-      describe: {
+      description: {
         type: 'string',
         title: intl.formatMessage({
           id: 'pages.table.describe',
@@ -210,31 +159,39 @@ const Role: React.FC = observer(() => {
         name: 'password',
         required: false,
       },
-      typeId: {
-        type: 'string',
-        'x-visible': false,
-        'x-decorator': 'FormItem',
-        'x-component': 'Input',
-        name: 'typeId',
-        default: 'role',
-      },
     },
   };
 
+  const location = useLocation();
+
+  useEffect(() => {
+    if ((location as any).query?.save === 'true') {
+      CurdModel.add();
+    }
+    const subscription = Store.subscribe(SystemConst.BASE_UPDATE_DATA, (data) => {
+      if ((window as any).onTabSaveSuccess) {
+        (window as any).onTabSaveSuccess(data);
+        setTimeout(() => window.close(), 300);
+      }
+    });
+    return () => subscription.unsubscribe();
+  }, []);
   return (
     <PageContainer>
       <BaseCrud<RoleItem>
         actionRef={actionRef}
+        moduleName="role"
         columns={columns}
         service={service}
+        search={false}
         title={intl.formatMessage({
           id: 'pages.system.role',
           defaultMessage: '角色管理',
         })}
         schema={schema}
-        defaultParams={{ typeId: 'role' }}
+        // defaultParams={{ typeId: 'role' }}
       />
-      <Modal
+      {/* <Modal
         visible={BindModel.visible}
         closable={false}
         onCancel={() => {
@@ -244,8 +201,8 @@ const Role: React.FC = observer(() => {
         width={BindModel.bind ? '90vw' : '60vw'}
       >
         <BindUser />
-      </Modal>
-      <Drawer
+      </Modal> */}
+      {/* <Drawer
         title={intl.formatMessage({
           id: 'pages.data.option.authorize',
           defaultMessage: '授权',
@@ -262,7 +219,7 @@ const Role: React.FC = observer(() => {
           }}
           target={autzModel.autzTarget}
         />
-      </Drawer>
+      </Drawer> */}
     </PageContainer>
   );
 });

+ 37 - 0
src/pages/system/Role/service.ts

@@ -0,0 +1,37 @@
+import BaseService from '@/utils/BaseService';
+import { request } from 'umi';
+import { defer, from } from 'rxjs';
+import { map } from 'rxjs/operators';
+import SystemConst from '@/utils/const';
+
+class Service extends BaseService<RoleItem> {
+  queryMenuList = (params: any) =>
+    defer(() =>
+      from(
+        request(`/${SystemConst.API_BASE}/menu/user-own/list`, {
+          method: 'GET',
+          params,
+        }),
+      ),
+    ).pipe(map((item) => item));
+  bindUser = (roleId: string, params: any) =>
+    defer(() =>
+      from(
+        request(`/${SystemConst.API_BASE}/role/${roleId}/users/_bind`, {
+          method: 'POST',
+          data: params,
+        }),
+      ),
+    ).pipe(map((item) => item));
+  unbindUser = (roleId: string, params: any) =>
+    defer(() =>
+      from(
+        request(`/${SystemConst.API_BASE}/role/${roleId}/users/_unbind`, {
+          method: 'POST',
+          data: params,
+        }),
+      ),
+    ).pipe(map((item) => item));
+}
+
+export default Service;

+ 1 - 1
src/pages/system/Role/typings.d.ts

@@ -4,5 +4,5 @@ type RoleItem = {
   name: string;
   path: string;
   sortIndex: number;
-  typeId: string;
+  description: string;
 };

+ 318 - 0
src/pages/system/User/Save/index.tsx

@@ -0,0 +1,318 @@
+import { message, Modal } from 'antd';
+import { useIntl } from 'umi';
+import type { Field } from '@formily/core';
+import { createForm } from '@formily/core';
+import { createSchemaField } from '@formily/react';
+import React, { useEffect, useState } from 'react';
+import * as ICONS from '@ant-design/icons';
+import { Form, FormItem, Input, Password, Select, Switch } from '@formily/antd';
+import type { ISchema } from '@formily/json-schema';
+import { PlusOutlined } from '@ant-design/icons';
+import { action } from '@formily/reactive';
+import type { Response } from '@/utils/typings';
+import { service } from '@/pages/system/User';
+
+interface Props {
+  model: 'add' | 'edit' | 'query';
+  data: Partial<UserItem>;
+  close: () => void;
+}
+
+const Save = (props: Props) => {
+  const { model } = props;
+  const intl = useIntl();
+
+  const [data, setData] = useState<Partial<UserItem>>(props.data);
+
+  const getRole = () => service.queryRoleList();
+
+  const getOrg = () => service.queryOrgList();
+
+  const useAsyncDataSource = (api: any) => (field: Field) => {
+    field.loading = true;
+    api(field).then(
+      action.bound!((resp: Response<any>) => {
+        field.dataSource = resp.result?.map((item: Record<string, unknown>) => ({
+          label: item.name,
+          value: item.id,
+        }));
+        field.loading = false;
+      }),
+    );
+  };
+
+  const getUser = async () => {
+    if (props.data.id) {
+      console.log('id');
+      const response: Response<UserItem> = await service.queryDetail(props.data?.id);
+      if (response.status === 200) {
+        const temp = response.result as UserItem;
+        temp.orgIdList = (temp.orgList as { id: string; name: string }[]).map((item) => item.id);
+        temp.roleIdList = (temp.roleList as { id: string; name: string }[]).map((item) => item.id);
+        setData(temp);
+      }
+    }
+  };
+  useEffect(() => {
+    if (model === 'edit') {
+      getUser();
+    } else {
+      setData({});
+    }
+  }, [props.data, props.model]);
+
+  const form = createForm({
+    validateFirst: true,
+    initialValues: data,
+  });
+
+  const SchemaField = createSchemaField({
+    components: {
+      FormItem,
+      Input,
+      Password,
+      Switch,
+      Select,
+    },
+    scope: {
+      icon(name: any) {
+        return React.createElement(ICONS[name]);
+      },
+    },
+  });
+
+  const schema: ISchema = {
+    type: 'object',
+    properties: {
+      name: {
+        title: intl.formatMessage({
+          id: 'pages.system.name',
+          defaultMessage: '姓名',
+        }),
+        type: 'string',
+        'x-decorator': 'FormItem',
+        'x-component': 'Input',
+        name: 'name',
+        'x-validator': [
+          {
+            max: 50,
+            message: '最多可输入50个字符',
+          },
+          {
+            required: true,
+            message: '请输入姓名',
+          },
+        ],
+        // required: true,
+      },
+      username: {
+        title: intl.formatMessage({
+          id: 'pages.system.username',
+          defaultMessage: '用户名',
+        }),
+        type: 'string',
+        'x-decorator': 'FormItem',
+        'x-component': 'Input',
+        'x-component-props': {
+          disabled: model === 'edit',
+        },
+        'x-validator': [
+          {
+            max: 50,
+            message: '最多可输入50个字符',
+          },
+          {
+            required: true,
+            message: '请输入用户名',
+          },
+        ],
+        name: 'username',
+        required: true,
+      },
+      password: {
+        type: 'string',
+        title: intl.formatMessage({
+          id: 'pages.system.password',
+          defaultMessage: '密码',
+        }),
+        'x-decorator': 'FormItem',
+        'x-component': 'Password',
+        'x-component-props': {
+          checkStrength: true,
+          placeholder: '********',
+        },
+        maxLength: 128,
+        minLength: 6,
+        'x-reactions': [
+          {
+            dependencies: ['.confirmPassword'],
+            fulfill: {
+              state: {
+                selfErrors:
+                  '{{$deps[0] && $self.value && $self.value !==$deps[0] ? "两次密码输入不一致" : ""}}',
+              },
+            },
+          },
+        ],
+        name: 'password',
+        'x-validator': [
+          {
+            max: 128,
+            message: '密码最多可输入128位',
+          },
+          {
+            min: 6,
+            message: '密码不能少于6位',
+          },
+          {
+            required: model === 'add',
+            message: '请输入密码',
+          },
+        ],
+      },
+      confirmPassword: {
+        type: 'string',
+        title: intl.formatMessage({
+          id: 'pages.system.confirmPassword',
+          defaultMessage: '确认密码?',
+        }),
+        'x-decorator': 'FormItem',
+        'x-component': 'Password',
+        'x-component-props': {
+          checkStrength: true,
+          placeholder: '********',
+        },
+        maxLength: 128,
+        minLength: 6,
+        'x-validator': [
+          {
+            max: 128,
+            message: '密码最多可输入128位',
+          },
+          {
+            min: 6,
+            message: '密码不能少于6位',
+          },
+          {
+            required: model === 'add',
+            message: '请输入确认密码',
+          },
+        ],
+        'x-reactions': [
+          {
+            dependencies: ['.password'],
+            fulfill: {
+              state: {
+                selfErrors:
+                  '{{$deps[0] && $self.value && $self.value !== $deps[0] ? "两次密码输入不一致" : ""}}',
+              },
+            },
+          },
+        ],
+        'x-decorator-props': {},
+        name: 'confirmPassword',
+      },
+      roleIdList: {
+        title: '角色',
+        'x-decorator': 'FormItem',
+        'x-component': 'Select',
+        'x-component-props': {
+          mode: 'multiple',
+          showArrow: true,
+          filterOption: (input: string, option: any) =>
+            option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0,
+        },
+        'x-reactions': ['{{useAsyncDataSource(getRole)}}'],
+        'x-decorator-props': {
+          addonAfter: (
+            <a
+              onClick={() => {
+                const tab: any = window.open(`${origin}/#/system/role?save=true`);
+                tab!.onTabSaveSuccess = (value: any) => {
+                  form.setFieldState('roleIdList', (state) => {
+                    state.dataSource = state.dataSource?.concat([
+                      { label: value.name, value: value.id },
+                    ]);
+                  });
+                };
+              }}
+            >
+              <PlusOutlined />
+            </a>
+          ),
+        },
+      },
+      orgIdList: {
+        title: '部门',
+        'x-decorator': 'FormItem',
+        'x-component': 'Select',
+        'x-component-props': {
+          mode: 'multiple',
+          showArrow: true,
+          filterOption: (input: string, option: any) =>
+            option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0,
+        },
+        'x-decorator-props': {
+          addonAfter: (
+            <a
+              onClick={() => {
+                const tab: any = window.open(`${origin}/#/system/department?save=true`);
+                tab!.onTabSaveSuccess = (value: any) => {
+                  form.setFieldState('orgIdList', (state) => {
+                    console.log(value, 'value');
+                    state.dataSource = state.dataSource?.concat([
+                      { label: value.name, value: value.id },
+                    ]);
+                  });
+                };
+              }}
+            >
+              <PlusOutlined />
+            </a>
+          ),
+        },
+        'x-reactions': ['{{useAsyncDataSource(getOrg)}}'],
+      },
+    },
+  };
+
+  const save = async () => {
+    const value = await form.submit<UserItem>();
+    const temp: any = {};
+    temp.id = value.id;
+    temp.user = value;
+    temp.orgIdList = value.orgIdList;
+    temp.roleIdList = value.roleIdList;
+    const response = await service.saveUser(temp, model);
+    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="30vw"
+    >
+      <Form form={form} labelCol={4} wrapperCol={18}>
+        <SchemaField schema={schema} scope={{ useAsyncDataSource, getRole, getOrg }} />
+      </Form>
+    </Modal>
+  );
+};
+export default Save;

+ 84 - 189
src/pages/system/User/index.tsx

@@ -1,44 +1,36 @@
+import Service from '@/pages/system/User/serivce';
 import { PageContainer } from '@ant-design/pro-layout';
-import { useEffect, useRef, useState } from 'react';
+import SearchComponent from '@/components/SearchComponent';
+import type { ActionType, ProColumns } from '@jetlinks/pro-table';
+import ProTable from '@jetlinks/pro-table';
+import { Button, Card, message, Popconfirm, Tooltip } from 'antd';
 import {
-  EditOutlined,
-  KeyOutlined,
   CloseCircleOutlined,
+  DeleteOutlined,
+  EditOutlined,
   PlayCircleOutlined,
+  PlusOutlined,
 } from '@ant-design/icons';
-import { Tooltip, Popconfirm, message, Drawer } from 'antd';
-import type { ProColumns, ActionType } from '@jetlinks/pro-table';
-import BaseCrud from '@/components/BaseCrud';
-import { CurdModel } from '@/components/BaseCrud/model';
-import BaseService from '@/utils/BaseService';
-import { observer } from '@formily/react';
-import { Store } from 'jetlinks-store';
-import SystemConst from '@/utils/const';
 import { useIntl } from '@@/plugin-locale/localeExports';
-import type { ISchema } from '@formily/json-schema';
-import Authorization from '@/components/Authorization';
-import autzModel from '@/components/Authorization/autz';
-// import SearchComponent from '@/components/SearchComponent';
+import { useRef, useState } from 'react';
+import Save from './Save';
+import { observer } from '@formily/react';
+
+export const service = new Service('user');
 
-export const service = new BaseService<UserItem>('user');
 const User = observer(() => {
   const intl = useIntl();
   const actionRef = useRef<ActionType>();
 
-  const [model1, setModel] = useState(CurdModel.model);
-
-  useEffect(() => {
-    const modelSubscription = Store.subscribe(SystemConst.BASE_CURD_MODEL, setModel);
-    return () => modelSubscription.unsubscribe();
-  }, [CurdModel.model]);
+  const [model, setMode] = useState<'add' | 'edit' | 'query'>('query');
+  const [current, setCurrent] = useState<Partial<UserItem>>({});
+  const edit = async (record: UserItem) => {
+    setMode('edit');
+    setCurrent(record);
+  };
 
   const columns: ProColumns<UserItem>[] = [
     {
-      dataIndex: 'index',
-      valueType: 'indexBorder',
-      width: 48,
-    },
-    {
       title: intl.formatMessage({
         id: 'pages.system.name',
         defaultMessage: '姓名',
@@ -47,12 +39,12 @@ const User = observer(() => {
       copyable: true,
       ellipsis: true,
       align: 'center',
-      tip: intl.formatMessage({
-        id: 'pages.system.name.tips',
-        defaultMessage: '姓名过长会自动收缩',
-      }),
-      sorter: true,
-      defaultSortOrder: 'ascend',
+      // tip: intl.formatMessage({
+      //   id: 'pages.system.name.tips',
+      //   defaultMessage: '姓名过长会自动收缩',
+      // }),
+      // sorter: true,
+      // defaultSortOrder: 'ascend',
       formItemProps: {
         rules: [
           {
@@ -74,10 +66,10 @@ const User = observer(() => {
       copyable: true,
       ellipsis: true,
       align: 'center',
-      tip: intl.formatMessage({
-        id: 'pages.system.userName.tips',
-        defaultMessage: '用户名过长会自动收缩',
-      }),
+      // tip: intl.formatMessage({
+      //   id: 'pages.system.userName.tips',
+      //   defaultMessage: '用户名过长会自动收缩',
+      // }),
       formItemProps: {
         rules: [
           {
@@ -96,18 +88,11 @@ const User = observer(() => {
         defaultMessage: '状态',
       }),
       dataIndex: 'status',
-      filters: true,
+      // filters: true,
       align: 'center',
-      onFilter: true,
+      // onFilter: true,
       valueType: 'select',
       valueEnum: {
-        all: {
-          text: intl.formatMessage({
-            id: 'pages.searchTable.titleStatus.all',
-            defaultMessage: '全部',
-          }),
-          status: 'Default',
-        },
         1: {
           text: intl.formatMessage({
             id: 'pages.searchTable.titleStatus.normal',
@@ -133,14 +118,7 @@ const User = observer(() => {
       align: 'center',
       width: 200,
       render: (text, record) => [
-        <a
-          key="editable"
-          onClick={() => {
-            CurdModel.update(record);
-            CurdModel.model = 'edit';
-            setModel('edit');
-          }}
-        >
+        <a key="editable" onClick={() => edit(record)}>
           <Tooltip
             title={intl.formatMessage({
               id: 'pages.data.option.edit',
@@ -150,23 +128,6 @@ const User = observer(() => {
             <EditOutlined />
           </Tooltip>
         </a>,
-        <a
-          key="auth"
-          onClick={() => {
-            autzModel.autzTarget.id = record.id;
-            autzModel.autzTarget.name = record.name;
-            autzModel.visible = true;
-          }}
-        >
-          <Tooltip
-            title={intl.formatMessage({
-              id: 'pages.data.option.authorize',
-              defaultMessage: '授权',
-            })}
-          >
-            <KeyOutlined />
-          </Tooltip>
-        </a>,
         <a key="changeState">
           <Popconfirm
             title={intl.formatMessage({
@@ -197,136 +158,70 @@ const User = observer(() => {
             </Tooltip>
           </Popconfirm>
         </a>,
+        <a key="delete">
+          <Popconfirm
+            onConfirm={async () => {
+              await service.remove(record.id);
+              actionRef.current?.reload();
+            }}
+            title="确认删除?"
+          >
+            <Tooltip title="删除">
+              <DeleteOutlined />
+            </Tooltip>
+          </Popconfirm>
+        </a>,
       ],
     },
   ];
 
-  const schema: ISchema = {
-    type: 'object',
-    properties: {
-      username: {
-        title: intl.formatMessage({
-          id: 'pages.system.username',
-          defaultMessage: '用户名',
-        }),
-        type: 'string',
-        'x-decorator': 'FormItem',
-        'x-component': 'Input',
-        'x-component-props': {
-          disabled: model1 === 'edit',
-        },
-        'x-decorator-props': {},
-        name: 'username',
-        required: true,
-      },
-      name: {
-        title: intl.formatMessage({
-          id: 'pages.system.name',
-          defaultMessage: '姓名',
-        }),
-        type: 'string',
-        'x-decorator': 'FormItem',
-        'x-component': 'Input',
-        'x-component-props': {},
-        'x-decorator-props': {},
-        name: 'name',
-        required: true,
-      },
-      password: {
-        type: 'string',
-        title: intl.formatMessage({
-          id: 'pages.system.password',
-          defaultMessage: '密码',
-        }),
-        'x-decorator': 'FormItem',
-        'x-component': 'Password',
-        'x-component-props': {
-          checkStrength: true,
-        },
-        // 'x-hidden': model === 'edit',
-        'x-reactions': [
-          {
-            dependencies: ['.confirmPassword'],
-            fulfill: {
-              state: {
-                errors:
-                  '{{$deps[0] && $self.value && $self.value !==$deps[0] ? "确认密码不匹配" : ""}}',
-              },
-            },
-          },
-        ],
-        'x-decorator-props': {},
-        name: 'password',
-        required: false,
-      },
-      confirmPassword: {
-        type: 'string',
-        title: intl.formatMessage({
-          id: 'pages.system.confirmPassword',
-          defaultMessage: '确认密码?',
-        }),
-        'x-decorator': 'FormItem',
-        'x-component': 'Password',
-        // 'x-hidden': model === 'edit',
-        'x-component-props': {
-          checkStrength: true,
-        },
-        'x-reactions': [
-          {
-            dependencies: ['.password'],
-            fulfill: {
-              state: {
-                errors:
-                  '{{$deps[0] && $self.value && $self.value !== $deps[0] ? "确认密码不匹配" : ""}}',
-              },
-            },
-          },
-        ],
-        'x-decorator-props': {},
-        name: 'confirmPassword',
-        required: false,
-      },
-    },
-  };
-
-  intl.formatMessage({
-    id: 'pages.system.user',
-    defaultMessage: '默认值',
-  });
+  const [param, setParam] = useState({});
   return (
     <PageContainer>
-      <BaseCrud<UserItem>
+      <Card style={{ marginBottom: '20px' }}>
+        <SearchComponent
+          field={columns}
+          onSearch={(data) => setParam({ terms: data, total: null })}
+          target="user"
+          onReset={() => {
+            setParam({});
+            actionRef.current?.reset?.();
+          }}
+        />
+      </Card>
+      <ProTable<UserItem>
         actionRef={actionRef}
+        params={param}
         columns={columns}
         search={false}
-        service={service}
-        title={intl.formatMessage({
-          id: 'pages.system.user',
-          defaultMessage: '用户管理',
-        })}
-        moduleName="user"
-        schema={schema}
+        request={async (params) =>
+          service.query({ ...params, sorts: [{ name: 'createTime', order: 'desc' }] })
+        }
+        toolBarRender={() => [
+          <Button
+            onClick={() => {
+              setMode('add');
+            }}
+            key="button"
+            icon={<PlusOutlined />}
+            type="primary"
+          >
+            {intl.formatMessage({
+              id: 'pages.data.option.add',
+              defaultMessage: '新增',
+            })}
+          </Button>,
+        ]}
       />
-      <Drawer
-        title={intl.formatMessage({
-          id: 'pages.data.option.authorize',
-          defaultMessage: '授权',
-        })}
-        width="50vw"
-        visible={autzModel.visible}
-        onClose={() => {
-          autzModel.visible = false;
+      <Save
+        model={model}
+        close={() => {
+          setMode('query');
+          actionRef.current?.reload();
         }}
-      >
-        <Authorization
-          close={() => {
-            autzModel.visible = false;
-          }}
-          target={autzModel.autzTarget}
-        />
-      </Drawer>
+        data={current}
+      />
     </PageContainer>
   );
 });
-
 export default User;

+ 41 - 0
src/pages/system/User/serivce.ts

@@ -0,0 +1,41 @@
+import BaseService from '@/utils/BaseService';
+import { request } from 'umi';
+import SystemConst from '@/utils/const';
+
+class Service extends BaseService<UserItem> {
+  queryRoleList = (params?: any) =>
+    request(`${SystemConst.API_BASE}/role/_query/no-paging?paging=false`, {
+      method: 'GET',
+      params,
+    });
+
+  queryOrgList = (params?: any) =>
+    request(`${SystemConst.API_BASE}/organization/_all/tree`, {
+      method: 'GET',
+      params,
+    });
+
+  queryDetail = (id: string) =>
+    request(`/${SystemConst.API_BASE}/user/detail/${id}`, {
+      method: 'GET',
+    });
+
+  saveUser = (data: UserItem, type: 'add' | 'edit' | 'query') => {
+    const map = {
+      add: {
+        api: '_create',
+        method: 'POST',
+      },
+      edit: {
+        api: `${data.id}/_update`,
+        method: 'PUT',
+      },
+    };
+    return request(`/${SystemConst.API_BASE}/user/detail/${map[type].api}`, {
+      method: map[type].method,
+      data,
+    });
+  };
+}
+
+export default Service;

+ 5 - 0
src/pages/system/User/typings.d.ts

@@ -8,4 +8,9 @@ type UserItem = {
   telephone?: string;
   avatar?: string;
   description?: string;
+
+  orgList?: { id: string; name: string }[] | string[];
+  roleList?: { id: string; name: string }[] | string[];
+  orgIdList?: string[];
+  roleIdList?: string[];
 };

+ 0 - 2
src/typings.d.ts

@@ -22,5 +22,3 @@ declare module 'bizcharts-plugin-slider';
 declare let ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION: 'site' | undefined;
 
 declare const REACT_APP_ENV: 'test' | 'dev' | 'pre' | false;
-
-declare module '@dabeng/react-orgchart';

+ 3 - 2
src/utils/BaseService.ts

@@ -31,12 +31,13 @@ class BaseService<T> implements IBaseService<T> {
     return request(`${this.uri}/${id}`, { method: 'DELETE' });
   }
 
-  save(data: T): Promise<unknown> {
+  save(data: Partial<T>): Promise<unknown> {
     return request(this.uri, { data, method: 'POST' });
   }
 
   update(data: Partial<T>): Promise<any> {
-    return request(this.uri, { data, method: 'PATCH' });
+    // @ts-ignore
+    return data.id ? request(this.uri, { data, method: 'PATCH' }) : this.save(data);
   }
 
   detail(id: string): Promise<any> {

+ 2 - 0
src/utils/const.ts

@@ -13,6 +13,8 @@ class SystemConst {
 
   static BASE_CURD_MODEL = 'BASE_CURD_MODEL';
 
+  static BASE_UPDATE_DATA = 'BASE_UPDATE_DATA';
+
   static GLOBAL_WEBSOCKET = 'GLOBAL-WEBSOCKET';
 
   static BIND_USER_STATE = 'false';