Преглед изворни кода

feat(部门管理): 新增部门管理模块

xieyonghong пре 3 година
родитељ
комит
5f22c2f675

+ 20 - 0
config/routes.ts

@@ -82,6 +82,26 @@
         icon: 'smile',
         icon: 'smile',
         component: './system/DataSource',
         component: './system/DataSource',
       },
       },
+      {
+        path: '/system/department',
+        name: 'department',
+        icon: 'smile',
+        component: './system/Department',
+      },
+      {
+        hideInMenu: true,
+        path: '/system/department/:id/assets',
+        name: 'Assets',
+        icon: 'smile',
+        component: './system/Department/Assets',
+      },
+      {
+        hideInMenu: true,
+        path: '/system/department/:id/user',
+        name: 'Member',
+        icon: 'smile',
+        component: './system/Department/Member',
+      },
     ],
     ],
   },
   },
   {
   {

+ 95 - 0
src/pages/system/Department/Assets/deivce/bind.tsx

@@ -0,0 +1,95 @@
+// 资产-产品分类-绑定
+import type { ProColumns, ActionType } from '@jetlinks/pro-table';
+import ProTable from '@jetlinks/pro-table';
+import { service } from './index';
+import { Modal } from 'antd';
+import { useParams } from 'umi';
+import Models from './model';
+import { useRef } from 'react';
+import { observer } from '@formily/react';
+import { useIntl } from '@@/plugin-locale/localeExports';
+import type { ProductCategoryItem } from '@/pages/system/Department/typings';
+
+interface Props {
+  reload: () => void;
+  visible: boolean;
+  onCancel: () => void;
+}
+
+const Bind = observer((props: Props) => {
+  const intl = useIntl();
+  const param = useParams<{ id: string }>();
+  const actionRef = useRef<ActionType>();
+  const columns: ProColumns<ProductCategoryItem>[] = [
+    {
+      dataIndex: 'name',
+      title: intl.formatMessage({
+        id: 'pages.system.name',
+        defaultMessage: '姓名',
+      }),
+      search: {
+        transform: (value) => ({ name$LIKE: value }),
+      },
+    },
+    {
+      dataIndex: 'username',
+      title: intl.formatMessage({
+        id: 'pages.system.username',
+        defaultMessage: '用户名',
+      }),
+      search: {
+        transform: (value) => ({ username$LIKE: value }),
+      },
+    },
+  ];
+
+  const handleBind = () => {
+    if (Models.bindUsers.length) {
+      // service.handleUser(param.id, Models.bindUsers, 'bind').subscribe({
+      //   next: () => message.success('操作成功'),
+      //   error: () => message.error('操作失败'),
+      //   complete: () => {
+      //     Models.bindUsers = [];
+      //     actionRef.current?.reload();
+      //     props.reload();
+      //     props.onCancel()
+      //   },
+      // });
+    } else {
+      props.onCancel();
+    }
+  };
+
+  return (
+    <Modal
+      visible={props.visible}
+      onOk={handleBind}
+      onCancel={props.onCancel}
+      width={990}
+      title="绑定"
+    >
+      <ProTable<ProductCategoryItem>
+        actionRef={actionRef}
+        columns={columns}
+        rowKey="id"
+        pagination={{
+          pageSize: 5,
+        }}
+        rowSelection={{
+          selectedRowKeys: Models.bindUsers.map((item) => item.userId),
+          onChange: (selectedRowKeys, selectedRows) => {
+            Models.bindUsers = selectedRows.map((item) => ({
+              name: item.name,
+              userId: item.id,
+            }));
+          },
+        }}
+        request={(params) => service.queryProductCategoryList(params)}
+        defaultParams={{
+          'id$tenant-user$not': param.id,
+        }}
+      />
+    </Modal>
+  );
+});
+export default Bind;

+ 165 - 0
src/pages/system/Department/Assets/deivce/index.tsx

@@ -0,0 +1,165 @@
+// 资产分配-产品分类
+import ProTable from '@jetlinks/pro-table';
+import type { ActionType, ProColumns } from '@jetlinks/pro-table';
+import { useIntl } from '@@/plugin-locale/localeExports';
+import { Button, Popconfirm, Tooltip } from 'antd';
+import { useRef } from 'react';
+import { useParams } from 'umi';
+import { observer } from '@formily/react';
+import type { ProductItem } from '@/pages/system/Department/typings';
+import { DisconnectOutlined, PlusOutlined } from '@ant-design/icons';
+import Models from '@/pages/system/Department/Assets/productCategory/model';
+import Service from '@/pages/system/Department/Assets/service';
+
+export const service = new Service<ProductItem>();
+
+export default observer(() => {
+  const intl = useIntl();
+  const actionRef = useRef<ActionType>();
+
+  const param = useParams<{ id: string }>();
+
+  const handleUnBind = () => {
+    // service.handleUser(param.id, Models.unBindUsers, 'unbind').subscribe({
+    //   next: () => message.success('操作成功'),
+    //   error: () => message.error('操作失败'),
+    //   complete: () => {
+    //     Models.unBindUsers = [];
+    //     actionRef.current?.reload();
+    //   },
+    // });
+  };
+
+  const singleUnBind = (key: string) => {
+    Models.unBindKeys = [key];
+    handleUnBind();
+  };
+
+  const columns: ProColumns<ProductItem>[] = [
+    {
+      dataIndex: 'index',
+      valueType: 'indexBorder',
+      width: 48,
+    },
+    {
+      dataIndex: 'name',
+      title: intl.formatMessage({
+        id: 'pages.system.name',
+        defaultMessage: '姓名',
+      }),
+      search: {
+        transform: (value) => ({ name$LIKE: value }),
+      },
+    },
+    {
+      title: intl.formatMessage({
+        id: 'pages.system.tenant.memberManagement.administrators',
+        defaultMessage: '管理员',
+      }),
+      dataIndex: 'adminMember',
+      renderText: (text) => (text ? '是' : '否'),
+      search: false,
+    },
+    {
+      title: intl.formatMessage({
+        id: 'pages.searchTable.titleStatus',
+        defaultMessage: '状态',
+      }),
+      dataIndex: 'state',
+      renderText: (text) => text.text,
+      search: false,
+    },
+    {
+      title: intl.formatMessage({
+        id: 'pages.data.option',
+        defaultMessage: '操作',
+      }),
+      valueType: 'option',
+      align: 'center',
+      width: 200,
+      render: (text, record) => [
+        <Popconfirm
+          title={intl.formatMessage({
+            id: 'pages.system.role.option.unBindUser',
+            defaultMessage: '是否解除绑定',
+          })}
+          key="unBind"
+          onConfirm={() => {
+            singleUnBind(record.id);
+          }}
+        >
+          <a href="#">
+            <Tooltip
+              title={intl.formatMessage({
+                id: 'pages.system.role.option.unBindUser',
+                defaultMessage: '解除绑定',
+              })}
+            >
+              <DisconnectOutlined />
+            </Tooltip>
+          </a>
+        </Popconfirm>,
+      ],
+    },
+  ];
+
+  return (
+    <ProTable<ProductItem>
+      actionRef={actionRef}
+      columns={columns}
+      // schema={schema}
+      rowKey="id"
+      defaultParams={{
+        id: {
+          termType: 'dim-assets',
+          value: {
+            assetType: 'device',
+            targets: [
+              {
+                type: 'org',
+                id: param.id,
+              },
+            ],
+          },
+        },
+      }}
+      request={(params) => service.queryDeviceList(params)}
+      rowSelection={{
+        selectedRowKeys: Models.unBindKeys,
+        onChange: (selectedRowKeys, selectedRows) => {
+          Models.unBindKeys = selectedRows.map((item) => item.id);
+        },
+      }}
+      toolBarRender={() => [
+        <Button
+          onClick={() => {
+            Models.bind = true;
+          }}
+          icon={<PlusOutlined />}
+          type="primary"
+          key="bind"
+        >
+          {intl.formatMessage({
+            id: 'pages.system.role.option.bindUser',
+            defaultMessage: '分配资产',
+          })}
+        </Button>,
+        <Popconfirm
+          title={intl.formatMessage({
+            id: 'pages.system.role.option.unBindUser',
+            defaultMessage: '是否批量解除绑定',
+          })}
+          key="unBind"
+          onConfirm={handleUnBind}
+        >
+          <Button icon={<DisconnectOutlined />} key="bind">
+            {intl.formatMessage({
+              id: 'pages.system.role.option.unBindUser',
+              defaultMessage: '批量解绑',
+            })}
+          </Button>
+        </Popconfirm>,
+      ]}
+    />
+  );
+});

+ 16 - 0
src/pages/system/Department/Assets/deivce/model.ts

@@ -0,0 +1,16 @@
+// 用户数据模型
+import { model } from '@formily/reactive';
+
+type ModelType = {
+  bind: boolean;
+  bindUsers: { name: string; userId: string }[];
+  unBindUsers: string[];
+};
+
+const Models = model<ModelType>({
+  bind: false,
+  bindUsers: [],
+  unBindUsers: [],
+});
+
+export default Models;

+ 55 - 0
src/pages/system/Department/Assets/index.tsx

@@ -0,0 +1,55 @@
+// 部门-资产分配
+import { PageContainer } from '@ant-design/pro-layout';
+import { Tabs } from 'antd';
+import { useIntl } from '@@/plugin-locale/localeExports';
+import ProductCategory from './productCategory';
+import Product from './product';
+import Device from '@/pages/system/Department/Assets/deivce';
+
+// 资产类型
+const TabsArray = [
+  {
+    intlTitle: '1',
+    defaultMessage: '产品分类',
+    key: 'ProductCategory',
+    components: ProductCategory,
+  },
+  {
+    intlTitle: '2',
+    defaultMessage: '产品',
+    key: 'Product',
+    components: Product,
+  },
+  {
+    intlTitle: '3',
+    defaultMessage: '设备',
+    key: 'Device',
+    components: Device,
+  },
+];
+
+const Assets = () => {
+  const intl = useIntl();
+
+  return (
+    <PageContainer>
+      <div style={{ background: '#fff', padding: 12 }}>
+        <Tabs tabPosition="left" defaultActiveKey="ProductCategory">
+          {TabsArray.map((item) => (
+            <Tabs.TabPane
+              tab={intl.formatMessage({
+                id: item.intlTitle,
+                defaultMessage: item.defaultMessage,
+              })}
+              key={item.key}
+            >
+              <item.components />
+            </Tabs.TabPane>
+          ))}
+        </Tabs>
+      </div>
+    </PageContainer>
+  );
+};
+
+export default Assets;

+ 104 - 0
src/pages/system/Department/Assets/permissionModal.tsx

@@ -0,0 +1,104 @@
+import { createForm } from '@formily/core';
+import { createSchemaField } from '@formily/react';
+import { Form, FormItem, Checkbox } from '@formily/antd';
+import { message, Modal } from 'antd';
+import { useIntl } from '@@/plugin-locale/localeExports';
+import type { ISchema } from '@formily/json-schema';
+import type { ModalProps } from 'antd/lib/modal/Modal';
+import { useParams } from 'umi';
+import Service from './service';
+
+type PermissionType = 'device' | 'product' | 'deviceCategory';
+
+export interface PerModalProps extends Omit<ModalProps, 'onOk' | 'onCancel'> {
+  type: PermissionType;
+  bindKeys: string[];
+  visible: boolean;
+  /**
+   * Model关闭事件
+   * @param type 是否为请求接口后关闭,用于外部table刷新数据
+   */
+  onCancel?: (type: boolean) => void;
+}
+
+const service = new Service('assets');
+
+export default (props: PerModalProps) => {
+  const intl = useIntl();
+  const params = useParams<{ id: string }>();
+
+  const SchemaField = createSchemaField({
+    components: {
+      Form,
+      FormItem,
+      Checkbox,
+    },
+  });
+
+  const form = createForm({
+    validateFirst: true,
+    initialValues: {},
+  });
+
+  /**
+   * 关闭Modal
+   * @param type 是否需要刷新外部table数据
+   */
+  const modalClose = (type: boolean) => {
+    if (typeof props.onCancel === 'function') {
+      props.onCancel(type);
+    }
+  };
+
+  const saveData = async () => {
+    const formData: any = await form.submit();
+    service
+      .bind(props.type, [
+        {
+          targetType: 'org',
+          targetId: params.id,
+          assetType: props.type,
+          assetIdList: props.bindKeys,
+          permission: formData.permission,
+        },
+      ])
+      .subscribe({
+        next: () => message.success('操作成功'),
+        error: () => message.error('操作失败'),
+        complete: () => {
+          modalClose(true);
+        },
+      });
+  };
+
+  const schema: ISchema = {
+    type: 'object',
+    properties: {
+      permission: {
+        type: 'array',
+        title: '资产权限',
+        'x-decorator': 'FormItem',
+        'x-component': 'Checkbox',
+        'x-options': [],
+      },
+    },
+  };
+
+  return (
+    <Modal
+      title={intl.formatMessage({
+        id: `pages.data.option.`,
+        defaultMessage: '资产权限',
+      })}
+      visible={props.visible}
+      onOk={saveData}
+      onCancel={() => {
+        modalClose(false);
+      }}
+    >
+      <Form form={form} labelCol={5} wrapperCol={16}>
+        <SchemaField schema={schema} />
+      </Form>
+    </Modal>
+  );
+};

+ 95 - 0
src/pages/system/Department/Assets/product/bind.tsx

@@ -0,0 +1,95 @@
+// 资产-产品分类-绑定
+import type { ProColumns, ActionType } from '@jetlinks/pro-table';
+import ProTable from '@jetlinks/pro-table';
+import { service } from './index';
+import { Modal } from 'antd';
+import { useParams } from 'umi';
+import Models from './model';
+import { useRef } from 'react';
+import { observer } from '@formily/react';
+import { useIntl } from '@@/plugin-locale/localeExports';
+import type { ProductCategoryItem } from '@/pages/system/Department/typings';
+
+interface Props {
+  reload: () => void;
+  visible: boolean;
+  onCancel: () => void;
+}
+
+const Bind = observer((props: Props) => {
+  const intl = useIntl();
+  const param = useParams<{ id: string }>();
+  const actionRef = useRef<ActionType>();
+  const columns: ProColumns<ProductCategoryItem>[] = [
+    {
+      dataIndex: 'name',
+      title: intl.formatMessage({
+        id: 'pages.system.name',
+        defaultMessage: '姓名',
+      }),
+      search: {
+        transform: (value) => ({ name$LIKE: value }),
+      },
+    },
+    {
+      dataIndex: 'username',
+      title: intl.formatMessage({
+        id: 'pages.system.username',
+        defaultMessage: '用户名',
+      }),
+      search: {
+        transform: (value) => ({ username$LIKE: value }),
+      },
+    },
+  ];
+
+  const handleBind = () => {
+    if (Models.bindUsers.length) {
+      // service.handleUser(param.id, Models.bindUsers, 'bind').subscribe({
+      //   next: () => message.success('操作成功'),
+      //   error: () => message.error('操作失败'),
+      //   complete: () => {
+      //     Models.bindUsers = [];
+      //     actionRef.current?.reload();
+      //     props.reload();
+      //     props.onCancel()
+      //   },
+      // });
+    } else {
+      props.onCancel();
+    }
+  };
+
+  return (
+    <Modal
+      visible={props.visible}
+      onOk={handleBind}
+      onCancel={props.onCancel}
+      width={990}
+      title="绑定"
+    >
+      <ProTable<ProductCategoryItem>
+        actionRef={actionRef}
+        columns={columns}
+        rowKey="id"
+        pagination={{
+          pageSize: 5,
+        }}
+        rowSelection={{
+          selectedRowKeys: Models.bindUsers.map((item) => item.userId),
+          onChange: (selectedRowKeys, selectedRows) => {
+            Models.bindUsers = selectedRows.map((item) => ({
+              name: item.name,
+              userId: item.id,
+            }));
+          },
+        }}
+        request={(params) => service.queryProductCategoryList(params)}
+        defaultParams={{
+          'id$tenant-user$not': param.id,
+        }}
+      />
+    </Modal>
+  );
+});
+export default Bind;

+ 165 - 0
src/pages/system/Department/Assets/product/index.tsx

@@ -0,0 +1,165 @@
+// 资产分配-产品分类
+import ProTable from '@jetlinks/pro-table';
+import type { ActionType, ProColumns } from '@jetlinks/pro-table';
+import { useIntl } from '@@/plugin-locale/localeExports';
+import { Button, Popconfirm, Tooltip } from 'antd';
+import { useRef } from 'react';
+import { useParams } from 'umi';
+import { observer } from '@formily/react';
+import type { ProductItem } from '@/pages/system/Department/typings';
+import { DisconnectOutlined, PlusOutlined } from '@ant-design/icons';
+import Models from '@/pages/system/Department/Assets/productCategory/model';
+import Service from '@/pages/system/Department/Assets/service';
+
+export const service = new Service<ProductItem>();
+
+export default observer(() => {
+  const intl = useIntl();
+  const actionRef = useRef<ActionType>();
+
+  const param = useParams<{ id: string }>();
+
+  const handleUnBind = () => {
+    // service.handleUser(param.id, Models.unBindUsers, 'unbind').subscribe({
+    //   next: () => message.success('操作成功'),
+    //   error: () => message.error('操作失败'),
+    //   complete: () => {
+    //     Models.unBindUsers = [];
+    //     actionRef.current?.reload();
+    //   },
+    // });
+  };
+
+  const singleUnBind = (key: string) => {
+    Models.unBindKeys = [key];
+    handleUnBind();
+  };
+
+  const columns: ProColumns<ProductItem>[] = [
+    {
+      dataIndex: 'index',
+      valueType: 'indexBorder',
+      width: 48,
+    },
+    {
+      dataIndex: 'name',
+      title: intl.formatMessage({
+        id: 'pages.system.name',
+        defaultMessage: '姓名',
+      }),
+      search: {
+        transform: (value) => ({ name$LIKE: value }),
+      },
+    },
+    {
+      title: intl.formatMessage({
+        id: 'pages.system.tenant.memberManagement.administrators',
+        defaultMessage: '管理员',
+      }),
+      dataIndex: 'adminMember',
+      renderText: (text) => (text ? '是' : '否'),
+      search: false,
+    },
+    {
+      title: intl.formatMessage({
+        id: 'pages.searchTable.titleStatus',
+        defaultMessage: '状态',
+      }),
+      dataIndex: 'state',
+      renderText: (text) => text.text,
+      search: false,
+    },
+    {
+      title: intl.formatMessage({
+        id: 'pages.data.option',
+        defaultMessage: '操作',
+      }),
+      valueType: 'option',
+      align: 'center',
+      width: 200,
+      render: (text, record) => [
+        <Popconfirm
+          title={intl.formatMessage({
+            id: 'pages.system.role.option.unBindUser',
+            defaultMessage: '是否解除绑定',
+          })}
+          key="unBind"
+          onConfirm={() => {
+            singleUnBind(record.id);
+          }}
+        >
+          <a href="#">
+            <Tooltip
+              title={intl.formatMessage({
+                id: 'pages.system.role.option.unBindUser',
+                defaultMessage: '解除绑定',
+              })}
+            >
+              <DisconnectOutlined />
+            </Tooltip>
+          </a>
+        </Popconfirm>,
+      ],
+    },
+  ];
+
+  return (
+    <ProTable<ProductItem>
+      actionRef={actionRef}
+      columns={columns}
+      // schema={schema}
+      rowKey="id"
+      defaultParams={{
+        id: {
+          termType: 'dim-assets',
+          value: {
+            assetType: 'product',
+            targets: [
+              {
+                type: 'org',
+                id: param.id,
+              },
+            ],
+          },
+        },
+      }}
+      request={(params) => service.queryProductList(params)}
+      rowSelection={{
+        selectedRowKeys: Models.unBindKeys,
+        onChange: (selectedRowKeys, selectedRows) => {
+          Models.unBindKeys = selectedRows.map((item) => item.id);
+        },
+      }}
+      toolBarRender={() => [
+        <Button
+          onClick={() => {
+            Models.bind = true;
+          }}
+          icon={<PlusOutlined />}
+          type="primary"
+          key="bind"
+        >
+          {intl.formatMessage({
+            id: 'pages.system.role.option.bindUser',
+            defaultMessage: '分配资产',
+          })}
+        </Button>,
+        <Popconfirm
+          title={intl.formatMessage({
+            id: 'pages.system.role.option.unBindUser',
+            defaultMessage: '是否批量解除绑定',
+          })}
+          key="unBind"
+          onConfirm={handleUnBind}
+        >
+          <Button icon={<DisconnectOutlined />} key="bind">
+            {intl.formatMessage({
+              id: 'pages.system.role.option.unBindUser',
+              defaultMessage: '批量解绑',
+            })}
+          </Button>
+        </Popconfirm>,
+      ]}
+    />
+  );
+});

+ 16 - 0
src/pages/system/Department/Assets/product/model.ts

@@ -0,0 +1,16 @@
+// 用户数据模型
+import { model } from '@formily/reactive';
+
+type ModelType = {
+  bind: boolean;
+  bindUsers: { name: string; userId: string }[];
+  unBindUsers: string[];
+};
+
+const Models = model<ModelType>({
+  bind: false,
+  bindUsers: [],
+  unBindUsers: [],
+});
+
+export default Models;

+ 132 - 0
src/pages/system/Department/Assets/productCategory/bind.tsx

@@ -0,0 +1,132 @@
+// 资产-产品分类-绑定
+import type { ProColumns, ActionType } from '@jetlinks/pro-table';
+import ProTable from '@jetlinks/pro-table';
+import { service } from './index';
+import { Modal } from 'antd';
+import { useParams } from 'umi';
+import Models from './model';
+import { useRef, useState } from 'react';
+import { observer } from '@formily/react';
+import { useIntl } from '@@/plugin-locale/localeExports';
+import type { ProductCategoryItem } from '@/pages/system/Department/typings';
+import PermissionModal from '@/pages/system/Department/Assets/permissionModal';
+
+interface Props {
+  reload: () => void;
+  visible: boolean;
+  onCancel: () => void;
+}
+
+const Bind = observer((props: Props) => {
+  const intl = useIntl();
+  const param = useParams<{ id: string }>();
+  const actionRef = useRef<ActionType>();
+  const [perVisible, setPerVisible] = useState(false);
+  const columns: ProColumns<ProductCategoryItem>[] = [
+    {
+      dataIndex: 'id',
+      title: 'ID',
+      width: 48,
+    },
+    {
+      dataIndex: 'key',
+      title: intl.formatMessage({
+        id: 'pages.system.name',
+        defaultMessage: '标识',
+      }),
+      search: {
+        transform: (value) => ({ name$LIKE: value }),
+      },
+    },
+    {
+      dataIndex: 'name',
+      title: intl.formatMessage({
+        id: 'pages.system.name',
+        defaultMessage: '分类名称',
+      }),
+      search: false,
+    },
+  ];
+
+  const handleBind = () => {
+    if (Models.bindKeys.length) {
+      // service.bind('deviceCategory', [{
+      //   "targetType": "org",
+      //   "targetId": param.id,
+      //   "assetType": "deviceCategory",
+      //   "assetIdList": Models.bindKeys,
+      //   "permission": [
+      //     "read"
+      //   ]
+      // }]).subscribe({
+      //   next: () => message.success('操作成功'),
+      //   error: () => message.error('操作失败'),
+      //   complete: () => {
+      //     Models.bindKeys = [];
+      //     actionRef.current?.reload();
+      //     props.reload();
+      //     props.onCancel()
+      //   },
+      // });
+      setPerVisible(true);
+    } else {
+      props.onCancel();
+    }
+  };
+
+  return (
+    <Modal
+      visible={props.visible}
+      onOk={handleBind}
+      onCancel={props.onCancel}
+      width={990}
+      title="绑定"
+    >
+      <PermissionModal
+        visible={perVisible}
+        type="deviceCategory"
+        bindKeys={Models.bindKeys}
+        onCancel={(type) => {
+          setPerVisible(false);
+          if (type) {
+            props.reload();
+            props.onCancel();
+          }
+        }}
+      />
+      <ProTable<ProductCategoryItem>
+        actionRef={actionRef}
+        columns={columns}
+        rowKey="id"
+        pagination={{
+          pageSize: 5,
+        }}
+        rowSelection={{
+          selectedRowKeys: Models.bindKeys,
+          onChange: (selectedRowKeys, selectedRows) => {
+            Models.bindKeys = selectedRows.map((item) => item.id);
+          },
+        }}
+        params={{
+          terms: [
+            {
+              column: 'id',
+              termType: 'dim-assets$not',
+              value: {
+                assetType: 'deviceCategory',
+                targets: [
+                  {
+                    type: 'org',
+                    id: param.id,
+                  },
+                ],
+              },
+            },
+          ],
+        }}
+        request={(params) => service.queryProductCategoryList(params)}
+      />
+    </Modal>
+  );
+});
+export default Bind;

+ 186 - 0
src/pages/system/Department/Assets/productCategory/index.tsx

@@ -0,0 +1,186 @@
+// 资产分配-产品分类
+import ProTable from '@jetlinks/pro-table';
+import type { ActionType, ProColumns } from '@jetlinks/pro-table';
+import { useIntl } from '@@/plugin-locale/localeExports';
+import { Button, Popconfirm, Tooltip } from 'antd';
+import { useRef } from 'react';
+import { useParams } from 'umi';
+import { observer } from '@formily/react';
+import type { ProductCategoryItem } from '@/pages/system/Department/typings';
+import { DisconnectOutlined, PlusOutlined } from '@ant-design/icons';
+import Models from '@/pages/system/Department/Assets/productCategory/model';
+import Service from '@/pages/system/Department/Assets/service';
+import Bind from './bind';
+
+export const service = new Service<ProductCategoryItem>('assets');
+
+export default observer(() => {
+  const intl = useIntl();
+  const actionRef = useRef<ActionType>();
+
+  const param = useParams<{ id: string }>();
+
+  const handleUnBind = () => {
+    // service.unBind({})
+    // service.handleUser(param.id, Models.unBindUsers, 'unbind').subscribe({
+    //   next: () => message.success('操作成功'),
+    //   error: () => message.error('操作失败'),
+    //   complete: () => {
+    //     Models.unBindUsers = [];
+    //     actionRef.current?.reload();
+    //   },
+    // });
+  };
+
+  const singleUnBind = (key: string) => {
+    Models.unBindKeys = [key];
+    handleUnBind();
+  };
+
+  const columns: ProColumns<ProductCategoryItem>[] = [
+    {
+      dataIndex: 'id',
+      title: 'ID',
+      width: 48,
+    },
+    {
+      dataIndex: 'key',
+      title: intl.formatMessage({
+        id: 'pages.device.category.key',
+        defaultMessage: '标识',
+      }),
+      search: {
+        transform: (value) => ({ name$LIKE: value }),
+      },
+    },
+    {
+      dataIndex: 'name',
+      title: intl.formatMessage({
+        id: 'pages.device.category.name',
+        defaultMessage: '分类名称',
+      }),
+      search: false,
+    },
+    {
+      dataIndex: 'description',
+      title: intl.formatMessage({
+        id: 'pages.system.description',
+        defaultMessage: '说明',
+      }),
+      search: false,
+    },
+    {
+      title: intl.formatMessage({
+        id: 'pages.data.option',
+        defaultMessage: '操作',
+      }),
+      valueType: 'option',
+      align: 'center',
+      width: 200,
+      render: (text, record) => [
+        <Popconfirm
+          title={intl.formatMessage({
+            id: 'pages.system.role.option.unBindUser',
+            defaultMessage: '是否解除绑定',
+          })}
+          key="unBind"
+          onConfirm={() => {
+            singleUnBind(record.id);
+          }}
+        >
+          <a href="#">
+            <Tooltip
+              title={intl.formatMessage({
+                id: 'pages.system.role.option.unBindUser',
+                defaultMessage: '解除绑定',
+              })}
+            >
+              <DisconnectOutlined />
+            </Tooltip>
+          </a>
+        </Popconfirm>,
+      ],
+    },
+  ];
+
+  const closeModal = () => {
+    Models.bind = false;
+  };
+
+  return (
+    <>
+      <Bind
+        visible={Models.bind}
+        onCancel={closeModal}
+        reload={() => actionRef.current?.reload()}
+      />
+      <ProTable<ProductCategoryItem>
+        actionRef={actionRef}
+        columns={columns}
+        // schema={schema}
+        rowKey="id"
+        params={{
+          terms: [
+            {
+              column: 'id',
+              termType: 'dim-assets',
+              value: {
+                assetType: 'deviceCategory',
+                targets: [
+                  {
+                    type: 'org',
+                    id: param.id,
+                  },
+                ],
+              },
+            },
+          ],
+        }}
+        request={(params) => {
+          console.log(params);
+          return service.queryProductCategoryList(params);
+        }}
+        postData={(data) => {
+          console.log(data);
+          return data;
+        }}
+        rowSelection={{
+          selectedRowKeys: Models.unBindKeys,
+          onChange: (selectedRowKeys, selectedRows) => {
+            Models.unBindKeys = selectedRows.map((item) => item.id);
+          },
+        }}
+        toolBarRender={() => [
+          <Button
+            onClick={() => {
+              Models.bind = true;
+            }}
+            icon={<PlusOutlined />}
+            type="primary"
+            key="bind"
+          >
+            {intl.formatMessage({
+              id: 'pages.system.role.option.bindUser',
+              defaultMessage: '分配资产',
+            })}
+          </Button>,
+          <Popconfirm
+            title={intl.formatMessage({
+              id: 'pages.system.role.option.unBindUser',
+              defaultMessage: '是否批量解除绑定',
+            })}
+            key="unBind"
+            onConfirm={handleUnBind}
+          >
+            <Button icon={<DisconnectOutlined />} key="bind">
+              {intl.formatMessage({
+                id: 'pages.system.role.option.unBindUser',
+                defaultMessage: '批量解绑',
+              })}
+            </Button>
+          </Popconfirm>,
+        ]}
+      />
+    </>
+  );
+});

+ 16 - 0
src/pages/system/Department/Assets/productCategory/model.ts

@@ -0,0 +1,16 @@
+// 用户数据模型
+import { model } from '@formily/reactive';
+
+type ProductCategoryModelType = {
+  bind: boolean;
+  bindKeys: string[];
+  unBindKeys: string[];
+};
+
+const ProductCategoryModel = model<ProductCategoryModelType>({
+  bind: false,
+  bindKeys: [],
+  unBindKeys: [],
+});
+
+export default ProductCategoryModel;

+ 39 - 0
src/pages/system/Department/Assets/service.ts

@@ -0,0 +1,39 @@
+import BaseService from '@/utils/BaseService';
+import { request } from '@@/plugin-request/request';
+import SystemConst from '@/utils/const';
+import { defer, from } from 'rxjs';
+import { filter, map } from 'rxjs/operators';
+
+class Service<T> extends BaseService<T> {
+  // 资产绑定
+  bind = (type: string, params: any) =>
+    defer(() => from(request(`${this.uri}/bind/${type}`, { method: 'POST', params }))).pipe(
+      filter((item) => item.status === 200),
+      map((item) => item.result),
+    );
+
+  // 资产解绑
+  unBind = (type: string, params: any) =>
+    request(`${this.uri}/unbind/${type}`, { method: 'POST', params });
+
+  // 资产-产品分类
+  queryProductCategoryList = (params: any) => {
+    return request(`${SystemConst.API_BASE}/device/category/_tree`, {
+      method: 'GET',
+      params: {
+        ...params,
+        paging: false,
+      },
+    });
+  };
+  // 资产-设备
+  queryDeviceList = (params: any) => {
+    return request<T>(`${SystemConst.API_BASE}/device/instance/_query`, { method: 'GET', params });
+  };
+  // 资产-产品
+  queryProductList = (params: any) => {
+    return request<T>(`${SystemConst.API_BASE}/device-product/_query`, { method: 'GET', params });
+  };
+}
+
+export default Service;

+ 98 - 0
src/pages/system/Department/Member/bind.tsx

@@ -0,0 +1,98 @@
+// 部门-用户绑定
+import type { ProColumns, ActionType } from '@jetlinks/pro-table';
+import ProTable from '@jetlinks/pro-table';
+import { service } from '@/pages/system/Department/Member';
+import { message, Modal } from 'antd';
+import { useParams } from 'umi';
+import MemberModel from '@/pages/system/Department/Member/model';
+import { observer } from '@formily/react';
+import { useEffect, useRef } from 'react';
+import { useIntl } from '@@/plugin-locale/localeExports';
+
+interface Props {
+  reload: () => void;
+  visible: boolean;
+  onCancel: () => void;
+}
+
+const Bind = observer((props: Props) => {
+  const intl = useIntl();
+  const param = useParams<{ id: string }>();
+  const actionRef = useRef<ActionType>();
+
+  useEffect(() => {
+    if (props.visible) {
+      actionRef.current?.reload();
+    }
+  }, [props.visible]);
+
+  const columns: ProColumns<UserItem>[] = [
+    {
+      dataIndex: 'name',
+      title: intl.formatMessage({
+        id: 'pages.system.name',
+        defaultMessage: '姓名',
+      }),
+      search: {
+        transform: (value) => ({ name$LIKE: value }),
+      },
+    },
+    {
+      dataIndex: 'username',
+      title: intl.formatMessage({
+        id: 'pages.system.username',
+        defaultMessage: '用户名',
+      }),
+      search: {
+        transform: (value) => ({ username$LIKE: value }),
+      },
+    },
+  ];
+
+  const handleBind = () => {
+    if (MemberModel.bindUsers.length) {
+      service.handleUser(param.id, MemberModel.bindUsers, 'bind').subscribe({
+        next: () => message.success('操作成功'),
+        error: () => message.error('操作失败'),
+        complete: () => {
+          MemberModel.bindUsers = [];
+          actionRef.current?.reload();
+          props.reload();
+          props.onCancel();
+        },
+      });
+    } else {
+      props.onCancel();
+    }
+  };
+
+  return (
+    <Modal
+      visible={props.visible}
+      onOk={handleBind}
+      onCancel={props.onCancel}
+      width={990}
+      title="绑定"
+    >
+      <ProTable
+        actionRef={actionRef}
+        columns={columns}
+        rowKey="id"
+        pagination={{
+          pageSize: 5,
+        }}
+        rowSelection={{
+          selectedRowKeys: MemberModel.bindUsers,
+          onChange: (selectedRowKeys, selectedRows) => {
+            MemberModel.bindUsers = selectedRows.map((item) => item.id);
+          },
+        }}
+        request={(params) => service.queryUser(params)}
+        defaultParams={{
+          'id$in-dimension$org$not': param.id,
+        }}
+      />
+    </Modal>
+  );
+});
+export default Bind;

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

@@ -0,0 +1,160 @@
+// 部门-用户管理
+import { PageContainer } from '@ant-design/pro-layout';
+import ProTable from '@jetlinks/pro-table';
+import type { ActionType, ProColumns } from '@jetlinks/pro-table';
+import { useIntl } from '@@/plugin-locale/localeExports';
+import { Button, message, Popconfirm, Tooltip } from 'antd';
+import { useRef } from 'react';
+import { useParams } from 'umi';
+import { observer } from '@formily/react';
+import MemberModel from '@/pages/system/Department/Member/model';
+import type { MemberItem } from '@/pages/system/Department/typings';
+import Service from '@/pages/system/Department/Member/service';
+import { PlusOutlined, DisconnectOutlined } from '@ant-design/icons';
+import Bind from './bind';
+
+export const service = new Service('tenant');
+
+const Member = observer(() => {
+  const intl = useIntl();
+  const actionRef = useRef<ActionType>();
+
+  const param = useParams<{ id: string }>();
+
+  const handleUnBind = () => {
+    service.handleUser(param.id, MemberModel.unBindUsers, 'unbind').subscribe({
+      next: () => message.success('操作成功'),
+      error: () => message.error('操作失败'),
+      complete: () => {
+        MemberModel.unBindUsers = [];
+        actionRef.current?.reload();
+      },
+    });
+  };
+
+  const singleUnBind = (key: string) => {
+    MemberModel.unBindUsers = [key];
+    handleUnBind();
+  };
+
+  const columns: ProColumns<MemberItem>[] = [
+    {
+      dataIndex: 'index',
+      valueType: 'indexBorder',
+      width: 48,
+    },
+    {
+      dataIndex: 'name',
+      title: intl.formatMessage({
+        id: 'pages.system.name',
+        defaultMessage: '姓名',
+      }),
+      search: {
+        transform: (value) => ({ name$LIKE: value }),
+      },
+    },
+    {
+      title: intl.formatMessage({
+        id: 'pages.system.tenant.memberManagement.administrators',
+        defaultMessage: '管理员',
+      }),
+      dataIndex: 'adminMember',
+      renderText: (text) => (text ? '是' : '否'),
+      search: false,
+    },
+    {
+      title: intl.formatMessage({
+        id: 'pages.data.option',
+        defaultMessage: '操作',
+      }),
+      valueType: 'option',
+      align: 'center',
+      width: 200,
+      render: (text, record) => [
+        <Popconfirm
+          title={intl.formatMessage({
+            id: 'pages.system.role.option.unBindUser',
+            defaultMessage: '是否解除绑定',
+          })}
+          key="unBindUser"
+          onConfirm={() => {
+            singleUnBind(record.id);
+          }}
+        >
+          <a href="#">
+            <Tooltip
+              title={intl.formatMessage({
+                id: 'pages.system.role.option.unBindUser',
+                defaultMessage: '解除绑定',
+              })}
+            >
+              <DisconnectOutlined />
+            </Tooltip>
+          </a>
+        </Popconfirm>,
+      ],
+    },
+  ];
+
+  const closeModal = () => {
+    MemberModel.bind = false;
+  };
+
+  return (
+    <PageContainer>
+      <Bind
+        visible={MemberModel.bind}
+        onCancel={closeModal}
+        reload={() => actionRef.current?.reload()}
+      />
+      <ProTable<MemberItem>
+        actionRef={actionRef}
+        columns={columns}
+        // schema={schema}
+        rowKey="id"
+        request={(params) => service.queryUser(params)}
+        rowSelection={{
+          selectedRowKeys: MemberModel.unBindUsers,
+          onChange: (selectedRowKeys, selectedRows) => {
+            MemberModel.unBindUsers = selectedRows.map((item) => item.id);
+          },
+        }}
+        defaultParams={{
+          'id$in-dimension$org': param.id,
+        }}
+        toolBarRender={() => [
+          <Button
+            onClick={() => {
+              MemberModel.bind = true;
+            }}
+            icon={<PlusOutlined />}
+            type="primary"
+            key="bind"
+          >
+            {intl.formatMessage({
+              id: 'pages.system.role.option.bindUser',
+              defaultMessage: '绑定用户',
+            })}
+          </Button>,
+          <Popconfirm
+            title={intl.formatMessage({
+              id: 'pages.system.role.option.unBindUser',
+              defaultMessage: '是否批量解除绑定',
+            })}
+            key="unBind"
+            onConfirm={handleUnBind}
+          >
+            <Button icon={<DisconnectOutlined />} key="bind">
+              {intl.formatMessage({
+                id: 'pages.system.role.option.unBindUser',
+                defaultMessage: '批量解绑',
+              })}
+            </Button>
+          </Popconfirm>,
+        ]}
+      />
+    </PageContainer>
+  );
+});
+
+export default Member;

+ 16 - 0
src/pages/system/Department/Member/model.ts

@@ -0,0 +1,16 @@
+// 用户数据模型
+import { model } from '@formily/reactive';
+
+type MemberModelType = {
+  bind: boolean;
+  bindUsers: string[];
+  unBindUsers: string[];
+};
+
+const MemberModel = model<MemberModelType>({
+  bind: false,
+  bindUsers: [],
+  unBindUsers: [],
+});
+
+export default MemberModel;

+ 29 - 0
src/pages/system/Department/Member/service.ts

@@ -0,0 +1,29 @@
+import BaseService from '@/utils/BaseService';
+import type { MemberItem } from '@/pages/system/Department/typings';
+import { defer, from } from 'rxjs';
+import { request } from '@@/plugin-request/request';
+import SystemConst from '@/utils/const';
+import { filter, map } from 'rxjs/operators';
+
+class Service extends BaseService<MemberItem> {
+  queryUser = (params: Record<string, unknown>) =>
+    request(`/${SystemConst.API_BASE}/user/_query`, {
+      method: 'GET',
+      params,
+    });
+
+  handleUser = (id: string, data: Record<string, unknown>[] | string[], type: 'bind' | 'unbind') =>
+    defer(() =>
+      from(
+        request(`/${SystemConst.API_BASE}/organization/${id}/users/_${type}`, {
+          method: 'POST',
+          data,
+        }),
+      ),
+    ).pipe(
+      filter((item) => item.status === 200),
+      map((item) => item.result),
+    );
+}
+
+export default Service;

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

@@ -0,0 +1,228 @@
+// 部门管理
+import { PageContainer } from '@ant-design/pro-layout';
+import ProTable from '@jetlinks/pro-table';
+import type { ActionType, ProColumns } from '@jetlinks/pro-table';
+import { useRef } from 'react';
+import { useIntl } from '@@/plugin-locale/localeExports';
+import { Button, message, Popconfirm, Tooltip } from 'antd';
+import { EditOutlined, PlusOutlined } from '@ant-design/icons';
+import Service from '@/pages/system/Department/service';
+import type { ISchema } from '@formily/json-schema';
+import type { DepartmentItem } from '@/pages/system/Department/typings';
+import { observer } from '@formily/react';
+import { model } from '@formily/reactive';
+import { Link } from 'umi';
+import Save from './save';
+
+export const service = new Service('organization');
+
+type ModelType = {
+  visible: boolean;
+  current: Partial<DepartmentItem>;
+  parentId: string | undefined;
+};
+export const State = model<ModelType>({
+  visible: false,
+  current: {},
+  parentId: undefined,
+});
+
+export default observer(() => {
+  const actionRef = useRef<ActionType>();
+  const intl = useIntl();
+
+  /**
+   * 根据部门ID删除数据
+   * @param id
+   */
+  const deleteItem = async (id: string) => {
+    const response: any = await service.remove(id);
+    if (response.status === 200) {
+      message.success(
+        intl.formatMessage({
+          id: 'pages.data.option.success',
+          defaultMessage: '操作成功!',
+        }),
+      );
+    }
+    actionRef.current?.reload();
+  };
+
+  const columns: ProColumns<DepartmentItem>[] = [
+    {
+      title: intl.formatMessage({
+        id: 'pages.table.name',
+        defaultMessage: '名称',
+      }),
+      dataIndex: 'name',
+    },
+    {
+      title: intl.formatMessage({
+        id: 'pages.device.instanceDetail.detail.sort',
+        defaultMessage: '排序',
+      }),
+      dataIndex: 'sortIndex',
+    },
+    {
+      title: intl.formatMessage({
+        id: 'pages.data.option',
+        defaultMessage: '操作',
+      }),
+      valueType: 'option',
+      align: 'center',
+      width: 240,
+      render: (text, record) => [
+        <a
+          key="editable"
+          onClick={() => {
+            State.current = record;
+            State.visible = true;
+          }}
+        >
+          <Tooltip
+            title={intl.formatMessage({
+              id: 'pages.data.option.edit',
+              defaultMessage: '编辑',
+            })}
+          >
+            <EditOutlined />
+          </Tooltip>
+        </a>,
+        <a
+          key="editable"
+          onClick={() => {
+            State.current = {
+              parentId: record.id,
+            };
+            State.visible = true;
+          }}
+        >
+          <Tooltip
+            title={intl.formatMessage({
+              id: 'pages.data.option.edit',
+              defaultMessage: '新增子部门',
+            })}
+          >
+            <EditOutlined />
+          </Tooltip>
+        </a>,
+        <Link key="assets" to={`/system/department/${record.id}/assets`}>
+          <Tooltip
+            title={intl.formatMessage({
+              id: 'pages.data.option.edit',
+              defaultMessage: '资产分配',
+            })}
+          >
+            <EditOutlined />
+          </Tooltip>
+        </Link>,
+        <Link key="user" to={`/system/department/${record.id}/user`}>
+          <Tooltip
+            title={intl.formatMessage({
+              id: 'pages.data.option.edit',
+              defaultMessage: '用户',
+            })}
+          >
+            <EditOutlined />
+          </Tooltip>
+        </Link>,
+        <Popconfirm
+          key="unBindUser"
+          title={intl.formatMessage({
+            id: 'pages.system.role.option.unBindUser',
+            defaultMessage: '是否批量解除绑定',
+          })}
+          onConfirm={() => {
+            deleteItem(record.id);
+          }}
+        >
+          <Tooltip
+            title={intl.formatMessage({
+              id: 'pages.data.option.delete',
+              defaultMessage: '删除',
+            })}
+          >
+            <a key="delete">
+              <EditOutlined />
+            </a>
+          </Tooltip>
+        </Popconfirm>,
+      ],
+    },
+  ];
+
+  const schema: ISchema = {
+    type: 'object',
+    properties: {
+      name: {
+        type: 'string',
+        title: '名称',
+        required: true,
+        'x-decorator': 'FormItem',
+        'x-component': 'Input',
+      },
+      sortIndex: {
+        type: 'string',
+        title: '排序',
+        'x-decorator': 'FormItem',
+        'x-component': 'Input',
+      },
+    },
+  };
+
+  return (
+    <PageContainer>
+      <ProTable<any>
+        columns={columns}
+        actionRef={actionRef}
+        request={async (params) => {
+          delete params.pageIndex;
+          const response = await service.queryOrgThree({ paging: false, ...params });
+          return {
+            code: response.message,
+            result: {
+              data: response.result as CategoryItem[],
+              pageIndex: 0,
+              pageSize: 0,
+              total: 0,
+            },
+            status: response.status,
+          };
+        }}
+        rowKey="id"
+        pagination={false}
+        toolBarRender={() => [
+          <Button
+            onClick={() => (State.visible = true)}
+            key="button"
+            icon={<PlusOutlined />}
+            type="primary"
+          >
+            {intl.formatMessage({
+              id: 'pages.data.option.add',
+              defaultMessage: '新增',
+            })}
+          </Button>,
+        ]}
+        headerTitle={intl.formatMessage({
+          id: 'pages.system.department',
+          defaultMessage: '部门列表',
+        })}
+        defaultParams={{ typeId: 'org' }}
+      />
+      <Save<DepartmentItem>
+        service={service}
+        onCancel={(type) => {
+          if (type) {
+            actionRef.current?.reload();
+          }
+          State.current = {};
+          State.visible = false;
+        }}
+        data={State.current}
+        visible={State.visible}
+        schema={schema}
+      />
+    </PageContainer>
+  );
+});

+ 117 - 0
src/pages/system/Department/save.tsx

@@ -0,0 +1,117 @@
+// Modal 弹窗,用于新增、修改数据
+import React from 'react';
+import { createForm } from '@formily/core';
+import { createSchemaField } from '@formily/react';
+import {
+  ArrayTable,
+  Editable,
+  Form,
+  FormGrid,
+  FormItem,
+  FormTab,
+  Input,
+  NumberPicker,
+  Password,
+  Select,
+  Switch,
+  Upload,
+  Checkbox,
+} from '@formily/antd';
+import { message, Modal } from 'antd';
+import { useIntl } from '@@/plugin-locale/localeExports';
+import type { ISchema } from '@formily/json-schema';
+import type { ModalProps } from 'antd/lib/modal/Modal';
+import FUpload from '@/components/Upload';
+import * as ICONS from '@ant-design/icons';
+import type BaseService from '@/utils/BaseService';
+
+export interface SaveModalProps<T> extends Omit<ModalProps, 'onOk' | 'onCancel'> {
+  service: BaseService<T>;
+  data?: Partial<T>;
+  /**
+   * Model关闭事件
+   * @param type 是否为请求接口后关闭,用于外部table刷新数据
+   */
+  onCancel?: (type: boolean) => void;
+  schema: ISchema;
+}
+
+const Save = <T extends object>(props: SaveModalProps<T>) => {
+  const { data, schema, onCancel, service } = props;
+  const intl = useIntl();
+
+  const SchemaField = createSchemaField({
+    components: {
+      FormItem,
+      FormTab,
+      Input,
+      Password,
+      Upload,
+      Select,
+      ArrayTable,
+      Switch,
+      FormGrid,
+      Editable,
+      NumberPicker,
+      FUpload,
+      Checkbox,
+    },
+    scope: {
+      icon(name: any) {
+        return React.createElement(ICONS[name]);
+      },
+    },
+  });
+
+  const form = createForm({
+    validateFirst: true,
+    initialValues: data || {},
+  });
+
+  /**
+   * 关闭Modal
+   * @param type 是否需要刷新外部table数据
+   */
+  const modalClose = (type: boolean) => {
+    if (typeof onCancel === 'function') {
+      onCancel(type);
+    }
+  };
+
+  /**
+   * 新增、修改数据
+   */
+  const saveData = async () => {
+    const formData: T = await form.submit();
+
+    const response =
+      data && 'id' in data ? await service.update(formData) : await service.save(formData);
+
+    if (response.status === 200) {
+      message.success('操作成功!');
+      modalClose(true);
+    } else {
+      message.error('操作成功!');
+    }
+  };
+
+  return (
+    <Modal
+      title={intl.formatMessage({
+        id: `pages.data.option.${data && 'id' in data ? 'edit' : 'add'}`,
+        defaultMessage: '新增',
+      })}
+      visible={props.visible}
+      onOk={saveData}
+      onCancel={() => {
+        modalClose(false);
+      }}
+    >
+      <Form form={form} labelCol={5} wrapperCol={16}>
+        <SchemaField schema={schema} />
+      </Form>
+    </Modal>
+  );
+};
+
+export default Save;

+ 11 - 0
src/pages/system/Department/service.ts

@@ -0,0 +1,11 @@
+import BaseService from '@/utils/BaseService';
+import type { DepartmentItem } from '@/pages/system/Department/typings';
+import { request } from '@@/plugin-request/request';
+
+class Service extends BaseService<DepartmentItem> {
+  // 根据ID查询部门
+  queryOrgThree = (params: any) =>
+    request(`${this.uri}/_all/tree`, { method: 'POST', data: params });
+}
+
+export default Service;

+ 42 - 0
src/pages/system/Department/typings.d.ts

@@ -0,0 +1,42 @@
+import type { State } from '@/utils/typings';
+
+// 部门
+export type DepartmentItem = {
+  id: string;
+  name: string;
+  path: string;
+  sortIndex: number;
+  level: number;
+  code: string;
+  parentId: string;
+  children: DepartmentItem[];
+};
+
+// 用户
+export type MemberItem = {
+  id: string;
+  name: string;
+  username: string;
+  status: number;
+  createTime: number;
+  creatorId: string;
+};
+
+// 产品
+export type ProductItem = {
+  id: string;
+  name: string;
+  description: string;
+};
+
+// 产品分类
+export type ProductCategoryItem = { key: string } & ProductItem;
+
+// 设备
+export type DeviceItem = {
+  id: string;
+  name: string;
+  productName: string;
+  createTime: string;
+  state: State;
+};

+ 27 - 0
src/utils/menu.ts

@@ -0,0 +1,27 @@
+// 路由components映射
+const findComponents = (files: __WebpackModuleApi.RequireContext) => {
+  const modules = {};
+  files.keys().forEach((key) => {
+    // 删除路径开头的./ 以及结尾的 /index;
+    const str = key.replace(/(\.\/|\.tsx)/g, '').replace('/index', '');
+    modules[str] = files(key).default;
+  });
+  return modules;
+};
+
+/**
+ * 处理为正确的路由格式
+ * @param extraRoutes 后端菜单数据
+ */
+const getRoutes = (extraRoutes: any[]) => {
+  const allComponents = findComponents(require.context('@/pages', true, /index(\.tsx)$/));
+  return extraRoutes.map((route) => {
+    const component = allComponents[route.key];
+    return {
+      ...route,
+      component,
+    };
+  });
+};
+
+export default getRoutes;