Files
KnowStreaming/km-console/packages/config-manager-fe/src/pages/UserManage/RoleTabContent.tsx
2022-08-25 20:32:49 +08:00

650 lines
19 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
import {
Form,
ProTable,
Button,
Input,
Modal,
Space,
Divider,
Drawer,
Transfer,
Row,
Col,
message,
Tooltip,
Spin,
AppContainer,
Utils,
Popover,
IconFont,
} from 'knowdesign';
import moment from 'moment';
import { CloseOutlined, LoadingOutlined, PlusOutlined } from '@ant-design/icons';
import { defaultPagination } from 'constants/common';
import { RoleProps, PermissionNode, AssignUser, RoleOperate, FormItemPermission } from './config';
import api from 'api';
import CheckboxGroupContainer from './CheckboxGroupContainer';
import { ConfigPermissionMap } from '../CommonConfig';
const { request } = Utils;
const { confirm } = Modal;
const { TextArea } = Input;
// 新增/编辑角色、查看角色详情抽屉
// eslint-disable-next-line react/display-name
const RoleDetailAndUpdate = forwardRef((props, ref): JSX.Element => {
const [global] = AppContainer.useGlobalValue();
const [form] = Form.useForm();
const [visible, setVisible] = useState<boolean>(false);
const [type, setType] = useState<RoleOperate>(RoleOperate.Add);
const [roleDetail, setRoleDetail] = useState<RoleProps>();
const [permissions, setPermissions] = useState<FormItemPermission[]>([]);
const [permissionFormLoading, setPermissionFormLoading] = useState<boolean>(false);
const [initSelectedPermissions, setInitSelectedPermission] = useState<{ [index: string]: [] }>({});
const [confirmLoading, setConfirmLoading] = useState(false);
const callback = useRef(() => {
return;
});
useEffect(() => {
const globalPermissions = global.permissions;
if (globalPermissions && globalPermissions.length) {
const sysPermissions = globalPermissions.map((sys: PermissionNode) => ({
id: sys.id,
name: sys.permissionName,
options: sys.childList.map((node) => ({ label: node.permissionName, value: node.id })),
}));
setPermissions(sysPermissions);
}
}, [global]);
useEffect(() => {
if (roleDetail) {
setPermissionFormLoading(true);
request(api.role(roleDetail.id)).then((res: any) => {
const initSelected = {};
const permissions = res.permissionTreeVO.childList;
permissions.forEach((sys) => {
initSelected[sys.id] = [];
sys.childList.forEach((node) => node.has && initSelected[sys.id].push(node.id));
});
setInitSelectedPermission(initSelected);
setPermissionFormLoading(false);
});
}
}, [roleDetail]);
const onSubmit = () => {
form.validateFields().then((formData) => {
formData.permissionIdList.forEach((arr, i) => {
// 如果分配的系统下的子权限,自动赋予该系统的权限
if (arr !== null && arr.length) {
arr.push(permissions[i].id);
}
});
formData.permissionIdList = formData.permissionIdList.flat();
setConfirmLoading(true);
request(api.editRole, {
method: type === RoleOperate.Add ? 'POST' : 'PUT',
data: Object.assign(formData, type === RoleOperate.Edit ? { id: roleDetail.id } : {}),
}).then(
(res) => {
callback.current();
onClose();
setInitSelectedPermission({});
message.success(`${type === RoleOperate.Add ? '新增' : '编辑'}角色成功`);
},
() => setConfirmLoading(false)
);
});
};
// 打开抽屉
const onOpen = (status: boolean, type: RoleOperate, cbk: () => { return }, record: RoleProps) => {
setInitSelectedPermission({});
form.setFieldsValue(record);
callback.current = cbk;
setRoleDetail(record);
setType(type);
setVisible(status);
};
// 关闭抽屉
const onClose = () => {
setVisible(false);
setRoleDetail(undefined);
form.resetFields();
setConfirmLoading(false);
};
useImperativeHandle(ref, () => ({
onOpen,
}));
return (
<Drawer
title={`${type === RoleOperate.Add ? '新增角色' : type === RoleOperate.Edit ? '编辑角色' : '查看角色详情'}`}
className="role-tab-detail"
width={600}
visible={visible}
maskClosable={false}
onClose={onClose}
extra={
<Space>
{type !== RoleOperate.View && (
<>
<Button size="small" onClick={onClose}>
</Button>
<Button type="primary" size="small" loading={confirmLoading} onClick={onSubmit}>
</Button>
<Divider type="vertical" />
</>
)}
</Space>
}
>
{type === RoleOperate.View ? (
// 查看角色详情
<>
<Row gutter={[12, 12]} className="desc-row">
<Col span={3} className="label-col">
:
</Col>
<Col span={21} className="value-col">
{(roleDetail && roleDetail.roleName) || '-'}
</Col>
<Col span={3} className="label-col">
:
</Col>
<Col span={21} className="value-col">
{(roleDetail && roleDetail.description) || '-'}
</Col>
</Row>
<div className="role-permissions-container">
<div className="title"></div>
<>
{permissions.length ? (
<Spin spinning={permissionFormLoading}>
{permissions.map((permission, i) => (
<CheckboxGroupContainer
key={i}
formInstance={form}
fieldName="permissionIdList"
options={permission.options}
initSelectedOptions={initSelectedPermissions[permission.id] || []}
groupIdx={i}
allCheckText={permission.name}
disabled
/>
))}
</Spin>
) : (
<></>
)}
</>
</div>
</>
) : (
// 新增/编辑角色
<Form form={form} layout="vertical">
<Form.Item
label="角色名称"
name="roleName"
rules={[
{ required: true, message: '角色名称不能为空' },
{
pattern: /^[\u4e00-\u9fa5a-zA-Z0-9_]{3,128}$/,
message: '角色名称只能由中英文大小写、数字、下划线(_)组成长度限制在3128字符',
},
]}
>
<Input placeholder="请输入角色名称" />
</Form.Item>
<Form.Item label="描述" name="description" rules={[{ required: true, message: '描述不能为空' }]}>
<TextArea placeholder="请输入描述" maxLength={200} />
</Form.Item>
<Form.Item
label="分配权限"
name="permissionIdList"
rules={[
() => ({
validator(_, value) {
if (Array.isArray(value) && value.some((item) => !!item.length)) {
return Promise.resolve();
}
return Promise.reject(new Error('请为角色至少分配一项权限'));
},
}),
]}
>
<>
{permissions.length ? (
<Spin spinning={permissionFormLoading}>
{permissions.map((permission, i) => (
<CheckboxGroupContainer
key={i}
formInstance={form}
fieldName="permissionIdList"
options={permission.options}
initSelectedOptions={initSelectedPermissions[permission.id] || []}
groupIdx={i}
allCheckText={permission.name}
/>
))}
</Spin>
) : (
<></>
)}
</>
</Form.Item>
</Form>
)}
</Drawer>
);
});
// 用户角色分配抽屉
// eslint-disable-next-line react/display-name
const AssignRoles = forwardRef((props, ref) => {
// TODO: check 状态
const [visible, setVisible] = useState<boolean>(false);
const [roleInfo, setRoleInfo] = useState<RoleProps>(undefined);
const [users, setUsers] = useState<AssignUser[]>([]);
const [selectedUsers, setSelectedUsers] = useState<string[]>([]);
const [loading, setLoading] = useState<boolean>(false);
const [confirmLoading, setConfirmLoading] = useState<boolean>(false);
const callback = useRef(() => {
return;
});
const onClose = () => {
setVisible(false);
setRoleInfo(undefined);
setUsers([]);
setSelectedUsers([]);
setLoading(false);
setConfirmLoading(false);
};
const filterOption = (inputValue, option) => option.name.includes(inputValue);
const onChange = (newTargetKeys) => {
setSelectedUsers(newTargetKeys);
};
const onSubmit = () => {
if (!roleInfo) return;
setConfirmLoading(true);
request(api.assignRoles, {
method: 'POST',
data: {
flag: false,
id: roleInfo.id,
idList: selectedUsers,
},
}).then(
(res) => {
message.success('成功为角色分配用户');
callback.current();
onClose();
},
() => setConfirmLoading(false)
);
};
useEffect(() => {
if (roleInfo && visible) {
setLoading(true);
request(api.getAssignedUsersByRoleId(roleInfo.id)).then((res: AssignUser[]) => {
const selectedUsers = [];
res.forEach((user) => user.has && selectedUsers.push(user.id));
setUsers(res);
setSelectedUsers(selectedUsers);
setLoading(false);
});
}
}, [visible, roleInfo]);
useImperativeHandle(ref, () => ({
setVisible: (status: boolean, record: RoleProps, cbk: () => { return }) => {
callback.current = cbk;
setRoleInfo(record);
setVisible(status);
},
}));
return (
<Drawer
title="分配角色"
className="role-tab-assign-user"
width={600}
visible={visible}
maskClosable={false}
onClose={onClose}
extra={
<Space>
<Button size="small" onClick={onClose}>
</Button>
<Button type="primary" size="small" disabled={loading} loading={confirmLoading} onClick={onSubmit}>
</Button>
<Divider type="vertical" />
</Space>
}
>
<Row gutter={[12, 12]} className="desc-row">
<Col span={3} className="label-col">
:
</Col>
<Col span={21} className="value-col">
{roleInfo && roleInfo.roleName}
</Col>
<Col span={3} className="label-col">
:
</Col>
<Col span={21} className="value-col">
{roleInfo && roleInfo.description}
</Col>
</Row>
<Spin spinning={loading}>
<Transfer
titles={['未分配用户', '已分配用户']}
dataSource={users}
rowKey={(record) => record.id}
render={(item: AssignUser) => item.name}
showSearch
filterOption={filterOption}
targetKeys={selectedUsers}
onChange={onChange}
pagination
suffix={<IconFont type="icon-fangdajing" />}
/>
</Spin>
</Drawer>
);
});
export default (props: { curTabKey: string }): JSX.Element => {
const [global] = AppContainer.useGlobalValue();
const { curTabKey } = props;
const [roles, setRoles] = useState<RoleProps[]>([]);
const [pagination, setPagination] = useState<any>(defaultPagination);
const [loading, setLoading] = useState<boolean>(true);
const [deleteBtnLoading, setDeleteBtnLoading] = useState<number>(-1);
const [searchKeywords, setSearchKeywords] = useState('');
const [searchKeywordsInput, setSearchKeywordsInput] = useState('');
const detailRef = useRef(null);
const assignRolesRef = useRef(null);
const columns = [
{
title: '角色ID',
dataIndex: 'roleCode',
},
{
title: '名称',
dataIndex: 'roleName',
},
{
title: '描述',
dataIndex: 'description',
lineClampOne: true,
},
{
title: '分配用户数',
dataIndex: 'authedUserCnt',
width: 100,
render(cnt: Pick<RoleProps, 'authedUserCnt'>, record: RoleProps) {
return (
<Popover
placement="right"
overlayClassName="tags-with-hide-popover"
content={
<div className="container-item-popover">
{record.authedUsers.map((username) => (
<div key={username} className="container-item">
{username}
</div>
))}
</div>
}
>
<Button size="small" type="link">
{cnt}
</Button>
</Popover>
);
},
},
{
title: '最后修改人',
dataIndex: 'lastReviser',
},
{
title: '最后更新时间',
dataIndex: 'updateTime',
width: 180,
render: (date) => moment(date).format('YYYY-MM-DD HH:mm:ss'),
},
{
title: '操作',
width: 260,
render(record) {
return (
<>
<Button
type="link"
size="small"
style={{ paddingLeft: 0 }}
onClick={() => detailRef.current.onOpen(true, RoleOperate.View, getRoleList, record)}
>
</Button>
{global.hasPermission && global.hasPermission(ConfigPermissionMap.ROLE_ASSIGN) ? (
<Button type="link" size="small" onClick={() => assignRolesRef.current.setVisible(true, record, getRoleList)}>
</Button>
) : (
<></>
)}
{global.hasPermission && global.hasPermission(ConfigPermissionMap.ROLE_EDIT) ? (
<Button type="link" size="small" onClick={() => detailRef.current.onOpen(true, RoleOperate.Edit, getRoleList, record)}>
</Button>
) : (
<></>
)}
{global.hasPermission && global.hasPermission(ConfigPermissionMap.ROLE_DEL) ? (
<Button type="link" size="small" onClick={() => onDelete(record)}>
{record.id === deleteBtnLoading ? <LoadingOutlined /> : '删除'}
</Button>
) : (
<></>
)}
</>
);
},
},
];
const getRoleList = (query = {}) => {
const data = {
page: pagination.current,
size: pagination.pageSize,
roleName: searchKeywords,
...query,
};
setLoading(true);
request(api.roleList, {
method: 'POST',
data,
}).then(
(res: any) => {
const { pageNo, pageSize, pages, total } = res.pagination;
if (pageNo > pages && pages !== 0) {
getRoleList({ page: pages });
return false;
}
setPagination({
...pagination,
current: pageNo,
pageSize,
total,
});
setRoles(res.bizData);
setLoading(false);
},
() => setLoading(false)
);
};
const onDelete = (record) => {
if (deleteBtnLoading !== -1) return;
setDeleteBtnLoading(record.id);
request(api.checkRole(record.id), {
method: 'DELETE',
}).then(
(res: any) => {
setDeleteBtnLoading(-1);
const userList = res && res.userNameList;
const couldDelete = !(userList && userList.length);
const isShowTooltip = couldDelete ? false : userList.length > 2;
confirm({
// 删除角色弹窗
title: couldDelete ? '确定删除以下角色吗?' : '请先解除角色引用关系',
content: (
<div>
<div>{record.roleName}</div>
{!couldDelete && (
<span>
{isShowTooltip ? (
<Tooltip title={userList.join('')}>
{userList.slice(0, 2).join('')}... {userList.length}
</Tooltip>
) : (
userList.join('')
)}
</span>
)}
</div>
),
okText: couldDelete ? '删除' : '确定',
okType: 'primary',
centered: true,
okButtonProps: {
size: 'small',
danger: couldDelete,
},
cancelButtonProps: {
size: 'small',
},
maskClosable: true,
onOk() {
return (
couldDelete &&
request(api.role(record.id), {
method: 'DELETE',
}).then((_) => {
message.success('删除成功');
getRoleList();
})
);
},
onCancel() {
return;
},
});
},
() => setDeleteBtnLoading(-1)
);
};
const onTableChange = (curPagination) => {
getRoleList({
page: curPagination.current,
size: curPagination.pageSize,
});
};
useEffect(() => {
if (curTabKey === 'role') {
getRoleList();
}
}, [curTabKey]);
useEffect(() => {
(searchKeywords || searchKeywords === '') && getRoleList({ pageNo: 1 });
}, [searchKeywords]);
return (
<>
<div className="operate-bar-right">
<Input
className="search-input"
suffix={
<IconFont
type="icon-fangdajing"
onClick={(_) => {
setSearchKeywords(searchKeywordsInput);
}}
style={{ fontSize: '16px' }}
/>
}
placeholder="请输入角色名称"
value={searchKeywordsInput}
onPressEnter={(_) => {
setSearchKeywords(searchKeywordsInput);
}}
onChange={(e) => {
setSearchKeywordsInput(e.target.value);
}}
/>
{global.hasPermission && global.hasPermission(ConfigPermissionMap.ROLE_ADD) ? (
<Button
type="primary"
icon={<PlusOutlined />}
onClick={() => detailRef.current.onOpen(true, RoleOperate.Add, getRoleList, undefined)}
>
</Button>
) : (
<></>
)}
</div>
<ProTable
tableProps={{
showHeader: false,
loading,
rowKey: 'id',
dataSource: roles,
paginationProps: pagination,
columns,
lineFillColor: true,
attrs: {
onChange: onTableChange,
scroll: {
scrollToFirstRowOnChange: true,
x: true,
y: 'calc(100vh - 326px)',
},
},
}}
/>
<RoleDetailAndUpdate ref={detailRef} />
<AssignRoles ref={assignRolesRef} />
</>
);
};