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

feat(menu): merge xyh

Next xyh
Lind 3 лет назад
Родитель
Сommit
fb83d9ab28

+ 38 - 0
src/components/BadgeStatus/index.tsx

@@ -0,0 +1,38 @@
+import { Badge } from 'antd';
+
+/**
+ * 状态色
+ */
+export enum StatusColorEnum {
+  'success' = 'success',
+  'error' = 'error',
+  'processing' = 'processing',
+  'warning' = 'warning',
+  'default' = 'default',
+}
+
+export type StatusColorType = keyof typeof StatusColorEnum;
+
+export interface BadgeStatusProps {
+  text: string;
+  status: string | number;
+  /**
+   * 自定义status值颜色
+   * @example {
+   *   1: 'success',
+   *   0: 'error'
+   * }
+   */
+  statusNames?: Record<string | number, StatusColorType>;
+}
+
+export default (props: BadgeStatusProps) => {
+  const handleStatusColor = (): StatusColorType | undefined => {
+    if ('statusNames' in props) {
+      return props.statusNames![props.status];
+    }
+    return StatusColorEnum['default'];
+  };
+
+  return <Badge status={handleStatusColor()} text={props.text} />;
+};

+ 37 - 16
src/components/ProTableCard/CardItems/device.tsx

@@ -1,24 +1,45 @@
-import { Card } from 'antd';
-import { EditOutlined, EllipsisOutlined, SettingOutlined } from '@ant-design/icons';
+import { Card, Avatar } from 'antd';
+import React from 'react';
+import type { DeviceInstance } from '@/pages/device/Instance/typings';
+import { BadgeStatus } from '@/components';
+import { StatusColorEnum } from '@/components/BadgeStatus';
+import '@/style/common.less';
 
-export interface DeviceCardProps {
-  id: string;
-  name: string;
-  photoUrl?: string;
+export interface DeviceCardProps extends DeviceInstance {
+  actions?: React.ReactNode[];
+  avatarSize?: number;
 }
 
 export default (props: DeviceCardProps) => {
   return (
-    <Card
-      style={{ width: 280 }}
-      cover={null}
-      actions={[
-        <SettingOutlined key="setting" />,
-        <EditOutlined key="edit" />,
-        <EllipsisOutlined key="ellipsis" />,
-      ]}
-    >
-      <div>{props.name}</div>
+    <Card style={{ width: 340 }} cover={null} actions={props.actions}>
+      <div className={'pro-table-card-item'}>
+        <div className={'card-item-avatar'}>
+          <Avatar size={props.avatarSize || 64} src={props.photoUrl} />
+        </div>
+        <div className={'card-item-body'}>
+          <div className={'card-item-header'}>
+            <span className={'card-item-header-name ellipsis'}>{props.name}</span>
+            <BadgeStatus
+              status={props.state.value}
+              text={props.state.text}
+              statusNames={{
+                online: StatusColorEnum.success,
+                offline: StatusColorEnum.error,
+                notActive: StatusColorEnum.processing,
+              }}
+            />
+          </div>
+          <div className={'card-item-content'}>
+            <label>设备类型:</label>
+            <span className={'ellipsis'}>{props.deviceType ? props.deviceType.text : '--'}</span>
+          </div>
+          <div className={'card-item-content'}>
+            <label>产品名称:</label>
+            <span className={'ellipsis'}>{props.productName || '--'}</span>
+          </div>
+        </div>
+      </div>
     </Card>
   );
 };

+ 39 - 6
src/components/ProTableCard/index.less

@@ -18,13 +18,46 @@
   }
 
   .pro-table-card-items {
-    display: flex;
-    flex-wrap: wrap;
-    padding-bottom: 32px;
+    display: grid;
+    grid-gap: 26px;
+    grid-template-columns: repeat(auto-fit, 340px);
+    //display: flex;
+    //flex-wrap: wrap;
+    padding-bottom: 38px;
 
-    > div {
-      margin-right: 14px;
-      margin-bottom: 14px;
+    .pro-table-card-item {
+      display: flex;
+
+      .card-item-avatar {
+        margin-right: 16px;
+      }
+
+      .card-item-body {
+        display: flex;
+        flex-direction: column;
+        flex-grow: 1;
+        width: 0;
+
+        .card-item-header {
+          display: flex;
+          width: 100%;
+          margin-bottom: 12px;
+
+          .card-item-header-name {
+            flex: 1;
+            font-weight: bold;
+            font-size: 16px;
+          }
+        }
+
+        .card-item-content {
+          display: flex;
+
+          > span {
+            flex: 1;
+          }
+        }
+      }
     }
   }
 

+ 12 - 7
src/components/ProTableCard/index.tsx

@@ -3,7 +3,7 @@ import ProTable from '@jetlinks/pro-table';
 import type { ParamsType } from '@ant-design/pro-provider';
 import React, { useState } from 'react';
 import { isFunction } from 'lodash';
-import { Pagination, Space } from 'antd';
+import { Space, Pagination, Empty } from 'antd';
 import { AppstoreOutlined, BarsOutlined } from '@ant-design/icons';
 import classNames from 'classnames';
 import './index.less';
@@ -13,6 +13,8 @@ enum ModelEnum {
   CARD = 'CARD',
 }
 
+const Default_Size = 5;
+
 type ModelType = keyof typeof ModelEnum;
 
 interface ProTableCardProps<T> {
@@ -31,7 +33,7 @@ const ProTableCard = <
   const [total, setTotal] = useState<number | undefined>(0);
   const [current, setCurrent] = useState(1); // 当前页
   const [pageIndex, setPageIndex] = useState(0);
-  const [pageSize, setPageSize] = useState(10); // 每页条数
+  const [pageSize, setPageSize] = useState(Default_Size * 2); // 每页条数
 
   /**
    * 处理 Card
@@ -40,11 +42,11 @@ const ProTableCard = <
   const handleCard = (dataSource: readonly T[] | undefined): JSX.Element => {
     return (
       <div className={'pro-table-card-items'}>
-        {dataSource
-          ? dataSource.map((item) =>
-              cardRender && isFunction(cardRender) ? cardRender(item) : null,
-            )
-          : null}
+        {dataSource ? (
+          dataSource.map((item) => (cardRender && isFunction(cardRender) ? cardRender(item) : null))
+        ) : (
+          <Empty />
+        )}
       </div>
     );
   };
@@ -61,6 +63,7 @@ const ProTableCard = <
             pageSize,
           } as any
         }
+        options={model === ModelEnum.CARD ? false : props.options}
         request={async (param, sort, filter) => {
           if (request) {
             const resp = await request(param, sort, filter);
@@ -86,6 +89,7 @@ const ProTableCard = <
           },
           pageSize: pageSize,
           current: current,
+          pageSizeOptions: [Default_Size * 2, Default_Size * 4, 50, 100],
         }}
         toolBarRender={(action, row) => {
           const oldBar = toolBarRender ? toolBarRender(action, row) : [];
@@ -139,6 +143,7 @@ const ProTableCard = <
             setPageIndex(page - 1);
             setPageSize(size);
           }}
+          pageSizeOptions={[Default_Size * 2, Default_Size * 4, 50, 100]}
           pageSize={pageSize}
           showTotal={(num) => {
             const minSize = pageIndex * pageSize + 1;

+ 5 - 4
src/components/Upload/Image/index.less

@@ -16,8 +16,8 @@
 
   .upload-image-border {
     position: relative;
-    width: @with;
-    height: @height;
+    //width: @with;
+    //height: @height;
     overflow: hidden;
     //border-radius: 50%;
     border: @border;
@@ -35,6 +35,7 @@
       flex-direction: column;
       width: @with;
       height: @height;
+      padding: 8px;
       background-color: rgba(#000, 0.06);
       cursor: pointer;
 
@@ -53,8 +54,8 @@
       }
 
       .upload-image {
-        width: 144px;
-        height: 138px;
+        width: 100%;
+        height: 100%;
         //border-radius: 50%;
         background-repeat: no-repeat;
         background-position: center;

+ 4 - 2
src/components/Upload/Image/index.tsx

@@ -1,5 +1,5 @@
 import { message, Upload } from 'antd';
-import { useEffect, useState } from 'react';
+import React, { useEffect, useState } from 'react';
 import { LoadingOutlined, PlusOutlined } from '@ant-design/icons';
 import SystemConst from '@/utils/const';
 import Token from '@/utils/token';
@@ -19,6 +19,7 @@ interface UploadImageProps {
    * 图片大小限制, 单位 M,默认 4 M
    */
   size?: number;
+  style?: React.CSSProperties;
 }
 
 export default ({ onChange, value, ...extraProps }: UploadImageProps) => {
@@ -70,7 +71,7 @@ export default ({ onChange, value, ...extraProps }: UploadImageProps) => {
           beforeUpload={beforeUpload}
           {...extraProps}
         >
-          <div className={'upload-image-content'}>
+          <div className={'upload-image-content'} style={extraProps.style}>
             {values ? (
               <>
                 {/*<img width={120} height={120} src={values} />*/}
@@ -89,6 +90,7 @@ export default ({ onChange, value, ...extraProps }: UploadImageProps) => {
             )}
           </div>
         </Upload>
+        {extraProps.disabled && <div className={'upload-loading-mask'} />}
         {values && loading ? (
           <div className={'upload-loading-mask'}>
             {loading ? <LoadingOutlined style={{ fontSize: 28 }} /> : null}

+ 1 - 0
src/components/index.ts

@@ -1,3 +1,4 @@
 export { default as RadioCard } from './RadioCard';
 export { default as UploadImage } from './Upload/Image';
 export { default as ProTableCard } from './ProTableCard';
+export { default as BadgeStatus } from './BadgeStatus';

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

@@ -146,6 +146,7 @@ export default {
   'pages.system.menu.detail': '基本信息',
   'pages.system.menu.buttons': '按钮管理',
   'pages.system.menu.root': '菜单权限',
+  'page.system.menu.sort': '排序',
   // 系统设置-第三方平台
   'pages.system.openApi': '第三方平台',
   'pages.system.openApi.username': '用户名',

+ 76 - 80
src/pages/device/Instance/index.tsx

@@ -65,13 +65,79 @@ const Instance = () => {
   const [bindKeys, setBindKeys] = useState<any[]>([]);
   const intl = useIntl();
 
+  const tools = (record: DeviceInstance) => [
+    <Link
+      onClick={() => {
+        InstanceModel.current = record;
+      }}
+      to={`/device/instance/detail/${record.id}`}
+      key="link"
+    >
+      <Tooltip
+        title={intl.formatMessage({
+          id: 'pages.data.option.detail',
+          defaultMessage: '查看',
+        })}
+        key={'detail'}
+      >
+        <EyeOutlined />
+      </Tooltip>
+    </Link>,
+    <a href={record.id} target="_blank" rel="noopener noreferrer" key="view">
+      <Popconfirm
+        title={intl.formatMessage({
+          id: 'pages.data.option.disabled.tips',
+          defaultMessage: '确认禁用?',
+        })}
+        onConfirm={async () => {
+          if (record.state.value !== 'notActive') {
+            await service.undeployDevice(record.id);
+          } else {
+            await service.deployDevice(record.id);
+          }
+          message.success(
+            intl.formatMessage({
+              id: 'pages.data.option.success',
+              defaultMessage: '操作成功!',
+            }),
+          );
+          actionRef.current?.reload();
+        }}
+      >
+        <Tooltip
+          title={intl.formatMessage({
+            id: `pages.data.option.${record.state.value !== 'notActive' ? 'disabled' : 'enabled'}`,
+            defaultMessage: record.state.value !== 'notActive' ? '禁用' : '启用',
+          })}
+        >
+          {record.state.value !== 'notActive' ? <StopOutlined /> : <CheckCircleOutlined />}
+        </Tooltip>
+      </Popconfirm>
+    </a>,
+
+    <a key={'delete'}>
+      <Popconfirm
+        title="确认删除"
+        onConfirm={async () => {
+          await service.remove(record.id);
+          message.success(
+            intl.formatMessage({
+              id: 'pages.data.option.success',
+              defaultMessage: '操作成功!',
+            }),
+          );
+          actionRef.current?.reload();
+        }}
+      >
+        <Tooltip title={'删除'}>
+          <DeleteOutlined />
+        </Tooltip>
+      </Popconfirm>
+    </a>,
+  ];
+
   const columns: ProColumns<DeviceInstance>[] = [
     {
-      dataIndex: 'index',
-      valueType: 'indexBorder',
-      width: 48,
-    },
-    {
       title: 'ID',
       dataIndex: 'id',
     },
@@ -152,78 +218,7 @@ const Instance = () => {
       valueType: 'option',
       align: 'center',
       width: 200,
-      render: (text, record) => [
-        <Link
-          onClick={() => {
-            InstanceModel.current = record;
-          }}
-          to={`/device/instance/detail/${record.id}`}
-          key="link"
-        >
-          <Tooltip
-            title={intl.formatMessage({
-              id: 'pages.data.option.detail',
-              defaultMessage: '查看',
-            })}
-            key={'detail'}
-          >
-            <EyeOutlined />
-          </Tooltip>
-        </Link>,
-        <a href={record.id} target="_blank" rel="noopener noreferrer" key="view">
-          <Popconfirm
-            title={intl.formatMessage({
-              id: 'pages.data.option.disabled.tips',
-              defaultMessage: '确认禁用?',
-            })}
-            onConfirm={async () => {
-              if (record.state.value !== 'notActive') {
-                await service.undeployDevice(record.id);
-              } else {
-                await service.deployDevice(record.id);
-              }
-              message.success(
-                intl.formatMessage({
-                  id: 'pages.data.option.success',
-                  defaultMessage: '操作成功!',
-                }),
-              );
-              actionRef.current?.reload();
-            }}
-          >
-            <Tooltip
-              title={intl.formatMessage({
-                id: `pages.data.option.${
-                  record.state.value !== 'notActive' ? 'disabled' : 'enabled'
-                }`,
-                defaultMessage: record.state.value !== 'notActive' ? '禁用' : '启用',
-              })}
-            >
-              {record.state.value !== 'notActive' ? <StopOutlined /> : <CheckCircleOutlined />}
-            </Tooltip>
-          </Popconfirm>
-        </a>,
-
-        <a key={'delete'}>
-          <Popconfirm
-            title="确认删除"
-            onConfirm={async () => {
-              await service.remove(record.id);
-              message.success(
-                intl.formatMessage({
-                  id: 'pages.data.option.success',
-                  defaultMessage: '操作成功!',
-                }),
-              );
-              actionRef.current?.reload();
-            }}
-          >
-            <Tooltip title={'删除'}>
-              <DeleteOutlined />
-            </Tooltip>
-          </Popconfirm>
-        </a>,
-      ],
+      render: (text, record) => tools(record),
     },
   ];
 
@@ -369,12 +364,13 @@ const Instance = () => {
             setBindKeys(selectedRows.map((item) => item.id));
           },
         }}
-        toolBarRender={() => [
+        headerTitle={[
           <Button
             onClick={() => {
               setVisible(true);
               setCurrent({});
             }}
+            style={{ marginRight: 12 }}
             key="button"
             icon={<PlusOutlined />}
             type="primary"
@@ -388,11 +384,11 @@ const Instance = () => {
             <Button>批量操作</Button>
           </Dropdown>,
         ]}
-        cardRender={(item) => <DeviceCard {...item} />}
+        cardRender={(record) => <DeviceCard {...record} actions={tools(record)} />}
       />
       <Save
         data={current}
-        model={!current ? 'add' : 'edit'}
+        model={!Object.keys(current).length ? 'add' : 'edit'}
         close={() => {
           setVisible(false);
         }}

+ 1 - 0
src/pages/device/Instance/typings.d.ts

@@ -37,6 +37,7 @@ export type DeviceInstance = {
   onlineTime: string | number;
   offlineTime: string | number;
   tags: any;
+  photoUrl: string;
 };
 
 type Unit = {

+ 225 - 102
src/pages/system/Menu/Detail/edit.tsx

@@ -1,20 +1,40 @@
-import { Form, Input, InputNumber, Button, message } from 'antd';
+import {
+  Form,
+  Input,
+  InputNumber,
+  Button,
+  message,
+  Row,
+  Col,
+  Card,
+  Switch,
+  Radio,
+  Select,
+  TreeSelect,
+  Tooltip,
+} from 'antd';
 import Permission from '@/pages/system/Menu/components/permission';
 import { useIntl } from '@@/plugin-locale/localeExports';
 import { useEffect, useState } from 'react';
 import { service } from '@/pages/system/Menu';
 import { useRequest } from 'umi';
 import type { MenuItem } from '@/pages/system/Menu/typing';
-import { debounce } from 'lodash';
+// import { debounce } from 'lodash';
+import Title from '../components/Title';
+import { UploadImage } from '@/components';
+import { QuestionCircleFilled } from '@ant-design/icons';
 
 type EditProps = {
   data: MenuItem;
-  onLoad: () => void;
+  onLoad: (id: string) => void;
 };
 
 export default (props: EditProps) => {
   const intl = useIntl();
   const [disabled, setDisabled] = useState(true);
+  const [show, setShow] = useState(true);
+  const [accessSupport, setAccessSupport] = useState('unsupported');
+
   const [form] = Form.useForm();
 
   const { data: permissions, run: queryPermissions } = useRequest(service.queryPermission, {
@@ -22,6 +42,16 @@ export default (props: EditProps) => {
     formatResult: (response) => response.result.data,
   });
 
+  const { data: menuThree, run: queryMenuThree } = useRequest(service.queryMenuThree, {
+    manual: true,
+    formatResult: (response) => response.result,
+  });
+
+  const { data: assetsType, run: queryAssetsType } = useRequest(service.queryAssetsType, {
+    manual: true,
+    formatResult: (response) => response.result,
+  });
+
   const saveData = async () => {
     const formData = await form.validateFields();
     if (formData) {
@@ -29,126 +59,219 @@ export default (props: EditProps) => {
       if (response.status === 200) {
         message.success('操作成功!');
         setDisabled(true);
-        props.onLoad();
+        props.onLoad(response.result.id);
       } else {
         message.error('操作失败!');
       }
     }
   };
 
-  const filterThree = (e: any) => {
-    const _data: any = {
-      paging: false,
-    };
-    if (e.target.value) {
-      _data.terms = [{ column: 'name', value: e.target.value }];
-    }
-    queryPermissions(_data);
-  };
+  // const filterThree = (e: any) => {
+  //   const _data: any = {
+  //     paging: false,
+  //   };
+  //   if (e.target.value) {
+  //     _data.terms = [{ column: 'name', value: e.target.value }];
+  //   }
+  //   queryPermissions(_data);
+  // };
 
   useEffect(() => {
     queryPermissions({ paging: false });
+    queryMenuThree({ paging: false });
+    queryAssetsType();
     /* eslint-disable */
   }, []);
 
   useEffect(() => {
     if (form) {
-      form.setFieldsValue(props.data);
+      form.setFieldsValue({
+        ...props.data,
+        accessSupport: props.data.accessSupport ? props.data.accessSupport.value : 'unsupported',
+      });
+      setAccessSupport(props.data.accessSupport ? props.data.accessSupport.value : 'unsupported');
     }
+    setDisabled(!!props.data.id);
     /* eslint-disable */
   }, [props.data]);
 
   return (
     <div>
-      <Form form={form} labelCol={{ span: 4 }} wrapperCol={{ span: 20 }}>
-        <Form.Item
-          name="code"
-          label={intl.formatMessage({
-            id: 'page.system.menu.encoding',
-            defaultMessage: '编码',
-          })}
-          required={true}
-          rules={[{ required: true, message: '该字段是必填字段' }]}
-        >
-          <Input disabled={disabled} />
-        </Form.Item>
-        <Form.Item
-          name="name"
-          label={intl.formatMessage({
-            id: 'pages.table.name',
-            defaultMessage: '名称',
-          })}
-          required={true}
-          rules={[{ required: true, message: '该字段是必填字段' }]}
-        >
-          <Input disabled={disabled} />
-        </Form.Item>
-        <Form.Item
-          name="url"
-          label={intl.formatMessage({
-            id: 'page.system.menu.url',
-            defaultMessage: '页面地址',
-          })}
-          required={true}
-          rules={[{ required: true, message: '该字段是必填字段' }]}
-        >
-          <Input disabled={disabled} />
-        </Form.Item>
-        <Form.Item
-          label={intl.formatMessage({
-            id: 'page.system.menu.permissions',
-            defaultMessage: '权限',
-          })}
-        >
-          <Input disabled={disabled} onChange={debounce(filterThree, 300)} />
-          <Form.Item name="permissions">
-            <Permission
-              title={intl.formatMessage({
-                id: 'page.system.menu.permissions.operate',
-                defaultMessage: '操作权限',
-              })}
-              disabled={disabled}
-              data={permissions}
-            />
-          </Form.Item>
-        </Form.Item>
-        <Form.Item
-          name="sortIndex"
-          label={intl.formatMessage({
-            id: 'page.system.menu.sort',
-            defaultMessage: '排序说明',
-          })}
-        >
-          <InputNumber style={{ width: '100%' }} disabled={disabled} />
-        </Form.Item>
-        <Form.Item
-          name="describe"
-          label={intl.formatMessage({
-            id: 'pages.table.describe',
-            defaultMessage: '描述',
-          })}
-        >
-          <Input.TextArea disabled={disabled} />
-        </Form.Item>
-        <Form.Item name="id" hidden={true}>
-          <Input />
-        </Form.Item>
+      <Form form={form} layout={'vertical'}>
+        <Card>
+          <Title title={'基本信息'} />
+          <Row>
+            <Col span={3}>
+              <Form.Item name={'icon'} label={'菜单图标'} required={true}>
+                <UploadImage disabled={disabled} style={{ width: 140, height: 130 }} />
+              </Form.Item>
+            </Col>
+            <Col span={21}>
+              <Row gutter={[24, 0]}>
+                <Col span={12}>
+                  <Form.Item
+                    name="name"
+                    label={intl.formatMessage({
+                      id: 'pages.table.name',
+                      defaultMessage: '名称',
+                    })}
+                    required={true}
+                    rules={[{ required: true, message: '该字段是必填字段' }]}
+                  >
+                    <Input disabled={disabled} />
+                  </Form.Item>
+                </Col>
+                <Col span={12}>
+                  <Form.Item
+                    name="code"
+                    label={intl.formatMessage({
+                      id: 'page.system.menu.encoding',
+                      defaultMessage: '编码',
+                    })}
+                    required={true}
+                    rules={[{ required: true, message: '该字段是必填字段' }]}
+                  >
+                    <Input disabled={disabled} />
+                  </Form.Item>
+                </Col>
+              </Row>
+              <Row gutter={[24, 0]}>
+                <Col span={12}>
+                  <Form.Item
+                    name="url"
+                    label={intl.formatMessage({
+                      id: 'page.system.menu.url',
+                      defaultMessage: '页面地址',
+                    })}
+                    required={true}
+                    rules={[
+                      { required: true, message: '该字段是必填字段' },
+                      { max: 120, message: '最多可输入120字符' },
+                    ]}
+                  >
+                    <Input disabled={disabled} />
+                  </Form.Item>
+                </Col>
+                <Col span={12}>
+                  <Form.Item
+                    name="sortIndex"
+                    label={intl.formatMessage({
+                      id: 'page.system.menu.sort',
+                      defaultMessage: '排序',
+                    })}
+                  >
+                    <InputNumber style={{ width: '100%' }} disabled={disabled} />
+                  </Form.Item>
+                </Col>
+              </Row>
+            </Col>
+          </Row>
+        </Card>
+        <Card style={{ marginTop: 24 }}>
+          <Title
+            title={'权限配置'}
+            toolbarRender={
+              <Switch
+                disabled={disabled}
+                checkedChildren="开启"
+                unCheckedChildren="关闭"
+                checked={show}
+                onChange={(checked) => {
+                  setShow(checked);
+                }}
+              />
+            }
+          />
+          {show && (
+            <Row gutter={[0, 10]}>
+              <Col span={24}>
+                <Form.Item
+                  label={'数据权限控制'}
+                  tooltip={'此菜单页面数据所对应的资产类型'}
+                  name={'accessSupport'}
+                >
+                  <Radio.Group
+                    onChange={(e) => {
+                      setAccessSupport(e.target.value);
+                    }}
+                    disabled={disabled}
+                  >
+                    <Radio value={'unsupported'}>不支持</Radio>
+                    <Radio value={'support'}>支持</Radio>
+                    <Radio value={'indirect'}>
+                      间接控制
+                      <Tooltip
+                        placement="topLeft"
+                        title={'此菜单内的数据基于其他菜单的数据权限控制'}
+                      >
+                        <QuestionCircleFilled style={{ marginLeft: 8 }} />
+                      </Tooltip>
+                    </Radio>
+                  </Radio.Group>
+                </Form.Item>
+                {accessSupport === 'support' && (
+                  <Form.Item name={'assetType'}>
+                    <Select
+                      style={{ width: 500 }}
+                      disabled={disabled}
+                      options={
+                        assetsType
+                          ? assetsType.map((item: any) => ({ label: item.name, value: item.id }))
+                          : []
+                      }
+                    />
+                  </Form.Item>
+                )}
+                {accessSupport === 'indirect' && (
+                  <Form.Item name={'indirectMenus'}>
+                    <TreeSelect
+                      style={{ width: 400 }}
+                      disabled={disabled}
+                      multiple
+                      fieldNames={{ label: 'name', value: 'id' }}
+                      treeData={menuThree}
+                    />
+                  </Form.Item>
+                )}
+                <Form.Item
+                  label={intl.formatMessage({
+                    id: 'page.system.menu.permissions',
+                    defaultMessage: '权限',
+                  })}
+                  name="permissions"
+                >
+                  {/*<Input disabled={disabled} onChange={debounce(filterThree, 300)} style={{ width: 300 }}/>*/}
+                  {/*<Form.Item name='permissions'>*/}
+                  <Permission
+                    title={intl.formatMessage({
+                      id: 'page.system.menu.permissions.operate',
+                      defaultMessage: '操作权限',
+                    })}
+                    disabled={disabled}
+                    data={permissions}
+                  />
+                  {/*</Form.Item>*/}
+                </Form.Item>
+              </Col>
+            </Row>
+          )}
+          <Button
+            type="primary"
+            onClick={() => {
+              if (disabled) {
+                setDisabled(false);
+              } else {
+                saveData();
+              }
+            }}
+          >
+            {intl.formatMessage({
+              id: `pages.data.option.${disabled ? 'edit' : 'save'}`,
+              defaultMessage: '编辑',
+            })}
+          </Button>
+        </Card>
       </Form>
-      <Button
-        type="primary"
-        onClick={() => {
-          if (disabled) {
-            setDisabled(false);
-          } else {
-            saveData();
-          }
-        }}
-      >
-        {intl.formatMessage({
-          id: `pages.data.option.${disabled ? 'edit' : 'save'}`,
-          defaultMessage: '编辑',
-        })}
-      </Button>
     </div>
   );
 };

+ 15 - 7
src/pages/system/Menu/Detail/index.tsx

@@ -15,6 +15,7 @@ type LocationType = {
 export default () => {
   const intl = useIntl();
   const [tabKey, setTabKey] = useState('detail');
+  const [pId, setPid] = useState<string | null>(null);
   const location = useLocation<LocationType>();
 
   const { data, run: queryData } = useRequest(service.queryDetail, {
@@ -27,12 +28,16 @@ export default () => {
   /**
    * 获取当前菜单详情
    */
-  const queryDetail = () => {
+  const queryDetail = (editId?: string) => {
     const params = new URLSearchParams(location.search);
-    const id = params.get('id');
+    const id = editId || params.get('id');
+    const _pId = params.get('pId');
     if (id) {
       queryData(id);
     }
+    if (_pId) {
+      setPid(_pId);
+    }
   };
 
   useEffect(() => {
@@ -63,11 +68,14 @@ export default () => {
       }}
     >
       {tabKey === 'detail' ? (
-        <div style={{ background: '#fff', padding: '16px 24px' }}>
-          <div style={{ width: 660 }}>
-            {' '}
-            <BaseDetail data={data} onLoad={queryDetail} />{' '}
-          </div>
+        <div style={{ padding: '16px 24px' }}>
+          <BaseDetail
+            data={{
+              ...data,
+              parentId: pId,
+            }}
+            onLoad={queryDetail}
+          />
         </div>
       ) : (
         <Buttons data={data} onLoad={queryDetail} />

+ 16 - 0
src/pages/system/Menu/components/Title.tsx

@@ -0,0 +1,16 @@
+import React from 'react';
+import './title.less';
+
+interface TitleProps {
+  title: string | React.ReactNode;
+  toolbarRender?: React.ReactNode;
+}
+
+export default (props: TitleProps) => {
+  return (
+    <div className={'descriptions-title'}>
+      <span>{props.title}</span>
+      {props.toolbarRender}
+    </div>
+  );
+};

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

@@ -2,7 +2,7 @@
 @border: 1px solid @border-color-base;
 
 .permission-container {
-  margin-top: 20px;
+  //margin-top: 20px;
   border: @border;
 
   .permission-header {

+ 26 - 0
src/pages/system/Menu/components/title.less

@@ -0,0 +1,26 @@
+@import '~antd/lib/style/themes/variable';
+
+.descriptions-title {
+  position: relative;
+  display: flex;
+  align-items: center;
+  margin-bottom: 20px;
+  padding: 4px 0 4px 12px;
+  font-weight: bold;
+  font-size: 16px;
+
+  > span {
+    margin-right: 12px;
+  }
+
+  &::before {
+    position: absolute;
+    top: 5px;
+    left: 0;
+    width: 4px;
+    height: calc(100% - 10px);
+    background-color: @primary-color-hover;
+    border-radius: 2px;
+    content: ' ';
+  }
+}

+ 79 - 75
src/pages/system/Menu/index.tsx

@@ -4,7 +4,7 @@ import ProTable from '@jetlinks/pro-table';
 import type { ActionType, ProColumns } from '@jetlinks/pro-table';
 import { useRef, useState } from 'react';
 import { useIntl } from '@@/plugin-locale/localeExports';
-import { Button, message, Popconfirm, Tooltip, Modal, Form, Input } from 'antd';
+import { Button, message, Popconfirm, Tooltip } from 'antd';
 import {
   SearchOutlined,
   PlusOutlined,
@@ -39,7 +39,7 @@ export default observer(() => {
   const intl = useIntl();
 
   const [param, setParam] = useState({});
-  const [form] = Form.useForm();
+  // const [form] = Form.useForm();
   const history = useHistory();
 
   const deleteItem = async (id: string) => {
@@ -58,10 +58,13 @@ export default observer(() => {
   /**
    * 跳转详情页
    * @param id
+   * @param pId
    */
-  const pageJump = (id: string) => {
+  const pageJump = (id?: string, pId?: string) => {
     // 跳转详情
-    history.push(`${getMenuPathByCode(MENUS_CODE['system/Menu/Detail'])}?id=${id}`);
+    history.push(
+      `${getMenuPathByCode(MENUS_CODE['system/Menu/Detail'])}?id=${id || ''}&pId=${pId || ''}`,
+    );
   };
 
   const columns: ProColumns<MenuItem>[] = [
@@ -145,7 +148,8 @@ export default observer(() => {
             State.current = {
               parentId: record.id,
             };
-            State.visible = true;
+            // State.visible = true;
+            pageJump('', record.id);
           }}
         >
           <Tooltip
@@ -192,29 +196,29 @@ export default observer(() => {
     });
   };
 
-  const modalCancel = () => {
-    State.current = {};
-    State.visible = false;
-    form.resetFields();
-  };
+  // const modalCancel = () => {
+  //   State.current = {};
+  //   State.visible = false;
+  //   form.resetFields();
+  // };
 
-  const saveData = async () => {
-    const formData = await form.validateFields();
-    if (formData) {
-      const _data = {
-        ...formData,
-        parentId: State.current.parentId,
-      };
-      const response: any = await service.save(_data);
-      if (response.status === 200) {
-        message.success('操作成功!');
-        modalCancel();
-        pageJump(response.result.id);
-      } else {
-        message.error('操作成功!');
-      }
-    }
-  };
+  // const saveData = async () => {
+  //   const formData = await form.validateFields();
+  //   if (formData) {
+  //     const _data = {
+  //       ...formData,
+  //       parentId: State.current.parentId,
+  //     };
+  //     const response: any = await service.save(_data);
+  //     if (response.status === 200) {
+  //       message.success('操作成功!');
+  //       modalCancel();
+  //       pageJump(response.result.id);
+  //     } else {
+  //       message.error('操作成功!');
+  //     }
+  //   }
+  // };
 
   return (
     <PageContainer>
@@ -242,7 +246,7 @@ export default observer(() => {
         toolBarRender={() => [
           <Button
             onClick={() => {
-              State.visible = true;
+              pageJump();
             }}
             key="button"
             icon={<PlusOutlined />}
@@ -259,53 +263,53 @@ export default observer(() => {
           defaultMessage: '菜单列表',
         })}
       />
-      <Modal
-        title={intl.formatMessage({
-          id: State.current.parentId
-            ? 'pages.system.menu.option.addChildren'
-            : 'pages.data.option.add',
-          defaultMessage: '新增',
-        })}
-        visible={State.visible}
-        width={660}
-        onOk={saveData}
-        onCancel={modalCancel}
-      >
-        <Form form={form} labelCol={{ span: 4 }} wrapperCol={{ span: 20 }}>
-          <Form.Item
-            name="code"
-            label={intl.formatMessage({
-              id: 'page.system.menu.encoding',
-              defaultMessage: '编码',
-            })}
-            required={true}
-            rules={[
-              { required: true, message: '请输入编码' },
-              { max: 64, message: '最多可输入64个字符' },
-              {
-                pattern: /^[a-zA-Z0-9`!@#$%^&*()_+\-={}|\\\]\[;':",.\/<>?]+$/,
-                message: '请输入英文+数字+特殊字符(`!@#$%^&*()_+-={}|\\][;\':",./<>?)',
-              },
-            ]}
-          >
-            <Input />
-          </Form.Item>
-          <Form.Item
-            name="name"
-            label={intl.formatMessage({
-              id: 'pages.table.name',
-              defaultMessage: '名称',
-            })}
-            required={true}
-            rules={[
-              { required: true, message: '请输入名称' },
-              { max: 64, message: '最多可输入64个字符' },
-            ]}
-          >
-            <Input />
-          </Form.Item>
-        </Form>
-      </Modal>
+      {/*<Modal*/}
+      {/*  title={intl.formatMessage({*/}
+      {/*    id: State.current.parentId*/}
+      {/*      ? 'pages.system.menu.option.addChildren'*/}
+      {/*      : 'pages.data.option.add',*/}
+      {/*    defaultMessage: '新增',*/}
+      {/*  })}*/}
+      {/*  visible={State.visible}*/}
+      {/*  width={660}*/}
+      {/*  onOk={saveData}*/}
+      {/*  onCancel={modalCancel}*/}
+      {/*>*/}
+      {/*  <Form form={form} labelCol={{ span: 4 }} wrapperCol={{ span: 20 }}>*/}
+      {/*    <Form.Item*/}
+      {/*      name="code"*/}
+      {/*      label={intl.formatMessage({*/}
+      {/*        id: 'page.system.menu.encoding',*/}
+      {/*        defaultMessage: '编码',*/}
+      {/*      })}*/}
+      {/*      required={true}*/}
+      {/*      rules={[*/}
+      {/*        { required: true, message: '请输入编码' },*/}
+      {/*        { max: 64, message: '最多可输入64个字符' },*/}
+      {/*        {*/}
+      {/*          pattern: /^[a-zA-Z0-9`!@#$%^&*()_+\-={}|\\\]\[;':",.\/<>?]+$/,*/}
+      {/*          message: '请输入英文+数字+特殊字符(`!@#$%^&*()_+-={}|\\][;\':",./<>?)',*/}
+      {/*        },*/}
+      {/*      ]}*/}
+      {/*    >*/}
+      {/*      <Input />*/}
+      {/*    </Form.Item>*/}
+      {/*    <Form.Item*/}
+      {/*      name="name"*/}
+      {/*      label={intl.formatMessage({*/}
+      {/*        id: 'pages.table.name',*/}
+      {/*        defaultMessage: '名称',*/}
+      {/*      })}*/}
+      {/*      required={true}*/}
+      {/*      rules={[*/}
+      {/*        { required: true, message: '请输入名称' },*/}
+      {/*        { max: 64, message: '最多可输入64个字符' },*/}
+      {/*      ]}*/}
+      {/*    >*/}
+      {/*      <Input />*/}
+      {/*    </Form.Item>*/}
+      {/*  </Form>*/}
+      {/*</Modal>*/}
     </PageContainer>
   );
 });

+ 3 - 0
src/pages/system/Menu/service.ts

@@ -18,6 +18,9 @@ class Service extends BaseService<MenuItem> {
     request(`${SystemConst.API_BASE}/permission/_query`, { method: 'POST', data });
 
   queryDetail = (id: string) => request(`${this.uri}/${id}`, { method: 'GET' });
+
+  // 资产类型
+  queryAssetsType = () => request(`${SystemConst.API_BASE}/asset/types`, { method: 'GET' });
 }
 
 export default Service;

+ 1 - 0
src/pages/system/Menu/typing.d.ts

@@ -59,6 +59,7 @@ export type MenuItem = {
   createTime: number;
   redirect?: string;
   children?: MenuItem[];
+  accessSupport?: { text: string; value: string };
 };
 
 /**

+ 5 - 0
src/style/common.less

@@ -0,0 +1,5 @@
+.ellipsis {
+  overflow: hidden;
+  white-space: nowrap; //文本不会换行
+  text-overflow: ellipsis; //文本溢出显示省略号
+}