Browse Source

add: role

dev
powersir 1 year ago
parent
commit
ac57f7a2b3
8 changed files with 452 additions and 48 deletions
  1. +2
    -1
      src/models/index.ts
  2. +21
    -0
      src/models/role.data.ts
  3. +2
    -1
      src/pages/system/menu/index.tsx
  4. +212
    -7
      src/pages/system/role/index.tsx
  5. +122
    -0
      src/pages/system/role/role-editor.tsx
  6. +1
    -1
      src/request/service/menu.ts
  7. +45
    -0
      src/request/service/role.ts
  8. +47
    -38
      types/global.d.ts

+ 2
- 1
src/models/index.ts View File

@@ -1,5 +1,6 @@
export * from './user.data.ts'
export * from './product.data.ts'
export * from './role.data.ts'

export interface ResponseDTO<T>{
code: number;
@@ -8,7 +9,7 @@ export interface ResponseDTO<T>{
}

export interface PageData<T> {
data: T[];
list: T[];
total: number;
}



+ 21
- 0
src/models/role.data.ts View File

@@ -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
}

+ 2
- 1
src/pages/system/menu/index.tsx View File

@@ -73,13 +73,14 @@ const MenuListPage: React.FC = () => {
title: '菜单名称',
dataIndex: 'name',
key: 'name',
align: 'center',
align: 'right',
width: 200,
},
{
title: '菜单类型',
dataIndex: 'type',
key: 'type',
align: 'right',
width: 100,
render: (value: number, record, index) => {
return (value === 1 ? <Tag color="purple">目录</Tag> : (value === 2 ? <Tag color="blue">菜单</Tag> : <Tag color="green">按钮</Tag>))


+ 212
- 7
src/pages/system/role/index.tsx View File

@@ -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} />
</>
);
};

+ 122
- 0
src/pages/system/role/role-editor.tsx View File

@@ -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;

+ 1
- 1
src/request/service/menu.ts View File

@@ -9,7 +9,7 @@ export default {
},
// 获取菜单详情
getMenu: (id: number) => {
return request.get(`${BASE_URL}//menu/get`, {params: { id}})
return request.get(`${BASE_URL}/get`, { params: { id }})
},

// 新增菜单


+ 45
- 0
src/request/service/role.ts View File

@@ -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}`);
},

};




+ 47
- 38
types/global.d.ts View File

@@ -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
};
}

Loading…
Cancel
Save