mirror of
https://github.com/whyour/qinglong.git
synced 2025-12-16 09:15:38 +08:00
Add user management frontend interface for admins
Co-authored-by: whyour <22700758+whyour@users.noreply.github.com>
This commit is contained in:
parent
15d94f469b
commit
5c798a0e93
|
|
@ -97,6 +97,7 @@ export default (app: Router) => {
|
||||||
username: authInfo.username,
|
username: authInfo.username,
|
||||||
avatar: authInfo.avatar,
|
avatar: authInfo.avatar,
|
||||||
twoFactorActivated: authInfo.twoFactorActivated,
|
twoFactorActivated: authInfo.twoFactorActivated,
|
||||||
|
role: req.user?.role,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
||||||
|
|
@ -530,5 +530,20 @@
|
||||||
"请输入自定义日志文件夹名称或绝对路径": "Please enter a custom log folder name or absolute path",
|
"请输入自定义日志文件夹名称或绝对路径": "Please enter a custom log folder name or absolute path",
|
||||||
"请输入自定义日志文件夹名称或 /dev/null": "Please enter a custom log folder name or /dev/null",
|
"请输入自定义日志文件夹名称或 /dev/null": "Please enter a custom log folder name or /dev/null",
|
||||||
"日志名称只能包含字母、数字、下划线和连字符": "Log name can only contain letters, numbers, underscores and hyphens",
|
"日志名称只能包含字母、数字、下划线和连字符": "Log name can only contain letters, numbers, underscores and hyphens",
|
||||||
"日志名称不能超过100个字符": "Log name cannot exceed 100 characters"
|
"日志名称不能超过100个字符": "Log name cannot exceed 100 characters",
|
||||||
|
"用户管理": "User Management",
|
||||||
|
"用户名": "Username",
|
||||||
|
"密码": "Password",
|
||||||
|
"角色": "Role",
|
||||||
|
"管理员": "Admin",
|
||||||
|
"普通用户": "User",
|
||||||
|
"启用": "Enabled",
|
||||||
|
"禁用": "Disabled",
|
||||||
|
"创建时间": "Created At",
|
||||||
|
"确认删除选中的用户吗": "Are you sure to delete selected users?",
|
||||||
|
"请输入用户名": "Please enter username",
|
||||||
|
"请输入密码": "Please enter password",
|
||||||
|
"密码长度至少为6位": "Password must be at least 6 characters",
|
||||||
|
"新增用户": "Add User",
|
||||||
|
"编辑用户": "Edit User"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -530,5 +530,20 @@
|
||||||
"请输入自定义日志文件夹名称或绝对路径": "请输入自定义日志文件夹名称或绝对路径",
|
"请输入自定义日志文件夹名称或绝对路径": "请输入自定义日志文件夹名称或绝对路径",
|
||||||
"请输入自定义日志文件夹名称或 /dev/null": "请输入自定义日志文件夹名称或 /dev/null",
|
"请输入自定义日志文件夹名称或 /dev/null": "请输入自定义日志文件夹名称或 /dev/null",
|
||||||
"日志名称只能包含字母、数字、下划线和连字符": "日志名称只能包含字母、数字、下划线和连字符",
|
"日志名称只能包含字母、数字、下划线和连字符": "日志名称只能包含字母、数字、下划线和连字符",
|
||||||
"日志名称不能超过100个字符": "日志名称不能超过100个字符"
|
"日志名称不能超过100个字符": "日志名称不能超过100个字符",
|
||||||
|
"用户管理": "用户管理",
|
||||||
|
"用户名": "用户名",
|
||||||
|
"密码": "密码",
|
||||||
|
"角色": "角色",
|
||||||
|
"管理员": "管理员",
|
||||||
|
"普通用户": "普通用户",
|
||||||
|
"启用": "启用",
|
||||||
|
"禁用": "禁用",
|
||||||
|
"创建时间": "创建时间",
|
||||||
|
"确认删除选中的用户吗": "确认删除选中的用户吗",
|
||||||
|
"请输入用户名": "请输入用户名",
|
||||||
|
"请输入密码": "请输入密码",
|
||||||
|
"密码长度至少为6位": "密码长度至少为6位",
|
||||||
|
"新增用户": "新增用户",
|
||||||
|
"编辑用户": "编辑用户"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ import './index.less';
|
||||||
import useResizeObserver from '@react-hook/resize-observer';
|
import useResizeObserver from '@react-hook/resize-observer';
|
||||||
import SystemLog from './systemLog';
|
import SystemLog from './systemLog';
|
||||||
import Dependence from './dependence';
|
import Dependence from './dependence';
|
||||||
|
import UserManagement from './userManagement';
|
||||||
|
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
const isDemoEnv = window.__ENV__DeployEnv === 'demo';
|
const isDemoEnv = window.__ENV__DeployEnv === 'demo';
|
||||||
|
|
@ -343,6 +344,15 @@ const Setting = () => {
|
||||||
label: intl.get('登录日志'),
|
label: intl.get('登录日志'),
|
||||||
children: <LoginLog height={height} data={loginLogData} />,
|
children: <LoginLog height={height} data={loginLogData} />,
|
||||||
},
|
},
|
||||||
|
...(user?.role === 0 && !isDemoEnv
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
key: 'user-management',
|
||||||
|
label: intl.get('用户管理'),
|
||||||
|
children: <UserManagement height={height} />,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
{
|
{
|
||||||
key: 'dependence',
|
key: 'dependence',
|
||||||
label: intl.get('依赖设置'),
|
label: intl.get('依赖设置'),
|
||||||
|
|
|
||||||
264
src/pages/setting/userManagement.tsx
Normal file
264
src/pages/setting/userManagement.tsx
Normal file
|
|
@ -0,0 +1,264 @@
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Table,
|
||||||
|
Space,
|
||||||
|
Modal,
|
||||||
|
Form,
|
||||||
|
Input,
|
||||||
|
Select,
|
||||||
|
message,
|
||||||
|
Tag,
|
||||||
|
} from 'antd';
|
||||||
|
import {
|
||||||
|
EditOutlined,
|
||||||
|
DeleteOutlined,
|
||||||
|
PlusOutlined,
|
||||||
|
} from '@ant-design/icons';
|
||||||
|
import { request } from '@/utils/http';
|
||||||
|
import config from '@/utils/config';
|
||||||
|
|
||||||
|
const { Option } = Select;
|
||||||
|
|
||||||
|
interface User {
|
||||||
|
id: number;
|
||||||
|
username: string;
|
||||||
|
password?: string;
|
||||||
|
role: number;
|
||||||
|
status: number;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const UserManagement: React.FC<{ height: number }> = ({ height }) => {
|
||||||
|
const [users, setUsers] = useState<User[]>([]);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||||
|
const [editingUser, setEditingUser] = useState<User | null>(null);
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: intl.get('用户名'),
|
||||||
|
dataIndex: 'username',
|
||||||
|
key: 'username',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: intl.get('角色'),
|
||||||
|
dataIndex: 'role',
|
||||||
|
key: 'role',
|
||||||
|
render: (role: number) => (
|
||||||
|
<Tag color={role === 0 ? 'red' : 'blue'}>
|
||||||
|
{role === 0 ? intl.get('管理员') : intl.get('普通用户')}
|
||||||
|
</Tag>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: intl.get('状态'),
|
||||||
|
dataIndex: 'status',
|
||||||
|
key: 'status',
|
||||||
|
render: (status: number) => (
|
||||||
|
<Tag color={status === 0 ? 'green' : 'default'}>
|
||||||
|
{status === 0 ? intl.get('启用') : intl.get('禁用')}
|
||||||
|
</Tag>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: intl.get('创建时间'),
|
||||||
|
dataIndex: 'createdAt',
|
||||||
|
key: 'createdAt',
|
||||||
|
render: (text: string) => text ? new Date(text).toLocaleString() : '-',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: intl.get('操作'),
|
||||||
|
key: 'action',
|
||||||
|
render: (_: any, record: User) => (
|
||||||
|
<Space size="middle">
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
icon={<EditOutlined />}
|
||||||
|
onClick={() => handleEdit(record)}
|
||||||
|
>
|
||||||
|
{intl.get('编辑')}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
danger
|
||||||
|
icon={<DeleteOutlined />}
|
||||||
|
onClick={() => handleDelete([record.id])}
|
||||||
|
>
|
||||||
|
{intl.get('删除')}
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const fetchUsers = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const { code, data } = await request.get(
|
||||||
|
`${config.apiPrefix}user-management`
|
||||||
|
);
|
||||||
|
if (code === 200) {
|
||||||
|
setUsers(data);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
message.error('Failed to fetch users');
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchUsers();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleAdd = () => {
|
||||||
|
setEditingUser(null);
|
||||||
|
form.resetFields();
|
||||||
|
setIsModalVisible(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEdit = (record: User) => {
|
||||||
|
setEditingUser(record);
|
||||||
|
form.setFieldsValue({
|
||||||
|
username: record.username,
|
||||||
|
role: record.role,
|
||||||
|
status: record.status,
|
||||||
|
});
|
||||||
|
setIsModalVisible(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = (ids: number[]) => {
|
||||||
|
Modal.confirm({
|
||||||
|
title: intl.get('确认删除'),
|
||||||
|
content: intl.get('确认删除选中的用户吗'),
|
||||||
|
onOk: async () => {
|
||||||
|
try {
|
||||||
|
const { code, message: msg } = await request.delete(
|
||||||
|
`${config.apiPrefix}user-management`,
|
||||||
|
{ data: ids }
|
||||||
|
);
|
||||||
|
if (code === 200) {
|
||||||
|
message.success(msg || intl.get('删除成功'));
|
||||||
|
fetchUsers();
|
||||||
|
} else {
|
||||||
|
message.error(msg || intl.get('删除失败'));
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
message.error(error.message || intl.get('删除失败'));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
const values = await form.validateFields();
|
||||||
|
|
||||||
|
if (editingUser) {
|
||||||
|
// Update user
|
||||||
|
const { code, message: msg } = await request.put(
|
||||||
|
`${config.apiPrefix}user-management`,
|
||||||
|
{ ...values, id: editingUser.id }
|
||||||
|
);
|
||||||
|
if (code === 200) {
|
||||||
|
message.success(msg || intl.get('更新成功'));
|
||||||
|
setIsModalVisible(false);
|
||||||
|
fetchUsers();
|
||||||
|
} else {
|
||||||
|
message.error(msg || intl.get('更新失败'));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Create user
|
||||||
|
const { code, message: msg } = await request.post(
|
||||||
|
`${config.apiPrefix}user-management`,
|
||||||
|
values
|
||||||
|
);
|
||||||
|
if (code === 200) {
|
||||||
|
message.success(msg || intl.get('创建成功'));
|
||||||
|
setIsModalVisible(false);
|
||||||
|
fetchUsers();
|
||||||
|
} else {
|
||||||
|
message.error(msg || intl.get('创建失败'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
message.error(error.message || intl.get('操作失败'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div style={{ marginBottom: 16 }}>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
icon={<PlusOutlined />}
|
||||||
|
onClick={handleAdd}
|
||||||
|
>
|
||||||
|
{intl.get('新增用户')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<Table
|
||||||
|
columns={columns}
|
||||||
|
dataSource={users}
|
||||||
|
rowKey="id"
|
||||||
|
loading={loading}
|
||||||
|
scroll={{ y: height - 120 }}
|
||||||
|
pagination={false}
|
||||||
|
/>
|
||||||
|
<Modal
|
||||||
|
title={editingUser ? intl.get('编辑用户') : intl.get('新增用户')}
|
||||||
|
open={isModalVisible}
|
||||||
|
onOk={handleSubmit}
|
||||||
|
onCancel={() => setIsModalVisible(false)}
|
||||||
|
>
|
||||||
|
<Form form={form} layout="vertical">
|
||||||
|
<Form.Item
|
||||||
|
name="username"
|
||||||
|
label={intl.get('用户名')}
|
||||||
|
rules={[{ required: true, message: intl.get('请输入用户名') }]}
|
||||||
|
>
|
||||||
|
<Input placeholder={intl.get('请输入用户名')} />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name="password"
|
||||||
|
label={intl.get('密码')}
|
||||||
|
rules={[
|
||||||
|
{ required: !editingUser, message: intl.get('请输入密码') },
|
||||||
|
{ min: 6, message: intl.get('密码长度至少为6位') },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input.Password placeholder={intl.get('请输入密码')} />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name="role"
|
||||||
|
label={intl.get('角色')}
|
||||||
|
rules={[{ required: true, message: intl.get('请选择角色') }]}
|
||||||
|
initialValue={1}
|
||||||
|
>
|
||||||
|
<Select>
|
||||||
|
<Option value={0}>{intl.get('管理员')}</Option>
|
||||||
|
<Option value={1}>{intl.get('普通用户')}</Option>
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name="status"
|
||||||
|
label={intl.get('状态')}
|
||||||
|
rules={[{ required: true, message: intl.get('请选择状态') }]}
|
||||||
|
initialValue={0}
|
||||||
|
>
|
||||||
|
<Select>
|
||||||
|
<Option value={0}>{intl.get('启用')}</Option>
|
||||||
|
<Option value={1}>{intl.get('禁用')}</Option>
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default UserManagement;
|
||||||
Loading…
Reference in New Issue
Block a user