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;