feat: 分类页面以树级结构显示

master
luoer 2023-09-26 17:07:31 +08:00
parent 8b551a72be
commit 9ce121af3f
6 changed files with 503 additions and 125 deletions

2
.env
View File

@ -6,7 +6,7 @@ VITE_TITLE = 绝弹管理后台
# 网站副标题
VITE_SUBTITLE = 快速开发web应用的模板工具
# 接口前缀 说明:参见 axios 的 baseURL
VITE_API = http://demo.dev.juetan.cn/
VITE_API = http://127.0.0.1:3030/
# =====================================================================================
# 开发设置

View File

@ -69,6 +69,7 @@ api.instance.interceptors.response.use(
if (error.response) {
const code = error.response.data?.code;
if (code === 4050 || code === 4051) {
Message.warning('提示:登陆过期,请重新登陆!')
userStore.clearUser();
api.tokenExpiredHandler?.();
}

View File

@ -169,6 +169,11 @@ export interface LoginLog {
* @example "1"
*/
description: string;
/**
*
* @example true
*/
status: boolean;
/**
* IP
* @example "127.0.0.1"
@ -199,39 +204,6 @@ export interface UpdateLogDto {
demo?: string;
}
export interface Role {
/**
*
* @example "管理员"
*/
name: string;
/**
*
* @example "admin"
*/
slug: string;
/**
*
* @example "拥有所有权限"
*/
description: string;
/**
*
* @example [1,2,3]
*/
permissions: Permission[];
/**
* ID
* @example [1,2,3]
*/
permissionIds: number[];
/**
*
* @example [1,2,3]
*/
user: User;
}
export interface Permission {
/**
*
@ -243,16 +215,16 @@ export interface Permission {
* @example "post:list"
*/
slug: string;
/**
*
* @example "menu"
*/
type: "menu" | "api";
/**
*
* @example "文章列表"
*/
description: string;
/**
*
* @example "文章列表"
*/
roles: Role[];
}
export interface CreateRoleDto {
@ -379,6 +351,120 @@ export interface UpdatePostDto {
content?: string;
}
export interface CreateCategoryDto {
/**
*
* @example "待分类"
*/
title: string;
/**
*
* @example "default"
*/
slug: string;
/**
*
* @example "默认分类"
*/
description?: string;
/**
*
* @example "default"
*/
icon?: string;
/**
*
* @example 0
*/
sort?: number;
/**
*
* @example "category"
*/
type: object;
/**
* ID
* @example 0
*/
parentId?: number;
}
export interface Category {
/**
*
* @example "待分类"
*/
title: string;
/**
*
* @example "default"
*/
slug: string;
/**
*
* @example "默认分类"
*/
description?: string;
/**
*
* @example "default"
*/
icon?: string;
/**
*
* @example 0
*/
sort?: number;
/**
*
* @example "category"
*/
type?: object;
/**
* ID
* @example 0
*/
parentId?: number;
}
export interface UpdateCategoryDto {
/**
*
* @example "待分类"
*/
title?: string;
/**
*
* @example "default"
*/
slug?: string;
/**
*
* @example "默认分类"
*/
description?: string;
/**
*
* @example "default"
*/
icon?: string;
/**
*
* @example 0
*/
sort?: number;
/**
*
* @example "category"
*/
type?: object;
/**
* ID
* @example 0
*/
parentId?: number;
}
export interface Response {
/**
*
@ -496,6 +582,33 @@ export interface GetPostsParams {
size?: number;
}
export interface GetCategorysParams {
/**
* (Swagger)
* @example "示例值"
*/
demo?: string;
/**
*
* @default "id:desc"
* @pattern /^(\w+:\w+,)*\w+:\w+$/
* @example "id:desc"
*/
sort?: string;
/**
*
* @min 1
* @example 1
*/
page?: number;
/**
*
* @min 0
* @example 10
*/
size?: number;
}
export namespace User {
/**
* @description
@ -676,7 +789,7 @@ export namespace Log {
};
}
/**
* No description
* @description
* @tags log
* @name GetLoginLogs
* @request GET:/api/v1/logs/login
@ -893,10 +1006,10 @@ export namespace Permission {
/**
* @description
* @tags permission
* @name UpdatePermission
* @name SetPermission
* @request PATCH:/api/v1/permissions/{id}
*/
export namespace UpdatePermission {
export namespace SetPermission {
export type RequestParams = {
id: string;
};
@ -1115,6 +1228,111 @@ export namespace Post {
}
}
export namespace Category {
/**
* @description
* @tags category
* @name AddCategory
* @request POST:/api/v1/categories
*/
export namespace AddCategory {
export type RequestParams = {};
export type RequestQuery = {};
export type RequestBody = CreateCategoryDto;
export type RequestHeaders = {};
export type ResponseBody = Response & {
data: number;
};
}
/**
* @description /
* @tags category
* @name GetCategorys
* @request GET:/api/v1/categories
*/
export namespace GetCategorys {
export type RequestParams = {};
export type RequestQuery = {
/**
* (Swagger)
* @example "示例值"
*/
demo?: string;
/**
*
* @default "id:desc"
* @pattern /^(\w+:\w+,)*\w+:\w+$/
* @example "id:desc"
*/
sort?: string;
/**
*
* @min 1
* @example 1
*/
page?: number;
/**
*
* @min 0
* @example 10
*/
size?: number;
};
export type RequestBody = never;
export type RequestHeaders = {};
export type ResponseBody = Response & {
data: Category[];
};
}
/**
* @description ID
* @tags category
* @name GetCategory
* @request GET:/api/v1/categories/{id}
*/
export namespace GetCategory {
export type RequestParams = {
id: number;
};
export type RequestQuery = {};
export type RequestBody = never;
export type RequestHeaders = {};
export type ResponseBody = Response & {
data: Category;
};
}
/**
* @description ID
* @tags category
* @name UpdateCategory
* @request PATCH:/api/v1/categories/{id}
*/
export namespace UpdateCategory {
export type RequestParams = {
id: number;
};
export type RequestQuery = {};
export type RequestBody = UpdateCategoryDto;
export type RequestHeaders = {};
export type ResponseBody = Response;
}
/**
* @description ID
* @tags category
* @name DelCategory
* @request DELETE:/api/v1/categories/{id}
*/
export namespace DelCategory {
export type RequestParams = {
id: number;
};
export type RequestQuery = {};
export type RequestBody = never;
export type RequestHeaders = {};
export type ResponseBody = Response;
}
}
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, HeadersDefaults, ResponseType } from "axios";
export type QueryParamsType = Record<string | number, any>;
@ -1426,7 +1644,7 @@ export class Api<SecurityDataType extends unknown> extends HttpClient<SecurityDa
},
/**
* No description
*
*
* @tags log
* @name GetLoginLogs
@ -1667,10 +1885,10 @@ export class Api<SecurityDataType extends unknown> extends HttpClient<SecurityDa
*
*
* @tags permission
* @name UpdatePermission
* @name SetPermission
* @request PATCH:/api/v1/permissions/{id}
*/
updatePermission: (id: string, data: UpdatePermissionDto, params: RequestParams = {}) => {
setPermission: (id: string, data: UpdatePermissionDto, params: RequestParams = {}) => {
return this.request<Response, any>({
path: `/api/v1/permissions/${id}`,
method: "PATCH",
@ -1912,4 +2130,105 @@ export class Api<SecurityDataType extends unknown> extends HttpClient<SecurityDa
});
},
};
category = {
/**
*
*
* @tags category
* @name AddCategory
* @request POST:/api/v1/categories
*/
addCategory: (data: CreateCategoryDto, params: RequestParams = {}) => {
return this.request<
Response & {
data: number;
},
any
>({
path: `/api/v1/categories`,
method: "POST",
body: data,
type: ContentType.Json,
format: "json",
...params,
});
},
/**
* /
*
* @tags category
* @name GetCategorys
* @request GET:/api/v1/categories
*/
getCategorys: (query: GetCategorysParams, params: RequestParams = {}) => {
return this.request<
Response & {
data: Category[];
},
any
>({
path: `/api/v1/categories`,
method: "GET",
query: query,
format: "json",
...params,
});
},
/**
* ID
*
* @tags category
* @name GetCategory
* @request GET:/api/v1/categories/{id}
*/
getCategory: (id: number, params: RequestParams = {}) => {
return this.request<
Response & {
data: Category;
},
any
>({
path: `/api/v1/categories/${id}`,
method: "GET",
format: "json",
...params,
});
},
/**
* ID
*
* @tags category
* @name UpdateCategory
* @request PATCH:/api/v1/categories/{id}
*/
updateCategory: (id: number, data: UpdateCategoryDto, params: RequestParams = {}) => {
return this.request<Response, any>({
path: `/api/v1/categories/${id}`,
method: "PATCH",
body: data,
type: ContentType.Json,
format: "json",
...params,
});
},
/**
* ID
*
* @tags category
* @name DelCategory
* @request DELETE:/api/v1/categories/{id}
*/
delCategory: (id: number, params: RequestParams = {}) => {
return this.request<Response, any>({
path: `/api/v1/categories/${id}`,
method: "DELETE",
format: "json",
...params,
});
},
};
}

View File

@ -1,6 +1,8 @@
<template>
<BreadPage>
<Table v-bind="table"></Table>
<Table v-bind="table">
</Table>
</BreadPage>
</template>
@ -8,53 +10,52 @@
import { api } from "@/api";
import { Table, useTable } from "@/components";
import { dayjs } from "@/libs/dayjs";
import { Tag } from "@arco-design/web-vue";
import { listToTree } from "@/utils/listToTree";
const table = useTable({
data: async (model, paging) => {
return api.log.getLoginLogs({ ...model, ...paging });
const res = await api.category.getCategorys({ ...model, ...paging });
const data = listToTree(res.data.data);
return { data: { data, total: (res.data as any).total } };
},
columns: [
{
title: "登陆账号",
dataIndex: "nickname",
width: 140,
title: "名称",
dataIndex: "title",
width: 240,
},
{
title: "操作描述",
title: "描述",
dataIndex: "description",
render: ({ record: { status, description } }) => {
return (
<span>
<Tag color={status === null || status ? "green" : "red"} class="mr-2">
{ status === null || status ? "成功" : "失败" }
</Tag>
{description}
</span>
);
},
},
{
title: "登陆地址",
dataIndex: "ip",
title: "别名",
dataIndex: "slug",
width: 200,
render: ({ record }) => `${record.addr || "未知"}(${record.ip})`,
},
{
title: "操作系统",
dataIndex: "os",
width: 160,
},
{
title: "浏览器",
dataIndex: "browser",
width: 160,
},
{
title: "登陆时间",
title: "创建时间",
dataIndex: "createdAt",
width: 200,
render: ({ record }) => dayjs(record.createdAt).format(),
},
{
type: "button",
title: "操作",
width: 120,
render: ({ record }) => dayjs(record.createdAt).fromNow(),
buttons: [
{
type: 'modify',
text: '修改'
},
{
type: "delete",
text: "删除",
onClick({ record }) {
return api.category.delCategory(record.id);
},
},
],
},
],
search: {
@ -65,14 +66,68 @@ const table = useTable({
type: "input",
required: false,
nodeProps: {
placeholder: '请输入登陆账号',
placeholder: "名称关键字",
},
itemProps: {
hideLabel: true,
}
},
},
],
},
create: {
title: "添加分类",
modalProps: {
width: 580,
},
items: [
{
field: "parentId",
label: "父级分类",
type: "select",
options: async () => {
const res = await api.category.getCategorys({ size: 0 });
return res.data.data.map(({ id, title }: any) => ({ value: id, label: title }));
},
},
{
field: "title",
label: "分类名称",
type: "input",
required: true,
nodeProps: {
placeholder: "请输入分类名称",
},
},
{
field: "slug",
label: "分类别名",
type: "input",
required: true,
nodeProps: {
placeholder: "请输入分类别名",
},
},
{
field: "description",
label: "描述",
type: "textarea",
required: false,
nodeProps: {
placeholder: "请输入描述",
},
},
],
submit: async ({ model }) => {
return api.category.addCategory(model);
},
},
modify: {
extend: true,
title: "修改分类",
submit: async ({ model }) => {
return api.category.updateCategory(model.id, model);
},
},
});
</script>

View File

@ -1,6 +1,15 @@
<template>
<BreadPage>
<Table v-bind="table"></Table>
<Table v-bind="table">
<template #action>
<a-button type="primary">
<template #icon>
<i class="icon-park-outline-upload"></i>
</template>
上传
</a-button>
</template>
</Table>
</BreadPage>
</template>
@ -8,71 +17,54 @@
import { api } from "@/api";
import { Table, useTable } from "@/components";
import { dayjs } from "@/libs/dayjs";
import { Tag } from "@arco-design/web-vue";
const table = useTable({
data: async (model, paging) => {
return api.log.getLoginLogs({ ...model, ...paging });
return api.upload.getUploads();
},
columns: [
{
title: "登陆账号",
dataIndex: "nickname",
width: 140,
title: "文件名称",
dataIndex: "name",
width: 260,
},
{
title: "操作描述",
dataIndex: "description",
render: ({ record: { status, description } }) => {
return (
<span>
<Tag color={status === null || status ? "green" : "red"} class="mr-2">
{ status === null || status ? "成功" : "失败" }
</Tag>
{description}
</span>
);
},
},
{
title: "登陆地址",
dataIndex: "ip",
width: 200,
render: ({ record }) => `${record.addr || "未知"}(${record.ip})`,
},
{
title: "操作系统",
dataIndex: "os",
width: 160,
},
{
title: "浏览器",
dataIndex: "browser",
width: 160,
},
{
title: "登陆时间",
dataIndex: "createdAt",
width: 200,
render: ({ record }) => dayjs(record.createdAt).format(),
},
{
type: "button",
title: "操作",
width: 120,
render: ({ record }) => dayjs(record.createdAt).fromNow(),
buttons: [
{
type: "modify",
},
{
type: "delete",
text: "删除",
onClick({ record }) {
return api.upload.delFile(record.id);
},
},
],
},
],
search: {
items: [
{
field: "nickname",
label: "登陆账号",
type: "input",
required: false,
nodeProps: {
placeholder: '请输入登陆账号',
},
itemProps: {
hideLabel: true,
}
},
],
},
field: 'name',
label: '文件名称',
type: 'input',
}
]
}
});
</script>

11
src/utils/listToTree.ts Normal file
View File

@ -0,0 +1,11 @@
export const listToTree = (list: any[], id = "id", pid = "parentId", cid = "children") => {
const map = list.reduce((res, v) => ((res[v[id]] = v), res), {});
return list.filter((item) => {
const parent = map[item[pid]];
if (parent) {
!parent[cid] && (parent[cid] = []);
parent[cid].push(item);
}
return !item[pid];
});
};