@@ -1,5 +1,6 @@ | |||||
export * from './user.data.ts' | export * from './user.data.ts' | ||||
export * from './product.data.ts' | export * from './product.data.ts' | ||||
export * from './role.data.ts' | |||||
export interface ResponseDTO<T>{ | export interface ResponseDTO<T>{ | ||||
code: number; | code: number; | ||||
@@ -8,7 +9,7 @@ export interface ResponseDTO<T>{ | |||||
} | } | ||||
export interface PageData<T> { | export interface PageData<T> { | ||||
data: T[]; | |||||
list: T[]; | |||||
total: number; | total: number; | ||||
} | } | ||||
@@ -0,0 +1,21 @@ | |||||
export interface RoleVO { | |||||
id: number | |||||
name: string | |||||
code: string | |||||
sort: number | |||||
status: number | |||||
type: number | |||||
createTime: Date | |||||
} | |||||
export interface RolePageReqVO extends PageParam { | |||||
name?: string | |||||
code?: string | |||||
status?: number | |||||
createTime?: Date[] | |||||
} | |||||
export interface UpdateStatusReqVO { | |||||
id: number | |||||
status: number | |||||
} |
@@ -73,13 +73,14 @@ const MenuListPage: React.FC = () => { | |||||
title: '菜单名称', | title: '菜单名称', | ||||
dataIndex: 'name', | dataIndex: 'name', | ||||
key: 'name', | key: 'name', | ||||
align: 'center', | |||||
align: 'right', | |||||
width: 200, | width: 200, | ||||
}, | }, | ||||
{ | { | ||||
title: '菜单类型', | title: '菜单类型', | ||||
dataIndex: 'type', | dataIndex: 'type', | ||||
key: 'type', | key: 'type', | ||||
align: 'right', | |||||
width: 100, | width: 100, | ||||
render: (value: number, record, index) => { | render: (value: number, record, index) => { | ||||
return (value === 1 ? <Tag color="purple">目录</Tag> : (value === 2 ? <Tag color="blue">菜单</Tag> : <Tag color="green">按钮</Tag>)) | return (value === 1 ? <Tag color="purple">目录</Tag> : (value === 2 ? <Tag color="blue">菜单</Tag> : <Tag color="green">按钮</Tag>)) | ||||
@@ -1,9 +1,214 @@ | |||||
import { Empty } from 'antd'; | |||||
import React, { useState, useEffect } from 'react'; | |||||
import { Space, Table, Button, Input, Select, Divider, Tag, Card, Badge, Form } from 'antd'; | |||||
import type { TableColumnsType } from 'antd'; | |||||
import { t } from '@/utils/i18n'; | |||||
import { PlusOutlined, ExclamationCircleFilled, SearchOutlined, UndoOutlined } from '@ant-design/icons'; | |||||
import { antdUtils } from '@/utils/antd'; | |||||
import { useRequest } from '@/hooks/use-request'; | |||||
import { formatDate } from '@/utils/formatTime' | |||||
import roleService from '@/request/service/role'; | |||||
import { RoleVO } from '@/models'; | |||||
import RoleEditor from './role-editor'; | |||||
const CustomMade = () => { | |||||
return ( | |||||
<Empty /> | |||||
); | |||||
}; | |||||
export default () => { | |||||
const [editorVisable, seEditorVisable] = useState<boolean>(false); | |||||
const [editRole, setEditRole] = useState<RoleVO>(); | |||||
const [dataSource, setDataSource] = useState<RoleVO[]>([]); | |||||
const [searchFrom] = Form.useForm(); | |||||
const { runAsync: getRoleList } = useRequest(roleService.getRoleList, { manual: true }); | |||||
const { runAsync: deleteRole } = useRequest(roleService.deleteRole, { manual: true }); | |||||
const loadRoles = async () => { | |||||
const [error, { code, msg, data }] = await getRoleList(searchFrom.getFieldsValue()); | |||||
if (error || code !== 0) { | |||||
antdUtils.message?.open({ type: 'error', content: msg ?? '操作失败' }); | |||||
return | |||||
} | |||||
setDataSource(data.list); | |||||
}; | |||||
const onReset = () => { | |||||
searchFrom.resetFields(); | |||||
loadRoles(); | |||||
}; | |||||
const showDeleteConfirm = (role: RoleVO) => { | |||||
antdUtils.modal?.confirm({ | |||||
title: '确认要将该角色删除吗?', | |||||
icon: <ExclamationCircleFilled />, | |||||
content: '请注意删除以后不可恢复!', | |||||
okText: '删除', | |||||
okType: 'danger', | |||||
cancelText: '取消', | |||||
onOk() { | |||||
return new Promise(async (resolve) => { | |||||
const [error, { code, msg }] = await deleteRole(role.id); | |||||
if (error || code !== 0) { | |||||
antdUtils.message?.open({ type: 'error', content: msg ?? '操作失败' }) | |||||
} else { | |||||
antdUtils.message?.open({ type: 'success', content: '删除成功' }) | |||||
} | |||||
await loadRoles(); | |||||
resolve('') | |||||
}).catch(() => antdUtils.message?.open({ | |||||
type: 'error', | |||||
content: '操作失败', | |||||
})); | |||||
}, | |||||
onCancel() { | |||||
}, | |||||
}); | |||||
}; | |||||
export default CustomMade; | |||||
const columns: TableColumnsType<RoleVO> = [ | |||||
{ | |||||
title: '角色编号', | |||||
dataIndex: 'id', | |||||
key: 'id', | |||||
align: 'center', | |||||
width: 100, | |||||
}, | |||||
{ | |||||
title: '角色名称', | |||||
dataIndex: 'name', | |||||
key: 'name', | |||||
align: 'center', | |||||
width: 200 | |||||
}, | |||||
{ | |||||
title: '角色类型', | |||||
dataIndex: 'type', | |||||
key: 'type', | |||||
align: 'center', | |||||
width: 150, | |||||
render: (value: number) => { | |||||
return (value === 1 ? <Tag color="purple">内置</Tag> : <Tag color="blue">内置</Tag>) | |||||
} | |||||
}, | |||||
{ | |||||
title: '角色标识', | |||||
dataIndex: 'code', | |||||
key: 'code', | |||||
align: 'center', | |||||
width: 150 | |||||
}, | |||||
{ | |||||
title: '显示顺序', | |||||
key: 'sort', | |||||
dataIndex: 'sort', | |||||
align: 'center', | |||||
}, | |||||
{ | |||||
title: '状态', | |||||
key: 'status', | |||||
dataIndex: 'status', | |||||
width: 100, | |||||
align: 'center', | |||||
render: (value: number) => { | |||||
return (value === 0 ? <Badge status="success" text="已开启" /> : <Badge status="error" text="已关闭" />) | |||||
} | |||||
}, | |||||
{ | |||||
title: '创建时间', | |||||
key: 'createTime', | |||||
dataIndex: 'createTime', | |||||
width: 200, | |||||
align: 'center', | |||||
render: (value: number) => { | |||||
return formatDate(new Date(value), "YYYY-mm-dd HH:MM:SS") | |||||
} | |||||
}, | |||||
{ | |||||
title: t("QkOmYwne" /* 操作 */), | |||||
key: 'action', | |||||
fixed: 'right', | |||||
align: 'center', | |||||
render: (role: RoleVO, record) => ( | |||||
<Space size="small" split={(<Divider type='vertical' />)}> | |||||
<a onClick={() => { | |||||
setEditRole(role); | |||||
seEditorVisable(true); | |||||
}}> | |||||
编辑 | |||||
</a> | |||||
<a onClick={() => { | |||||
}}> | |||||
菜单权限 | |||||
</a> | |||||
<a onClick={() => { | |||||
}}> | |||||
数据权限 | |||||
</a> | |||||
<a onClick={() => { | |||||
showDeleteConfirm(role) | |||||
}}> | |||||
删除 | |||||
</a> | |||||
</Space> | |||||
), | |||||
}, | |||||
]; | |||||
useEffect(() => { | |||||
loadRoles(); | |||||
}, []); | |||||
return ( | |||||
<> | |||||
<div> | |||||
<Card className='mt-[4px] dark:bg-[rgb(33,41,70)] bg-white roundle-lg px[12px]' bodyStyle={{ paddingTop: 4, paddingBottom: 4 }}> | |||||
<div className='flex justify-between content-center'> | |||||
<div className='flex justify-normal items-center'> | |||||
<Form layout='inline' form={searchFrom}> | |||||
<Form.Item name="name" label="角色名称"> | |||||
<Input className='w-[150px]' placeholder='请输入名称' allowClear /> | |||||
</Form.Item> | |||||
<Form.Item name="code" label="角色标识"> | |||||
<Input className='w-[150px]' placeholder='请输入标识' allowClear /> | |||||
</Form.Item> | |||||
<Form.Item className='ml-2 w-[130px]' name="status" label="状态"> | |||||
<Select placeholder="请选择" allowClear > | |||||
<Select.Option value="">全部</Select.Option> | |||||
<Select.Option value="0">开启</Select.Option> | |||||
<Select.Option value="1">关闭</Select.Option> | |||||
</Select> | |||||
</Form.Item> | |||||
</Form> | |||||
<Space.Compact className="ml-5"> | |||||
<Button type='primary' size='large' icon={<SearchOutlined />} onClick={loadRoles}> 搜索 </Button> | |||||
<Button type='primary' size='large' icon={<UndoOutlined />} onClick={onReset}> 重置 </Button> | |||||
</Space.Compact> | |||||
</div> | |||||
<div className="py-[4px] flex justify-normal items-center"> | |||||
<Button className="ml-5" type='primary' size='large' icon={<PlusOutlined />} onClick={() => { | |||||
setEditRole(undefined); | |||||
seEditorVisable(true); | |||||
}}> 创建角色 </Button> | |||||
</div> | |||||
</div> | |||||
</Card> | |||||
<Card className='mt-[4px] dark:bg-[rgb(33,41,70)] bg-white roundle-lg px[12px]'> | |||||
<Table rowKey="id" | |||||
scroll={{ x: true }} | |||||
columns={columns} | |||||
dataSource={dataSource} | |||||
className='bg-transparent' | |||||
pagination={{ position: ['bottomRight'] }} /> | |||||
</Card> | |||||
</div> | |||||
<RoleEditor | |||||
onSave={() => { | |||||
loadRoles(); | |||||
seEditorVisable(false); | |||||
}} | |||||
onCancel={() => { seEditorVisable(false) }} | |||||
visible={editorVisable} | |||||
data={editRole} /> | |||||
</> | |||||
); | |||||
}; |
@@ -0,0 +1,122 @@ | |||||
import React, { useEffect, useState } from 'react' | |||||
import { Form, Input, InputNumber, Radio, Modal, Switch } from 'antd'; | |||||
import roleService from '@/request/service/role'; | |||||
import { useRequest } from '@/hooks/use-request'; | |||||
import type { RoleVO } from '@/models' | |||||
import { antdUtils } from '@/utils/antd'; | |||||
const layout = { | |||||
labelCol: { span: 4, }, | |||||
wrapperCol: { span: 16 }, | |||||
bordered: false, | |||||
}; | |||||
interface RoleEditorProps { | |||||
visible: boolean; | |||||
onCancel: (flag?: boolean) => void; | |||||
onSave: (role: RoleVO) => void; | |||||
data?: RoleVO | null; | |||||
} | |||||
const RoleEditor: React.FC<RoleEditorProps> = (props) => { | |||||
const { visible, onCancel, onSave, data } = props; | |||||
const { runAsync: updateRole } = useRequest(roleService.updateRole, { manual: true }); | |||||
const { runAsync: createRole } = useRequest(roleService.createRole, { manual: true }); | |||||
const isEdit = !!data; | |||||
const [saveLoading, setSaveLoading] = useState(false); | |||||
const [form] = Form.useForm(); | |||||
useEffect(() => { | |||||
if (visible) { | |||||
if (data) { | |||||
form.setFieldsValue(data); | |||||
} | |||||
} else { | |||||
form.resetFields(); | |||||
} | |||||
}, [visible]); | |||||
const save = async () => { | |||||
setSaveLoading(true); | |||||
const fieldValues = form.getFieldsValue(); | |||||
const newValue = isEdit ? { ...data, ...fieldValues } : fieldValues; | |||||
const [error, { msg, code }] = isEdit ? await updateRole(newValue) : await createRole(newValue); | |||||
if (!error && code === 0) { | |||||
onSave(newValue); | |||||
} else { | |||||
antdUtils.message?.open({ | |||||
type: 'error', | |||||
content: msg ?? '操作失败', | |||||
}); | |||||
} | |||||
setSaveLoading(false); | |||||
} | |||||
return ( | |||||
<> | |||||
<Modal | |||||
open={visible} | |||||
title={isEdit ? "编辑" : "新建"} | |||||
width={640} | |||||
onOk={save} | |||||
onCancel={() => onCancel()} | |||||
destroyOnClose | |||||
> | |||||
<Form | |||||
form={form} | |||||
{...layout} | |||||
onFinish={save} | |||||
labelCol={{ flex: '0 0 100px' }} | |||||
wrapperCol={{ span: 16 }} | |||||
> | |||||
<Form.Item name="name" label="角色名称" | |||||
rules={[ | |||||
{ | |||||
required: true, | |||||
message: '请输入角色名称', | |||||
}, | |||||
]} | |||||
> | |||||
<Input /> | |||||
</Form.Item> | |||||
<Form.Item name="code" label="角色标识" | |||||
rules={[ | |||||
{ | |||||
required: true, | |||||
message: '请输入角色标识', | |||||
}, | |||||
]} | |||||
> | |||||
<Input /> | |||||
</Form.Item> | |||||
<Form.Item name="sort" label="显示排序" > | |||||
<InputNumber min={1} max={100} /> | |||||
</Form.Item> | |||||
<Form.Item name="remark" label="备注" > | |||||
<Input.TextArea showCount maxLength={100} /> | |||||
</Form.Item> | |||||
<Form.Item name="status" label="状态"> | |||||
<Radio.Group options={[ | |||||
{ value: 0, label: "开启" }, | |||||
{ value: 1, label: "关闭" } | |||||
]} optionType="default"> | |||||
</Radio.Group> | |||||
</Form.Item> | |||||
</Form> | |||||
</Modal> | |||||
</> | |||||
) | |||||
} | |||||
export default RoleEditor; |
@@ -9,7 +9,7 @@ export default { | |||||
}, | }, | ||||
// 获取菜单详情 | // 获取菜单详情 | ||||
getMenu: (id: number) => { | getMenu: (id: number) => { | ||||
return request.get(`${BASE_URL}//menu/get`, {params: { id}}) | |||||
return request.get(`${BASE_URL}/get`, { params: { id }}) | |||||
}, | }, | ||||
// 新增菜单 | // 新增菜单 | ||||
@@ -0,0 +1,45 @@ | |||||
import request from '@/request'; | |||||
import { RolePageReqVO, RoleVO, UpdateStatusReqVO, PageData } from '@/models'; | |||||
const BASE_URL = '/admin-api/system/role'; | |||||
export default { | |||||
// 查询角色列表 | |||||
getRoleList: (params: RolePageReqVO) => { | |||||
return request.get<PageData<RoleVO>>(`${BASE_URL}/page`, {params}); | |||||
}, | |||||
// 查询角色(精简)列表 | |||||
listSimpleRoles: () => { | |||||
return request.get(`${BASE_URL}/list-all-simple`); | |||||
}, | |||||
// 查询角色详情 | |||||
getRole: (id: number) => { | |||||
return request.get(`${BASE_URL}/get?id=${id}`); | |||||
}, | |||||
// 新增角色 | |||||
createRole: (data: RoleVO) => { | |||||
return request.post(`${BASE_URL}/create`, data); | |||||
}, | |||||
// 修改角色 | |||||
updateRole: (data: RoleVO) => { | |||||
return request.put(`${BASE_URL}/update`, data); | |||||
}, | |||||
// 修改角色状态 | |||||
updateRoleStatus: (data: UpdateStatusReqVO) => { | |||||
return request.put(`${BASE_URL}/update-status`, data); | |||||
}, | |||||
// 删除角色 | |||||
deleteRole: (id: number) => { | |||||
return request.delete(`${BASE_URL}/delete?id=${id}`); | |||||
}, | |||||
}; | |||||
@@ -1,43 +1,52 @@ | |||||
export type Writable<T> = { | |||||
-readonly [P in keyof T]: T[P]; | |||||
}; | |||||
export {} | |||||
declare type Nullable<T> = T | null; | |||||
declare type NonNullable<T> = T extends null | undefined ? never : T; | |||||
export declare type Recordable<T = any> = Record<string, T>; | |||||
declare type ReadonlyRecordable<T = any> = { | |||||
readonly [key: string]: T; | |||||
}; | |||||
declare type Indexable<T = any> = { | |||||
[key: string]: T; | |||||
}; | |||||
declare type DeepPartial<T> = { | |||||
[P in keyof T]?: DeepPartial<T[P]>; | |||||
}; | |||||
declare type TimeoutHandle = ReturnType<typeof setTimeout>; | |||||
declare type IntervalHandle = ReturnType<typeof setInterval>; | |||||
declare global { | |||||
export type Writable<T> = { | |||||
-readonly [P in keyof T]: T[P]; | |||||
}; | |||||
declare type Nullable<T> = T | null; | |||||
declare type NonNullable<T> = T extends null | undefined ? never : T; | |||||
declare interface ChangeEvent extends Event { | |||||
target: HTMLInputElement; | |||||
} | |||||
export declare type Recordable<T = any> = Record<string, T>; | |||||
declare type ReadonlyRecordable<T = any> = { | |||||
readonly [key: string]: T; | |||||
}; | |||||
declare type Indexable<T = any> = { | |||||
[key: string]: T; | |||||
}; | |||||
declare type DeepPartial<T> = { | |||||
[P in keyof T]?: DeepPartial<T[P]>; | |||||
}; | |||||
declare type TimeoutHandle = ReturnType<typeof setTimeout>; | |||||
declare type IntervalHandle = ReturnType<typeof setInterval>; | |||||
interface ImportMetaEnv extends ViteEnv { | |||||
__: unknown; | |||||
} | |||||
declare interface ChangeEvent extends Event { | |||||
target: HTMLInputElement; | |||||
} | |||||
interface ImportMetaEnv extends ViteEnv { | |||||
__: unknown; | |||||
}; | |||||
export declare interface ViteEnv { | |||||
VITE_PORT: number; | |||||
VITE_USE_MOCK: boolean; | |||||
VITE_USE_PWA: boolean; | |||||
VITE_PUBLIC_PATH: string; | |||||
VITE_PROXY: [string, string][]; | |||||
VITE_GLOB_APP_TITLE: string; | |||||
VITE_GLOB_APP_SHORT_NAME: string; | |||||
VITE_USE_CDN: boolean; | |||||
VITE_DROP_CONSOLE: boolean; | |||||
VITE_BUILD_COMPRESS: 'gzip' | 'brotli' | 'none'; | |||||
VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE: boolean; | |||||
VITE_LEGACY: boolean; | |||||
VITE_USE_IMAGEMIN: boolean; | |||||
VITE_GENERATE_UI: string; | |||||
}; | |||||
export declare interface ViteEnv { | |||||
VITE_PORT: number; | |||||
VITE_USE_MOCK: boolean; | |||||
VITE_USE_PWA: boolean; | |||||
VITE_PUBLIC_PATH: string; | |||||
VITE_PROXY: [string, string][]; | |||||
VITE_GLOB_APP_TITLE: string; | |||||
VITE_GLOB_APP_SHORT_NAME: string; | |||||
VITE_USE_CDN: boolean; | |||||
VITE_DROP_CONSOLE: boolean; | |||||
VITE_BUILD_COMPRESS: 'gzip' | 'brotli' | 'none'; | |||||
VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE: boolean; | |||||
VITE_LEGACY: boolean; | |||||
VITE_USE_IMAGEMIN: boolean; | |||||
VITE_GENERATE_UI: string; | |||||
declare interface PageParam { | |||||
pageSize?: number | |||||
pageNo?: number | |||||
}; | |||||
} | } |