树状图antd的tree使用

在这里插入图片描述

在这里插入图片描述

permission

import { Checkbox, Tree } from 'antd';
import { CheckboxChangeEvent } from 'antd/lib/checkbox';
import type { DataNode, EventDataNode } from 'antd/lib/tree';
import _ from 'lodash';
import { FC, useState } from 'react';
import { store, toCreateSelectSelectTreeData } from '../../store';
import { convert2Tree } from '../../util';
import styles from './index.less';
interface CheckInfo {
  checked: boolean;
  checkedNodes: DataNode[];
  halfCheckedKeys?: React.Key[];
  node: EventDataNode;
}
type CheckedKeys =
  | React.Key[]
  | {
      checked: React.Key[];
      halfChecked: React.Key[];
    };

interface IPagePermissionProps {
  onChange?: (value: string[]) => void;
}
const PagePermission: FC<IPagePermissionProps> = ({ onChange }) => {
  const { treeData, navigationList, pageList, expandedKeys, checkedKeys, halfCheckedKeys, selectedPage, buttonList } =
    store.useState();
  const [autoExpandParent, setAutoExpandParent] = useState<boolean>(true);

  const onExpand = (expandedKeysValue: React.Key[]) => {
    store.changeExpandedKeys(expandedKeysValue);
    setAutoExpandParent(false);
  };

  const onCheck = (checkedKeys: CheckedKeys, { halfCheckedKeys, node }: CheckInfo) => {
    if (Array.isArray(checkedKeys)) {
      store.changeCheckedKeys({ checkedKeys, halfCheckedKeys: halfCheckedKeys || [] });
      store.changeSelectedButtonKeys(
        _.mapValues(_.pick(buttonList, checkedKeys), (items) => items.map((item) => item.navigationCode)),
      );

      toCreateSelectSelectTreeData();
      store.changeSelectedPage(node);
      onChange && onChange(checkedKeys.concat(halfCheckedKeys || []) as string[]);
    }
  };

  const onSelect = (selectKeys: React.Key[], { node }: { node: EventDataNode }) => {
    store.changeSelectedPage(node);
  };

  const toggleAll = (e: CheckboxChangeEvent) => {
    if (e.target.checked) {
      const _checkedKeys = pageList.map((item) => item.navigationCode);
      store.changeCheckedKeys({ checkedKeys: _checkedKeys, halfCheckedKeys: [] });
      store.changeSelectData(convert2Tree(navigationList));
      store.changeSelectedButtonKeys(_.mapValues(buttonList, (items) => items.map((item) => item.navigationCode)));
      onChange && onChange(_checkedKeys);
      return;
    }
    store.changeCheckedKeys({ checkedKeys: [], halfCheckedKeys: [] });
    store.changeSelectData([]);
    store.changeSelectedButtonKeys({});
    onChange && onChange([]);
  };

  return (
    <div className={styles.pagePermission}>
      <div style={{ paddingLeft: 24, marginBottom: 16 }}>
        <Checkbox checked={checkedKeys.concat(halfCheckedKeys).length === pageList.length} onChange={toggleAll}>
          全选
        </Checkbox>
      </div>
      <Tree
        checkable
        onExpand={onExpand}
        expandedKeys={expandedKeys}
        autoExpandParent={autoExpandParent}
        checkedKeys={checkedKeys}
        onCheck={onCheck}
        selectedKeys={[selectedPage.key]}
        onSelect={onSelect}
        treeData={treeData}
      />
    </div>
  );
};

export default PagePermission;

store.ts

import { message } from '@/components';
import { reduxStore } from '@/createStore';
import { findAllNavigationUsingGET, getRoleDetailUsingGET } from '@/services/StoreQueryServices';
import { addRoleUsingPOST, updateRoleUsingPOST } from '@/services/StoreServices';
import type { DataNode, EventDataNode } from 'antd/lib/tree';
import _ from 'lodash';
import { history } from 'umi';
import { convert2Tree, loadSelectTreeData } from './util';
interface IButtonVOs {
  [key: string]: StoreNavigationVo[];
}
interface IButtonKeys {
  [key: string]: string[];
}
export const store = reduxStore.defineLeaf({
  namespace: 'roleEdit',
  initialState: {
    drawerVisible: false,
    roleDetail: {} as StoreRoleVo,
    treeData: [] as DataNode[],
    selectData: [] as DataNode[],
    navigationList: [] as StoreNavigationVo[],
    pageList: [] as StoreNavigationVo[],
    buttonList: {} as IButtonVOs,
    expandedKeys: [] as React.Key[],
    checkedKeys: [] as React.Key[],
    halfCheckedKeys: [] as React.Key[],
    selectedPage: {} as EventDataNode,
    selectedButtonKeys: {} as IButtonKeys,
  },
  reducers: {
    changeDrawerVisible(state, payload: boolean) {
      state.drawerVisible = payload;
    },
    changeRoleDetail(state, payload: StoreRoleVo) {
      state.roleDetail = payload;
    },
    changeNavigation(state, payload: StoreNavigationVo[]) {
      state.navigationList = payload;
    },
    changePageList(state, payload: StoreNavigationVo[]) {
      state.pageList = payload;
    },
    changeButtons(state, payload: IButtonVOs) {
      state.buttonList = payload;
    },

    changeSelectedPage(state, payload: EventDataNode) {
      state.selectedPage = payload;
    },

    changeSelectedButtonKeys(state, payload: IButtonKeys) {
      state.selectedButtonKeys = payload;
    },

    changeTreeData(state, payload: DataNode[]) {
      state.treeData = payload;
    },
    changeExpandedKeys(state, payload: React.Key[]) {
      state.expandedKeys = payload;
    },
    changeCheckedKeys(state, payload: { checkedKeys: React.Key[]; halfCheckedKeys: React.Key[] }) {
      state.checkedKeys = payload.checkedKeys;
      state.halfCheckedKeys = payload.halfCheckedKeys;
    },
    changeSelectData(state, payload: DataNode[]) {
      state.selectData = payload;
    },
  },
});

export const findAllNavigation = async () => {
  const { data, success } = await findAllNavigationUsingGET();
  if (success && data) {
    store.changeExpandedKeys(data.filter((i) => !i.parentId).map((v) => v.navigationCode));
    const navigationByType = _.groupBy(data, 'navigationType');

    const _buttons: (StoreNavigationVo & { parentCode: string })[] = navigationByType['button'].map((item) => ({
      ...item,
      parentCode: _.chain(item.navigationCode).split('_').drop().dropRight().value().join('_'),
    }));

    store.changeButtons(_.groupBy(_buttons, 'parentCode'));
    store.changeTreeData(convert2Tree(navigationByType['menu']));
    store.changePageList(navigationByType['menu']);
    store.changeNavigation(data);
  }
};

/**添加角色 */
export const addRole = async (params: AddRoleForm) => {
  const _selectedButtonKeys = store.getState().selectedButtonKeys;
  const { keys } = params;
  const _btnKeys = Object.entries(_selectedButtonKeys).reduce((pre, current) => {
    if (current[0] && keys.includes(current[0])) {
      return [...pre, ...current[1]];
    }
    return pre;
  }, [] as string[]);

  const { data, success, errMessage } = await addRoleUsingPOST({ body: { ...params, keys: keys.concat(_btnKeys) } });
  if (success && data) {
    message.success('添加成功');
    history.push('/employee/role');
    return;
  }
  message.error(errMessage || '添加失败');
};

/**查询角色详情*/
export const loadRoleDetail = async (params: { id: string }) => {
  const { data, success } = await getRoleDetailUsingGET({ query: params });
  if (success && data) {
    store.changeRoleDetail(data);

    const pageNavigationVoList = data.navigationVoList?.filter((item) => item.navigationType === 'menu');
    const navigationKeyById = _.keyBy(pageNavigationVoList, 'navigationId');
    const navigationGroupByParentId = _.groupBy(pageNavigationVoList, 'parentId');
    const _halfCheckedId = Object.keys(navigationGroupByParentId);
    const _halfCheckedKeys = _halfCheckedId.map((parentId) => navigationKeyById[`${parentId}`]?.navigationCode);

    const _checkedKeys = pageNavigationVoList
      ?.filter((item) => !_halfCheckedId.includes(item.navigationId))
      .map((item) => item.navigationCode);
    store.changeCheckedKeys({ checkedKeys: _checkedKeys || [], halfCheckedKeys: _halfCheckedKeys });
    store.changeSelectData(convert2Tree(pageNavigationVoList || []));
    store.changeSelectedButtonKeys(data.buttonKeys as unknown as IButtonKeys);
    return;
  }
  message.success('获取角色信息失败');
};

export const doEditRole = async (params: UpdateRoleForm) => {
  const _selectedButtonKeys = store.getState().selectedButtonKeys;
  const { keys } = params;
  const _btnKeys = Object.entries(_selectedButtonKeys).reduce((pre, current) => {
    if (current[0] && keys.includes(current[0])) {
      return [...pre, ...current[1]];
    }
    return pre;
  }, [] as string[]);
  const { data, success, errMessage } = await updateRoleUsingPOST({ body: { ...params, keys: keys.concat(_btnKeys) } });
  if (success && data) {
    message.success('编辑成功');
    history.push('/employee/role');
    return;
  }
  message.error(errMessage || '编辑失败');
};

export const toCreateSelectSelectTreeData = () => {
  const { checkedKeys, halfCheckedKeys, navigationList, selectedButtonKeys } = store.getState();
  const selectData = loadSelectTreeData(
    checkedKeys.concat(halfCheckedKeys).concat(_.chain(selectedButtonKeys).values().flattenDeep().value()),
    navigationList,
  );
  store.changeSelectData(selectData);
};

util.ts

import { DataNode } from 'antd/lib/tree';
export const convert2Tree = (data: StoreNavigationVo[]): DataNode[] => {
  const mapper = {} as {
    [k: string]: DataNode;
  };
  const result: DataNode[] = [];
  //将不可变数据转为可变
  const TreeData = data.map(
    (item) =>
      ({
        id: item.navigationId,
        pid: item.parentId,
        key: item.navigationCode,
        title: item.navigationName,
        className: item.navigationType === 'button' ? 'leaf' : '',
      } as DataNode & { id: string; pid: string }),
  );

  TreeData.forEach((item) => (mapper[item.id] = item));
  TreeData.forEach((item) => {
    if (!item.pid) {
      result.push(item);
      return;
    }
    // 这里加个判断 主要原因是 有些脏数据 不在这个树里面
    if (item.pid && mapper[item.pid]) {
      mapper[item.pid].children = mapper[item.pid].children || [];
      mapper[item.pid].children?.push(item);
    }
  });

  return result;
};

export const loadSelectTreeData = (keys: React.Key[], data: StoreNavigationVo[]): DataNode[] => {
  return convert2Tree(data.filter((item) => keys.includes(item.navigationCode)));
};

RoleForm.ts

import ProForm, { ProFormText, ProFormTextArea } from '@ant-design/pro-form';
import type { FormInstance } from 'antd';
import { FC, useEffect, useRef } from 'react';
import { history } from 'umi';
import { addRole, doEditRole, store } from '../../store';
import Permission from '../Permission';
const RoleForm: FC<{ isEdit: boolean }> = ({ isEdit }) => {
  const formRef = useRef<FormInstance>();
  const { roleDetail } = store.useState();
  useEffect(() => {
    if (!roleDetail.roleId) return;
    const { description, roleName, pageKeys } = roleDetail;
    formRef?.current?.setFieldsValue({ description, roleName, keys: pageKeys });
  }, [roleDetail]);
  return (
    <ProForm<AddRoleForm>
      submitter={{
        searchConfig: {
          resetText: '取消',
        },
        onReset: () => history.goBack(),
      }}
      formRef={formRef}
      onFinish={async (values) => {
        isEdit ? doEditRole({ ...values, roleId: roleDetail.roleId }) : addRole(values);
      }}
    >
      <ProFormText
        placeholder="请输入角色名称"
        rules={[
          {
            required: true,
            message: '请输入角色名称',
          },
          {
            pattern: /^.{1,10}$/,
            message: '请输入1~10字的角色名称',
          },
        ]}
        width="md"
        name="roleName"
        label="角色名称"
      />
      <ProFormTextArea
        rules={[
          {
            pattern: /^.{0,50}$/,
            message: '角色描述不能超过50个字符',
          },
        ]}
        placeholder="请输入角色描述"
        width="md"
        name="description"
        label="角色描述"
      />
      <ProForm.Item
        rules={[
          {
            required: true,
            message: '请选择角色权限',
          },
        ]}
        name="keys"
        label="角色权限"
      >
        <Permission />
      </ProForm.Item>
    </ProForm>
  );
};

export default RoleForm;

Permission.ts

import { PlusOutlined } from '@ant-design/icons';
import ProCard from '@ant-design/pro-card';
import { Button, Drawer } from 'antd';
import { FC } from 'react';
import { store } from '../../store';
import ButtonPermission from '../ButtonPermission';
import PagePermission from '../PagePermission';
import PermissionSelected from '../PermissionSelected';
import styles from './index.less';

interface IPermissionProps {
  value?: string[];
  onChange?: (value: string[]) => void;
}
const Permission: FC<IPermissionProps> = ({ onChange }) => {
  const { drawerVisible, checkedKeys, halfCheckedKeys } = store.useState();
  const keys = checkedKeys.concat(halfCheckedKeys);
  // useEffect(() => {
  //   onChange && onChange(keys as string[]);
  // }, [keys]);
  return (
    <>
      <Button icon={keys.length === 0 && <PlusOutlined />} onClick={() => store.changeDrawerVisible(true)}>
        {keys.length > 0 ? '查看已配置权限' : '添加权限'}
      </Button>
      <Drawer
        className={styles.role_auth_drawer}
        title="添加权限"
        width="78%"
        visible={drawerVisible}
        onClose={() => store.changeDrawerVisible(false)}
      >
        <ProCard gutter={16} ghost>
          <ProCard
            colSpan={12}
            bordered={false}
            headerBordered
            // title={
            //   <div className={styles.cardTitle}>
            //     <label>数据权限</label>
            //     <span>配置后可查看相应数据</span>
            //   </div>
            // }
            title="数据权限"
            tooltip="配置后可查看相应数据"
          >
            <PagePermission onChange={onChange} />
          </ProCard>
          <ProCard
            colSpan={6}
            bordered={false}
            headerBordered
            title="操作权限"
            tooltip="配置后可对数据进行操作"
          >
            <ButtonPermission />
          </ProCard>
          <ProCard colSpan={6} bordered={false} headerBordered title="已配置权限">
            <PermissionSelected />
          </ProCard>
        </ProCard>
      </Drawer>
    </>
  );
};

export default Permission;

PermissionSelected.ts

import { Button, Tree } from 'antd';
import { FC } from 'react';
import { store } from '../../store';
import styles from './index.less';

const PermissionSelected: FC = () => {
  const { selectData } = store.useState();

  return (
    <div className={styles.tree_res}>
      <Tree treeData={selectData} />
      <div className={styles.btns}>
        <Button onClick={() => store.changeDrawerVisible(false)} style={{ marginRight: 8 }}>
          取消
        </Button>
        <Button type="primary" onClick={() => store.changeDrawerVisible(false)}>
          保存配置
        </Button>
      </div>
    </div>
  );
};

export default PermissionSelected;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值