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

feat(permission): permission 、 role

Lind 3 лет назад
Родитель
Сommit
273d93efe3

+ 126 - 0
src/pages/system/Role/Edit/Permission/DataPermission.tsx

@@ -0,0 +1,126 @@
+import { useEffect, useState } from 'react';
+import type { TableColumnsType } from 'antd';
+import { Form } from 'antd';
+import { Checkbox, Select, Table } from 'antd';
+import Service from '@/pages/system/Role/service';
+import _ from 'lodash';
+
+interface Props {
+  initialValues?: any;
+  data: any;
+  change: (data: any) => void;
+}
+
+const DataPermission = (props: Props) => {
+  const service = new Service('role-permissions-data');
+  const [form] = Form.useForm();
+  const [typeList, setTypeList] = useState<any[]>([]);
+  const [dimensionsList, setDimensionsList] = useState<any>({});
+
+  const menuAssetsTypes = (data: any) => {
+    service.queryAssetTypeList(data).subscribe((resp) => {
+      if (resp.status === 200) {
+        setTypeList(resp.result);
+      }
+    });
+  };
+
+  useEffect(() => {
+    if (typeList.length > 0) {
+      typeList.map((item) => {
+        service.queryDimensionsList(item.id).subscribe((resp) => {
+          if (resp.status === 200) {
+            dimensionsList[item.id] = resp.result;
+            setDimensionsList({ ...dimensionsList });
+          } else {
+            dimensionsList[item.id] = undefined;
+            setDimensionsList({ ...dimensionsList });
+          }
+        });
+      });
+    }
+  }, [typeList]);
+
+  useEffect(() => {
+    if (Array.isArray(props.data) && props.data.length > 0) {
+      menuAssetsTypes(props.data);
+    }
+  }, [props.data]);
+
+  useEffect(() => {
+    props.initialValues.map((item: { dimensions: any[]; assetType: string }) => {
+      const type = _.map(item?.dimensions || [], 'dimensionType');
+      form.setFieldsValue({
+        [item.assetType]: {
+          value: true,
+          type,
+        },
+      });
+    });
+  }, [props.initialValues]);
+
+  const dataColumns: TableColumnsType<PermissionItem> = [
+    {
+      title: '数据权限',
+      dataIndex: 'name',
+      render: (text: string, record) => (
+        <div style={{ display: 'flex', alignItems: 'center' }}>
+          <Form.Item name={[`${record.id}`, 'value']} valuePropName="checked">
+            <Checkbox
+              style={{ width: '150px' }}
+              onChange={(e) => {
+                form.setFieldsValue({
+                  [`${record.id}`]: {
+                    value: e.target.checked,
+                    type: form.getFieldValue(`${record.id}`).type,
+                  },
+                });
+                props.change(form.getFieldsValue());
+              }}
+            >
+              {record.name}
+            </Checkbox>
+          </Form.Item>
+          <Form.Item name={[record.id, 'type']}>
+            <Select
+              style={{ width: '300px' }}
+              showSearch
+              placeholder="请选择"
+              mode="multiple"
+              onChange={(value: string) => {
+                form.setFieldsValue({
+                  [`${record.id}`]: {
+                    type: value,
+                    value: form.getFieldValue(`${record.id}`).value,
+                  },
+                });
+                props.change(form.getFieldsValue());
+              }}
+              filterOption={(input, option: any) =>
+                option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
+              }
+            >
+              {(dimensionsList[record.id] || []).map((item: { id: string; name: string }) => (
+                <Select.Option key={item.id} value={item.id}>
+                  {item.name}
+                </Select.Option>
+              ))}
+            </Select>
+          </Form.Item>
+        </div>
+      ),
+    },
+  ];
+
+  return (
+    <div>
+      <div>
+        {/* <Input.Search enterButton placeholder="请输入权限名称" onSearch={() => { }} style={{ width: 300, marginBottom: '15px' }} /> */}
+        <Form form={form} wrapperCol={{ span: 20 }} labelCol={{ span: 3 }}>
+          <Table rowKey="id" pagination={false} columns={dataColumns} dataSource={typeList} />
+        </Form>
+      </div>
+    </div>
+  );
+};
+export default DataPermission;

+ 188 - 0
src/pages/system/Role/Edit/Permission/MenuPermission.tsx

@@ -0,0 +1,188 @@
+import { CaretDownOutlined } from '@ant-design/icons';
+import { Checkbox } from 'antd';
+import type { CheckboxValueType } from 'antd/lib/checkbox/Group';
+import _ from 'lodash';
+import { useEffect, useState } from 'react';
+
+interface Props {
+  value: any;
+  initialValues: any;
+  check?: boolean;
+  change: (data: any) => void;
+}
+
+const MenuPermission = (props: Props) => {
+  const { value } = props;
+  const [checkAll, setCheckAll] = useState<boolean>(
+    value.buttons?.length > 0 || value.children?.length > 0
+      ? (props.initialValues?.buttons?.length || 0) +
+          ((props.initialValues?.children || []).filter((item: any) => item.check)?.length || 0) ===
+          (value.buttons?.length || 0) + (value.children?.length || 0)
+      : props.initialValues.check,
+  );
+  const [menuList, setMenuList] = useState<any[]>(props.initialValues?.buttons || []);
+  const [visible, setVisible] = useState<boolean>(true);
+  const [children, setChildren] = useState<any>({});
+  const [initialChildrenValues, setInitialChildrenValues] = useState<any[]>(
+    props.initialValues?.children || [],
+  );
+  const [indeterminate, setIndeterminate] = useState<boolean>(
+    props.initialValues?.indeterminate || false,
+  );
+
+  useEffect(() => {
+    if (props.initialValues && Object.keys(props.initialValues).length > 0) {
+      setMenuList(props.initialValues?.buttons || []);
+      setIndeterminate(props.initialValues?.indeterminate);
+      setCheckAll(props?.initialValues?.check);
+      setInitialChildrenValues(props.initialValues?.children || []);
+    } else {
+      setMenuList([]);
+      setInitialChildrenValues([]);
+      setIndeterminate(false);
+      setCheckAll(false);
+    }
+  }, [props.initialValues]);
+
+  const getInitValues = (list: any[]) => {
+    if (Array.isArray(list) && list.length > 0) {
+      return list.map((item) => {
+        let child: any[] = [];
+        if (item.children && item.children.length > 0) {
+          child = getInitValues(item.children);
+        }
+        return {
+          id: item.id,
+          check: true,
+          indeterminate: false,
+          buttons: item.buttons && item.buttons.length > 0 ? _.map(item.buttons, 'id') : [],
+          children: child,
+        };
+      });
+    }
+    return [];
+  };
+
+  useEffect(() => {
+    const list = initialChildrenValues.map((item) => {
+      if (item.id === children.id) return children;
+      return item;
+    });
+    const flag = list.find((i) => i.id === children.id);
+    if (!flag) {
+      list.push(children);
+    }
+    const lenB = menuList?.length || 0;
+    const ilen: number = (list || []).filter((i: any) => i.indeterminate || i.check)?.length || 0;
+    const clen: number = (list || []).filter((i: any) => i.check)?.length || 0;
+    const check = clen + lenB === (value?.children?.length || 0) + (value?.buttons?.length || 0);
+    setIndeterminate((ilen > 0 || lenB > 0) && !check);
+    setCheckAll(check);
+    props.change({
+      id: value.id,
+      indeterminate: (ilen > 0 || lenB > 0) && !check,
+      check: check,
+      children: [...list],
+      buttons: [...menuList],
+    });
+  }, [children]);
+
+  return (
+    <div key={value?.id} style={{ margin: '10px 0' }}>
+      <div
+        style={{
+          display: 'flex',
+          padding: '10px 0',
+          alignItems: 'center',
+          borderBottom: '1px solid #f0f0f0',
+          transition: 'background .3s',
+        }}
+      >
+        <div
+          style={{
+            marginRight: '10px',
+            width: '15px',
+            transform: !visible ? 'rotate(-90deg)' : 'none',
+          }}
+        >
+          {value?.children && value?.children?.length > 0 && (
+            <CaretDownOutlined
+              onClick={() => {
+                setVisible(!visible);
+              }}
+            />
+          )}
+        </div>
+        <div>
+          <Checkbox
+            indeterminate={indeterminate}
+            checked={checkAll}
+            style={{ width: '200px' }}
+            onChange={(e) => {
+              setCheckAll(e.target.checked);
+              setIndeterminate(false);
+              const data = e.target.checked ? (value?.buttons || []).map((i: any) => i.id) : [];
+              setMenuList([...data]);
+              const initialData = e.target.checked ? [...getInitValues(value?.children || [])] : [];
+              props.change({
+                id: value.id,
+                check: e.target.checked,
+                indeterminate: false,
+                children: [...initialData],
+                buttons: [...data],
+              });
+            }}
+          >
+            {value?.name}
+          </Checkbox>
+        </div>
+        <div>
+          <Checkbox.Group
+            name={value?.id}
+            value={menuList}
+            onChange={(data: CheckboxValueType[]) => {
+              const len = (value.buttons?.length || 0) + (value.children?.length || 0);
+              const lenB = data?.length || 0;
+              const lenC = initialChildrenValues?.length || 0;
+              setIndeterminate(lenB + lenC < len);
+              setCheckAll(lenB + lenC === len);
+              setMenuList([...data]);
+              props.change({
+                id: value.id,
+                check: lenB + lenC === len,
+                indeterminate: !(lenB + lenC === len),
+                children: [...initialChildrenValues],
+                buttons: [...data],
+              });
+            }}
+            options={(value?.buttons || []).map((i: any) => ({
+              label: i.name,
+              value: i.id,
+              key: i.id,
+            }))}
+          />
+        </div>
+      </div>
+      {visible && value?.children && (
+        <div style={{ paddingLeft: '20px' }}>
+          {(value?.children || []).map((item: { id: string }) => (
+            <div key={item.id}>
+              <MenuPermission
+                initialValues={
+                  (initialChildrenValues || []).find((i: any) => i.id === item.id) || {}
+                }
+                value={item}
+                change={(data: any) => {
+                  if (Object.keys(data).length > 0) {
+                    setChildren(data);
+                  }
+                }}
+              />
+            </div>
+          ))}
+        </div>
+      )}
+    </div>
+  );
+};
+export default MenuPermission;

+ 16 - 0
src/pages/system/Role/Edit/Permission/index.less

@@ -0,0 +1,16 @@
+.rolePermission {
+  :global {
+    .ant-table-cell {
+      .ant-form-item {
+        margin: 0;
+      }
+    }
+    .ant-table-cell-with-append {
+      display: flex;
+      align-items: center;
+    }
+    .ant-table-row-expand-icon {
+      margin-top: 0;
+    }
+  }
+}

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

@@ -0,0 +1,259 @@
+import { useEffect, useState } from 'react';
+import { Button, Card, message, Steps } from 'antd';
+import Service from '@/pages/system/Role/service';
+import styles from './index.less';
+import DataPermission from './DataPermission';
+import MenuPermission from './MenuPermission';
+import encodeQuery from '@/utils/encodeQuery';
+import { useParams } from 'umi';
+import _ from 'lodash';
+
+const Permission = () => {
+  const service = new Service('role');
+  const params = useParams<{ id: string }>();
+  const [current, setCurrent] = useState<number>(0);
+  const [dataSource, setDataSource] = useState<any[]>([]);
+  const [initialValues, setInitialValues] = useState<any>({});
+  const [menuPermissions, setMenuPermissions] = useState<any[]>([]);
+  const [dataPermissions, setDataPermissions] = useState<any[]>([]);
+  const [info, setInfo] = useState<RoleItem>();
+
+  const getDetail = async (id: string) => {
+    const res = await service.detail(id);
+    if (res.status === 200) {
+      setInfo(res.result);
+      setDataPermissions(res.result?.dataAccess || []);
+    }
+  };
+
+  const handleSearch = (data: any) => {
+    service.queryMenuTreeList(encodeQuery(data)).subscribe((resp) => {
+      if (resp.status === 200) {
+        setDataSource(resp.result);
+      }
+    });
+  };
+
+  const breadthQuery = (tree: any[], id: string) => {
+    let stark: any[] = [];
+    stark = stark.concat(tree);
+    while (stark.length) {
+      const temp = stark.shift();
+      if (temp.children) {
+        stark = stark.concat(temp.children);
+      }
+      if (temp.id === id) {
+        return temp;
+      }
+    }
+    return undefined;
+  };
+
+  const initToMenu = (initList: any[], list: any[]) => {
+    if (Array.isArray(initList) && initList.length > 0) {
+      return initList.map((item) => {
+        const data = breadthQuery(list, item.id);
+        if ((item?.children.length > 0 || item?.buttons.length > 0 || item.check) && data) {
+          const dt: any = { ...data, buttons: [], children: [] };
+          if (item?.children && item?.children?.length > 0) {
+            dt.children = initToMenu(item.children, list);
+          }
+          if (
+            item?.buttons &&
+            item?.buttons?.length > 0 &&
+            data?.buttons &&
+            data?.buttons?.length > 0
+          ) {
+            const buttons = data.buttons.filter((i: any) => item.buttons.includes(i.id));
+            dt.buttons = [...buttons];
+          }
+          return dt;
+        }
+      });
+    }
+    return [];
+  };
+
+  const initToPermission = (list: any[]): any[] => {
+    if (Array.isArray(list) && list.length > 0) {
+      return list.map((item) => {
+        const data = breadthQuery(dataSource, item.id);
+        const ilen: number =
+          (initToPermission(item.children) || []).filter((i: any) => i.indeterminate || i.check)
+            ?.length || 0;
+        const clen: number =
+          (initToPermission(item.children) || []).filter((i: any) => i.check)?.length || 0;
+        const check =
+          clen + (item?.buttons?.length || 0) ===
+          (data?.children?.length || 0) + (data?.buttons?.length || 0);
+        return {
+          id: item.id,
+          check: check,
+          indeterminate: (ilen > 0 || item?.buttons?.length > 0) && !check,
+          buttons: _.map(item.buttons || [], 'id') || [],
+          children: initToPermission(item.children) || [],
+        };
+      });
+    }
+    return [];
+  };
+
+  const initialMenu = (id: string) => {
+    service.queryGrantTree('role', id).subscribe((resp) => {
+      if (resp.status === 200) {
+        const data = initToPermission(resp.result);
+        const len = data.filter((i: any) => i.indeterminate)?.length;
+        const lenC = data.filter((i: any) => i.check)?.length;
+        const d = {
+          id: 'menu-permission',
+          check: dataSource.length === lenC,
+          children: [...data],
+          indeterminate: len > 0,
+          buttons: [],
+        };
+        setInitialValues(d);
+      }
+    });
+  };
+
+  useEffect(() => {
+    handleSearch({ paging: false });
+    if (params?.id) {
+      getDetail(params.id);
+    }
+  }, []);
+
+  useEffect(() => {
+    if (dataSource.length > 0) {
+      initialMenu(params.id);
+    }
+  }, [dataSource]);
+
+  return (
+    <Card className={styles.rolePermission}>
+      <Steps current={current}>
+        <Steps.Step title="菜单权限" />
+        <Steps.Step title="数据权限" />
+      </Steps>
+      <div style={{ marginTop: '15px' }}>
+        {current === 0 && (
+          <div className={styles.rolePermission}>
+            {/* <Input.Search enterButton placeholder="请输入权限名称" onSearch={() => { }} style={{ width: 300, marginBottom: '15px' }} /> */}
+            <div style={{ border: '1px solid #f0f0f0' }}>
+              <div
+                style={{
+                  textAlign: 'center',
+                  fontSize: '15px',
+                  width: '100%',
+                  paddingLeft: '10px',
+                  backgroundColor: '#fafafa',
+                  height: '55px',
+                  lineHeight: '55px',
+                  fontWeight: 600,
+                }}
+              >
+                菜单权限
+              </div>
+              <div style={{ padding: '0 20px', overflowY: 'scroll', height: '500px' }}>
+                <MenuPermission
+                  initialValues={initialValues}
+                  key={'menu-permission'}
+                  value={{
+                    id: 'menu-permission',
+                    buttons: [],
+                    name: '菜单权限',
+                    children: [...dataSource],
+                  }}
+                  change={(data: any) => {
+                    setInitialValues(data);
+                  }}
+                />
+              </div>
+            </div>
+          </div>
+        )}
+        {current === 1 && (
+          <DataPermission
+            initialValues={info?.dataAccess || []}
+            data={menuPermissions}
+            change={(data: any) => {
+              const dataAccess: any[] = [];
+              Object.keys(data).forEach((key) => {
+                if (data[key].value) {
+                  const dimensions = (data[key]?.type || []).map((i: string) => {
+                    return { dimensionType: i };
+                  });
+                  dataAccess.push({
+                    assetType: key,
+                    dimensions,
+                  });
+                }
+              });
+              setDataPermissions(dataAccess);
+            }}
+          />
+        )}
+      </div>
+      <div style={{ marginTop: '15px' }}>
+        {current === 0 && (
+          <Button
+            type="primary"
+            onClick={() => {
+              const data = initToMenu(initialValues.children, dataSource).filter((i) => i);
+              if (data.length > 0) {
+                setCurrent(1);
+                setMenuPermissions(data);
+              } else {
+                message.error('请选择菜单权限!');
+              }
+            }}
+          >
+            下一步
+          </Button>
+        )}
+        {current === 1 && (
+          <>
+            <Button
+              style={{ margin: '0 8px' }}
+              onClick={() => {
+                setCurrent(0);
+                initialMenu(params.id);
+              }}
+            >
+              上一步
+            </Button>
+            <Button
+              type="primary"
+              onClick={async () => {
+                const res = await service.modify(params?.id, {
+                  id: params?.id || '',
+                  name: info?.name,
+                  description: info?.description,
+                  dataAccess: [...dataPermissions],
+                });
+                if (res.status === 200) {
+                  getDetail(params.id);
+                }
+                service
+                  .saveGrantTree('role', params.id, {
+                    merge: true,
+                    priority: 0,
+                    menus: [...menuPermissions],
+                  })
+                  .subscribe((resp) => {
+                    if (resp.status === 200) {
+                      message.success('操作成功!');
+                      initialMenu(params.id);
+                    }
+                  });
+              }}
+            >
+              保存
+            </Button>
+          </>
+        )}
+      </div>
+    </Card>
+  );
+};
+export default Permission;

+ 9 - 9
src/pages/system/Role/Edit/index.tsx

@@ -3,7 +3,7 @@ import { PageContainer } from '@ant-design/pro-layout';
 import { useState } from 'react';
 import { history } from 'umi';
 import UserManage from '@/pages/system/Role/Edit/UserManage';
-// import Permission from '@/pages/system/Role/Edit/Permission';
+import Permission from '@/pages/system/Role/Edit/Permission';
 import Info from '@/pages/system/Role/Edit/Info';
 import { useIntl } from '@@/plugin-locale/localeExports';
 
@@ -20,14 +20,14 @@ const RoleEdit = observer(() => {
       }),
       component: <Info />,
     },
-    // {
-    //     key: 'permission',
-    //     tab: intl.formatMessage({
-    //         id: 'pages.system.role.access.permission',
-    //         defaultMessage: '权限分配',
-    //     }),
-    //     component: <Permission />,
-    // },
+    {
+      key: 'permission',
+      tab: intl.formatMessage({
+        id: 'pages.system.role.access.permission',
+        defaultMessage: '权限分配',
+      }),
+      component: <Permission />,
+    },
     {
       key: 'userManagement',
       tab: intl.formatMessage({

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

@@ -14,6 +14,67 @@ class Service extends BaseService<RoleItem> {
         }),
       ),
     ).pipe(map((item) => item));
+  queryMenuTreeList = (data: any) =>
+    defer(() =>
+      from(
+        request(`/${SystemConst.API_BASE}/menu/_all/tree`, {
+          method: 'POST',
+          data,
+        }),
+      ),
+    ).pipe(map((item) => item));
+  queryPermissionsList = (data: any) =>
+    defer(() =>
+      from(
+        request(`/${SystemConst.API_BASE}/menu/permissions`, {
+          method: 'POST',
+          data,
+        }),
+      ),
+    ).pipe(map((item) => item));
+  queryAssetTypeList = (data: any) =>
+    defer(() =>
+      from(
+        request(`/${SystemConst.API_BASE}/menu/asset-types`, {
+          method: 'POST',
+          data,
+        }),
+      ),
+    ).pipe(map((item) => item));
+  queryDimensionsList = (type: string) =>
+    defer(() =>
+      from(
+        request(`/${SystemConst.API_BASE}/asset/${type}/dimensions`, {
+          method: 'GET',
+        }),
+      ),
+    ).pipe(map((item) => item));
+  queryGrantTree = (targetType: string, targetId: string) =>
+    defer(() =>
+      from(
+        request(`/${SystemConst.API_BASE}/menu/${targetType}/${targetId}/_grant/tree`, {
+          method: 'GET',
+        }),
+      ),
+    ).pipe(map((item) => item));
+  saveGrantTree = (targetType: string, targetId: string, data: any) =>
+    defer(() =>
+      from(
+        request(`/${SystemConst.API_BASE}/menu/${targetType}/${targetId}/_grant`, {
+          method: 'PUT',
+          data,
+        }),
+      ),
+    ).pipe(map((item) => item));
+  saveAutz = (data: any) =>
+    defer(() =>
+      from(
+        request(`/${SystemConst.API_BASE}/autz-setting/detail/_save`, {
+          method: 'POST',
+          data,
+        }),
+      ),
+    ).pipe(map((item) => item));
   bindUser = (roleId: string, params: any) =>
     defer(() =>
       from(

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

@@ -5,4 +5,5 @@ type RoleItem = {
   path: string;
   sortIndex: number;
   description: string;
+  dataAccess?: any;
 };