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

feat(菜单管理): 修改菜单管理新增页面

xieyonghong 3 лет назад
Родитель
Сommit
cdfda1d7f7

+ 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/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': '用户名',

+ 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 };
 };
 
 /**