feat: 添加文件分类功能

master
luoer 2023-11-02 17:37:40 +08:00
parent 93c9c3185a
commit c84da369cf
14 changed files with 804 additions and 186 deletions

2
.env
View File

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

View File

@ -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 = {
/**
*

View File

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

View File

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

View File

@ -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;
props.pagination.current = 1;
}
},
{
immediate: true,
watchEffect(() => {
if (Array.isArray(props.data)) {
renderData.value = props.data;
props.pagination.total = props.data.length;
props.pagination.current = 1;
}
);
});
onMounted(() => {
loadData();

View File

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

View File

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

View File

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

View File

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

View File

@ -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: "名称",

View File

@ -37,16 +37,14 @@ const [dictTable, dict] = useAniTable({
{
title: "字典项",
dataIndex: "name",
render: ({ record }) => {
return (
render: ({ record }) => (
<div>
<div>
<div>
<span class="text-gray-900">{record.name}</span>: {record.code}
</div>
<div class="text-gray-400 text-xs">{record.description}</div>
{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,
},

View File

@ -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 (
<span>
菜单名称
<a-link class="ml-1 select-none" onClick={toggleExpand}>
{expanded.value ? "收起全部" : "展开全部"}
</a-link>
</span>
);
},
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",
},