mirror of
https://github.com/didi/KnowStreaming.git
synced 2026-01-05 21:12:13 +08:00
初始化3.0.0版本
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
import React, { useLayoutEffect } from 'react';
|
||||
import { Utils, AppContainer } from 'knowdesign';
|
||||
|
||||
// 权限对应表
|
||||
export enum ConfigPermissionMap {
|
||||
SYS_MANAGE = '系统管理',
|
||||
// 配置管理
|
||||
CONFIG_ADD = '配置管理-新增配置',
|
||||
CONFIG_EDIT = '配置管理-编辑配置',
|
||||
CONFIG_DEL = '配置管理-删除配置',
|
||||
// 用户管理
|
||||
USER_DEL = '用户管理-删除人员',
|
||||
USER_CHANGE_PASS = '用户管理-修改人员密码',
|
||||
USER_EDIT = '用户管理-编辑人员',
|
||||
USER_ADD = '用户管理-新增人员',
|
||||
// 角色管理
|
||||
ROLE_DEL = '用户管理-删除角色',
|
||||
ROLE_ASSIGN = '用户管理-分配用户角色',
|
||||
ROLE_EDIT = '用户管理-编辑角色',
|
||||
ROLE_ADD = '用户管理-新增角色',
|
||||
}
|
||||
|
||||
export interface PermissionNode {
|
||||
id: number;
|
||||
permissionName: ConfigPermissionMap | null;
|
||||
parentId: number | null;
|
||||
has: boolean;
|
||||
leaf: boolean;
|
||||
childList: PermissionNode[];
|
||||
}
|
||||
|
||||
const CommonConfig = (): JSX.Element => {
|
||||
const [global, setGlobal] = AppContainer.useGlobalValue();
|
||||
|
||||
// 获取权限树
|
||||
const getPermissionTree = () => {
|
||||
// 如果未登录,直接退出
|
||||
const userInfo = localStorage.getItem('userInfo');
|
||||
if (!userInfo) return false;
|
||||
|
||||
const userId = JSON.parse(userInfo).id;
|
||||
const getUserInfo = Utils.request(`/logi-security/api/v1/user/${userId}`);
|
||||
const getPermissionTree = Utils.request('/logi-security/api/v1/permission/tree');
|
||||
|
||||
Promise.all([getPermissionTree, getUserInfo]).then(([permissionTree, userDetail]: [PermissionNode, any]) => {
|
||||
const allPermissions = permissionTree.childList;
|
||||
|
||||
// 获取用户在系统管理拥有的权限
|
||||
const userPermissionTree = userDetail.permissionTreeVO.childList;
|
||||
const configPermissions = userPermissionTree.find((sys) => sys.permissionName === ConfigPermissionMap.SYS_MANAGE);
|
||||
const userPermissions: ConfigPermissionMap[] = [];
|
||||
configPermissions && configPermissions.childList.forEach((node) => node.has && userPermissions.push(node.permissionName));
|
||||
|
||||
const hasPermission = (permissionName: ConfigPermissionMap) => permissionName && userPermissions.includes(permissionName);
|
||||
|
||||
setGlobal((curState: any) => ({ ...curState, permissions: allPermissions, userPermissions, hasPermission, userInfo }));
|
||||
});
|
||||
};
|
||||
|
||||
useLayoutEffect(() => {
|
||||
getPermissionTree();
|
||||
}, []);
|
||||
|
||||
return <></>;
|
||||
};
|
||||
|
||||
export default CommonConfig;
|
||||
@@ -0,0 +1,17 @@
|
||||
export enum ConfigOperate {
|
||||
Add,
|
||||
Edit,
|
||||
}
|
||||
|
||||
export type ConfigProps = {
|
||||
id?: number;
|
||||
valueGroup?: string;
|
||||
valueName?: string;
|
||||
value?: string;
|
||||
status?: 0 | 1;
|
||||
operator?: string;
|
||||
memo?: string;
|
||||
};
|
||||
|
||||
export type AddConfigProps = Omit<ConfigProps, 'id' | 'operator'>;
|
||||
export type EditConfigProps = Omit<ConfigProps, 'operator'>;
|
||||
@@ -0,0 +1,72 @@
|
||||
.config-manage-page {
|
||||
.d-table {
|
||||
.text-overflow-two-row {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
.hover-light:hover {
|
||||
color: #556ee6;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 新增/编辑配置抽屉 代码编辑器样式
|
||||
.config-manage-edit-drawer {
|
||||
.codemirror-form-item {
|
||||
> .cm-s-default {
|
||||
border: 1px solid transparent;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
transition: all 0.3s;
|
||||
&:hover,
|
||||
&.CodeMirror-focused {
|
||||
border-color: #74788d;
|
||||
}
|
||||
.CodeMirror-scroll {
|
||||
background: rgba(33, 37, 41, 0.06);
|
||||
transition: all 0.3s;
|
||||
.CodeMirror-gutters {
|
||||
background: transparent;
|
||||
border-right: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dcloud-form-item-has-error {
|
||||
.codemirror-form-item {
|
||||
> .cm-s-default {
|
||||
border-color: #ff7066;
|
||||
.CodeMirror-scroll {
|
||||
background: #fffafa;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 列表配置值弹窗
|
||||
.config-manage-value-modal {
|
||||
.dcloud-modal-header {
|
||||
border-bottom: 0;
|
||||
}
|
||||
.dcloud-modal-body {
|
||||
padding: 0 8px 8px 8px;
|
||||
.react-codemirror2 {
|
||||
> .cm-s-default {
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
.CodeMirror-scroll {
|
||||
background: #556de60a;
|
||||
.CodeMirror-gutters {
|
||||
background: transparent;
|
||||
border-right: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,490 @@
|
||||
import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react';
|
||||
import {
|
||||
Button,
|
||||
Form,
|
||||
Input,
|
||||
Select,
|
||||
Switch,
|
||||
Modal,
|
||||
message,
|
||||
ProTable,
|
||||
Drawer,
|
||||
Space,
|
||||
Divider,
|
||||
Tooltip,
|
||||
AppContainer,
|
||||
Utils,
|
||||
} from 'knowdesign';
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import moment from 'moment';
|
||||
// 引入代码编辑器
|
||||
import { Controlled as CodeMirror } from 'react-codemirror2';
|
||||
import 'codemirror/lib/codemirror.css';
|
||||
//代码高亮
|
||||
import 'codemirror/addon/edit/matchbrackets';
|
||||
import 'codemirror/addon/selection/active-line';
|
||||
import 'codemirror/addon/edit/closebrackets';
|
||||
require('codemirror/mode/xml/xml');
|
||||
require('codemirror/mode/javascript/javascript');
|
||||
import api from 'api';
|
||||
import { defaultPagination } from 'constants/common';
|
||||
import TypicalListCard from '../../components/TypicalListCard';
|
||||
import { ConfigPermissionMap } from '../CommonConfig';
|
||||
import { ConfigOperate, ConfigProps } from './config';
|
||||
import './index.less';
|
||||
|
||||
const { request } = Utils;
|
||||
const { confirm } = Modal;
|
||||
const { TextArea } = Input;
|
||||
|
||||
// 新增/编辑配置抽屉
|
||||
const EditConfigDrawer = forwardRef((_, ref) => {
|
||||
const [config, setConfig] = useState<ConfigProps>({});
|
||||
const [type, setType] = useState<ConfigOperate>(ConfigOperate.Add);
|
||||
const [form] = Form.useForm();
|
||||
const [visible, setVisible] = useState<boolean>(false);
|
||||
const [groupOptions, setGroupOpions] = useState<{ label: string; value: string }[]>([]);
|
||||
const [confirmLoading, setConfirmLoading] = useState<boolean>(false);
|
||||
const [codeMirrorInput, setCodeMirrorInput] = useState<string>('');
|
||||
const callback = useRef(() => {
|
||||
return;
|
||||
});
|
||||
|
||||
// 提交表单
|
||||
const onSubmit = () => {
|
||||
form.validateFields().then((formData) => {
|
||||
setConfirmLoading(true);
|
||||
formData.status = formData.status ? 1 : 2;
|
||||
const isAdd = type === ConfigOperate.Add;
|
||||
const submitApi = isAdd ? api.addConfig : api.editConfig;
|
||||
request(submitApi, {
|
||||
method: isAdd ? 'PUT' : 'POST',
|
||||
data: Object.assign(formData, isAdd ? {} : { id: config.id }),
|
||||
}).then(
|
||||
(res) => {
|
||||
// 执行回调,刷新列表数据
|
||||
callback.current();
|
||||
|
||||
onClose();
|
||||
message.success(`成功${isAdd ? '新增' : '更新'}配置`);
|
||||
},
|
||||
() => setConfirmLoading(false)
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
// 展开抽屉
|
||||
const onOpen = (status: boolean, type: ConfigOperate, cbk: () => void, groupOptions, config: ConfigProps = {}) => {
|
||||
if (config.value) {
|
||||
try {
|
||||
// 如果内容可以格式化为 JSON,进行处理
|
||||
config.value = JSON.stringify(JSON.parse(config.value), null, 2);
|
||||
} catch (_) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
form.setFieldsValue({ ...config, status: config.status === 1 });
|
||||
setConfig(config);
|
||||
setGroupOpions(groupOptions);
|
||||
setCodeMirrorInput(config.value);
|
||||
setType(type);
|
||||
setVisible(status);
|
||||
callback.current = cbk;
|
||||
};
|
||||
|
||||
// 关闭抽屉
|
||||
const onClose = () => {
|
||||
setVisible(false);
|
||||
setConfirmLoading(false);
|
||||
setConfig({});
|
||||
form.resetFields();
|
||||
setCodeMirrorInput('');
|
||||
};
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
onOpen,
|
||||
}));
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
className="config-manage-edit-drawer"
|
||||
title={`${type === ConfigOperate.Add ? '新增' : '编辑'}配置`}
|
||||
width={480}
|
||||
visible={visible}
|
||||
maskClosable={false}
|
||||
onClose={onClose}
|
||||
extra={
|
||||
<Space>
|
||||
<Button size="small" onClick={onClose}>
|
||||
取消
|
||||
</Button>
|
||||
<Button type="primary" size="small" loading={confirmLoading} onClick={onSubmit}>
|
||||
确定
|
||||
</Button>
|
||||
<Divider type="vertical" />
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
<Form form={form} layout="vertical">
|
||||
<Form.Item label="模块" name="valueGroup" rules={[{ required: true, message: '模块不能为空' }]}>
|
||||
<Select options={groupOptions} placeholder="请选择模块" />
|
||||
</Form.Item>
|
||||
<Form.Item label="配置键" name="valueName" rules={[{ required: true, message: '配置键不能为空' }]}>
|
||||
<Input placeholder="请输入配置键" maxLength={100} />
|
||||
</Form.Item>
|
||||
<Form.Item label="配置值" name="value" rules={[{ required: true, message: '配置值不能为空' }]}>
|
||||
<div>
|
||||
<CodeMirror
|
||||
className="codemirror-form-item"
|
||||
value={codeMirrorInput}
|
||||
options={{
|
||||
mode: 'application/json',
|
||||
lineNumbers: true,
|
||||
lineWrapper: true,
|
||||
autoCloseBrackets: true,
|
||||
smartIndent: true,
|
||||
tabSize: 2,
|
||||
}}
|
||||
onBeforeChange={(editor, data, value) => {
|
||||
form.setFieldsValue({ value });
|
||||
form.validateFields(['value']);
|
||||
setCodeMirrorInput(value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Form.Item>
|
||||
<Form.Item label="描述" name="memo" rules={[{ required: true, message: '必须输入描述' }]}>
|
||||
<TextArea placeholder="请输入描述" maxLength={200} />
|
||||
</Form.Item>
|
||||
<Form.Item label="启用状态" name="status" valuePropName="checked">
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Drawer>
|
||||
);
|
||||
});
|
||||
|
||||
// 配置值详情弹窗
|
||||
// eslint-disable-next-line react/display-name
|
||||
const ConfigValueDetail = forwardRef((_, ref) => {
|
||||
const [visible, setVisible] = useState<boolean>(false);
|
||||
const [content, setContent] = useState<string>('');
|
||||
|
||||
const onClose = () => {
|
||||
setVisible(false);
|
||||
setContent('');
|
||||
};
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
setVisible: (status: boolean, content: string) => {
|
||||
let transformedContent = '';
|
||||
|
||||
try {
|
||||
// 如果内容可以格式化为 JSON,进行处理
|
||||
transformedContent = JSON.stringify(JSON.parse(content), null, 2);
|
||||
} catch (_) {
|
||||
transformedContent = content;
|
||||
}
|
||||
|
||||
setContent(transformedContent);
|
||||
setVisible(status);
|
||||
},
|
||||
}));
|
||||
|
||||
return (
|
||||
<Modal
|
||||
className="config-manage-value-modal"
|
||||
title="配置值"
|
||||
visible={visible}
|
||||
centered={true}
|
||||
footer={null}
|
||||
onCancel={onClose}
|
||||
maskClosable={false}
|
||||
destroyOnClose
|
||||
>
|
||||
<CodeMirror
|
||||
value={content}
|
||||
options={{
|
||||
mode: 'application/json',
|
||||
lineNumbers: true,
|
||||
lineWrapper: true,
|
||||
autoCloseBrackets: true,
|
||||
smartIndent: true,
|
||||
tabSize: 2,
|
||||
}}
|
||||
onBeforeChange={() => {
|
||||
return;
|
||||
}}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
});
|
||||
|
||||
export default () => {
|
||||
const [global] = AppContainer.useGlobalValue();
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [configGroupList, setConfigGroupList] = useState<{ label: string; value: string }[]>([]);
|
||||
const [data, setData] = useState<ConfigProps[]>([]);
|
||||
const [pagination, setPagination] = useState<any>(defaultPagination);
|
||||
const [form] = Form.useForm();
|
||||
const editDrawerRef = useRef(null);
|
||||
const configValueModalRef = useRef(null);
|
||||
|
||||
const getConfigList = (query = {}) => {
|
||||
const formData = form.getFieldsValue();
|
||||
const queryParams = {
|
||||
page: pagination.current,
|
||||
size: pagination.pageSize,
|
||||
...formData,
|
||||
...query,
|
||||
};
|
||||
|
||||
setLoading(true);
|
||||
request(api.configList, {
|
||||
method: 'POST',
|
||||
data: queryParams,
|
||||
}).then(
|
||||
(res: any) => {
|
||||
const { pageNo, pageSize, pages, total } = res.pagination;
|
||||
if (pageNo > pages && pages !== 0) {
|
||||
getConfigList({ page: pages });
|
||||
return false;
|
||||
}
|
||||
|
||||
setPagination({
|
||||
...pagination,
|
||||
current: pageNo,
|
||||
pageSize,
|
||||
total,
|
||||
});
|
||||
setData(res.bizData);
|
||||
setLoading(false);
|
||||
},
|
||||
() => setLoading(false)
|
||||
);
|
||||
};
|
||||
|
||||
const columns = useCallback(() => {
|
||||
const baseColumns = [
|
||||
{
|
||||
title: '模块',
|
||||
dataIndex: 'valueGroup',
|
||||
lineClampOne: true,
|
||||
},
|
||||
{
|
||||
title: '配置键',
|
||||
dataIndex: 'valueName',
|
||||
width: 150,
|
||||
lineClampOne: true,
|
||||
render(content) {
|
||||
return (
|
||||
<Tooltip title={content}>
|
||||
<div className="text-overflow-two-row">{content}</div>
|
||||
</Tooltip>
|
||||
);
|
||||
},
|
||||
},
|
||||
// TODO: 两行省略
|
||||
{
|
||||
title: '配置值',
|
||||
dataIndex: 'value',
|
||||
width: 180,
|
||||
lineClampOne: true,
|
||||
render(content) {
|
||||
return (
|
||||
<div className="text-overflow-two-row hover-light" onClick={() => configValueModalRef.current.setVisible(true, content)}>
|
||||
{content}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '描述',
|
||||
dataIndex: 'memo',
|
||||
width: 180,
|
||||
lineClampOne: true,
|
||||
},
|
||||
{
|
||||
title: '启用状态',
|
||||
dataIndex: 'status',
|
||||
render(status: number, record) {
|
||||
return (
|
||||
<div style={{ width: 60 }}>
|
||||
<Switch
|
||||
checked={status === 1}
|
||||
size="small"
|
||||
onChange={() => {
|
||||
request(api.configSwtichStatus, {
|
||||
method: 'POST',
|
||||
data: {
|
||||
id: record.id,
|
||||
status: status === 1 ? 2 : 1,
|
||||
},
|
||||
}).then((_) => {
|
||||
getConfigList();
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '最后更新时间',
|
||||
dataIndex: 'updateTime',
|
||||
render: (date) => moment(date).format('YYYY-MM-DD HH:mm:ss'),
|
||||
},
|
||||
{
|
||||
title: '最后更新人',
|
||||
dataIndex: 'operator',
|
||||
width: 100,
|
||||
lineClampOne: true,
|
||||
},
|
||||
];
|
||||
if (
|
||||
global.hasPermission &&
|
||||
(global.hasPermission(ConfigPermissionMap.CONFIG_EDIT) || global.hasPermission(ConfigPermissionMap.CONFIG_DEL))
|
||||
) {
|
||||
baseColumns.push({
|
||||
title: '操作',
|
||||
dataIndex: '',
|
||||
width: 130,
|
||||
lineClampOne: false,
|
||||
render(record: ConfigProps) {
|
||||
return (
|
||||
<>
|
||||
{global.hasPermission && global.hasPermission(ConfigPermissionMap.CONFIG_EDIT) ? (
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
onClick={() => editDrawerRef.current.onOpen(true, ConfigOperate.Edit, getConfigList, configGroupList, record)}
|
||||
style={{ paddingLeft: 0 }}
|
||||
>
|
||||
编辑
|
||||
</Button>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
{global.hasPermission && global.hasPermission(ConfigPermissionMap.CONFIG_DEL) ? (
|
||||
<Button type="link" size="small" onClick={() => onDelete(record)}>
|
||||
删除
|
||||
</Button>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return baseColumns;
|
||||
}, [global, getConfigList, configGroupList]);
|
||||
|
||||
const onDelete = (record: ConfigProps) => {
|
||||
confirm({
|
||||
title: '确定删除配置吗?',
|
||||
content: `配置⌈${record.valueName}⌋${record.status === 1 ? '为启用状态,无法删除' : ''}`,
|
||||
centered: true,
|
||||
okText: '删除',
|
||||
okType: 'primary',
|
||||
okButtonProps: {
|
||||
size: 'small',
|
||||
disabled: record.status === 1,
|
||||
danger: true,
|
||||
},
|
||||
cancelButtonProps: {
|
||||
size: 'small',
|
||||
},
|
||||
maskClosable: true,
|
||||
onOk() {
|
||||
return request(api.editConfig, {
|
||||
method: 'POST',
|
||||
data: record.id,
|
||||
}).then((_) => {
|
||||
message.success('删除成功');
|
||||
getConfigList();
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const onTableChange = (curPagination) => {
|
||||
getConfigList({ page: curPagination.current, size: curPagination.pageSize });
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// 获取模块列表
|
||||
request(api.configGroupList).then((res: string[]) => {
|
||||
const options = res.map((item) => ({
|
||||
label: item,
|
||||
value: item,
|
||||
}));
|
||||
setConfigGroupList(options);
|
||||
});
|
||||
// 获取配置列表
|
||||
getConfigList();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<TypicalListCard title="配置管理">
|
||||
<div className="config-manage-page">
|
||||
<div className="operate-bar">
|
||||
<Form form={form} layout="inline" onFinish={() => getConfigList({ page: 1 })}>
|
||||
<Form.Item name="valueGroup">
|
||||
<Select placeholder="请选择模块" options={configGroupList} allowClear />
|
||||
</Form.Item>
|
||||
<Form.Item name="valueName">
|
||||
<Input placeholder="请输入配置键" />
|
||||
</Form.Item>
|
||||
<Form.Item name="memo">
|
||||
<Input placeholder="请输入描述" />
|
||||
</Form.Item>
|
||||
<Form.Item>
|
||||
<Button type="primary" ghost htmlType="submit">
|
||||
查询
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
{global.hasPermission && global.hasPermission(ConfigPermissionMap.CONFIG_ADD) ? (
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => editDrawerRef.current.onOpen(true, ConfigOperate.Add, getConfigList, configGroupList)}
|
||||
>
|
||||
新增配置
|
||||
</Button>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<ProTable
|
||||
tableProps={{
|
||||
showHeader: false,
|
||||
loading,
|
||||
rowKey: 'id',
|
||||
dataSource: data,
|
||||
paginationProps: pagination,
|
||||
columns,
|
||||
lineFillColor: true,
|
||||
attrs: {
|
||||
onChange: onTableChange,
|
||||
scroll: {
|
||||
scrollToFirstRowOnChange: true,
|
||||
x: true,
|
||||
y: 'calc(100vh - 270px)',
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</TypicalListCard>
|
||||
|
||||
{/* 新增/编辑配置抽屉 */}
|
||||
<EditConfigDrawer ref={editDrawerRef} />
|
||||
<ConfigValueDetail ref={configValueModalRef} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
import React from 'react';
|
||||
import { Redirect } from 'react-router-dom';
|
||||
const HomePage = () => {
|
||||
return <Redirect to="/setting" />;
|
||||
};
|
||||
|
||||
export default HomePage;
|
||||
@@ -0,0 +1,158 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Button, Form, Input, Select, ProTable, DatePicker, Utils } from 'knowdesign';
|
||||
import api from 'api';
|
||||
import { defaultPagination } from 'constants/common';
|
||||
import TypicalListCard from '../../components/TypicalListCard';
|
||||
import './index.less';
|
||||
import moment from 'moment';
|
||||
|
||||
const { request } = Utils;
|
||||
const { RangePicker } = DatePicker;
|
||||
|
||||
export default () => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [configGroupList, setConfigGroupList] = useState<{ label: string; value: string }[]>([]);
|
||||
const [data, setData] = useState([]);
|
||||
const [pagination, setPagination] = useState<any>(defaultPagination);
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '模块',
|
||||
dataIndex: 'targetType',
|
||||
lineClampOne: true,
|
||||
},
|
||||
{
|
||||
title: '操作对象',
|
||||
dataIndex: 'target',
|
||||
width: 350,
|
||||
lineClampOne: true,
|
||||
},
|
||||
{
|
||||
title: '行为',
|
||||
dataIndex: 'operateType',
|
||||
width: 80,
|
||||
lineClampOne: true,
|
||||
},
|
||||
{
|
||||
title: '操作内容',
|
||||
dataIndex: 'detail',
|
||||
width: 350,
|
||||
lineClampOne: true,
|
||||
},
|
||||
{
|
||||
title: '操作时间',
|
||||
dataIndex: 'updateTime',
|
||||
width: 200,
|
||||
render: (date) => moment(date).format('YYYY-MM-DD HH:mm:ss'),
|
||||
},
|
||||
{
|
||||
title: '操作人',
|
||||
dataIndex: 'operator',
|
||||
},
|
||||
];
|
||||
|
||||
const getData = (query = {}) => {
|
||||
const formData = form.getFieldsValue();
|
||||
if (formData.time) {
|
||||
formData.startTime = moment(formData.time[0]).valueOf();
|
||||
formData.endTime = moment(formData.time[1]).valueOf();
|
||||
}
|
||||
delete formData.time;
|
||||
const data = {
|
||||
page: pagination.current,
|
||||
size: pagination.pageSize,
|
||||
...formData,
|
||||
...query,
|
||||
};
|
||||
|
||||
setLoading(true);
|
||||
request(api.oplogList, {
|
||||
method: 'POST',
|
||||
data,
|
||||
}).then(
|
||||
(res: any) => {
|
||||
const { pageNo, pageSize, pages, total } = res.pagination;
|
||||
if (pageNo > pages && pages !== 0) {
|
||||
getData({ page: pages });
|
||||
return false;
|
||||
}
|
||||
|
||||
setPagination({
|
||||
...pagination,
|
||||
current: pageNo,
|
||||
pageSize,
|
||||
total,
|
||||
});
|
||||
setData(res.bizData);
|
||||
setLoading(false);
|
||||
},
|
||||
() => setLoading(false)
|
||||
);
|
||||
};
|
||||
|
||||
const onTableChange = (curPagination) => {
|
||||
getData({ page: curPagination.current, size: curPagination.pageSize });
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// 获取模块列表
|
||||
request(api.oplogTypeList).then((res: string[]) => {
|
||||
const options = res.map((item) => ({
|
||||
label: item,
|
||||
value: item,
|
||||
}));
|
||||
setConfigGroupList(options);
|
||||
});
|
||||
// 获取数据
|
||||
getData();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<TypicalListCard title="操作记录">
|
||||
<div className="operate-bar">
|
||||
<Form form={form} layout="inline" onFinish={() => getData({ page: 1 })}>
|
||||
<Form.Item name="targetType">
|
||||
<Select placeholder="请选择模块" options={configGroupList} style={{ width: 160 }} allowClear />
|
||||
</Form.Item>
|
||||
<Form.Item name="target">
|
||||
<Input placeholder="请输入操作对象" />
|
||||
</Form.Item>
|
||||
<Form.Item name="detail">
|
||||
<Input placeholder="请输入操作内容" />
|
||||
</Form.Item>
|
||||
<Form.Item name="time">
|
||||
<RangePicker showTime />
|
||||
</Form.Item>
|
||||
<Form.Item>
|
||||
<Button type="primary" ghost htmlType="submit">
|
||||
查询
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</div>
|
||||
|
||||
<ProTable
|
||||
tableProps={{
|
||||
showHeader: false,
|
||||
loading,
|
||||
rowKey: 'id',
|
||||
dataSource: data,
|
||||
paginationProps: pagination,
|
||||
columns,
|
||||
lineFillColor: true,
|
||||
attrs: {
|
||||
onChange: onTableChange,
|
||||
scroll: {
|
||||
scrollToFirstRowOnChange: true,
|
||||
x: true,
|
||||
y: 'calc(100vh - 270px)',
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</TypicalListCard>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,92 @@
|
||||
import React, { useLayoutEffect, useState } from 'react';
|
||||
import { Checkbox, Col, FormInstance, Row } from 'knowdesign';
|
||||
|
||||
type Option = { label: string; value: string | number };
|
||||
type CheckValue = string | number;
|
||||
interface CheckboxGroupType {
|
||||
options: Option[];
|
||||
initSelectedOptions?: CheckValue[];
|
||||
formInstance: FormInstance;
|
||||
fieldName: string;
|
||||
groupIdx: number;
|
||||
disabled?: boolean;
|
||||
allCheckText?: string;
|
||||
}
|
||||
|
||||
const CheckboxGroupContainer = (props: CheckboxGroupType) => {
|
||||
const { options, initSelectedOptions = [], formInstance, fieldName, groupIdx, disabled = false, allCheckText = '全选' } = props;
|
||||
const [checkedList, setCheckedList] = useState<CheckValue[]>([]);
|
||||
const [indeterminate, setIndeterminate] = useState<boolean>(false);
|
||||
const [checkAll, setCheckAll] = useState<boolean>(false);
|
||||
|
||||
// 更新表单项内容
|
||||
const updateFieldValue = (curList: CheckValue[]) => {
|
||||
const curFieldValue = formInstance.getFieldValue(fieldName);
|
||||
const newFieldValue = [].concat(curFieldValue);
|
||||
newFieldValue[groupIdx] = curList;
|
||||
formInstance.setFieldsValue({
|
||||
[fieldName]: newFieldValue,
|
||||
});
|
||||
};
|
||||
|
||||
// 选择
|
||||
const onCheckedChange = (list: CheckValue[]) => {
|
||||
list.sort();
|
||||
updateFieldValue(list);
|
||||
setCheckedList(list);
|
||||
setIndeterminate(!!list.length && list.length < options.length);
|
||||
setCheckAll(list.length === options.length);
|
||||
};
|
||||
|
||||
// 全选
|
||||
const onCheckAllChange = (e) => {
|
||||
const newOptions = e.target.checked ? options : [];
|
||||
updateFieldValue(newOptions.map((option) => option.value));
|
||||
setCheckedList(newOptions.map((option) => option.value));
|
||||
setIndeterminate(false);
|
||||
setCheckAll(e.target.checked);
|
||||
};
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const newInitOptions = [...initSelectedOptions].sort();
|
||||
if (checkedList.length !== newInitOptions.length) {
|
||||
setCheckedList(newInitOptions);
|
||||
updateFieldValue(newInitOptions);
|
||||
} else {
|
||||
if (checkedList.some((option, i) => option !== newInitOptions[i])) {
|
||||
setCheckedList(newInitOptions);
|
||||
updateFieldValue(newInitOptions);
|
||||
}
|
||||
}
|
||||
}, [initSelectedOptions]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
setIndeterminate(!!initSelectedOptions.length && initSelectedOptions.length < options.length);
|
||||
setCheckAll(!!initSelectedOptions.length && options.length === initSelectedOptions.length);
|
||||
}, [options, initSelectedOptions]);
|
||||
|
||||
return (
|
||||
<div style={{ marginBottom: 30 }}>
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<Checkbox disabled={disabled} indeterminate={indeterminate} onChange={onCheckAllChange} checked={checkAll}>
|
||||
{allCheckText}
|
||||
</Checkbox>
|
||||
</div>
|
||||
<Checkbox.Group disabled={disabled} style={{ width: '100%' }} value={checkedList} onChange={onCheckedChange}>
|
||||
<Row gutter={[34, 10]}>
|
||||
{options.map((option) => {
|
||||
return (
|
||||
<Col span={8} key={option.value}>
|
||||
<Checkbox value={option.value} className="checkbox-content-ellipsis">
|
||||
{option.label}
|
||||
</Checkbox>
|
||||
</Col>
|
||||
);
|
||||
})}
|
||||
</Row>
|
||||
</Checkbox.Group>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CheckboxGroupContainer;
|
||||
@@ -0,0 +1,635 @@
|
||||
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: '角色名称只能由中英文大小写、数字、下划线(_)组成,长度限制在3~128字符',
|
||||
},
|
||||
]}
|
||||
>
|
||||
<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 [form] = Form.useForm();
|
||||
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 formData = form.getFieldsValue();
|
||||
const data = {
|
||||
page: pagination.current,
|
||||
size: pagination.pageSize,
|
||||
...formData,
|
||||
...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]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="operate-bar">
|
||||
<Form form={form} layout="inline" onFinish={() => getRoleList({ page: 1 })}>
|
||||
<Form.Item name="roleName">
|
||||
<Input placeholder="请输入角色名称" />
|
||||
</Form.Item>
|
||||
<Form.Item>
|
||||
<Button type="primary" ghost htmlType="submit">
|
||||
查询
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
{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} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,396 @@
|
||||
import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react';
|
||||
import { Form, ProTable, Select, Button, Input, Modal, message, Drawer, Space, Divider, AppContainer, Utils } from 'knowdesign';
|
||||
import { PlusOutlined, QuestionCircleOutlined } from '@ant-design/icons';
|
||||
import moment from 'moment';
|
||||
import { defaultPagination } from 'constants/common';
|
||||
import { UserProps, UserOperate } from './config';
|
||||
import CheckboxGroupContainer from './CheckboxGroupContainer';
|
||||
import TagsWithHide from '../../components/TagsWithHide/index';
|
||||
import api from 'api';
|
||||
import { ConfigPermissionMap } from '../CommonConfig';
|
||||
|
||||
const { confirm } = Modal;
|
||||
const { request } = Utils;
|
||||
|
||||
// 正则表达式
|
||||
const PASSWORD_REGEXP = /^[a-zA-Z0-9_]{6,12}$/;
|
||||
const USERNAME_REGEXP = /^[a-zA-Z0-9_]{3,128}$/;
|
||||
const REALNAME_REGEXP = /^[\u4e00-\u9fa5a-zA-Z0-9_]{1,128}$/;
|
||||
|
||||
// 编辑用户抽屉
|
||||
// eslint-disable-next-line react/display-name
|
||||
const EditUserDrawer = forwardRef((props, ref) => {
|
||||
const [global] = AppContainer.useGlobalValue();
|
||||
const [form] = Form.useForm();
|
||||
const [visible, setVisible] = useState<boolean>(false);
|
||||
const [confirmLoading, setConfirmLoading] = useState<boolean>(false);
|
||||
const [type, setType] = useState<UserOperate>(UserOperate.Add);
|
||||
const [user, setUser] = useState<UserProps>();
|
||||
const [roleOptions, setRoleOptions] = useState<{ label: string; value: number }[]>([]);
|
||||
const [initSelectedOptions, setInitSelectedOptions] = useState<number[]>([]);
|
||||
const callback = useRef(() => {
|
||||
return;
|
||||
});
|
||||
|
||||
// 提交表单
|
||||
const onSubmit = () => {
|
||||
form.validateFields().then((formData) => {
|
||||
setConfirmLoading(true);
|
||||
formData.roleIds = formData.roleIds.flat();
|
||||
if (!formData.pw) {
|
||||
delete formData.pw;
|
||||
}
|
||||
// 密码加密
|
||||
// formData.pw = Utils.encryptAES(formData.pw, systemCipherKey);
|
||||
|
||||
const requestPromise =
|
||||
type === UserOperate.Add
|
||||
? request(api.addUser, {
|
||||
method: 'PUT',
|
||||
data: formData,
|
||||
})
|
||||
: request(api.editUser, {
|
||||
method: 'POST',
|
||||
data: { ...formData },
|
||||
});
|
||||
requestPromise.then(
|
||||
(res) => {
|
||||
callback.current();
|
||||
onClose();
|
||||
message.success(`${type === UserOperate.Add ? '新增' : '编辑'}用户成功`);
|
||||
},
|
||||
() => setConfirmLoading(false)
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
// 打开抽屉
|
||||
const onOpen = (status: boolean, type: UserOperate, cbk: () => { return }, userDetail: UserProps, roles) => {
|
||||
form.setFieldsValue(userDetail);
|
||||
setUser(userDetail);
|
||||
setInitSelectedOptions(userDetail?.roleList ? userDetail.roleList.map((role) => role.id) : []);
|
||||
setRoleOptions(roles);
|
||||
setType(type);
|
||||
setVisible(status);
|
||||
callback.current = cbk;
|
||||
};
|
||||
|
||||
// 关闭抽屉
|
||||
const onClose = () => {
|
||||
setVisible(false);
|
||||
form.resetFields();
|
||||
setUser(undefined);
|
||||
setInitSelectedOptions([]);
|
||||
setRoleOptions([]);
|
||||
setConfirmLoading(false);
|
||||
};
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
onOpen,
|
||||
}));
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
title={`${type === UserOperate.Add ? '新增' : '编辑'}用户`}
|
||||
width={480}
|
||||
visible={visible}
|
||||
maskClosable={false}
|
||||
onClose={onClose}
|
||||
extra={
|
||||
<Space>
|
||||
<Button size="small" onClick={onClose}>
|
||||
取消
|
||||
</Button>
|
||||
<Button type="primary" size="small" loading={confirmLoading} onClick={onSubmit}>
|
||||
确定
|
||||
</Button>
|
||||
<Divider type="vertical" />
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
<Form form={form} layout="vertical">
|
||||
<Form.Item
|
||||
label="用户账号"
|
||||
name="userName"
|
||||
rules={[
|
||||
{ required: true, message: '用户账号不能为空' },
|
||||
{ pattern: USERNAME_REGEXP, message: '用户账号只能由英文大小写、数字、下划线(_)组成,长度限制在3~128字符' },
|
||||
]}
|
||||
>
|
||||
<Input disabled={type === UserOperate.Edit} placeholder="请输入用户账号" />
|
||||
</Form.Item>
|
||||
{type === UserOperate.Add || global.hasPermission(ConfigPermissionMap.USER_EDIT) ? (
|
||||
<Form.Item
|
||||
label="用户实名"
|
||||
name="realName"
|
||||
rules={[
|
||||
{ required: true, message: '用户实名不能为空' },
|
||||
{ pattern: REALNAME_REGEXP, message: '用户实名只能由中英文大小写、数字、下划线组成,长度限制在1~128字符' },
|
||||
]}
|
||||
>
|
||||
<Input placeholder="请输入用户实名" />
|
||||
</Form.Item>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
{type === UserOperate.Add || global.hasPermission(ConfigPermissionMap.USER_CHANGE_PASS) ? (
|
||||
<Form.Item
|
||||
label={`${type === UserOperate.Edit ? '新' : ''}密码`}
|
||||
name="pw"
|
||||
tooltip={{ title: '密码支持英文、数字、下划线(_),长度限制在6~12字符', icon: <QuestionCircleOutlined /> }}
|
||||
rules={[
|
||||
{ required: type === UserOperate.Add, message: '密码不能为空' },
|
||||
{ pattern: PASSWORD_REGEXP, message: '密码只能由英文、数字、下划线(_)组成,长度限制在6~12字符' },
|
||||
]}
|
||||
>
|
||||
<Input.Password placeholder="请输入密码" />
|
||||
</Form.Item>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
{type === UserOperate.Add || global.hasPermission(ConfigPermissionMap.USER_EDIT) ? (
|
||||
<Form.Item
|
||||
label="分配角色"
|
||||
name="roleIds"
|
||||
rules={[
|
||||
() => ({
|
||||
validator(_, value) {
|
||||
if (Array.isArray(value) && value.some((item) => !!item.length)) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject(new Error('请为用户至少分配一名角色'));
|
||||
},
|
||||
}),
|
||||
]}
|
||||
>
|
||||
<CheckboxGroupContainer
|
||||
formInstance={form}
|
||||
fieldName="roleIds"
|
||||
options={roleOptions}
|
||||
initSelectedOptions={initSelectedOptions}
|
||||
groupIdx={0}
|
||||
/>
|
||||
</Form.Item>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</Form>
|
||||
</Drawer>
|
||||
);
|
||||
});
|
||||
|
||||
export default (props: { curTabKey: string }) => {
|
||||
const { curTabKey } = props;
|
||||
const [global] = AppContainer.useGlobalValue();
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [users, setUsers] = useState<UserProps[]>([]);
|
||||
const [pagination, setPagination] = useState<any>(defaultPagination);
|
||||
const [simpleRoleList, setSimpleRoleList] = useState<{ value: number; label: string }[]>([]);
|
||||
const [form] = Form.useForm();
|
||||
const modalRef = useRef(null);
|
||||
const editRef = useRef(null);
|
||||
|
||||
const getUserList = (query = {}) => {
|
||||
const formData = form.getFieldsValue();
|
||||
const data = {
|
||||
page: pagination.current,
|
||||
size: pagination.pageSize,
|
||||
...formData,
|
||||
...query,
|
||||
};
|
||||
setLoading(true);
|
||||
|
||||
request(api.userList, {
|
||||
method: 'POST',
|
||||
data,
|
||||
}).then(
|
||||
(res: any) => {
|
||||
const { pageNo, pageSize, pages, total } = res.pagination;
|
||||
if (pageNo > pages && pages !== 0) {
|
||||
getUserList({ page: pages });
|
||||
return false;
|
||||
}
|
||||
|
||||
setPagination({
|
||||
...pagination,
|
||||
current: pageNo,
|
||||
pageSize,
|
||||
total,
|
||||
});
|
||||
setUsers(res.bizData);
|
||||
setLoading(false);
|
||||
},
|
||||
() => setLoading(false)
|
||||
);
|
||||
};
|
||||
|
||||
const delUser = (record: UserProps) => {
|
||||
confirm({
|
||||
title: '删除提示',
|
||||
content: `确定要删除用户⌈${record.userName}⌋吗?`,
|
||||
centered: true,
|
||||
maskClosable: true,
|
||||
okType: 'primary',
|
||||
okText: '删除',
|
||||
okButtonProps: {
|
||||
size: 'small',
|
||||
danger: true,
|
||||
},
|
||||
cancelButtonProps: {
|
||||
size: 'small',
|
||||
},
|
||||
onOk() {
|
||||
return request(api.user(record.id), {
|
||||
method: 'DELETE',
|
||||
}).then((_) => {
|
||||
message.success('删除成功');
|
||||
getUserList();
|
||||
});
|
||||
},
|
||||
onCancel() {
|
||||
return;
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const onTableChange = (curPagination) => {
|
||||
getUserList({
|
||||
page: curPagination.current,
|
||||
size: curPagination.pageSize,
|
||||
});
|
||||
};
|
||||
|
||||
const columns = useCallback(() => {
|
||||
const baseColumns = [
|
||||
{
|
||||
title: '用户账号',
|
||||
dataIndex: 'userName',
|
||||
},
|
||||
{
|
||||
title: '用户实名',
|
||||
dataIndex: 'realName',
|
||||
},
|
||||
{
|
||||
title: '分配角色',
|
||||
dataIndex: 'roleList',
|
||||
width: 560,
|
||||
render(roleList) {
|
||||
const roles = roleList.map((role) => role.roleName);
|
||||
return <TagsWithHide list={roles} expandTagContent={(num) => `共有${num}个角色`} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '最后更新时间',
|
||||
dataIndex: 'updateTime',
|
||||
render: (date) => moment(date).format('YYYY-MM-DD HH:mm:ss'),
|
||||
},
|
||||
];
|
||||
|
||||
if (
|
||||
global.hasPermission &&
|
||||
(global.hasPermission(ConfigPermissionMap.USER_CHANGE_PASS) ||
|
||||
global.hasPermission(ConfigPermissionMap.USER_EDIT) ||
|
||||
global.hasPermission(ConfigPermissionMap.USER_DEL))
|
||||
) {
|
||||
baseColumns.push({
|
||||
title: '操作',
|
||||
dataIndex: '',
|
||||
width: 140,
|
||||
render(record: UserProps) {
|
||||
return (
|
||||
<>
|
||||
{global.hasPermission &&
|
||||
(global.hasPermission(ConfigPermissionMap.USER_EDIT) || global.hasPermission(ConfigPermissionMap.USER_CHANGE_PASS)) ? (
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
style={{ paddingLeft: 0 }}
|
||||
onClick={() => editRef.current.onOpen(true, UserOperate.Edit, getUserList, record, simpleRoleList)}
|
||||
>
|
||||
编辑
|
||||
</Button>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
{global.hasPermission && global.hasPermission(ConfigPermissionMap.USER_DEL) ? (
|
||||
<Button type="link" size="small" onClick={() => delUser(record)}>
|
||||
删除
|
||||
</Button>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return baseColumns;
|
||||
}, [global, getUserList, simpleRoleList]);
|
||||
|
||||
useEffect(() => {
|
||||
if (curTabKey === 'user') {
|
||||
getUserList();
|
||||
request(api.simpleRoleList).then((res: { id: number; roleName: string }[]) => {
|
||||
const roles = res.map(({ id, roleName }) => ({ label: roleName, value: id }));
|
||||
setSimpleRoleList(roles);
|
||||
});
|
||||
}
|
||||
}, [curTabKey]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="operate-bar">
|
||||
<Form form={form} layout="inline" onFinish={() => getUserList({ page: 1 })}>
|
||||
<Form.Item name="userName">
|
||||
<Input placeholder="请输入用户账号" />
|
||||
</Form.Item>
|
||||
<Form.Item name="realName">
|
||||
<Input placeholder="请输入用户实名" />
|
||||
</Form.Item>
|
||||
<Form.Item name="roleId">
|
||||
<Select style={{ width: 190 }} placeholder="选择平台已创建的角色名" allowClear options={simpleRoleList} />
|
||||
</Form.Item>
|
||||
<Form.Item>
|
||||
<Button type="primary" ghost htmlType="submit">
|
||||
查询
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
{global.hasPermission && global.hasPermission(ConfigPermissionMap.USER_ADD) ? (
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => editRef.current.onOpen(true, UserOperate.Add, getUserList, {}, simpleRoleList)}
|
||||
>
|
||||
新增用户
|
||||
</Button>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<ProTable
|
||||
tableProps={{
|
||||
showHeader: false,
|
||||
loading,
|
||||
rowKey: 'id',
|
||||
dataSource: users,
|
||||
paginationProps: pagination,
|
||||
columns,
|
||||
lineFillColor: true,
|
||||
attrs: {
|
||||
onChange: onTableChange,
|
||||
scroll: {
|
||||
scrollToFirstRowOnChange: true,
|
||||
x: true,
|
||||
y: 'calc(100vh - 326px)',
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
<EditUserDrawer ref={editRef} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,63 @@
|
||||
export type PermissionNode = {
|
||||
id: number;
|
||||
parentId: number;
|
||||
permissionName: string;
|
||||
has: boolean;
|
||||
leaf: boolean;
|
||||
childList: PermissionNode[];
|
||||
};
|
||||
|
||||
export type UserProps = {
|
||||
id: number;
|
||||
userName: string;
|
||||
realName: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
updateTime: number;
|
||||
roleList: {
|
||||
id: number;
|
||||
roleName: string;
|
||||
}[];
|
||||
deptList: {
|
||||
id: number;
|
||||
parentId: number;
|
||||
deptName: string;
|
||||
}[];
|
||||
permissionTreeV0: PermissionNode;
|
||||
};
|
||||
|
||||
export type RoleProps = {
|
||||
id: number;
|
||||
roleCode: string;
|
||||
roleName: string;
|
||||
description: string;
|
||||
authedUserCnt: number;
|
||||
authedUsers: string[];
|
||||
lastReviser: string | null;
|
||||
createTime: string;
|
||||
updateTime: string;
|
||||
permissionTreeV0: PermissionNode;
|
||||
};
|
||||
|
||||
export interface AssignUser {
|
||||
id: number;
|
||||
name: string;
|
||||
has: boolean;
|
||||
}
|
||||
|
||||
export enum UserOperate {
|
||||
Add,
|
||||
Edit,
|
||||
}
|
||||
|
||||
export enum RoleOperate {
|
||||
Add,
|
||||
Edit,
|
||||
View,
|
||||
}
|
||||
|
||||
export interface FormItemPermission {
|
||||
id: number;
|
||||
name: string;
|
||||
options: { label: string; value: number }[];
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
.checkbox-content-ellipsis {
|
||||
width: 100%;
|
||||
& > span:last-child {
|
||||
width: calc(100% - 16px);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.role-tab-detail,
|
||||
.role-tab-assign-user {
|
||||
.desc-row {
|
||||
.label-col,
|
||||
.value-col {
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
}
|
||||
.label-col {
|
||||
color: #74788d;
|
||||
text-align: right;
|
||||
}
|
||||
.value-col {
|
||||
color: #495057;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.role-tab-detail .role-permissions-container {
|
||||
margin-top: 24px;
|
||||
.title {
|
||||
height: 40px;
|
||||
padding: 10px 16px;
|
||||
border-radius: 8px;
|
||||
background: #f9f9fa;
|
||||
font-family: @font-family-bold;
|
||||
font-size: 13px;
|
||||
color: #353a40;
|
||||
text-align: left;
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.role-tab-assign-user .desc-row {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { Tabs } from 'knowdesign';
|
||||
import React, { useState } from 'react';
|
||||
import TypicalListCard from '../../components/TypicalListCard';
|
||||
import UserTabContent from './UserTabContent';
|
||||
import RoleTabContent from './RoleTabContent';
|
||||
import './index.less';
|
||||
|
||||
const { TabPane } = Tabs;
|
||||
|
||||
const UserManage = () => {
|
||||
const [curTabKey, setCurTabKey] = useState<string>(window.history.state?.tab || 'user');
|
||||
|
||||
const onTabChange = (key) => {
|
||||
setCurTabKey(key);
|
||||
window.history.replaceState({ tab: key }, '');
|
||||
};
|
||||
|
||||
return (
|
||||
<TypicalListCard title="用户管理">
|
||||
<Tabs defaultActiveKey={curTabKey} onChange={onTabChange}>
|
||||
<TabPane tab="人员管理" key="user">
|
||||
<UserTabContent curTabKey={curTabKey} />
|
||||
</TabPane>
|
||||
<TabPane tab="角色管理" key="role">
|
||||
<RoleTabContent curTabKey={curTabKey} />
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
</TypicalListCard>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserManage;
|
||||
31
km-console/packages/config-manager-fe/src/pages/index.ts
Normal file
31
km-console/packages/config-manager-fe/src/pages/index.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import Setting from './ConfigManage';
|
||||
import User from './UserManage';
|
||||
import OperationLog from './OperationLog';
|
||||
import HomePage from './HomePage';
|
||||
import CommonConfig from './CommonConfig';
|
||||
|
||||
export const pageRoutes = [
|
||||
{
|
||||
path: '',
|
||||
exact: false,
|
||||
component: HomePage,
|
||||
commonRoute: CommonConfig,
|
||||
children: [
|
||||
{
|
||||
path: 'setting',
|
||||
exact: true,
|
||||
component: Setting,
|
||||
},
|
||||
{
|
||||
path: 'user',
|
||||
exact: true,
|
||||
component: User,
|
||||
},
|
||||
{
|
||||
path: 'operation-log',
|
||||
exact: true,
|
||||
component: OperationLog,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
Reference in New Issue
Block a user