Ver código fonte

add menu list page

dev
powersir 1 ano atrás
pai
commit
253c32ff90
7 arquivos alterados com 372 adições e 6 exclusões
  1. +2
    -1
      src/layout/index.tsx
  2. +4
    -0
      src/models/user.data.ts
  3. +167
    -0
      src/pages/system/menu/index.tsx
  4. +14
    -5
      src/request/index.ts
  5. +1
    -0
      src/request/service/index.ts
  6. +8
    -0
      src/request/service/menu.ts
  7. +176
    -0
      src/utils/formatTime.ts

+ 2
- 1
src/layout/index.tsx Ver arquivo

@@ -101,10 +101,12 @@ const BasicLayout: React.FC = () => {
if(!item.parentPaths) {
item.parentPaths = []
}

if(!item.path.startsWith('/')) {
item.path = `/${item.path}`
}
item.path = [...item.parentPaths, item.path].join('')
item.component = `${item.path}/index.tsx`
routes.push(item)
if(item.children && item.children.length > 0) {
item.children.forEach(it => {
@@ -115,7 +117,6 @@ const BasicLayout: React.FC = () => {
}
menuList.data.forEach(item => fixAndPushMenu(item))
setMenus([...formatedMenus, ...menuList.data]);
debugger
console.log('components', components);
replaceRoutes('*', [
...routes.map(menu => ({


+ 4
- 0
src/models/user.data.ts Ver arquivo

@@ -59,6 +59,7 @@ export interface UserDTO {

export interface Menu {
id: number;
permission?: string;
parentId?: number;
name?: string;
icon?: string;
@@ -67,7 +68,10 @@ export interface Menu {
component?: string;
keepAlive: boolean;
type?: number;
sort?: number;
status?: number;
parentPaths?: string[];
createTime: number;
children?: Array<Menu>;
}



+ 167
- 0
src/pages/system/menu/index.tsx Ver arquivo

@@ -0,0 +1,167 @@
import { Space, Table, Button, Image, Divider, Tag, Card, Badge } from 'antd';
import type { TableColumnsType } from 'antd';
import { t } from '@/utils/i18n';
import React, { useState, useEffect } from 'react';
import { PlusOutlined, ExclamationCircleFilled, SearchOutlined, UndoOutlined } from '@ant-design/icons';
import type { Menu } from '@/models'
import { antdUtils } from '@/utils/antd';
import { useRequest } from '@/hooks/use-request';
import menuService from '@/request/service/menu'
import { formatDate } from '@/utils/formatTime'

const MenuListPage: React.FC = () => {

const [dataSource, setDataSource] = useState<Menu[]>([]);
const { runAsync: getMenuList } = useRequest(menuService.getMenuList, { manual: true });
const loadMenus = async () => {
const [error, { data: menus }] = await getMenuList();
if (!error) {
const menuIds = menus.map(it => it.id)
const rootMenus = menus.filter((menu) => menuIds.indexOf(menu.parentId??0) < 0)
menus.forEach(menu => {
const parentMenu = menus.find(it => it.id === menu.parentId); //menu.parentId
if(parentMenu) {
if(!parentMenu.children) {
parentMenu.children = []
}
parentMenu.children.push(menu)
}
})
setDataSource(rootMenus);
}
};


const showDeleteConfirm = () => {
antdUtils.modal?.confirm({
title: '确认要将该菜单删除吗?',
icon: <ExclamationCircleFilled />,
content: '请注意删除以后不可恢复!',
okText: '删除',
okType: 'danger',
cancelText: '取消',
onOk() {
return new Promise((resolve, reject) => {

}).catch(() => antdUtils.message?.open({
type: 'error',
content: '操作失败',
}));
},
onCancel() {
},
});
};

const columns: TableColumnsType<Menu> = [
{
title: '菜单名称',
dataIndex: 'name',
key: 'name',
align: 'center',
width: 200,
},
{
title: '菜单类型',
dataIndex: 'type',
key: 'type',
width: 100,
render: (value: number, record, index) => {
return (value === 1 ? <Tag color="purple">目录</Tag> : (value === 2 ?<Tag color="blue">菜单</Tag>: <Tag color="green">按钮</Tag>))
}
},
{
title: '路由地址',
dataIndex: 'path',
key: 'path',
width: 150
},
{
title: '组件路径',
dataIndex: 'component',
key: 'component',
width: 150
},
{
title: '权限标识',
key: 'permission',
dataIndex: 'permission',
},
{
title: '排序',
key: 'sort',
dataIndex: 'sort',
width: 100,
},
{
title: '显示状态',
key: 'visible',
dataIndex: 'visible',
width: 100,
render: (value: boolean) => {
return (value? <Badge status="success" text="已显示" /> : <Badge status="warning" text="已隐藏" />)
}
},
{
title: '开启状态',
key: 'status',
dataIndex: 'status',
width: 100,
render: (value: number) => {
return (value === 0 ? <Badge status="success" text="已开启" /> : <Badge status="error" text="已关闭" />)
}
},
{
title: '创建时间',
key: 'createTime',
dataIndex: 'createTime',
width: 200,
render:(value: number) => {
return formatDate(new Date(value), "YYYY-mm-dd HH:MM:SS")
}
},
{
title: t("QkOmYwne" /* 操作 */),
key: 'action',
render: (_, record) => (
<Space size="small" split={(
<Divider type='vertical' />
)}>
<a
onClick={() => {

}}>
编辑
</a>
<a
onClick={() => {
showDeleteConfirm()
}}>
删除
</a>
</Space>
),
},
];

useEffect(() => {
loadMenus();
}, []);

return (
<>
<div>
<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>
</>
);
};

export default MenuListPage;

+ 14
- 5
src/request/index.ts Ver arquivo

@@ -14,7 +14,7 @@ import { ResponseDTO } from '@/models';
const { apiUrl = '' } = useGlobSetting();

const loginUrl = '/auth/login';
const refreshTokenUrl = '/app-api/system/auth/refresh-token';
const refreshTokenUrl = '/admin-api/system/auth/refresh-token';

export type Response<T> = Promise<[boolean, T, AxiosResponse<T>]>;

@@ -134,9 +134,19 @@ class Request {
response: AxiosResponse<any, any>
): Promise<any> {
if (response.config.url !== refreshTokenUrl) {
this.requestingCount -= 1;
if (this.requestQueue.length) {
this.requestByQueue();
if(response.data && response.data.code && response.data.code === 401) {
return new Promise((resolve) => {
this.requestQueue.unshift({resolve, config: response.config, type: 'response'});
if (this.refreshTokenFlag) return;
this.refreshTokenFlag = true;
this.refreshToken();
});
} else {
this.requestingCount -= 1;
if (this.requestQueue.length) {
this.requestByQueue();
}
}
}

@@ -146,7 +156,6 @@ class Request {
private async responseErrorInterceptor(error: any): Promise<any> {
this.requestingCount -= 1;
const {config, status} = error?.response || {};

if (status === 401) {
return new Promise((resolve) => {
this.requestQueue.unshift({resolve, config, type: 'response'});


+ 1
- 0
src/request/service/index.ts Ver arquivo

@@ -0,0 +1 @@
export * as menuService from './menu'

+ 8
- 0
src/request/service/menu.ts Ver arquivo

@@ -0,0 +1,8 @@
import request from '@/request';
import { Menu } from '@/models';

export default {
getMenuList: (params?: {name?: string, status?: number}) => {
return request.get<Menu[]>('/admin-api/system/menu/list', {params});
},
};

+ 176
- 0
src/utils/formatTime.ts Ver arquivo

@@ -0,0 +1,176 @@
/**
* 时间日期转换
* @param date 当前时间,new Date() 格式
* @param format 需要转换的时间格式字符串
* @description format 字符串随意,如 `YYYY-mm、YYYY-mm-dd`
* @description format 季度:"YYYY-mm-dd HH:MM:SS QQQQ"
* @description format 星期:"YYYY-mm-dd HH:MM:SS WWW"
* @description format 几周:"YYYY-mm-dd HH:MM:SS ZZZ"
* @description format 季度 + 星期 + 几周:"YYYY-mm-dd HH:MM:SS WWW QQQQ ZZZ"
* @returns 返回拼接后的时间字符串
*/
export function formatDate(date: Date, format: string): string {
const we = date.getDay() // 星期
const z = getWeek(date) // 周
const qut = Math.floor((date.getMonth() + 3) / 3).toString() // 季度
const opt: { [key: string]: string } = {
'Y+': date.getFullYear().toString(), // 年
'm+': (date.getMonth() + 1).toString(), // 月(月份从0开始,要+1)
'd+': date.getDate().toString(), // 日
'H+': date.getHours().toString(), // 时
'M+': date.getMinutes().toString(), // 分
'S+': date.getSeconds().toString(), // 秒
'q+': qut // 季度
}
// 中文数字 (星期)
const week: { [key: string]: string } = {
'0': '日',
'1': '一',
'2': '二',
'3': '三',
'4': '四',
'5': '五',
'6': '六'
}
// 中文数字(季度)
const quarter: { [key: string]: string } = {
'1': '一',
'2': '二',
'3': '三',
'4': '四'
}
if (/(W+)/.test(format))
format = format.replace(
RegExp.$1,
RegExp.$1.length > 1 ? (RegExp.$1.length > 2 ? '星期' + week[we] : '周' + week[we]) : week[we]
)
if (/(Q+)/.test(format))
format = format.replace(
RegExp.$1,
RegExp.$1.length == 4 ? '第' + quarter[qut] + '季度' : quarter[qut]
)
if (/(Z+)/.test(format))
format = format.replace(RegExp.$1, RegExp.$1.length == 3 ? '第' + z + '周' : z + '')
for (const k in opt) {
const r = new RegExp('(' + k + ')').exec(format)
// 若输入的长度不为1,则前面补零
if (r)
format = format.replace(
r[1],
RegExp.$1.length == 1 ? opt[k] : opt[k].padStart(RegExp.$1.length, '0')
)
}
return format
}

/**
* 获取当前日期是第几周
* @param dateTime 当前传入的日期值
* @returns 返回第几周数字值
*/
export function getWeek(dateTime: Date): number {
const temptTime = new Date(dateTime.getTime())
// 周几
const weekday = temptTime.getDay() || 7
// 周1+5天=周六
temptTime.setDate(temptTime.getDate() - weekday + 1 + 5)
let firstDay = new Date(temptTime.getFullYear(), 0, 1)
const dayOfWeek = firstDay.getDay()
let spendDay = 1
if (dayOfWeek != 0) spendDay = 7 - dayOfWeek + 1
firstDay = new Date(temptTime.getFullYear(), 0, 1 + spendDay)
const d = Math.ceil((temptTime.valueOf() - firstDay.valueOf()) / 86400000)
const result = Math.ceil(d / 7)
return result
}

/**
* 将时间转换为 `几秒前`、`几分钟前`、`几小时前`、`几天前`
* @param param 当前时间,new Date() 格式或者字符串时间格式
* @param format 需要转换的时间格式字符串
* @description param 10秒: 10 * 1000
* @description param 1分: 60 * 1000
* @description param 1小时: 60 * 60 * 1000
* @description param 24小时:60 * 60 * 24 * 1000
* @description param 3天: 60 * 60* 24 * 1000 * 3
* @returns 返回拼接后的时间字符串
*/
export function formatPast(param: string | Date, format = 'YYYY-mm-dd HH:MM:SS'): string {
// 传入格式处理、存储转换值
let t: any, s: number
// 获取js 时间戳
let time: number = new Date().getTime()
// 是否是对象
typeof param === 'string' || 'object' ? (t = new Date(param).getTime()) : (t = param)
// 当前时间戳 - 传入时间戳
time = Number.parseInt(`${time - t}`)
if (time < 10000) {
// 10秒内
return '刚刚'
} else if (time < 60000 && time >= 10000) {
// 超过10秒少于1分钟内
s = Math.floor(time / 1000)
return `${s}秒前`
} else if (time < 3600000 && time >= 60000) {
// 超过1分钟少于1小时
s = Math.floor(time / 60000)
return `${s}分钟前`
} else if (time < 86400000 && time >= 3600000) {
// 超过1小时少于24小时
s = Math.floor(time / 3600000)
return `${s}小时前`
} else if (time < 259200000 && time >= 86400000) {
// 超过1天少于3天内
s = Math.floor(time / 86400000)
return `${s}天前`
} else {
// 超过3天
const date = typeof param === 'string' || 'object' ? new Date(param) : param
return formatDate(date, format)
}
}

/**
* 时间问候语
* @param param 当前时间,new Date() 格式
* @description param 调用 `formatAxis(new Date())` 输出 `上午好`
* @returns 返回拼接后的时间字符串
*/
export function formatAxis(param: Date): string {
const hour: number = new Date(param).getHours()
if (hour < 6) return '凌晨好'
else if (hour < 9) return '早上好'
else if (hour < 12) return '上午好'
else if (hour < 14) return '中午好'
else if (hour < 17) return '下午好'
else if (hour < 19) return '傍晚好'
else if (hour < 22) return '晚上好'
else return '夜里好'
}

/**
* 将毫秒,转换成时间字符串。例如说,xx 分钟
*
* @param ms 毫秒
* @returns {string} 字符串
*/
export function formatPast2(ms: number) {
const day = Math.floor(ms / (24 * 60 * 60 * 1000))
const hour = Math.floor(ms / (60 * 60 * 1000) - day * 24)
const minute = Math.floor(ms / (60 * 1000) - day * 24 * 60 - hour * 60)
const second = Math.floor(ms / 1000 - day * 24 * 60 * 60 - hour * 60 * 60 - minute * 60)
if (day > 0) {
return day + '天' + hour + '小时' + minute + '分钟'
}
if (hour > 0) {
return hour + '小时' + minute + '分钟'
}
if (minute > 0) {
return minute + '分钟'
}
if (second > 0) {
return second + '秒'
} else {
return 0 + '秒'
}
}

Carregando…
Cancelar
Salvar