feat: 添加文件分类功能
parent
93c9c3185a
commit
c84da369cf
2
.env
2
.env
|
|
@ -6,7 +6,7 @@ VITE_TITLE = 绝弹项目管理
|
|||
# 网站副标题
|
||||
VITE_SUBTITLE = 快速开发web应用的模板工具
|
||||
# 接口前缀 说明:参见 axios 的 baseURL
|
||||
VITE_API = /
|
||||
VITE_API = http://127.0.0.1:3030/
|
||||
|
||||
# =====================================================================================
|
||||
# 开发设置
|
||||
|
|
|
|||
|
|
@ -152,7 +152,7 @@ export interface CreateRoleDto {
|
|||
* 角色标识
|
||||
* @example "admin"
|
||||
*/
|
||||
slug: string;
|
||||
code: string;
|
||||
/**
|
||||
* 角色描述
|
||||
* @example "一段很长的描述"
|
||||
|
|
@ -175,7 +175,7 @@ export interface UpdateRoleDto {
|
|||
* 角色标识
|
||||
* @example "admin"
|
||||
*/
|
||||
slug?: string;
|
||||
code?: string;
|
||||
/**
|
||||
* 角色描述
|
||||
* @example "一段很长的描述"
|
||||
|
|
@ -310,7 +310,7 @@ export interface File {
|
|||
mimetype: string;
|
||||
/**
|
||||
* 文件路径
|
||||
* @example "/upload/2021/10/01/xxx.jpg"
|
||||
* @example "/upload/2021-10-01/xxx.jpg"
|
||||
*/
|
||||
path: string;
|
||||
/**
|
||||
|
|
@ -323,6 +323,11 @@ export interface File {
|
|||
* @example ".jpg"
|
||||
*/
|
||||
extension: string;
|
||||
/**
|
||||
* 分类ID
|
||||
* @example 0
|
||||
*/
|
||||
categoryId: number;
|
||||
/**
|
||||
* 自增ID
|
||||
* @example 1
|
||||
|
|
@ -357,12 +362,110 @@ export interface UpdateFileDto {
|
|||
* 文件名
|
||||
* @example "头像.jpg"
|
||||
*/
|
||||
name: string;
|
||||
name?: string;
|
||||
/**
|
||||
* 描述
|
||||
* @example "一段很长的描述"
|
||||
*/
|
||||
description?: string;
|
||||
/**
|
||||
* 分类ID
|
||||
* @example 1
|
||||
*/
|
||||
categoryId?: number;
|
||||
}
|
||||
|
||||
export interface CreateFileCategoryDto {
|
||||
/**
|
||||
* 分类名称
|
||||
* @example "风景"
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* 分类编码
|
||||
* @example "view"
|
||||
*/
|
||||
code: string;
|
||||
/**
|
||||
* 分类描述
|
||||
* @example "这是一段很长的描述"
|
||||
*/
|
||||
description?: string;
|
||||
/**
|
||||
* 父级ID
|
||||
* @example 0
|
||||
*/
|
||||
parentId?: number;
|
||||
}
|
||||
|
||||
export interface FileCategory {
|
||||
/**
|
||||
* 分类名称
|
||||
* @example "风景"
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* 分类编码
|
||||
* @example "view"
|
||||
*/
|
||||
code: string;
|
||||
/**
|
||||
* 分类描述
|
||||
* @example "这是一段很长的描述"
|
||||
*/
|
||||
description?: string;
|
||||
/** 父级ID */
|
||||
parentId?: number;
|
||||
/**
|
||||
* 自增ID
|
||||
* @example 1
|
||||
*/
|
||||
id: number;
|
||||
/**
|
||||
* 创建时间
|
||||
* @format date-time
|
||||
* @example "2022-01-01 10:10:10"
|
||||
*/
|
||||
createdAt: string;
|
||||
/**
|
||||
* 创建人
|
||||
* @example "绝弹"
|
||||
*/
|
||||
createdBy: string;
|
||||
/**
|
||||
* 更新时间
|
||||
* @format date-time
|
||||
* @example "2022-01-02 11:11:11"
|
||||
*/
|
||||
updatedAt: string;
|
||||
/**
|
||||
* 更新人
|
||||
* @example "绝弹"
|
||||
*/
|
||||
updatedBy: string;
|
||||
}
|
||||
|
||||
export interface UpdateFileCategoryDto {
|
||||
/**
|
||||
* 分类名称
|
||||
* @example "风景"
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* 分类编码
|
||||
* @example "view"
|
||||
*/
|
||||
code?: string;
|
||||
/**
|
||||
* 分类描述
|
||||
* @example "这是一段很长的描述"
|
||||
*/
|
||||
description?: string;
|
||||
/**
|
||||
* 父级ID
|
||||
* @example 0
|
||||
*/
|
||||
parentId?: number;
|
||||
}
|
||||
|
||||
export interface CreatePostDto {
|
||||
|
|
@ -985,6 +1088,75 @@ export interface GetLoginLogsParams {
|
|||
createdFrom?: string;
|
||||
}
|
||||
|
||||
export interface GetFilesParams {
|
||||
/**
|
||||
* 文件名称
|
||||
* @example "风景"
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* 分类ID
|
||||
* @example 1
|
||||
*/
|
||||
categoryId?: number;
|
||||
/**
|
||||
* 排序规则
|
||||
* @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;
|
||||
/**
|
||||
* 创建起始事件
|
||||
* @example "2020-02-02 02:02:02"
|
||||
*/
|
||||
createdFrom?: string;
|
||||
}
|
||||
|
||||
export interface GetFileCategorysParams {
|
||||
/**
|
||||
* 分类名称
|
||||
* @example "风景"
|
||||
*/
|
||||
name?: 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;
|
||||
/**
|
||||
* 创建起始事件
|
||||
* @example "2020-02-02 02:02:02"
|
||||
*/
|
||||
createdFrom?: string;
|
||||
}
|
||||
|
||||
export interface GetPostsParams {
|
||||
/**
|
||||
* 排序规则
|
||||
|
|
@ -1685,10 +1857,84 @@ export namespace File {
|
|||
*/
|
||||
export namespace GetFiles {
|
||||
export type RequestParams = {};
|
||||
export type RequestQuery = {};
|
||||
export type RequestQuery = {
|
||||
/**
|
||||
* 文件名称
|
||||
* @example "风景"
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* 分类ID
|
||||
* @example 1
|
||||
*/
|
||||
categoryId?: number;
|
||||
/**
|
||||
* 排序规则
|
||||
* @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;
|
||||
/**
|
||||
* 创建起始事件
|
||||
* @example "2020-02-02 02:02:02"
|
||||
*/
|
||||
createdFrom?: string;
|
||||
};
|
||||
export type RequestBody = never;
|
||||
export type RequestHeaders = {};
|
||||
export type ResponseBody = Response;
|
||||
export type ResponseBody = {
|
||||
/**
|
||||
* 状态码
|
||||
* @format int32
|
||||
* @example 2000
|
||||
*/
|
||||
code: number;
|
||||
/**
|
||||
* 提示信息
|
||||
* @example "请求成功"
|
||||
*/
|
||||
message: string;
|
||||
data?: object;
|
||||
};
|
||||
}
|
||||
/**
|
||||
* @description 批量删除文件
|
||||
* @tags file
|
||||
* @name DelFiles
|
||||
* @request DELETE:/api/v1/file
|
||||
*/
|
||||
export namespace DelFiles {
|
||||
export type RequestParams = {};
|
||||
export type RequestQuery = {};
|
||||
export type RequestBody = string[];
|
||||
export type RequestHeaders = {};
|
||||
export type ResponseBody = {
|
||||
/**
|
||||
* 状态码
|
||||
* @format int32
|
||||
* @example 2000
|
||||
*/
|
||||
code: number;
|
||||
/**
|
||||
* 提示信息
|
||||
* @example "请求成功"
|
||||
*/
|
||||
message: string;
|
||||
data?: object;
|
||||
};
|
||||
}
|
||||
/**
|
||||
* @description 查询
|
||||
|
|
@ -1731,20 +1977,7 @@ export namespace File {
|
|||
export type RequestQuery = {};
|
||||
export type RequestBody = UpdateFileDto;
|
||||
export type RequestHeaders = {};
|
||||
export type ResponseBody = {
|
||||
/**
|
||||
* 状态码
|
||||
* @format int32
|
||||
* @example 2000
|
||||
*/
|
||||
code: number;
|
||||
/**
|
||||
* 提示信息
|
||||
* @example "请求成功"
|
||||
*/
|
||||
message: string;
|
||||
data?: string;
|
||||
};
|
||||
export type ResponseBody = Response;
|
||||
}
|
||||
/**
|
||||
* @description 删除
|
||||
|
|
@ -1791,6 +2024,149 @@ export namespace File {
|
|||
}
|
||||
}
|
||||
|
||||
export namespace FileCategory {
|
||||
/**
|
||||
* @description 新增文件分类
|
||||
* @tags fileCategory
|
||||
* @name AddFileCategory
|
||||
* @request POST:/api/v1/fileCategorys
|
||||
*/
|
||||
export namespace AddFileCategory {
|
||||
export type RequestParams = {};
|
||||
export type RequestQuery = {};
|
||||
export type RequestBody = CreateFileCategoryDto;
|
||||
export type RequestHeaders = {};
|
||||
export type ResponseBody = {
|
||||
/**
|
||||
* 状态码
|
||||
* @format int32
|
||||
* @example 2000
|
||||
*/
|
||||
code: number;
|
||||
/**
|
||||
* 提示信息
|
||||
* @example "请求成功"
|
||||
*/
|
||||
message: string;
|
||||
data?: number;
|
||||
};
|
||||
}
|
||||
/**
|
||||
* @description 查询文件分类
|
||||
* @tags fileCategory
|
||||
* @name GetFileCategorys
|
||||
* @request GET:/api/v1/fileCategorys
|
||||
*/
|
||||
export namespace GetFileCategorys {
|
||||
export type RequestParams = {};
|
||||
export type RequestQuery = {
|
||||
/**
|
||||
* 分类名称
|
||||
* @example "风景"
|
||||
*/
|
||||
name?: 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;
|
||||
/**
|
||||
* 创建起始事件
|
||||
* @example "2020-02-02 02:02:02"
|
||||
*/
|
||||
createdFrom?: string;
|
||||
};
|
||||
export type RequestBody = never;
|
||||
export type RequestHeaders = {};
|
||||
export type ResponseBody = {
|
||||
/**
|
||||
* 状态码
|
||||
* @format int32
|
||||
* @example 2000
|
||||
*/
|
||||
code: number;
|
||||
/**
|
||||
* 提示信息
|
||||
* @example "请求成功"
|
||||
*/
|
||||
message: string;
|
||||
data?: FileCategory[];
|
||||
};
|
||||
}
|
||||
/**
|
||||
* @description 获取文件分类
|
||||
* @tags fileCategory
|
||||
* @name GetFileCategory
|
||||
* @request GET:/api/v1/fileCategorys/{id}
|
||||
*/
|
||||
export namespace GetFileCategory {
|
||||
export type RequestParams = {
|
||||
id: number;
|
||||
};
|
||||
export type RequestQuery = {};
|
||||
export type RequestBody = never;
|
||||
export type RequestHeaders = {};
|
||||
export type ResponseBody = {
|
||||
/**
|
||||
* 状态码
|
||||
* @format int32
|
||||
* @example 2000
|
||||
*/
|
||||
code: number;
|
||||
/**
|
||||
* 提示信息
|
||||
* @example "请求成功"
|
||||
*/
|
||||
message: string;
|
||||
data?: FileCategory;
|
||||
};
|
||||
}
|
||||
/**
|
||||
* @description 更新文件分类
|
||||
* @tags fileCategory
|
||||
* @name SetFileCategory
|
||||
* @request PATCH:/api/v1/fileCategorys/{id}
|
||||
*/
|
||||
export namespace SetFileCategory {
|
||||
export type RequestParams = {
|
||||
id: number;
|
||||
};
|
||||
export type RequestQuery = {};
|
||||
export type RequestBody = UpdateFileCategoryDto;
|
||||
export type RequestHeaders = {};
|
||||
export type ResponseBody = Response;
|
||||
}
|
||||
/**
|
||||
* @description 删除文件分类
|
||||
* @tags fileCategory
|
||||
* @name DelFileCategory
|
||||
* @request DELETE:/api/v1/fileCategorys/{id}
|
||||
*/
|
||||
export namespace DelFileCategory {
|
||||
export type RequestParams = {
|
||||
id: number;
|
||||
};
|
||||
export type RequestQuery = {};
|
||||
export type RequestBody = never;
|
||||
export type RequestHeaders = {};
|
||||
export type ResponseBody = Response;
|
||||
}
|
||||
}
|
||||
|
||||
export namespace Post {
|
||||
/**
|
||||
* @description 创建文章
|
||||
|
|
@ -3183,10 +3559,61 @@ export class Api<SecurityDataType extends unknown> extends HttpClient<SecurityDa
|
|||
* @name GetFiles
|
||||
* @request GET:/api/v1/file
|
||||
*/
|
||||
getFiles: (params: RequestParams = {}) => {
|
||||
return this.request<Response, any>({
|
||||
getFiles: (query: GetFilesParams, params: RequestParams = {}) => {
|
||||
return this.request<
|
||||
{
|
||||
/**
|
||||
* 状态码
|
||||
* @format int32
|
||||
* @example 2000
|
||||
*/
|
||||
code: number;
|
||||
/**
|
||||
* 提示信息
|
||||
* @example "请求成功"
|
||||
*/
|
||||
message: string;
|
||||
data?: object;
|
||||
},
|
||||
any
|
||||
>({
|
||||
path: `/api/v1/file`,
|
||||
method: "GET",
|
||||
query: query,
|
||||
format: "json",
|
||||
...params,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 批量删除文件
|
||||
*
|
||||
* @tags file
|
||||
* @name DelFiles
|
||||
* @request DELETE:/api/v1/file
|
||||
*/
|
||||
delFiles: (data: string[], params: RequestParams = {}) => {
|
||||
return this.request<
|
||||
{
|
||||
/**
|
||||
* 状态码
|
||||
* @format int32
|
||||
* @example 2000
|
||||
*/
|
||||
code: number;
|
||||
/**
|
||||
* 提示信息
|
||||
* @example "请求成功"
|
||||
*/
|
||||
message: string;
|
||||
data?: object;
|
||||
},
|
||||
any
|
||||
>({
|
||||
path: `/api/v1/file`,
|
||||
method: "DELETE",
|
||||
body: data,
|
||||
type: ContentType.Json,
|
||||
format: "json",
|
||||
...params,
|
||||
});
|
||||
|
|
@ -3232,23 +3659,7 @@ export class Api<SecurityDataType extends unknown> extends HttpClient<SecurityDa
|
|||
* @request PATCH:/api/v1/file/{id}
|
||||
*/
|
||||
setFile: (id: number, data: UpdateFileDto, params: RequestParams = {}) => {
|
||||
return this.request<
|
||||
{
|
||||
/**
|
||||
* 状态码
|
||||
* @format int32
|
||||
* @example 2000
|
||||
*/
|
||||
code: number;
|
||||
/**
|
||||
* 提示信息
|
||||
* @example "请求成功"
|
||||
*/
|
||||
message: string;
|
||||
data?: string;
|
||||
},
|
||||
any
|
||||
>({
|
||||
return this.request<Response, any>({
|
||||
path: `/api/v1/file/${id}`,
|
||||
method: "PATCH",
|
||||
body: data,
|
||||
|
|
@ -3306,6 +3717,140 @@ export class Api<SecurityDataType extends unknown> extends HttpClient<SecurityDa
|
|||
});
|
||||
},
|
||||
};
|
||||
fileCategory = {
|
||||
/**
|
||||
* 新增文件分类
|
||||
*
|
||||
* @tags fileCategory
|
||||
* @name AddFileCategory
|
||||
* @request POST:/api/v1/fileCategorys
|
||||
*/
|
||||
addFileCategory: (data: CreateFileCategoryDto, params: RequestParams = {}) => {
|
||||
return this.request<
|
||||
{
|
||||
/**
|
||||
* 状态码
|
||||
* @format int32
|
||||
* @example 2000
|
||||
*/
|
||||
code: number;
|
||||
/**
|
||||
* 提示信息
|
||||
* @example "请求成功"
|
||||
*/
|
||||
message: string;
|
||||
data?: number;
|
||||
},
|
||||
any
|
||||
>({
|
||||
path: `/api/v1/fileCategorys`,
|
||||
method: "POST",
|
||||
body: data,
|
||||
type: ContentType.Json,
|
||||
format: "json",
|
||||
...params,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 查询文件分类
|
||||
*
|
||||
* @tags fileCategory
|
||||
* @name GetFileCategorys
|
||||
* @request GET:/api/v1/fileCategorys
|
||||
*/
|
||||
getFileCategorys: (query: GetFileCategorysParams, params: RequestParams = {}) => {
|
||||
return this.request<
|
||||
{
|
||||
/**
|
||||
* 状态码
|
||||
* @format int32
|
||||
* @example 2000
|
||||
*/
|
||||
code: number;
|
||||
/**
|
||||
* 提示信息
|
||||
* @example "请求成功"
|
||||
*/
|
||||
message: string;
|
||||
data?: FileCategory[];
|
||||
},
|
||||
any
|
||||
>({
|
||||
path: `/api/v1/fileCategorys`,
|
||||
method: "GET",
|
||||
query: query,
|
||||
format: "json",
|
||||
...params,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取文件分类
|
||||
*
|
||||
* @tags fileCategory
|
||||
* @name GetFileCategory
|
||||
* @request GET:/api/v1/fileCategorys/{id}
|
||||
*/
|
||||
getFileCategory: (id: number, params: RequestParams = {}) => {
|
||||
return this.request<
|
||||
{
|
||||
/**
|
||||
* 状态码
|
||||
* @format int32
|
||||
* @example 2000
|
||||
*/
|
||||
code: number;
|
||||
/**
|
||||
* 提示信息
|
||||
* @example "请求成功"
|
||||
*/
|
||||
message: string;
|
||||
data?: FileCategory;
|
||||
},
|
||||
any
|
||||
>({
|
||||
path: `/api/v1/fileCategorys/${id}`,
|
||||
method: "GET",
|
||||
format: "json",
|
||||
...params,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 更新文件分类
|
||||
*
|
||||
* @tags fileCategory
|
||||
* @name SetFileCategory
|
||||
* @request PATCH:/api/v1/fileCategorys/{id}
|
||||
*/
|
||||
setFileCategory: (id: number, data: UpdateFileCategoryDto, params: RequestParams = {}) => {
|
||||
return this.request<Response, any>({
|
||||
path: `/api/v1/fileCategorys/${id}`,
|
||||
method: "PATCH",
|
||||
body: data,
|
||||
type: ContentType.Json,
|
||||
format: "json",
|
||||
...params,
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 删除文件分类
|
||||
*
|
||||
* @tags fileCategory
|
||||
* @name DelFileCategory
|
||||
* @request DELETE:/api/v1/fileCategorys/{id}
|
||||
*/
|
||||
delFileCategory: (id: number, params: RequestParams = {}) => {
|
||||
return this.request<Response, any>({
|
||||
path: `/api/v1/fileCategorys/${id}`,
|
||||
method: "DELETE",
|
||||
format: "json",
|
||||
...params,
|
||||
});
|
||||
},
|
||||
};
|
||||
post = {
|
||||
/**
|
||||
* 创建文章
|
||||
|
|
|
|||
|
|
@ -75,3 +75,32 @@ export const config = {
|
|||
return data;
|
||||
},
|
||||
};
|
||||
|
||||
export function initOptions({ item, model }: any, key = "options") {
|
||||
if (Array.isArray(item.options)) {
|
||||
item.nodeProps[key] = item.options;
|
||||
}
|
||||
if (item.options && typeof item.options === "object") {
|
||||
const { value, source } = item.options;
|
||||
item._updateOptions = async () => {};
|
||||
}
|
||||
if (typeof item.options === "function") {
|
||||
const loadData = item.options;
|
||||
item.nodeProps[key] = reactive([]);
|
||||
item._updateOptions = async () => {
|
||||
let data = await loadData({ item, model });
|
||||
if (Array.isArray(data?.data?.data)) {
|
||||
data = data.data.data.map((i: any) => ({
|
||||
...i,
|
||||
label: i.name,
|
||||
value: i.id,
|
||||
}));
|
||||
}
|
||||
if (Array.isArray(data)) {
|
||||
item.nodeProps[key].splice(0);
|
||||
item.nodeProps[key].push(...data);
|
||||
}
|
||||
};
|
||||
item._updateOptions();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,27 +16,7 @@ import {
|
|||
TimePicker,
|
||||
TreeSelect,
|
||||
} from "@arco-design/web-vue";
|
||||
|
||||
const initOptions = ({ item, model }: any, key = "options") => {
|
||||
if (Array.isArray(item.options)) {
|
||||
item.nodeProps[key] = item.options;
|
||||
}
|
||||
if (typeof item.options === "function") {
|
||||
const loadData = item.options;
|
||||
item.nodeProps[key] = reactive([]);
|
||||
item._updateOptions = async () => {
|
||||
let data = await loadData({ item, model });
|
||||
if (Array.isArray(data?.data?.data)) {
|
||||
data = data.data.data.map((i: any) => ({ label: i.name, value: i.id }));
|
||||
}
|
||||
if (Array.isArray(data)) {
|
||||
item.nodeProps[key].splice(0);
|
||||
item.nodeProps[key].push(...data);
|
||||
}
|
||||
};
|
||||
item._updateOptions();
|
||||
}
|
||||
};
|
||||
import { initOptions } from "./form-config";
|
||||
|
||||
/**
|
||||
* 表单项组件映射
|
||||
|
|
@ -114,7 +94,10 @@ export const nodeMap = {
|
|||
placeholder: "请选择",
|
||||
allowClear: true,
|
||||
allowSearch: true,
|
||||
options: [{}],
|
||||
options: [],
|
||||
onChange(value) {
|
||||
value;
|
||||
},
|
||||
} as InstanceType<typeof TreeSelect>["$props"],
|
||||
init: (arg: any) => initOptions(arg, "data"),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,11 +1,6 @@
|
|||
import {
|
||||
TableColumnData as BaseColumn,
|
||||
TableData as BaseData,
|
||||
Table as BaseTable,
|
||||
Message,
|
||||
} from "@arco-design/web-vue";
|
||||
import { TableColumnData as BaseColumn, TableData as BaseData, Table as BaseTable } from "@arco-design/web-vue";
|
||||
import { merge } from "lodash-es";
|
||||
import { PropType, computed, defineComponent, reactive, ref, watch } from "vue";
|
||||
import { PropType, computed, defineComponent, reactive, ref } from "vue";
|
||||
import { Form, FormInstance, FormModal, FormModalInstance, FormModalProps, FormProps } from "../form";
|
||||
import { config } from "./table.config";
|
||||
|
||||
|
|
@ -72,7 +67,7 @@ export const Table = defineComponent({
|
|||
},
|
||||
setup(props) {
|
||||
const loading = ref(false);
|
||||
const tableRef = ref<InstanceType<typeof BaseTable>>()
|
||||
const tableRef = ref<InstanceType<typeof BaseTable>>();
|
||||
const searchRef = ref<FormInstance>();
|
||||
const createRef = ref<FormModalInstance>();
|
||||
const modifyRef = ref<FormModalInstance>();
|
||||
|
|
@ -81,10 +76,16 @@ export const Table = defineComponent({
|
|||
const reloadData = () => loadData({ current: 1, pageSize: 10 });
|
||||
const openModifyModal = (data: any) => modifyRef.value?.open(data);
|
||||
|
||||
/**
|
||||
* 加载数据
|
||||
* @param pagination 自定义分页
|
||||
*/
|
||||
const loadData = async (pagination: Partial<any> = {}) => {
|
||||
const merged = { ...props.pagination, ...pagination };
|
||||
const paging = { page: merged.current, size: merged.pageSize };
|
||||
const model = searchRef.value?.getModel() ?? {};
|
||||
|
||||
// 本地加载
|
||||
if (Array.isArray(props.data)) {
|
||||
const filters = Object.entries(model);
|
||||
const data = props.data.filter((item) => {
|
||||
|
|
@ -99,6 +100,8 @@ export const Table = defineComponent({
|
|||
props.pagination.total = renderData.value.length;
|
||||
props.pagination.current = 1;
|
||||
}
|
||||
|
||||
// 远程加载
|
||||
if (typeof props.data === "function") {
|
||||
try {
|
||||
loading.value = true;
|
||||
|
|
@ -107,30 +110,19 @@ export const Table = defineComponent({
|
|||
renderData.value = data;
|
||||
props.pagination.total = total;
|
||||
props.pagination.current = paging.page;
|
||||
} catch (error) {
|
||||
const message = config.getApiErrorMessage(error);
|
||||
if (message) {
|
||||
Message.error(`提示:${message}`);
|
||||
}
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.data,
|
||||
(data) => {
|
||||
if (Array.isArray(data)) {
|
||||
renderData.value = data;
|
||||
props.pagination.total = data.length;
|
||||
watchEffect(() => {
|
||||
if (Array.isArray(props.data)) {
|
||||
renderData.value = props.data;
|
||||
props.pagination.total = props.data.length;
|
||||
props.pagination.current = 1;
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
loadData();
|
||||
|
|
|
|||
|
|
@ -248,12 +248,39 @@ export const useAniTable = (options: UseTableOptions): TableReturnType => {
|
|||
props,
|
||||
tableRef,
|
||||
refresh: () => tableRef.value?.reloadData(),
|
||||
getTableInstance() {
|
||||
return tableRef.value?.tableRef;
|
||||
},
|
||||
getSearchInstance() {
|
||||
return tableRef.value?.searchRef;
|
||||
},
|
||||
getCreateInstance() {
|
||||
return tableRef.value?.createRef;
|
||||
},
|
||||
/**
|
||||
* 获取创建表单组件实例
|
||||
*/
|
||||
getCreateFormInstance() {
|
||||
return this.getCreateInstance()?.formRef;
|
||||
},
|
||||
/**
|
||||
* 获取修改表单弹窗组件实例
|
||||
*/
|
||||
getModifyInstance() {
|
||||
return tableRef.value?.modifyRef;
|
||||
},
|
||||
/**
|
||||
* 获取修改表单组件实例
|
||||
*/
|
||||
getModifyFormInstance() {
|
||||
return this.getModifyInstance()?.formRef;
|
||||
},
|
||||
};
|
||||
const aniTable = defineComponent({
|
||||
name: "AniTableWrapper",
|
||||
setup() {
|
||||
setup(p, { slots }) {
|
||||
const onRef = (el: TableInstance) => (tableRef.value = el);
|
||||
return () => <Table ref={onRef} {...props}></Table>;
|
||||
return () => <Table ref={onRef} {...props}>{slots}</Table>;
|
||||
},
|
||||
});
|
||||
return [aniTable, context];
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
<template>
|
||||
<div class="w-[210px] h-full overflow-hidden grid grid-rows-[auto_1fr]">
|
||||
<div class="flex gap-2">
|
||||
<a-input-search allow-clear placeholder="分组名称..." class="mb-2"></a-input-search>
|
||||
<a-button @click="onCreateRow">
|
||||
<a-input-search allow-clear placeholder="文件分类" class="mb-2"></a-input-search>
|
||||
<a-button @click="formCtx.open">
|
||||
<template #icon>
|
||||
<i class="icon-park-outline-add"></i>
|
||||
</template>
|
||||
|
|
@ -13,15 +13,15 @@
|
|||
<ul class="pl-0 mt-0">
|
||||
<li
|
||||
v-for="item in list"
|
||||
:key="item.id"
|
||||
:key="item.code"
|
||||
:class="{ active: item.id === current?.id }"
|
||||
class="group flex items-center justify-between gap-1 h-8 rounded mb-2 pl-3 hover:bg-gray-100 cursor-pointer"
|
||||
>
|
||||
<div>
|
||||
<div class="flex-1 h-full flex items-center gap-2 overflow-hidden" @click="emit('change', item)">
|
||||
<i class="icon-park-outline-folder-close align-[-2px]"></i>
|
||||
{{ item.title }}
|
||||
<span class="text-xs text-gray-500"> ({{ item.count }}) </span>
|
||||
<span class="flex-1 truncate">{{ item.name }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<div class="">
|
||||
<a-dropdown>
|
||||
<a-button size="small" type="text">
|
||||
<template #icon>
|
||||
|
|
@ -29,13 +29,13 @@
|
|||
</template>
|
||||
</a-button>
|
||||
<template #content>
|
||||
<a-doption @click="onModifyRow(item)">
|
||||
<a-doption @click="formCtx.open(item)">
|
||||
<template #icon>
|
||||
<i class="icon-park-outline-edit"></i>
|
||||
</template>
|
||||
修改
|
||||
</a-doption>
|
||||
<a-doption class="!text-red-500" @click="onDeleteRow">
|
||||
<a-doption class="!text-red-500" @click="onDeleteRow(item)">
|
||||
<template #icon>
|
||||
<i class="icon-park-outline-delete"></i>
|
||||
</template>
|
||||
|
|
@ -51,86 +51,78 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { FileCategory, api } from "@/api";
|
||||
import { useAniFormModal } from "@/components";
|
||||
import { delConfirm } from "@/utils";
|
||||
import { Message } from "@arco-design/web-vue";
|
||||
import { PropType } from "vue";
|
||||
|
||||
const data = [
|
||||
{
|
||||
id: 1,
|
||||
title: "生活笔记",
|
||||
count: 23,
|
||||
defineProps({
|
||||
current: {
|
||||
type: Object as PropType<FileCategory>,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "微信头像",
|
||||
count: 52,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: "文章封面",
|
||||
count: 19,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: "山水诗画",
|
||||
count: 81,
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
title: "虾米沙雕",
|
||||
count: 12,
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
const list = ref(data);
|
||||
const emit = defineEmits(["change"]);
|
||||
const list = ref<FileCategory[]>([]);
|
||||
|
||||
const onModifyRow = (row: any) => {
|
||||
formCtx.props.title = "修改分组";
|
||||
formCtx.open(row);
|
||||
const updateFileCategories = async () => {
|
||||
const res = await api.fileCategory.getFileCategorys({ size: 0 });
|
||||
list.value = res.data.data ?? [];
|
||||
list.value.unshift({ id: undefined, name: '全部' } as any)
|
||||
list.value.length && emit("change", list.value[0]);
|
||||
};
|
||||
|
||||
const onCreateRow = () => {
|
||||
formCtx.props.title = "新建分组";
|
||||
formCtx.open();
|
||||
};
|
||||
onMounted(updateFileCategories);
|
||||
|
||||
const onDeleteRow = async () => {
|
||||
const onDeleteRow = async (row: FileCategory) => {
|
||||
await delConfirm();
|
||||
const res = await api.dictType.delDictType(row.id);
|
||||
Message.success(res.data.message);
|
||||
};
|
||||
|
||||
const [formModal, formCtx] = useAniFormModal({
|
||||
title: "修改分组",
|
||||
title: ({ model }) => (!model.id ? "新建分类" : "修改分类"),
|
||||
trigger: false,
|
||||
modalProps: {
|
||||
width: 432,
|
||||
width: 580,
|
||||
},
|
||||
model: {
|
||||
id: undefined,
|
||||
},
|
||||
items: [
|
||||
{
|
||||
field: "title",
|
||||
label: "分组名称",
|
||||
field: "name",
|
||||
label: "分类名称",
|
||||
type: "input",
|
||||
},
|
||||
{
|
||||
field: "code",
|
||||
label: "分类编码",
|
||||
type: "input",
|
||||
},
|
||||
{
|
||||
field: "description",
|
||||
label: "备注",
|
||||
type: "textarea",
|
||||
},
|
||||
],
|
||||
submit: async ({ model }) => {
|
||||
let res;
|
||||
if (model.id) {
|
||||
const item = list.value.find((i) => i.id === model.id);
|
||||
if (item) {
|
||||
item.title = model.title;
|
||||
}
|
||||
res = await api.fileCategory.setFileCategory(model.id, model);
|
||||
} else {
|
||||
const ids = list.value.map((i) => i.id);
|
||||
const maxId = Math.max.apply(null, ids);
|
||||
list.value.push({
|
||||
id: maxId,
|
||||
title: model.title,
|
||||
count: 0,
|
||||
});
|
||||
res = await api.fileCategory.addFileCategory(model);
|
||||
}
|
||||
updateFileCategories();
|
||||
return res;
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
<style lang="less" scoped>
|
||||
.active {
|
||||
color: rgb(var(--primary-6));
|
||||
background-color: rgb(var(--primary-1));
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -36,11 +36,14 @@
|
|||
<ul v-if="fileList.length" class="h-[424px] overflow-hidden p-0 m-0">
|
||||
<a-scrollbar outer-class="h-full overflow-hidden" class="h-full overflow-auto pr-[20px] divide-y">
|
||||
<li v-for="item in fileList" :key="item.uid" class="flex items-center gap-2 py-3">
|
||||
<div class="text-2xl">
|
||||
<i :class="getIconnameByMimetype(item.file?.type ?? 'video')"></i>
|
||||
</div>
|
||||
<div class="flex-1 overflow-hidden">
|
||||
<div class="truncate text-slate-900">
|
||||
{{ item.name }}
|
||||
</div>
|
||||
<div class="flex items-center justify-between gap-2 text-gray-400 mb-[-4px] mt-1">
|
||||
<div class="flex items-center justify-between gap-2 text-gray-400 mb-[-4px] mt-0.5">
|
||||
<span class="text-xs text-gray-400">
|
||||
{{ numeral(item.file?.size).format("0 b") }}
|
||||
</span>
|
||||
|
|
@ -48,10 +51,8 @@
|
|||
<span v-if="item.status === 'init'"> </span>
|
||||
<span v-else-if="item.status === 'uploading'">
|
||||
<span class="text-xs">
|
||||
速度:{{ numeral(fileMap.get(item.uid)?.speed || 0).format("0 b") }}/s, 进度:{{
|
||||
Math.floor((item.percent || 0) * 100)
|
||||
}}
|
||||
%
|
||||
速度:{{ numeral(fileMap.get(item.uid)?.speed || 0).format("0 b") }}/s,
|
||||
进度:{{ Math.floor((item.percent || 0) * 100) }}%
|
||||
</span>
|
||||
</span>
|
||||
<span v-else-if="item.status === 'done'" class="text-green-600">
|
||||
|
|
@ -99,6 +100,7 @@ import { delConfirm } from "@/utils";
|
|||
import { FileItem, Message, RequestOption, UploadInstance } from "@arco-design/web-vue";
|
||||
import axios from "axios";
|
||||
import numeral from "numeral";
|
||||
import { getIconnameByMimetype } from "./util";
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: "success", item: FileItem): void;
|
||||
|
|
@ -121,6 +123,9 @@ const fileMap = reactive<
|
|||
>
|
||||
>(new Map());
|
||||
|
||||
/**
|
||||
* 统计信息
|
||||
*/
|
||||
const stat = computed(() => {
|
||||
const result = {
|
||||
initCount: 0,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
const typeIconMap: Record<string, string> = {
|
||||
video: "icon-park-outline-video-file",
|
||||
audio: "icon-park-outline-audio-file",
|
||||
image: "icon-park-outline-file-pdf",
|
||||
text: "icon-park-outline-file-txt",
|
||||
application: "icon-park-outline-file-code",
|
||||
unknown: "icon-park-outline-file-question",
|
||||
};
|
||||
|
||||
const imageIconMap: Record<string, string> = {
|
||||
jpg: "icon-park-outline-file-jpg",
|
||||
png: "icon-park-outline-file-jpg",
|
||||
};
|
||||
|
||||
function getIconnameByMimetype(mimetype: string) {
|
||||
const [type, subtype] = mimetype.split("/");
|
||||
return typeIconMap[type] || typeIconMap.unknown;
|
||||
}
|
||||
|
||||
export { getIconnameByMimetype };
|
||||
|
|
@ -1,16 +1,16 @@
|
|||
<template>
|
||||
<BreadPage>
|
||||
<div class="overflow-hidden h-full grid grid-cols-[auto_1fr] gap-4">
|
||||
<ani-group></ani-group>
|
||||
<ani-group :current="current" @change="onCategoryChange"></ani-group>
|
||||
<div>
|
||||
<Table v-bind="table">
|
||||
<file-table>
|
||||
<template #action>
|
||||
<ani-upload></ani-upload>
|
||||
<a-button type="outline" status="danger" :disabled="!selected.length" @click="onDeleteMany">
|
||||
<a-button type="primary" status="danger" :disabled="!selected.length" @click="onDeleteMany">
|
||||
批量删除
|
||||
</a-button>
|
||||
</template>
|
||||
</Table>
|
||||
</file-table>
|
||||
<a-image-preview v-model:visible="visible" :src="image"></a-image-preview>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -18,16 +18,18 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="tsx">
|
||||
import { api } from "@/api";
|
||||
import { Table, createColumn, updateColumn, useTable } from "@/components";
|
||||
import { FileCategory, api } from "@/api";
|
||||
import { createColumn, updateColumn, useAniTable } from "@/components";
|
||||
import { delConfirm } from "@/utils";
|
||||
import numeral from "numeral";
|
||||
import AniGroup from "./components/group.vue";
|
||||
import AniUpload from "./components/upload.vue";
|
||||
import { Message } from "@arco-design/web-vue";
|
||||
|
||||
const visible = ref(false);
|
||||
const image = ref("");
|
||||
const selected = ref<number[]>([]);
|
||||
const current = ref<FileCategory>();
|
||||
const preview = (record: any) => {
|
||||
if (!record.mimetype.startsWith("image")) {
|
||||
window.open(record.path, "_blank");
|
||||
|
|
@ -39,6 +41,18 @@ const preview = (record: any) => {
|
|||
|
||||
const onDeleteMany = async () => {
|
||||
await delConfirm();
|
||||
const res = await api.file.delFiles(selected.value as any[]);
|
||||
selected.value = [];
|
||||
Message.success(res.data.message);
|
||||
fileCtx.refresh();
|
||||
};
|
||||
|
||||
const onCategoryChange = (category: FileCategory) => {
|
||||
if (fileCtx.props.search?.model) {
|
||||
fileCtx.props.search.model.categoryId = category.id;
|
||||
}
|
||||
current.value = category;
|
||||
fileCtx.refresh();
|
||||
};
|
||||
|
||||
const getIcon = (mimetype: string) => {
|
||||
|
|
@ -57,9 +71,9 @@ const getIcon = (mimetype: string) => {
|
|||
return "icon-file-iunknown";
|
||||
};
|
||||
|
||||
const table = useTable({
|
||||
const [fileTable, fileCtx] = useAniTable({
|
||||
data: async (model, paging) => {
|
||||
return api.file.getFiles();
|
||||
return api.file.getFiles({ ...model, ...paging });
|
||||
},
|
||||
tableProps: {
|
||||
rowSelection: {
|
||||
|
|
@ -121,6 +135,9 @@ const table = useTable({
|
|||
],
|
||||
search: {
|
||||
button: false,
|
||||
model: {
|
||||
categoryId: undefined,
|
||||
},
|
||||
items: [
|
||||
{
|
||||
field: "name",
|
||||
|
|
@ -143,6 +160,12 @@ const table = useTable({
|
|||
width: 580,
|
||||
},
|
||||
items: [
|
||||
{
|
||||
field: "categoryId",
|
||||
label: "分类",
|
||||
type: "select",
|
||||
options: () => api.fileCategory.getFileCategorys({ size: 0 }),
|
||||
},
|
||||
{
|
||||
field: "name",
|
||||
label: "名称",
|
||||
|
|
|
|||
|
|
@ -37,16 +37,14 @@ const [dictTable, dict] = useAniTable({
|
|||
{
|
||||
title: "字典项",
|
||||
dataIndex: "name",
|
||||
render: ({ record }) => {
|
||||
return (
|
||||
render: ({ record }) => (
|
||||
<div>
|
||||
<div>
|
||||
<span class="text-gray-900">{record.name}</span>: {record.code}
|
||||
{record.name}<span class="text-gray-400 ml-2 text-xs">{record.code}</span>
|
||||
</div>
|
||||
<div class="text-gray-400 text-xs">{record.description}</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
),
|
||||
},
|
||||
createColumn,
|
||||
updateColumn,
|
||||
|
|
@ -88,7 +86,7 @@ const [dictTable, dict] = useAniTable({
|
|||
],
|
||||
},
|
||||
create: {
|
||||
title: '新增字典',
|
||||
title: "新增字典",
|
||||
model: {
|
||||
typeId: undefined,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import { api } from "@/api";
|
|||
import { createColumn, updateColumn, useAniTable } from "@/components";
|
||||
import { MenuTypes, MenuType } from "@/constants/menu";
|
||||
import { flatedMenus } from "@/router";
|
||||
import { listToTree } from "@/utils/listToTree";
|
||||
|
||||
const menuArr = flatedMenus.map((i) => ({ label: i.title, value: i.id }));
|
||||
|
||||
|
|
@ -24,23 +25,21 @@ const toggleExpand = () => {
|
|||
|
||||
const [menuTable, menu] = useAniTable({
|
||||
data: (search, paging) => {
|
||||
return api.menu.getMenus({ ...search, ...paging, tree: true });
|
||||
return api.menu.getMenus({ ...search, ...paging, tree: true, size: 0 });
|
||||
},
|
||||
tableProps: {
|
||||
defaultExpandAllRows: true,
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
title: () => {
|
||||
return (
|
||||
title: () => (
|
||||
<span>
|
||||
菜单名称
|
||||
<a-link class="ml-1 select-none" onClick={toggleExpand}>
|
||||
{expanded.value ? "收起全部" : "展开全部"}
|
||||
</a-link>
|
||||
</span>
|
||||
);
|
||||
},
|
||||
),
|
||||
dataIndex: "name",
|
||||
render({ record }) {
|
||||
let id = "";
|
||||
|
|
@ -125,19 +124,24 @@ const [menuTable, menu] = useAniTable({
|
|||
label: "父级",
|
||||
type: "treeSelect",
|
||||
async options() {
|
||||
const res = await api.menu.getMenus({ size: 0, tree: true });
|
||||
const data = res.data.data;
|
||||
const res = await api.menu.getMenus({ size: 0 });
|
||||
const data = res.data.data?.filter((i) => i.type !== MenuType.BUTTON) ?? [];
|
||||
for (const item of data) {
|
||||
const type = MenuTypes.fmt(item.type);
|
||||
// @ts-ignore
|
||||
item.icon = () => `[${type}]`;
|
||||
}
|
||||
const list = listToTree(data);
|
||||
return [
|
||||
{
|
||||
id: 0,
|
||||
name: "主类目",
|
||||
children: data,
|
||||
children: list,
|
||||
},
|
||||
];
|
||||
},
|
||||
nodeProps: {
|
||||
fieldNames: {
|
||||
icon: undefined,
|
||||
key: "id",
|
||||
title: "name",
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in New Issue