feat(system): 新增角色管理功能

- 添加角色列表、角色详情、新增角色、编辑角色、删除角色等功能
- 实现角色与菜单权限的绑定和管理
- 优化搜索栏和表格样式,提升用户体验
main
Tuzki 5 months ago
parent 9f8cec8233
commit ed5d3dba95
  1. 24
      web/client/api/index.ts
  2. 97
      web/client/api/system/index.ts
  3. 0
      web/components/common/treeCheckAble.tsx
  4. 5
      web/locales/en/common.ts
  5. 5
      web/locales/zh/common.ts
  6. 6
      web/next.config.js
  7. 19
      web/pages/system/organization/index.module.scss
  8. 7
      web/pages/system/organization/index.tsx
  9. 58
      web/pages/system/role/index.module.scss
  10. 510
      web/pages/system/role/index.tsx
  11. 58
      web/pages/system/user/index.module.scss
  12. 510
      web/pages/system/user/index.tsx
  13. 1
      web/types/prompt.ts

@ -6,7 +6,7 @@ import { STORAGE_USERINFO_KEY } from '@/utils/constants/index';
// 定义 API 响应数据的类型
export type ResponseType<T = any> = {
data: T;
err_code: string | null;
err_code: 'E0001' | 'E0002' | 'E0003' | string | null;
err_msg: string | null;
success: boolean;
};
@ -45,6 +45,7 @@ const LONG_TIME_API: string[] = [
'/personal/agent/upload',
];
let authToken = '';
// 请求拦截器,设置请求头和超时时间
ins.interceptors.request.use(request => {
if (typeof window !== 'undefined') {
@ -60,6 +61,27 @@ ins.interceptors.request.use(request => {
return request;
});
// 响应拦截器,处理响应数据
ins.interceptors.response.use(
response => response,
(error: AxiosError<ResponseType>) => {
// 仅处理业务逻辑错误
if (error.response?.data?.err_code === 'E0003') {
// 清理认证信息
localStorage.removeItem(STORAGE_USERINFO_KEY);
// 安全的环境判断(避免服务端渲染报错)
if (typeof window !== 'undefined' && !window.location.pathname.startsWith('/login')) {
// 带原路径的重定向(登录后可跳回)
window.location.href = `/login?redirect=${encodeURIComponent(window.location.pathname)}`;
}
}
// 继续传递错误
return Promise.reject(error);
}
);
// 发送 GET 请求的函数
export const GET = <Params = any, Response = any, D = any>(
url: string,

@ -32,58 +32,45 @@ export const deleteAllOrg = (id: number) => {
};
// export const promptTemplateLoad = (props: PromptTemplateLoadProps) => {
// return POST<PromptTemplateLoadProps, PromptTemplateLoadResponse>(
// `/prompt/template/load?prompt_type=${props.prompt_type}&target=${props.target}`,
// props,
// );
// };
// export const promptResponseVerify = (props: PromptResponseVerifyProps) => {
// return POST<PromptResponseVerifyProps, Record<string, string>>('/prompt/response/verify', props);
// };
// /**
// * 创建prompt
// */
// export const addPrompt = (data: OperatePromptParams) => {
// return POST<OperatePromptParams, []>('/prompt/add', data);
// };
// /**
// * 编辑prompt
// */
// export const updatePrompt = (data: OperatePromptParams) => {
// return POST<OperatePromptParams, []>('/prompt/update', data);
// };
// /**
// * 删除prompt
// */
// export const deletePrompt = (data: OperatePromptParams) => {
// return POST<OperatePromptParams, null>('/prompt/delete', data);
// };
// /**
// * prompt列表
// */
// export const getPromptList = (data: Record<string, any>) => {
// return POST<Record<string, any>, PromptListResponse>(
// `/prompt/query_page?page=${data.page}&page_size=${data.page_size}`,
// data,
// );
// };
// /**
// * LLM测试
// */
// export const llmTest = (data: DebugParams) => {
// return POST<DebugParams, Record<string, any>>('/prompt/template/debug', data);
// };
// /**
// * llm输出验证
// */
// export const llmOutVerify = (data: LlmOutVerifyParams) => {
// return POST<LlmOutVerifyParams, Record<string, any>>('/prompt/response/verify', data);
// };
//角色列表-分页
export const getRoleList = (prpos: any) => {
return GET<any, any>(`/api/role/list`,prpos);
};
//角色列表-用户绑定角色下拉,仅返回状态正常
export const getRoleListAuth = () => {
return GET<any, any>(`/api/role/list_auth`);
};
//获取角色详情
export const getRoleDetail = (id: number) => {
return GET<any, any>(`/api/role/`+id);
};
//新增角色
export const addRole = (prpos:any) => {
return POST<any,[]>('/api/role/create', prpos);
};
//删除角色
export const deleteRole = (id: number) => {
return DELETE<any, any>(`/api/role/delete/`+id);
};
//修改角色状态
export const updateRoleStatus = (prpos:any) => {
// 调用POST函数,发送一个POST请求,请求地址为'/api/dept/create',参数为prpos,返回一个Promise对象
return PUT<any,[]>('/api/role/update/'+prpos.id+'/'+ prpos.status);
};
//修改角色
export const updateRole = (prpos:any) => {
// 调用POST函数,发送一个POST请求,请求地址为'/api/dept/create',参数为prpos,返回一个Promise对象
return PUT<any,[]>('/api/role/update',prpos);
};
//获取菜单树形结构-菜单授权页面使用-仅查询状态正常,可见的菜单
export const getAllMenuTree = () => {
return GET<any, any>(`/api/menu/auth_tree`);
};
//获取角色绑定的菜单列表
export const getRoleMenusId = (id: number) => {
return GET<any, any>(`/api/role/role_menu/`+id);
};
//保存角色菜单的绑定关系-全量更新
export const addRoleAndMenus = (prpos:any) => {
return POST<any,[]>('/api/role/save_role_menu', prpos);
};

@ -367,4 +367,9 @@ export const CommonEn = {
principal:'Principal',
sort:'Sort',
create_time:'Create Time',
role_id:'Role ID',
role_name:'Role Name',
role_identity:'Role Identity',
role_sort:'Role Sort',
menu_permission:'Menu Permission',
} as const;

@ -372,4 +372,9 @@ export const CommonZh: Resources['translation'] = {
principal:'负责人',
sort:'排序',
create_time:'创建时间',
role_id:'角色编号',
role_name:'角色名称',
role_identity:'角色标识',
role_sort:'显示顺序',
menu_permission:'菜单权限',
} as const;

@ -20,6 +20,12 @@ const nextConfig = {
trailingSlash: true,
images: { unoptimized: true },
skipTrailingSlashRedirect: true,
css: {
modules: {
localsConvention: 'dashes', // 修改css modules的类名规则 可以改成驼峰命名 或者 xxx-xxx命名等
generateScopedName: '[name]__[local]___[hash:base64:5]', // 修改css modules的类名规则
},
},
webpack: (config, { isServer }) => {
config.resolve.fallback = { fs: false };
if (!isServer) {

@ -12,4 +12,23 @@
.construc-container table{
display: table;;
}
.search-bar-box{
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 10px;
.search-bar-left,.search-bar-right{
display: flex;
align-items: center;
}
.search-bar-left{
width: 60%;
}
.search-bar-right{
width: 40%;
}
}

@ -59,7 +59,7 @@ const Prompt = () => {
return (
<Popconfirm title='确认删除吗?' onConfirm={async () => await deletePromptRun(record)}>
<Button loading={deleteLoading}>{t('Delete')}</Button>
<Button type="link" danger loading={deleteLoading}>{t('Delete')}</Button>
</Popconfirm>
);
};
@ -104,7 +104,7 @@ const Prompt = () => {
onClick={() => {
handleEdit(record);
}}
type='primary'
type="link"
>
{t('Edit')}
</Button>
@ -234,7 +234,7 @@ const Prompt = () => {
return (
<SystemConstructConstruct>
<div className={`px-6 py-2 ${styles['construc-container']} md:p-6 h-[90vh] overflow-y-auto`}>
<div className={`px-6 py-2 ${styles['construc-container']} h-[90vh] overflow-y-auto`}>
<Form className={`${styles['p-10']} ${styles['color-red ']}`}
onFinish={handleSearch}
layout={'inline'}
@ -353,6 +353,7 @@ const Prompt = () => {
loading={loadingOrgs}
childrenColumnName="children"
defaultExpandAllRows={true}
pagination={false}
// pagination={{
// pageSize: 10,
// total: promptList?.total_count,

@ -0,0 +1,58 @@
.color-red {
color: red
}
.text-center {
text-align: center
}
.p-10 {
padding: 10px
}
.construc-container table {
display: table;
;
}
.searchBarBox {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;
width: 100%;
}
.search-bar-left,
.search-bar-right {
display: flex;
align-items: center;
}
.search-bar-left {
width: 70%;
flex-wrap: wrap;
}
.ant-form-items{
margin-bottom: 5px;
}
.search-bar-right {
width: 30%;
position: relative;
&::after {
content: '';
width: 2px;
height: 90%;
background-color: #eeeeee;
position: absolute;
top: 50%;
transform: translateY(-50%);
left: 0;
}
}

@ -0,0 +1,510 @@
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 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 { 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 [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 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('获取菜单权限失败');
}
};
const handleSaveMenuAuth = async () => {
if (!currentRoleId) return;
try {
const params = {
role_id: currentRoleId,
menu_ids: checkedKeys,
};
const [, res] = await apiInterceptors(addRoleAndMenus(params));
if (res) {
message.success('菜单权限保存成功');
setMenuModalVisible(false);
setCurrentRoleId(null);
setMenuTreeData([]);
setCheckedKeys([]);
} else {
message.error('菜单权限保存失败');
}
} catch (err) {
message.error('菜单权限保存失败');
}
};
const onCheck: TreeProps['onCheck'] = (checkedKeysValue: number[]) => {
setCheckedKeys(checkedKeysValue.map(key => Number(key)));
};
const DeleteBtn: React.FC<{ record: IPrompt; }> = ({ record }) => {
const userInfo = useUser();
const { t } = useTranslation();
const { message } = App.useApp();
// 删除prompt
const { run: deletePromptRun, loading: deleteLoading } = useRequest(
async record => {
await deleteRole(record?.id); // 使用正确的角色删除接口
},
{
manual: true,
onSuccess: async () => {
message.success('删除成功');
await getPrompts();
},
onError: (err: any) => {
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>
);
};
const getColumns = (t: TFunction, handleEdit: (prompt: IPrompt) => void): ColumnsType<IPrompt> => [
{
title: t('role_id'),
dataIndex: 'id',
key: 'id',
width: '8%',
ellipsis: true,
showSorterTooltip: true,
},
{
title: t('role_name'),
dataIndex: 'name',
key: 'name',
// width: '10%',
},
{
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) => (
<Switch
checked={status === 0}
checkedChildren="开启"
unCheckedChildren="关闭"
onChange={(checked) => handleStatusChange(record.id, checked)}
/>
),
}, {
title: t('create_time'),
dataIndex: 'create_time',
width: '20%',
key: 'create_time',
}, {
title: t('Operation'),
dataIndex: 'operate',
key: 'operate',
width: '20%',
render: (_, record) => (
<Space align='center'>
<Button
onClick={() => {
handleEdit(record);
}}
type="link"
>
{t('Edit')}
</Button>
<Button
onClick={() => {
handleMenuAuth(record);
}}
type="link"
>
</Button>
<DeleteBtn record={record} />
</Space>
),
},
];
const handleEditBtn = async (prompt: IPrompt) => {
setModalType('edit');
setEditId(prompt.id);
try {
const [_, data] = await apiInterceptors(getRoleDetail(prompt.id));
if (data) {
addForm.setFieldsValue({
...data,
status: data.status === 0 // 根据接口返回0/1转换Switch状态
});
setModalVisible(true);
}
} catch (err) {
message.error('获取详情失败');
}
};
const handleStatusChange = async (roleId: number, checked: boolean) => {
try {
const [_, res] = await apiInterceptors(
updateRoleStatus({
id: roleId,
status: checked ? 0 : 1 // 根据Switch状态转换
})
);
debugger
if (res) {
message.success('状态更新成功');
await getPrompts();
// 局部更新数据(可选)
} else {
message.error('状态更新失败');
}
} catch (err) {
message.error('状态更新失败');
}
};
const {
run: getPrompts, loading: loadingOrgs
} = 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({
...params,
pageNum: params?.page || 1, // 添加分页参数
pageSize: params?.page_size || 10
})
);
return data;
},
{
manual: true,
onSuccess: data => {
// 移除部门树形结构转换逻辑
setPromptList(data); // 直接使用接口返回数据
setTotal(data.total_count); // 设置总条数
},
},
);
const handleSearch = async () => {
try {
const values = await addForm.validateFields();
const timeRange = values.createTime; // 获取时间范围值
await getPrompts({
...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
});
setPagination(prev => ({
...prev,
pageNum: 1,
page: 1 // 同步新增的page状态
}));
} catch (err) {
console.error('表单验证失败:', err);
}
};
const handleReset = async () => {
addForm.resetFields(); // 清空表单
await getPrompts(); // 重新获取全部数据
};
const [modalVisible, setModalVisible] = useState(false);
const buttonItemLayout =
formLayout === 'inline'
? {
wrapperCol: { span: 14, offset: 6 },
}
: null;
useEffect(() => {
getPrompts();
}, []);
return (
<SystemConstructConstruct>
<div className={`px-6 py-2 ${styles['construc-container']} h-[90vh] overflow-y-auto`}>
<Form className={`${styles['p-10']} ${styles['color-red ']}`}
onFinish={handleSearch}
layout={'inline'}
form={addForm}
initialValues={{ layout: formLayout }}
>
<div className={`${styles['searchBarBox']}`}>
<div className={`${styles['search-bar-left']}`}>
<Form.Item className={`mb-1`} label="角色名称" name="name">
<Input placeholder="请输入角色名称" />
</Form.Item>
<Form.Item className={`mb-1`} label="角色标识" name="code" >
<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>
<Form.Item className={`mb-1`} label="创建时间" name="createTime">
<RangePicker />
</Form.Item>
</div>
<div className={`${styles['search-bar-right']}`}>
<Form.Item {...buttonItemLayout}>
<Button htmlType="submit" type="primary"></Button>
</Form.Item>
<Form.Item {...buttonItemLayout}>
<Button onClick={handleReset} type="primary"></Button>
</Form.Item>
<Form.Item {...buttonItemLayout}>
<Button type="primary" onClick={() => {
setModalType('add');
setModalVisible(true);
addForm.resetFields();
}}></Button>
</Form.Item>
</div>
</div>
</Form>
<Modal
title={modalType === 'add' ? '新增角色' : '编辑角色'}
open={modalVisible}
onCancel={() => {
setModalVisible(false);
addForm.resetFields();
}}
footer={null}
>
<Form
form={addForm}
initialValues={{
status: true // 确保新增时默认开启
}}
labelCol={{ span: 6 }}
wrapperCol={{ span: 16 }}
onFinish={async (values) => {
setAddLoading(true);
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 }); // 使用角色更新接口
} else {
apiCall = addRole(params); // 使用角色新增接口
}
const [_, data] = await apiInterceptors(apiCall);
if (data) {
message.success(modalType === 'edit' ? '编辑成功' : '新增成功');
setModalVisible(false);
addForm.resetFields();
await getPrompts();
setEditId(null);
}
} catch (err) {
console.error('操作失败:', err);
} finally {
setAddLoading(false);
}
}}
>
{/* 角色名称 */}
<Form.Item label="角色名称" name="name" rules={[{ required: true }]}>
<Input placeholder="请输入角色名称" />
</Form.Item>
{/* 角色标识 */}
<Form.Item label="角色标识" name="code" rules={[
{ required: true },
{ pattern: /^[a-z_]+$/, message: '只允许小写字母和下划线' }
]}>
<Input placeholder="请输入唯一标识(如:admin)" />
</Form.Item>
{/* 排序 */}
<Form.Item label="排序" name="sort" rules={[{ required: true, type: 'number' }]}>
<InputNumber min={0} style={{ width: '100%' }} />
</Form.Item>
{/* 备注 */}
<Form.Item label="备注" name="remark">
<Input.TextArea placeholder="请输入角色描述" rows={3} />
</Form.Item>
{/* 状态 */}
{/* <Form.Item label="" name="status" valuePropName="checked">
<Switch
checkedChildren="开启"
unCheckedChildren="关闭"
// defaultChecked={true} // 默认开启状态
// onChange={switchOnChange}
/>
</Form.Item> */}
<Form.Item wrapperCol={{ offset: 8, span: 16 }}>
<Space>
<Button onClick={() => setModalVisible(false)}></Button>
<Button type="primary" htmlType="submit" loading={addLoading}></Button>
</Space>
</Form.Item>
</Form>
</Modal>
<Modal
title="菜单权限"
open={menuModalVisible}
onCancel={() => {
setMenuModalVisible(false);
setCurrentRoleId(null);
setMenuTreeData([]);
setCheckedKeys([]);
}}
footer={[
<Button key="cancel" onClick={() => setMenuModalVisible(false)}>
</Button>,
<Button key="submit" type="primary" onClick={handleSaveMenuAuth}>
</Button>,
]}
>
<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>
</div>
</Modal>
<Table
columns={getColumns(t, handleEditBtn)}
dataSource={promptList?.roles || []} // 改为使用roles字段
rowKey={record => record.id}
loading={loadingOrgs}
// 移除childrenColumnName和defaultExpandAllRows属性
pagination={{
current: pagination.page, // 使用page字段
pageSize: pagination.page_size,
total: total,
onChange: (page, pageSize) => {
setPagination(prev => ({
...prev,
page: page,
page_size: pageSize
}));
getPrompts({
page,
page_size: pageSize
});
}
}}
/>
</div>
</SystemConstructConstruct>
);
};
export default Prompt;

@ -0,0 +1,58 @@
.color-red {
color: red
}
.text-center {
text-align: center
}
.p-10 {
padding: 10px
}
.construc-container table {
display: table;
;
}
.searchBarBox {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;
width: 100%;
}
.search-bar-left,
.search-bar-right {
display: flex;
align-items: center;
}
.search-bar-left {
width: 70%;
flex-wrap: wrap;
}
.ant-form-items{
margin-bottom: 5px;
}
.search-bar-right {
width: 30%;
position: relative;
&::after {
content: '';
width: 2px;
height: 90%;
background-color: #eeeeee;
position: absolute;
top: 50%;
transform: translateY(-50%);
left: 0;
}
}

@ -0,0 +1,510 @@
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 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 { 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 [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 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('获取菜单权限失败');
}
};
const handleSaveMenuAuth = async () => {
if (!currentRoleId) return;
try {
const params = {
role_id: currentRoleId,
menu_ids: checkedKeys,
};
const [, res] = await apiInterceptors(addRoleAndMenus(params));
if (res) {
message.success('菜单权限保存成功');
setMenuModalVisible(false);
setCurrentRoleId(null);
setMenuTreeData([]);
setCheckedKeys([]);
} else {
message.error('菜单权限保存失败');
}
} catch (err) {
message.error('菜单权限保存失败');
}
};
const onCheck: TreeProps['onCheck'] = (checkedKeysValue: number[]) => {
setCheckedKeys(checkedKeysValue.map(key => Number(key)));
};
const DeleteBtn: React.FC<{ record: IPrompt; }> = ({ record }) => {
const userInfo = useUser();
const { t } = useTranslation();
const { message } = App.useApp();
// 删除prompt
const { run: deletePromptRun, loading: deleteLoading } = useRequest(
async record => {
await deleteRole(record?.id); // 使用正确的角色删除接口
},
{
manual: true,
onSuccess: async () => {
message.success('删除成功');
await getPrompts();
},
onError: (err: any) => {
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>
);
};
const getColumns = (t: TFunction, handleEdit: (prompt: IPrompt) => void): ColumnsType<IPrompt> => [
{
title: t('role_id'),
dataIndex: 'id',
key: 'id',
width: '8%',
ellipsis: true,
showSorterTooltip: true,
},
{
title: t('role_name'),
dataIndex: 'name',
key: 'name',
// width: '10%',
},
{
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) => (
<Switch
checked={status === 0}
checkedChildren="开启"
unCheckedChildren="关闭"
onChange={(checked) => handleStatusChange(record.id, checked)}
/>
),
}, {
title: t('create_time'),
dataIndex: 'create_time',
width: '20%',
key: 'create_time',
}, {
title: t('Operation'),
dataIndex: 'operate',
key: 'operate',
width: '20%',
render: (_, record) => (
<Space align='center'>
<Button
onClick={() => {
handleEdit(record);
}}
type="link"
>
{t('Edit')}
</Button>
<Button
onClick={() => {
handleMenuAuth(record);
}}
type="link"
>
</Button>
<DeleteBtn record={record} />
</Space>
),
},
];
const handleEditBtn = async (prompt: IPrompt) => {
setModalType('edit');
setEditId(prompt.id);
try {
const [_, data] = await apiInterceptors(getRoleDetail(prompt.id));
if (data) {
addForm.setFieldsValue({
...data,
status: data.status === 0 // 根据接口返回0/1转换Switch状态
});
setModalVisible(true);
}
} catch (err) {
message.error('获取详情失败');
}
};
const handleStatusChange = async (roleId: number, checked: boolean) => {
try {
const [_, res] = await apiInterceptors(
updateRoleStatus({
id: roleId,
status: checked ? 0 : 1 // 根据Switch状态转换
})
);
debugger
if (res) {
message.success('状态更新成功');
await getPrompts();
// 局部更新数据(可选)
} else {
message.error('状态更新失败');
}
} catch (err) {
message.error('状态更新失败');
}
};
const {
run: getPrompts, loading: loadingOrgs
} = 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({
...params,
pageNum: params?.page || 1, // 添加分页参数
pageSize: params?.page_size || 10
})
);
return data;
},
{
manual: true,
onSuccess: data => {
// 移除部门树形结构转换逻辑
setPromptList(data); // 直接使用接口返回数据
setTotal(data.total_count); // 设置总条数
},
},
);
const handleSearch = async () => {
try {
const values = await addForm.validateFields();
const timeRange = values.createTime; // 获取时间范围值
await getPrompts({
...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
});
setPagination(prev => ({
...prev,
pageNum: 1,
page: 1 // 同步新增的page状态
}));
} catch (err) {
console.error('表单验证失败:', err);
}
};
const handleReset = async () => {
addForm.resetFields(); // 清空表单
await getPrompts(); // 重新获取全部数据
};
const [modalVisible, setModalVisible] = useState(false);
const buttonItemLayout =
formLayout === 'inline'
? {
wrapperCol: { span: 14, offset: 6 },
}
: null;
useEffect(() => {
getPrompts();
}, []);
return (
<SystemConstructConstruct>
<div className={`px-6 py-2 ${styles['construc-container']} h-[90vh] overflow-y-auto`}>
<Form className={`${styles['p-10']} ${styles['color-red ']}`}
onFinish={handleSearch}
layout={'inline'}
form={addForm}
initialValues={{ layout: formLayout }}
>
<div className={`${styles['searchBarBox']}`}>
<div className={`${styles['search-bar-left']}`}>
<Form.Item className={`mb-1`} label="角色名称" name="name">
<Input placeholder="请输入角色名称" />
</Form.Item>
<Form.Item className={`mb-1`} label="角色标识" name="code" >
<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>
<Form.Item className={`mb-1`} label="创建时间" name="createTime">
<RangePicker />
</Form.Item>
</div>
<div className={`${styles['search-bar-right']}`}>
<Form.Item {...buttonItemLayout}>
<Button htmlType="submit" type="primary"></Button>
</Form.Item>
<Form.Item {...buttonItemLayout}>
<Button onClick={handleReset} type="primary"></Button>
</Form.Item>
<Form.Item {...buttonItemLayout}>
<Button type="primary" onClick={() => {
setModalType('add');
setModalVisible(true);
addForm.resetFields();
}}></Button>
</Form.Item>
</div>
</div>
</Form>
<Modal
title={modalType === 'add' ? '新增角色' : '编辑角色'}
open={modalVisible}
onCancel={() => {
setModalVisible(false);
addForm.resetFields();
}}
footer={null}
>
<Form
form={addForm}
initialValues={{
status: true // 确保新增时默认开启
}}
labelCol={{ span: 6 }}
wrapperCol={{ span: 16 }}
onFinish={async (values) => {
setAddLoading(true);
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 }); // 使用角色更新接口
} else {
apiCall = addRole(params); // 使用角色新增接口
}
const [_, data] = await apiInterceptors(apiCall);
if (data) {
message.success(modalType === 'edit' ? '编辑成功' : '新增成功');
setModalVisible(false);
addForm.resetFields();
await getPrompts();
setEditId(null);
}
} catch (err) {
console.error('操作失败:', err);
} finally {
setAddLoading(false);
}
}}
>
{/* 角色名称 */}
<Form.Item label="角色名称" name="name" rules={[{ required: true }]}>
<Input placeholder="请输入角色名称" />
</Form.Item>
{/* 角色标识 */}
<Form.Item label="角色标识" name="code" rules={[
{ required: true },
{ pattern: /^[a-z_]+$/, message: '只允许小写字母和下划线' }
]}>
<Input placeholder="请输入唯一标识(如:admin)" />
</Form.Item>
{/* 排序 */}
<Form.Item label="排序" name="sort" rules={[{ required: true, type: 'number' }]}>
<InputNumber min={0} style={{ width: '100%' }} />
</Form.Item>
{/* 备注 */}
<Form.Item label="备注" name="remark">
<Input.TextArea placeholder="请输入角色描述" rows={3} />
</Form.Item>
{/* 状态 */}
{/* <Form.Item label="" name="status" valuePropName="checked">
<Switch
checkedChildren="开启"
unCheckedChildren="关闭"
// defaultChecked={true} // 默认开启状态
// onChange={switchOnChange}
/>
</Form.Item> */}
<Form.Item wrapperCol={{ offset: 8, span: 16 }}>
<Space>
<Button onClick={() => setModalVisible(false)}></Button>
<Button type="primary" htmlType="submit" loading={addLoading}></Button>
</Space>
</Form.Item>
</Form>
</Modal>
<Modal
title="菜单权限"
open={menuModalVisible}
onCancel={() => {
setMenuModalVisible(false);
setCurrentRoleId(null);
setMenuTreeData([]);
setCheckedKeys([]);
}}
footer={[
<Button key="cancel" onClick={() => setMenuModalVisible(false)}>
</Button>,
<Button key="submit" type="primary" onClick={handleSaveMenuAuth}>
</Button>,
]}
>
<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>
</div>
</Modal>
<Table
columns={getColumns(t, handleEditBtn)}
dataSource={promptList?.roles || []} // 改为使用roles字段
rowKey={record => record.id}
loading={loadingOrgs}
// 移除childrenColumnName和defaultExpandAllRows属性
pagination={{
current: pagination.page, // 使用page字段
pageSize: pagination.page_size,
total: total,
onChange: (page, pageSize) => {
setPagination(prev => ({
...prev,
page: page,
page_size: pageSize
}));
getPrompts({
page,
page_size: pageSize
});
}
}}
/>
</div>
</SystemConstructConstruct>
);
};
export default Prompt;

@ -102,4 +102,5 @@ export interface PromptListResponse {
export interface orginzationResponse {
dept_list: any[];
roles:any[];
}

Loading…
Cancel
Save