feat(user): 实现用户管理功能

- 新增用户管理相关的 API 接口
- 实现用户列表、用户详情、用户新增、用户编辑、用户删除等功能
- 添加用户重置密码和分配角色功能
- 优化用户搜索和分页功能
- 新增用户相关的多语言文案
main
Tuzki 5 months ago
parent ed5d3dba95
commit e1932a717b
  1. 39
      web/client/api/system/index.ts
  2. 79
      web/client/api/tools/interceptors.ts
  3. 4
      web/locales/en/common.ts
  4. 4
      web/locales/zh/common.ts
  5. 2
      web/pages/system/organization/index.tsx
  6. 8
      web/pages/system/role/index.tsx
  7. 655
      web/pages/system/user/index.tsx
  8. 31
      web/types/prompt.ts

@ -73,4 +73,43 @@ export const getRoleMenusId = (id: number) => {
//保存角色菜单的绑定关系-全量更新
export const addRoleAndMenus = (prpos:any) => {
return POST<any,[]>('/api/role/save_role_menu', prpos);
};
//用户管理-分页
export const getUserList = (prpos: any) => {
return GET<any, any>(`/api/user/list`,prpos);
};
//用户管理-详情
export const getUserDetail = (id: number) => {
return GET<any, any>(`/api/user/`+id);
};
//用户管理-新增
export const addUsers = (prpos:any) => {
return POST<any,[]>('/api/user/create', prpos);
};
//用户管理-修改
export const updateUser = (prpos:any) => {
return PUT<any,[]>('/api/user/update', prpos);
};
//用户管理-删除
export const deleteUser = (id: number) => {
return DELETE<any, any>(`/api/user/delete/`+id);
};
//用户管理-修改密码
export const updateUserPassword = (prpos:any) => {
return PUT<any,[]>('/api/user/update-pwd', prpos);
};
//用户管理-修改状态
export const updateUserStatus = (prpos:any) => {
return PUT<any,[]>('/api/user/update/'+prpos.id+'/'+ prpos.status);
};
//用户管理-查询用户角色绑定关系
export const getUserRole = (id: number) => {
return GET<any, any>(`/api/role/user_role/`+id);
};
//用户管理-保存用户角色绑定关系
export const addUserAndRoles = (prpos:any) => {
return POST<any,[]>('/api/role/save_user_role', prpos);
};

@ -1,50 +1,47 @@
import { notification } from 'antd';
import { AxiosError } from 'axios';
import { ApiResponse, FailedTuple, ResponseType, SuccessTuple } from '../';
import { ApiResponse } from '../';
/**
* Response processing
*
* @param promise request
* @param ignoreCodes ignore error codes
* @returns
* API useRequest.onError
*/
export const apiInterceptors = <T = any, D = any>(
export const apiInterceptors = async <T = any, D = any>(
promise: Promise<ApiResponse<T, D>>,
ignoreCodes?: '*' | (number | string)[],
) => {
return promise
.then<SuccessTuple<T, D>>(response => {
const { data } = response;
if (!data) {
throw new Error('Network Error!');
}
if (!data.success) {
if (ignoreCodes === '*' || (data.err_code && ignoreCodes && ignoreCodes.includes(data.err_code))) {
return [null, data.data, data, response];
} else {
notification.error({
message: `Request error`,
description: data?.err_msg ?? 'The interface is abnormal. Please try again later',
});
}
}
return [null, data.data, data, response];
})
.catch<FailedTuple<T, D>>((err: Error | AxiosError<T, D>) => {
let errMessage = err.message;
if (err instanceof AxiosError) {
try {
const { err_msg } = JSON.parse(err.request.response) as ResponseType<null>;
err_msg && (errMessage = err_msg);
} catch {
/* empty */
}
}
): Promise<[null, T] | [Error, null]> => {
try {
const response = await promise;
const { data } = response;
if (!data.success) {
const error = new Error(data.err_msg || '未知错误');
(error as any).code = data.err_code;
(error as any).raw = data;
notification.error({
message: `Request error`,
description: errMessage,
message: '请求失败',
description: data.err_msg || '接口异常,请重试',
});
return [err, null, null, null];
return [error, null];
}
return [null, data.data];
} catch (err: any) {
let error: Error;
if (err.isAxiosError) {
error = new Error('网络请求异常');
} else {
error = err;
}
const errMsg = err instanceof AxiosError
? JSON.parse(err.request.response)?.err_msg ?? '接口异常'
: err.message;
notification.error({
message: '请求异常',
description: errMsg,
});
};
return [error, null];
}
};

@ -372,4 +372,8 @@ export const CommonEn = {
role_identity:'Role Identity',
role_sort:'Role Sort',
menu_permission:'Menu Permission',
user_id:'userId',
user_name:'userName',
user_nickname:'nickName',
user_phone:'phone',
} as const;

@ -377,4 +377,8 @@ export const CommonZh: Resources['translation'] = {
role_identity:'角色标识',
role_sort:'显示顺序',
menu_permission:'菜单权限',
user_id:'用户编号',
user_name:'用户名称',
user_nickname:'用户昵称',
user_phone:'手机号码',
} as const;

@ -59,7 +59,7 @@ const Prompt = () => {
return (
<Popconfirm title='确认删除吗?' onConfirm={async () => await deletePromptRun(record)}>
<Button type="link" danger loading={deleteLoading}>{t('Delete')}</Button>
<Button type="link" size="small" danger loading={deleteLoading}>{t('Delete')}</Button>
</Popconfirm>
);
};

@ -50,7 +50,7 @@ const Prompt = () => {
const [, allMenusRes] = await apiInterceptors(getAllMenuTree());
const treeData = allMenusRes || [];
debugger
setMenuTreeData(treeData);
setCheckedKeys(menuIds);
setUserCode(roleId.code);
@ -117,7 +117,7 @@ const Prompt = () => {
return (
<Popconfirm title='确认删除吗?' onConfirm={async () => await deletePromptRun(record)}>
<Button type="link" danger loading={deleteLoading}>{t('Delete')}</Button>
<Button type="link" size="small" danger loading={deleteLoading}>{t('Delete')}</Button>
</Popconfirm>
);
};
@ -221,7 +221,7 @@ const Prompt = () => {
status: checked ? 0 : 1 // 根据Switch状态转换
})
);
debugger
if (res) {
message.success('状态更新成功');
await getPrompts();
@ -373,7 +373,7 @@ const Prompt = () => {
status:0 , // 根据Switch值转换状态
remark: values.remark
};
debugger
let apiCall;
if (modalType === 'edit' && editId) {
apiCall = updateRole({ ...params, id: editId }); // 使用角色更新接口

@ -1,129 +1,166 @@
import { Space, Col, Row, Switch, Input, Table, Form, Button, Select, Popconfirm, App, Modal, InputNumber, TreeSelect, DatePicker, Tree, TreeProps } from 'antd';
import { apiInterceptors, getRoleList, getRoleDetail, addRole, deleteRole, updateRoleStatus, updateRole, getAllMenuTree, getRoleMenusId, addRoleAndMenus } from '@/client/api';
import { Space, Switch, Input, Table, Form, Button, Select, Popconfirm, App, Modal, InputNumber, TreeSelect, DatePicker, Tree, TreeProps } from 'antd';
import { apiInterceptors, getAllOrg, getRoleListAuth, getUserList, getUserDetail, addUsers, updateUser, deleteUser, updateUserPassword, updateUserStatus, getUserRole, addUserAndRoles } from '@/client/api';
import useUser from '@/hooks/use-user';
import SystemConstructConstruct from '@/new-components/layout/SystemConstruct';
import styles from './index.module.scss';
import type { ColumnsType } from 'antd/es/table';
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { IPrompt, orginzationResponse } from '@/types/prompt';
import { orginzationResponse, IUser, IOrganization } from '@/types/prompt';
import { useRequest } from 'ahooks';
import { TFunction } from 'i18next';
import dayjs from 'dayjs';
import 'dayjs/locale/zh-cn';
dayjs.locale('zh-cn');
const { Option } = Select;
const { RangePicker } = DatePicker;
type LayoutType = Parameters<typeof Form>[0]['layout'];
const Prompt = () => {
const [editId, setEditId] = useState<number | null>(null);
const [modalType, setModalType] = useState<'add' | 'edit'>('add');
const [addForm] = Form.useForm();
const { message } = App.useApp();
const [addLoading, setAddLoading] = useState(false);
const { t } = useTranslation();
const [formLayout] = useState<LayoutType>('inline');
const [promptList, setPromptList] = useState<orginzationResponse>();
const [modalVisible, setModalVisible] = useState(false);
const [modalType, setModalType] = useState<'add' | 'edit'>('add');
const [currentEditId, setCurrentEditId] = useState<number | null>(null);
const addForm = Form.useForm()[0];
const [resetPasswordVisible, setResetPasswordVisible] = useState(false);
const [currentUserId, setCurrentUserId] = useState<number | null>(null);
const resetPasswordForm = Form.useForm()[0];
const [pagination, setPagination] = useState({
page: 1, // 改为page
page_size: 10 // 改为page_size
});
const [total, setTotal] = useState(0);
const [menuModalVisible, setMenuModalVisible] = useState(false);
const [currentRoleId, setCurrentRoleId] = useState<number | null>(null);
const [menuTreeData, setMenuTreeData] = useState<any[]>([]);
const [checkedKeys, setCheckedKeys] = useState<number[]>([]);
const [roleName, setUserName] = useState<string[]>([]);
const [roleCode, setUserCode] = useState<string[]>([]);
const [deptTreeData, setDeptTreeData] = useState<IOrganization[]>([]);
// 分配角色弹窗相关状态
const [assignRoleVisible, setAssignRoleVisible] = useState(false);
const [currentUserIdForRole, setCurrentUserIdForRole] = useState<number | null>(null);
const [currentUserNames, setCurrentUserNames] = useState<{ username: string; nickName: string }>({
username: '',
nickName: '',
});
const [roleList, setRoleList] = useState<{ id: number; name: string }[]>([]);
const [selectedRoles, setSelectedRoles] = useState<number[]>([]);
const [loadingRoles, setLoadingRoles] = useState(false);
const buildTree = (data: IOrganization[]): IOrganization[] => {
const map = new Map<number, IOrganization>();
const tree: IOrganization[] = [];
const handleMenuAuth = async (roleId: any) => {
setCurrentRoleId(roleId.id);
try {
const [, roleMenusRes] = await apiInterceptors(getRoleMenusId(roleId.id));
const menuIds = roleMenusRes?.menu_ids || [];
const [, allMenusRes] = await apiInterceptors(getAllMenuTree());
const treeData = allMenusRes || [];
debugger
setMenuTreeData(treeData);
setCheckedKeys(menuIds);
setUserCode(roleId.code);
setUserName(roleId.name);
setMenuModalVisible(true);
} catch (err) {
message.error('获取菜单权限失败');
}
data.forEach(item => {
map.set(item.id, { ...item });
});
data.forEach(item => {
const node = map.get(item.id)!;
if (item.parent_id !== 0) {
const parent = map.get(item.parent_id);
if (parent) {
// 只在需要时初始化children数组
parent.children = parent.children || [];
parent.children.push(node);
} else {
tree.push(node);
}
} else {
tree.push(node);
}
});
// 后处理:移除空children
const removeEmptyChildren = (nodes: IOrganization[]): IOrganization[] => {
return nodes.map(node => ({
...node,
children: node.children?.length ? removeEmptyChildren(node.children) : undefined
}));
};
return removeEmptyChildren(tree);
};
// 获取并构建树
const { data: orgData } = useRequest(getAllOrg);
useEffect(() => {
if (orgData?.data?.data?.dept_list) {
const treeData = buildTree(orgData.data.data.dept_list); // 构建层级树
const formatted = treeData.map(item => ({
...item,
children: item.children ? formatToTreeSelect(item.children) : [],
}));
setDeptTreeData(formatted);
}
}, [orgData]);
const handleSaveMenuAuth = async () => {
if (!currentRoleId) return;
const formatToTreeSelect = (list: IOrganization[]) => {
return list.map(item => ({
...item,
children: item.children ? formatToTreeSelect(item.children) : [],
}));
};
const assignRoles = async (user: IUser) => {
setCurrentUserIdForRole(user.id);
setCurrentUserNames({
username: user.username,
nickName: user.nick_name,
});
try {
const params = {
role_id: currentRoleId,
menu_ids: checkedKeys,
};
const [, res] = await apiInterceptors(addRoleAndMenus(params));
// 加载所有可用角色
setLoadingRoles(true);
const [err, roleData] = await apiInterceptors(getRoleListAuth());
debugger;
if (!err && roleData) {
setRoleList(roleData || []);
}
setLoadingRoles(false);
if (res) {
message.success('菜单权限保存成功');
setMenuModalVisible(false);
setCurrentRoleId(null);
setMenuTreeData([]);
setCheckedKeys([]);
} else {
message.error('菜单权限保存失败');
}
} catch (err) {
message.error('菜单权限保存失败');
// 加载当前用户已绑定的角色
const [userErr, userData] = await apiInterceptors(getUserRole(user.id));
if (!userErr && userData?.role_ids) {
setSelectedRoles(userData.role_ids);
}
setAssignRoleVisible(true);
};
const onCheck: TreeProps['onCheck'] = (checkedKeysValue: number[]) => {
setCheckedKeys(checkedKeysValue.map(key => Number(key)));
const handleResetPassword = (user: IUser) => {
setCurrentUserId(user.id);
setResetPasswordVisible(true);
resetPasswordForm.resetFields();
};
const DeleteBtn: React.FC<{ record: IPrompt; }> = ({ record }) => {
const userInfo = useUser();
const { t } = useTranslation();
const DeleteBtn: React.FC<{ record: IUser }> = ({ record }) => {
const { message } = App.useApp();
// 删除prompt
const { run: deletePromptRun, loading: deleteLoading } = useRequest(
async record => {
await deleteRole(record?.id); // 使用正确的角色删除接口
const { run: deleteUserRun, loading: deleteLoading } = useRequest(
async () => {
await deleteUser(record.id);
},
{
manual: true,
onSuccess: async () => {
onSuccess: async (res) => {
console.log(res);
message.success('删除成功');
await getPrompts();
await getUsers(); // 刷新列表
},
onError: (err: any) => {
message.error(err?.message);
message.error(err.message || '删除失败');
},
},
}
);
// if (userInfo?.id !== record?.id) {
// return null;
// }
return (
<Popconfirm title='确认删除吗?' onConfirm={async () => await deletePromptRun(record)}>
<Button type="link" danger loading={deleteLoading}>{t('Delete')}</Button>
<Popconfirm title="确认删除吗?" onConfirm={async () => await deleteUserRun()}>
<Button type="link" size="small" danger loading={deleteLoading}>
</Button>
</Popconfirm>
);
};
const getColumns = (t: TFunction, handleEdit: (prompt: IPrompt) => void): ColumnsType<IPrompt> => [
const getColumns = (t: TFunction, handleEdit: (user: IUser) => void): ColumnsType<IUser> => [
{
title: t('role_id'),
title: t('user_id'),
dataIndex: 'id',
key: 'id',
width: '8%',
@ -131,101 +168,102 @@ const Prompt = () => {
showSorterTooltip: true,
},
{
title: t('role_name'),
dataIndex: 'name',
key: 'name',
// width: '10%',
title: t('user_name'),
dataIndex: 'username',
key: 'username',
},
{
title: t('user_nickname'),
dataIndex: 'nick_name',
key: 'nick_name',
},
{
title: t('department_name'),
dataIndex: 'dept_name',
key: 'dept_name',
render: (dept_name: string) => {
// 可以通过 deptId 查询部门名称(如果后续需要),这里先显示 ID
return dept_name || '-';
},
},
{
title: t('user_phone'),
dataIndex: 'phone',
key: 'phone',
render: (phone: string | null) => phone || '-',
},
{
title: t('role_identity'),
dataIndex: 'code',
// width: '10%',
key: 'code',
}, {
title: t('sort'),
dataIndex: 'sort',
// width: '10%',
key: 'sort',
}, {
title: t('Remark'),
dataIndex: 'remark',
// width: '10%',
key: 'remark',
}, {
title: t('Status'),
dataIndex: 'status',
key: 'status',
render: (status: number, record: IPrompt) => (
render: (status: string, record) => (
<Switch
checked={status === 0}
checked={status === '0'}
checkedChildren="开启"
unCheckedChildren="关闭"
onChange={(checked) => handleStatusChange(record.id, checked)}
/>
),
}, {
},
{
title: t('create_time'),
dataIndex: 'create_time',
width: '20%',
key: 'create_time',
}, {
width: '13%',
render: (time: string) => dayjs(time).format('YYYY-MM-DD HH:mm:ss'),
},
{
title: t('Operation'),
dataIndex: 'operate',
key: 'operate',
width: '20%',
render: (_, record) => (
<Space align='center'>
<Button
onClick={() => {
handleEdit(record);
}}
type="link"
>
<Button type="link" size="small" onClick={() => handleEdit(record)}>
{t('Edit')}
</Button>
<Button
onClick={() => {
handleMenuAuth(record);
}}
type="link"
>
<Button type="link" size="small" style={{ color: 'orange' }} onClick={() => handleResetPassword(record)}>
</Button>
<Button type="link" size="small" onClick={() => assignRoles(record)}>
</Button>
<DeleteBtn record={record} />
</Space>
),
},
];
const handleEditBtn = async (prompt: IPrompt) => {
const handleEditBtn = async (record: IUser) => {
setModalType('edit');
setEditId(prompt.id);
setCurrentEditId(record.id);
try {
const [_, data] = await apiInterceptors(getRoleDetail(prompt.id));
const [_, data] = await apiInterceptors(getUserDetail(record.id));
debugger;
if (data) {
addForm.setFieldsValue({
...data,
status: data.status === 0 // 根据接口返回0/1转换Switch状态
status: data.status === '0',
});
setModalVisible(true);
}
} catch (err) {
message.error('获取详情失败');
message.error('获取用户详情失败');
}
};
const handleStatusChange = async (roleId: number, checked: boolean) => {
const handleStatusChange = async (userId: number, checked: boolean) => {
try {
const [_, res] = await apiInterceptors(
updateRoleStatus({
id: roleId,
status: checked ? 0 : 1 // 根据Switch状态转换
updateUserStatus({
id: userId,
status: checked ? '0' : '1',
})
);
debugger
if (res) {
message.success('状态更新成功');
await getPrompts();
// 局部更新数据(可选)
await getUsers(); // 刷新列表
} else {
message.error('状态更新失败');
}
@ -234,62 +272,55 @@ const Prompt = () => {
}
};
const {
run: getPrompts, loading: loadingOrgs
run: getUsers,
loading: loadingUsers,
} = useRequest(
async (params?: {
name?: string;
status?: string;
code?: string;
create_time_start?: string;
create_time_end?: string;
page?: number;
page_size?: number;
}) => {
const [_, data] = await apiInterceptors(
getRoleList({
getUserList({
...params,
pageNum: params?.page || 1, // 添加分页参数
pageSize: params?.page_size || 10
page: params?.page || 1,
page_size: params?.page_size || 10,
})
);
return data;
},
{
manual: true,
onSuccess: data => {
// 移除部门树形结构转换逻辑
setPromptList(data); // 直接使用接口返回数据
setTotal(data.total_count); // 设置总条数
onSuccess: (data) => {
setPromptList(data);
setTotal(data.total_count);
},
},
}
);
const handleSearch = async () => {
try {
const values = await addForm.validateFields();
const timeRange = values.createTime; // 获取时间范围值
const timeRange = values.createTime;
await getPrompts({
await getUsers({
...values,
create_time_start: timeRange?.[0]?.format('YYYY-MM-DD'), // 转换开始时间
create_time_end: timeRange?.[1]?.format('YYYY-MM-DD'), // 转换结束时间
page: 1, // 使用page参数名
page_size: pagination.page_size
create_time_start: timeRange?.[0]?.format('YYYY-MM-DD'),
create_time_end: timeRange?.[1]?.format('YYYY-MM-DD'),
page: 1,
page_size: pagination.page_size,
});
setPagination(prev => ({
...prev,
pageNum: 1,
page: 1 // 同步新增的page状态
}));
setPagination(prev => ({ ...prev, page: 1 }));
} catch (err) {
console.error('表单验证失败:', err);
}
};
const handleReset = async () => {
addForm.resetFields(); // 清空表单
await getPrompts(); // 重新获取全部数据
await getUsers(); // 重新获取全部数据
};
const [modalVisible, setModalVisible] = useState(false);
const buttonItemLayout =
formLayout === 'inline'
@ -299,7 +330,7 @@ const Prompt = () => {
: null;
useEffect(() => {
getPrompts();
getUsers();
}, []);
return (
@ -313,20 +344,24 @@ const Prompt = () => {
>
<div className={`${styles['searchBarBox']}`}>
<div className={`${styles['search-bar-left']}`}>
<Form.Item className={`mb-1`} label="角色名称" name="name">
<Input placeholder="请输入角色名称" />
<Form.Item className={`mb-1`} label="用户名称" name="username">
<Input placeholder="请输入用户名称" />
</Form.Item>
<Form.Item className={`mb-1`} label="角色标识" name="code" >
<Input placeholder="请输入角色标识" />
<Form.Item className={`mb-1`} label="用户昵称" name="nick_name" >
<Input placeholder="请输入用户昵称" />
</Form.Item>
<Form.Item className={`mb-1`} label="角色状态" name="status">
<Select style={{ width: 100 }} placeholder="请选择角色状态">
<Option value="0"></Option>
<Option value="1"></Option>
</Select>
<Form.Item className={`mb-1`} label="手机号码" name="phone" >
<Input placeholder="请输入手机号码" />
</Form.Item>
<Form.Item className={`mb-1`} label="创建时间" name="createTime">
<RangePicker />
<Form.Item className={`mb-1`} label="组织部门" name="dept_id">
<TreeSelect
showSearch
placeholder="请选择部门"
allowClear
treeDefaultExpandAll
fieldNames={{ value: 'id', label: 'name', children: 'children' }}
treeData={deptTreeData}
/>
</Form.Item>
</div>
<div className={`${styles['search-bar-right']}`}>
@ -346,8 +381,9 @@ const Prompt = () => {
</div>
</div>
</Form>
<Modal
title={modalType === 'add' ? '新增角色' : '编辑角色'}
title={modalType === 'add' ? '新增用户' : '编辑用户'}
open={modalVisible}
onCancel={() => {
setModalVisible(false);
@ -357,149 +393,260 @@ const Prompt = () => {
>
<Form
form={addForm}
initialValues={{
status: true // 确保新增时默认开启
}}
labelCol={{ span: 6 }}
wrapperCol={{ span: 16 }}
onFinish={async (values) => {
setAddLoading(true);
const params = {
...values,
status: '0', // Switch 值转换
};
try {
const params = {
name: values.name,
code: values.code,
sort: values.sort,
// status: values.status ? 0 : 1, // 根据Switch值转换状态
status:0 , // 根据Switch值转换状态
remark: values.remark
};
debugger
let apiCall;
if (modalType === 'edit' && editId) {
apiCall = updateRole({ ...params, id: editId }); // 使用角色更新接口
if (modalType === 'edit' && currentEditId) {
apiCall = updateUser({ ...params, id: currentEditId });
} else {
apiCall = addRole(params); // 使用角色新增接口
apiCall = addUsers(params);
}
const [_, data] = await apiInterceptors(apiCall);
if (data) {
message.success(modalType === 'edit' ? '编辑成功' : '新增成功');
setModalVisible(false);
addForm.resetFields();
await getPrompts();
setEditId(null);
await getUsers(); // 刷新列表
} else {
message.error(data.err_msg || '操作失败');
}
} catch (err) {
console.error('操作失败:', err);
message.error('操作失败');
} finally {
setAddLoading(false);
}
}}
>
{/* 角色名称 */}
<Form.Item label="角色名称" name="name" rules={[{ required: true }]}>
<Input placeholder="请输入角色名称" />
{/* 用户名 */}
<Form.Item label="账号" name="username" rules={[{ required: true }]}>
<Input placeholder="请输入账号" />
</Form.Item>
{/* 角色标识 */}
<Form.Item label="角色标识" name="code" rules={[
{ required: true },
{ pattern: /^[a-z_]+$/, message: '只允许小写字母和下划线' }
]}>
<Input placeholder="请输入唯一标识(如:admin)" />
{/* 昵称 */}
<Form.Item label="昵称" name="nick_name" rules={[{ required: true }]}>
<Input placeholder="请输入昵称" />
</Form.Item>
{/* 排序 */}
<Form.Item label="排序" name="sort" rules={[{ required: true, type: 'number' }]}>
<InputNumber min={0} style={{ width: '100%' }} />
{/* 密码(仅新增时显示) */}
{modalType === 'add' && (
<Form.Item label="密码" name="password" rules={[{ required: true }]}>
<Input.Password placeholder="请输入密码" />
</Form.Item>
)}
{/* 手机号码 */}
<Form.Item label="手机号码" name="phone" rules={[{ required: true }]}>
<Input placeholder="请输入手机号码" />
</Form.Item>
{/* 备注 */}
<Form.Item label="备注" name="remark">
<Input.TextArea placeholder="请输入角色描述" rows={3} />
{/* 部门选择 */}
<Form.Item label="所属部门" name="dept_id" rules={[{ required: true }]}>
<TreeSelect
showSearch
placeholder="请选择部门"
allowClear
treeDefaultExpandAll
fieldNames={{ value: 'id', label: 'name', children: 'children' }}
treeData={deptTreeData}
/>
</Form.Item>
{/* 性别 */}
<Form.Item label="性别" name="sex">
<Select placeholder="请选择性别">
<Option value="0"></Option>
<Option value="1"></Option>
<Option value="2"></Option>
</Select>
</Form.Item>
{/* 状态 */}
{/* <Form.Item label="" name="status" valuePropName="checked">
<Switch
checkedChildren="开启"
unCheckedChildren="关闭"
// defaultChecked={true} // 默认开启状态
// onChange={switchOnChange}
/>
{/* <Form.Item label="" name="status" valuePropName="checked" initialValue={true}>
<Switch checkedChildren="开启" unCheckedChildren="关闭" />
</Form.Item> */}
<Form.Item wrapperCol={{ offset: 8, span: 16 }}>
{/* 邮箱 */}
<Form.Item label="邮箱" name="email">
<Input placeholder="请输入邮箱" />
</Form.Item>
{/* 备注 */}
<Form.Item label="备注" name="remark">
<Input.TextArea rows={3} placeholder="请输入备注信息" />
</Form.Item>
{/* 按钮 */}
<Form.Item wrapperCol={{ offset: 6, span: 16 }}>
<Space>
<Button onClick={() => setModalVisible(false)}></Button>
<Button type="primary" htmlType="submit" loading={addLoading}></Button>
<Button type="primary" htmlType="submit" loading={addLoading}>
</Button>
</Space>
</Form.Item>
</Form>
</Modal>
<Modal
title="菜单权限"
open={menuModalVisible}
title="重置密码"
open={resetPasswordVisible}
onCancel={() => {
setMenuModalVisible(false);
setCurrentRoleId(null);
setMenuTreeData([]);
setCheckedKeys([]);
setResetPasswordVisible(false);
resetPasswordForm.resetFields();
}}
footer={[
<Button key="cancel" onClick={() => setMenuModalVisible(false)}>
</Button>,
<Button key="submit" type="primary" onClick={handleSaveMenuAuth}>
</Button>,
]}
footer={null}
>
<div style={{ maxHeight: '400px', overflowY: 'auto' }}>
<div className='mb-1 flex'>
<div>:&nbsp;&nbsp;</div>
<div>{roleName}</div>
</div>
<div className='mb-1 flex '>
<div>:&nbsp;&nbsp;</div>
<div>{roleCode}</div>
</div>
<div className='mb-1 flex'>
<div>:&nbsp;&nbsp;</div>
<div>
<Tree
checkable
fieldNames={{ title: 'name', key: 'id' }}
treeData={menuTreeData}
checkedKeys={checkedKeys}
onCheck={onCheck}
/>
</div>
</div>
<Form
form={resetPasswordForm}
onFinish={async (values) => {
if (!currentUserId) return;
</div>
try {
const [err, data] = await apiInterceptors(
updateUserPassword({
id: currentUserId,
password: values.password,
})
);
if (!err && data) {
message.success('密码重置成功');
setResetPasswordVisible(false);
} else {
message.error(data?.err_msg || '密码重置失败');
}
} catch (e) {
message.error('请求失败,请重试');
}
}}
>
<Form.Item
label="请输入新密码"
name="password"
rules={[
{ required: true, message: '请输入新密码' },
{ min: 6, message: '密码长度不能小于6位' },
]}
>
<Input.Password placeholder="请输入新密码" />
</Form.Item>
<Form.Item wrapperCol={{ offset: 6, span: 16 }}>
<Space>
<Button onClick={() => setResetPasswordVisible(false)}></Button>
<Button type="primary" htmlType="submit">
</Button>
</Space>
</Form.Item>
</Form>
</Modal>
<Modal
title="分配角色"
open={assignRoleVisible}
onCancel={() => {
setAssignRoleVisible(false);
setSelectedRoles([]); // 清空选中角色
setCurrentUserIdForRole(null); // 可选:清空用户 ID
setCurrentUserNames({ username: '', nickName: '' }); // 可选:清空用户名
}}
footer={null}
>
<Form layout="vertical">
<Form.Item label="用户名">
<Input value={currentUserNames.username} disabled />
</Form.Item>
<Form.Item label="用户昵称">
<Input value={currentUserNames.nickName} disabled />
</Form.Item>
<Form.Item label="选择角色">
<Select
mode="multiple"
loading={loadingRoles}
value={selectedRoles}
onChange={(values) => setSelectedRoles(values as number[])}
options={roleList.map((role) => ({
label: role.name,
value: role.id,
}))}
placeholder="请选择角色"
showSearch
filterOption={(input, option) =>
option?.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
/>
</Form.Item>
<Form.Item wrapperCol={{ span: 16, offset: 8 }}>
<Space>
<Button
onClick={() => {
setAssignRoleVisible(false);
setSelectedRoles([]);
setCurrentUserIdForRole(null);
setCurrentUserNames({ username: '', nickName: '' });
setRoleList([]);
}}
>
</Button>
<Button
type="primary"
onClick={async () => {
if (!currentUserIdForRole) return;
try {
const [err, res] = await apiInterceptors(
addUserAndRoles({
user_id: currentUserIdForRole,
role_ids: selectedRoles,
})
);
if (!err && res) {
message.success('角色分配成功');
setAssignRoleVisible(false);
setSelectedRoles([]); // 关闭前清空
setCurrentUserIdForRole(null);
setCurrentUserNames({ username: '', nickName: '' });
setRoleList([]);
} else {
message.error(res?.err_msg || '角色分配失败');
}
} catch (e) {
message.error('请求失败,请重试');
}
}}
>
</Button>
</Space>
</Form.Item>
</Form>
</Modal>
<Table
columns={getColumns(t, handleEditBtn)}
dataSource={promptList?.roles || []} // 改为使用roles字段
rowKey={record => record.id}
loading={loadingOrgs}
// 移除childrenColumnName和defaultExpandAllRows属性
dataSource={promptList?.user_list || []}
rowKey="id"
loading={loadingUsers}
pagination={{
current: pagination.page, // 使用page字段
current: pagination.page,
pageSize: pagination.page_size,
total: total,
onChange: (page, pageSize) => {
setPagination(prev => ({
...prev,
page: page,
page_size: pageSize
}));
getPrompts({
page,
page_size: pageSize
});
}
setPagination({ page, page_size: pageSize });
getUsers({ page, page_size: pageSize });
},
}}
/>
</div>

@ -102,5 +102,36 @@ export interface PromptListResponse {
export interface orginzationResponse {
dept_list: any[];
user_list:any[];
roles:any[];
}
export interface IUser {
id: number;
username: string;
nick_name: string;
sex: string;
phone: string | null;
dept_id: number;
status: string;
avatar: string | null;
email: string | null;
remark: string;
create_time: string;
creator: string | null;
post_ids: number[] | null;
}
export interface UserListResponse {
total_count: number;
total_page: number;
current_page: number;
user_list: IUser[];
}
export interface IOrganization {
id: number;
name: string;
parent_id: number;
children?: IOrganization[];
}
Loading…
Cancel
Save