feat: 优化表格组件

master
luoer 2023-08-02 20:15:48 +08:00
parent 5feab8d981
commit f7818bda5c
13 changed files with 1757 additions and 1589 deletions

30
.env
View File

@ -1,42 +1,30 @@
# ============================================================ # =====================================================================================
# 项目配置文件 # 应用配置
# ============================================================ # =====================================================================================
# 网站标题 # 网站标题
VITE_APP_TITLE = 绝弹管理系统 VITE_APP_TITLE = 绝弹管理系统
# 网站副标题 # 网站副标题
VITE_APP_SUBTITLE = 快速开发web应用的模板工具 VITE_APP_SUBTITLE = 快速开发web应用的模板工具
# Axios基本URL # Axios基本URL
VITE_APP_API_BASE_URL = /api VITE_APP_API_BASE_URL = /api
# =====================================================================================
# ============================================================
# 开发设置 # 开发设置
# ============================================================ # =====================================================================================
# API接口地址(开发环境) # API接口地址(开发环境)
VITE_API_BASE_URL = http://127.0.0.1:3030 VITE_API_BASE_URL = http://127.0.0.1:3030
# API代理地址(开发环境) # API代理地址(开发环境)
VITE_API_PROXY_URL = /api VITE_API_PROXY_URL = /api
# API文档地址(开发环境) 备注需为openapi规范的json文件 # API文档地址(开发环境) 备注需为openapi规范的json文件
VITE_API_DOCS_URL = http://127.0.0.1:3030/openapi.json VITE_API_DOCS_URL = http://127.0.0.1:3030/openapi.json
# 端口号(开发环境) # 端口号(开发环境)
VITE_DEV_PORT = 3020 VITE_DEV_PORT = 3020
# 主机地址(开发环境) # 主机地址(开发环境)
VITE_DEV_HOST = 0.0.0.0 VITE_DEV_HOST = 0.0.0.0
# =====================================================================================
# ============================================================
# 构建设置 # 构建设置
# ============================================================ # =====================================================================================
# 构建时加载的文件后缀.
# 构建时加载的文件后缀. 例如设置为todo则会首先尝试加载index.todo.vue文件不存在时再加载index.vue文件 # 例如设置为todo则会首先尝试加载index.todo.vue文件不存在时再加载index.vue文件
VITE_BUILD_EXTENSION = todo VITE_BUILD_EXTENSION = todo

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,4 @@
## 修改
route-docs.ejs
- 移除 `@description` 关键字

View File

@ -1,2 +0,0 @@
# 修改
- procedure-call.ejs 添加return

View File

@ -4,7 +4,7 @@ const { _, formatDescription, fmtToJSDocLine, pascalCase, require } = utils;
const { raw, request, routeName } = route; const { raw, request, routeName } = route;
const jsDocDescription = raw.description ? const jsDocDescription = raw.description ?
` * @description ${formatDescription(raw.description, true)}` : ` * ${formatDescription(raw.description, true)}` :
fmtToJSDocLine('No description', { eol: false }); fmtToJSDocLine('No description', { eol: false });
const jsDocLines = _.compact([ const jsDocLines = _.compact([
_.size(raw.tags) && ` * @tags ${raw.tags.join(", ")}`, _.size(raw.tags) && ` * @tags ${raw.tags.join(", ")}`,

View File

@ -178,10 +178,6 @@ export interface UpdateRoleDto {
permissions?: Permission[]; permissions?: Permission[];
} }
export type CreatePostDto = object;
export type UpdatePostDto = object;
export interface CreatePermissionDto { export interface CreatePermissionDto {
name: string; name: string;
slug: string; slug: string;
@ -194,16 +190,68 @@ export interface UpdatePermissionDto {
description?: string; description?: string;
} }
export interface CreateUploadDto {
/** @format binary */
file: File;
}
export type CreatePostDto = object;
export interface Post {
/**
*
* @example "文章标题"
*/
title: string;
/**
*
* @example "文章描述"
*/
description: string;
/**
*
* @example "文章内容"
*/
content: string;
/**
*
* @example "文章作者"
*/
author: User;
}
export type UpdatePostDto = object;
export interface Response {
/**
*
* @format int32
* @example 2000
*/
code: number;
/**
*
* @example "请求成功"
*/
message: string;
}
export interface GetUsersParams { export interface GetUsersParams {
/**
*
* @example "绝弹"
*/
nickname?: string; nickname?: string;
/** /**
* *
* @min 1 * @min 1
* @example 1
*/ */
page?: number; page?: number;
/** /**
* *
* @min 1 * @min 1
* @example 10
*/ */
size?: number; size?: number;
} }
@ -339,7 +387,7 @@ export class HttpClient<SecurityDataType = unknown> {
} }
/** /**
* @title * @title Appnify
* @version 1.0 * @version 1.0
* @externalDocs /openapi.json * @externalDocs /openapi.json
* @contact * @contact
@ -349,15 +397,19 @@ export class HttpClient<SecurityDataType = unknown> {
export class Api<SecurityDataType extends unknown> extends HttpClient<SecurityDataType> { export class Api<SecurityDataType extends unknown> extends HttpClient<SecurityDataType> {
user = { user = {
/** /**
* No description *
* *
* @tags user * @tags user
* @name AddUser * @name AddUser
* @summary
* @request POST:/api/v1/users * @request POST:/api/v1/users
*/ */
addUser: (data: CreateUserDto, params: RequestParams = {}) => { addUser: (data: CreateUserDto, params: RequestParams = {}) => {
return this.request<number, any>({ return this.request<
Response & {
data?: number;
},
any
>({
path: `/api/v1/users`, path: `/api/v1/users`,
method: "POST", method: "POST",
body: data, body: data,
@ -372,11 +424,16 @@ export class Api<SecurityDataType extends unknown> extends HttpClient<SecurityDa
* *
* @tags user * @tags user
* @name GetUsers * @name GetUsers
* @summary * @summary
* @request GET:/api/v1/users * @request GET:/api/v1/users
*/ */
getUsers: (query: GetUsersParams, params: RequestParams = {}) => { getUsers: (query: GetUsersParams, params: RequestParams = {}) => {
return this.request<User[], any>({ return this.request<
Response & {
data?: User[];
},
any
>({
path: `/api/v1/users`, path: `/api/v1/users`,
method: "GET", method: "GET",
query: query, query: query,
@ -394,7 +451,12 @@ export class Api<SecurityDataType extends unknown> extends HttpClient<SecurityDa
* @request GET:/api/v2/users/{id} * @request GET:/api/v2/users/{id}
*/ */
getUserv2: (id: number, params: RequestParams = {}) => { getUserv2: (id: number, params: RequestParams = {}) => {
return this.request<User, any>({ return this.request<
Response & {
data?: User;
},
any
>({
path: `/api/v2/users/${id}`, path: `/api/v2/users/${id}`,
method: "GET", method: "GET",
format: "json", format: "json",
@ -406,11 +468,11 @@ export class Api<SecurityDataType extends unknown> extends HttpClient<SecurityDa
* No description * No description
* *
* @tags user * @tags user
* @name SetUser * @name UpdateUser
* @summary * @summary
* @request PATCH:/api/v1/users/{id} * @request PATCH:/api/v1/users/{id}
*/ */
setUser: (id: number, data: UpdateUserDto, params: RequestParams = {}) => { updateUser: (id: number, data: UpdateUserDto, params: RequestParams = {}) => {
return this.request<void, any>({ return this.request<void, any>({
path: `/api/v1/users/${id}`, path: `/api/v1/users/${id}`,
method: "PATCH", method: "PATCH",
@ -424,11 +486,11 @@ export class Api<SecurityDataType extends unknown> extends HttpClient<SecurityDa
* No description * No description
* *
* @tags user * @tags user
* @name DelUser * @name DeleteUser
* @summary * @summary
* @request DELETE:/api/v1/users/{id} * @request DELETE:/api/v1/users/{id}
*/ */
delUser: (id: number, params: RequestParams = {}) => { deleteUser: (id: number, params: RequestParams = {}) => {
return this.request<void, any>({ return this.request<void, any>({
path: `/api/v1/users/${id}`, path: `/api/v1/users/${id}`,
method: "DELETE", method: "DELETE",
@ -446,7 +508,12 @@ export class Api<SecurityDataType extends unknown> extends HttpClient<SecurityDa
* @request POST:/api/v1/auth/login * @request POST:/api/v1/auth/login
*/ */
login: (data: AuthUserDto, params: RequestParams = {}) => { login: (data: AuthUserDto, params: RequestParams = {}) => {
return this.request<string, void>({ return this.request<
Response & {
data?: string;
},
void
>({
path: `/api/v1/auth/login`, path: `/api/v1/auth/login`,
method: "POST", method: "POST",
body: data, body: data,
@ -466,7 +533,12 @@ export class Api<SecurityDataType extends unknown> extends HttpClient<SecurityDa
* @request POST:/api/v1/roles * @request POST:/api/v1/roles
*/ */
addRole: (data: CreateRoleDto, params: RequestParams = {}) => { addRole: (data: CreateRoleDto, params: RequestParams = {}) => {
return this.request<number, any>({ return this.request<
Response & {
data?: number;
},
any
>({
path: `/api/v1/roles`, path: `/api/v1/roles`,
method: "POST", method: "POST",
body: data, body: data,
@ -501,7 +573,12 @@ export class Api<SecurityDataType extends unknown> extends HttpClient<SecurityDa
* @request GET:/api/v1/roles/{id} * @request GET:/api/v1/roles/{id}
*/ */
getRole: (id: string, params: RequestParams = {}) => { getRole: (id: string, params: RequestParams = {}) => {
return this.request<string, any>({ return this.request<
Response & {
data?: string;
},
any
>({
path: `/api/v1/roles/${id}`, path: `/api/v1/roles/${id}`,
method: "GET", method: "GET",
format: "json", format: "json",
@ -518,12 +595,11 @@ export class Api<SecurityDataType extends unknown> extends HttpClient<SecurityDa
* @request PATCH:/api/v1/roles/{id} * @request PATCH:/api/v1/roles/{id}
*/ */
updateRole: (id: string, data: UpdateRoleDto, params: RequestParams = {}) => { updateRole: (id: string, data: UpdateRoleDto, params: RequestParams = {}) => {
return this.request<string, any>({ return this.request<void, any>({
path: `/api/v1/roles/${id}`, path: `/api/v1/roles/${id}`,
method: "PATCH", method: "PATCH",
body: data, body: data,
type: ContentType.Json, type: ContentType.Json,
format: "json",
...params, ...params,
}); });
}, },
@ -537,7 +613,12 @@ export class Api<SecurityDataType extends unknown> extends HttpClient<SecurityDa
* @request DELETE:/api/v1/roles/{id} * @request DELETE:/api/v1/roles/{id}
*/ */
delRole: (id: string, params: RequestParams = {}) => { delRole: (id: string, params: RequestParams = {}) => {
return this.request<string, any>({ return this.request<
Response & {
data?: string;
},
any
>({
path: `/api/v1/roles/${id}`, path: `/api/v1/roles/${id}`,
method: "DELETE", method: "DELETE",
format: "json", format: "json",
@ -545,181 +626,6 @@ export class Api<SecurityDataType extends unknown> extends HttpClient<SecurityDa
}); });
}, },
}; };
upload = {
/**
* No description
*
* @tags upload
* @name Upload
* @summary
* @request POST:/api/v1/upload
*/
upload: (params: RequestParams = {}) => {
return this.request<number, any>({
path: `/api/v1/upload`,
method: "POST",
format: "json",
...params,
});
},
/**
* No description
*
* @tags upload
* @name GetUploads
* @summary
* @request GET:/api/v1/upload
*/
getUploads: (params: RequestParams = {}) => {
return this.request<void, any>({
path: `/api/v1/upload`,
method: "GET",
...params,
});
},
/**
* No description
*
* @tags upload
* @name GetUpload
* @summary
* @request GET:/api/v1/upload/{id}
*/
getUpload: (id: string, params: RequestParams = {}) => {
return this.request<string, any>({
path: `/api/v1/upload/${id}`,
method: "GET",
format: "json",
...params,
});
},
/**
* No description
*
* @tags upload
* @name UpdateUpload
* @summary
* @request PATCH:/api/v1/upload/{id}
*/
updateUpload: (id: string, params: RequestParams = {}) => {
return this.request<string, any>({
path: `/api/v1/upload/${id}`,
method: "PATCH",
format: "json",
...params,
});
},
/**
* No description
*
* @tags upload
* @name DelUpload
* @summary
* @request DELETE:/api/v1/upload/{id}
*/
delUpload: (id: string, params: RequestParams = {}) => {
return this.request<string, any>({
path: `/api/v1/upload/${id}`,
method: "DELETE",
format: "json",
...params,
});
},
};
post = {
/**
* No description
*
* @tags post
* @name AddPost
* @summary
* @request POST:/api/v1/post
*/
addPost: (data: CreatePostDto, params: RequestParams = {}) => {
return this.request<string, any>({
path: `/api/v1/post`,
method: "POST",
body: data,
type: ContentType.Json,
format: "json",
...params,
});
},
/**
* No description
*
* @tags post
* @name GetPosts
* @summary
* @request GET:/api/v1/post
*/
getPosts: (params: RequestParams = {}) => {
return this.request<string, any>({
path: `/api/v1/post`,
method: "GET",
format: "json",
...params,
});
},
/**
* No description
*
* @tags post
* @name GetPost
* @summary
* @request GET:/api/v1/post/{id}
*/
getPost: (id: string, params: RequestParams = {}) => {
return this.request<string, any>({
path: `/api/v1/post/${id}`,
method: "GET",
format: "json",
...params,
});
},
/**
* No description
*
* @tags post
* @name UpdatePost
* @summary
* @request PATCH:/api/v1/post/{id}
*/
updatePost: (id: string, data: UpdatePostDto, params: RequestParams = {}) => {
return this.request<string, any>({
path: `/api/v1/post/${id}`,
method: "PATCH",
body: data,
type: ContentType.Json,
format: "json",
...params,
});
},
/**
* No description
*
* @tags post
* @name DelPost
* @summary
* @request DELETE:/api/v1/post/{id}
*/
delPost: (id: string, params: RequestParams = {}) => {
return this.request<string, any>({
path: `/api/v1/post/${id}`,
method: "DELETE",
format: "json",
...params,
});
},
};
permission = { permission = {
/** /**
* No description * No description
@ -730,7 +636,12 @@ export class Api<SecurityDataType extends unknown> extends HttpClient<SecurityDa
* @request POST:/api/v1/permissions * @request POST:/api/v1/permissions
*/ */
addPermission: (data: CreatePermissionDto, params: RequestParams = {}) => { addPermission: (data: CreatePermissionDto, params: RequestParams = {}) => {
return this.request<number, any>({ return this.request<
Response & {
data?: number;
},
any
>({
path: `/api/v1/permissions`, path: `/api/v1/permissions`,
method: "POST", method: "POST",
body: data, body: data,
@ -765,7 +676,12 @@ export class Api<SecurityDataType extends unknown> extends HttpClient<SecurityDa
* @request GET:/api/v1/permissions/{id} * @request GET:/api/v1/permissions/{id}
*/ */
getPermission: (id: string, params: RequestParams = {}) => { getPermission: (id: string, params: RequestParams = {}) => {
return this.request<string, any>({ return this.request<
Response & {
data?: string;
},
any
>({
path: `/api/v1/permissions/${id}`, path: `/api/v1/permissions/${id}`,
method: "GET", method: "GET",
format: "json", format: "json",
@ -782,12 +698,11 @@ export class Api<SecurityDataType extends unknown> extends HttpClient<SecurityDa
* @request PATCH:/api/v1/permissions/{id} * @request PATCH:/api/v1/permissions/{id}
*/ */
updatePermission: (id: string, data: UpdatePermissionDto, params: RequestParams = {}) => { updatePermission: (id: string, data: UpdatePermissionDto, params: RequestParams = {}) => {
return this.request<string, any>({ return this.request<void, any>({
path: `/api/v1/permissions/${id}`, path: `/api/v1/permissions/${id}`,
method: "PATCH", method: "PATCH",
body: data, body: data,
type: ContentType.Json, type: ContentType.Json,
format: "json",
...params, ...params,
}); });
}, },
@ -801,7 +716,12 @@ export class Api<SecurityDataType extends unknown> extends HttpClient<SecurityDa
* @request DELETE:/api/v1/permissions/{id} * @request DELETE:/api/v1/permissions/{id}
*/ */
delPermission: (id: string, params: RequestParams = {}) => { delPermission: (id: string, params: RequestParams = {}) => {
return this.request<string, any>({ return this.request<
Response & {
data?: string;
},
any
>({
path: `/api/v1/permissions/${id}`, path: `/api/v1/permissions/${id}`,
method: "DELETE", method: "DELETE",
format: "json", format: "json",
@ -809,4 +729,208 @@ export class Api<SecurityDataType extends unknown> extends HttpClient<SecurityDa
}); });
}, },
}; };
upload = {
/**
* No description
*
* @tags upload
* @name Upload
* @summary
* @request POST:/api/v1/upload
*/
upload: (data: CreateUploadDto, params: RequestParams = {}) => {
return this.request<
Response & {
data?: number;
},
any
>({
path: `/api/v1/upload`,
method: "POST",
body: data,
type: ContentType.FormData,
format: "json",
...params,
});
},
/**
* No description
*
* @tags upload
* @name GetUploads
* @summary
* @request GET:/api/v1/upload
*/
getUploads: (params: RequestParams = {}) => {
return this.request<void, any>({
path: `/api/v1/upload`,
method: "GET",
...params,
});
},
/**
* No description
*
* @tags upload
* @name GetUpload
* @summary
* @request GET:/api/v1/upload/{id}
*/
getUpload: (id: string, params: RequestParams = {}) => {
return this.request<
Response & {
data?: string;
},
any
>({
path: `/api/v1/upload/${id}`,
method: "GET",
format: "json",
...params,
});
},
/**
* No description
*
* @tags upload
* @name UpdateUpload
* @summary
* @request PATCH:/api/v1/upload/{id}
*/
updateUpload: (id: string, params: RequestParams = {}) => {
return this.request<
Response & {
data?: string;
},
any
>({
path: `/api/v1/upload/${id}`,
method: "PATCH",
format: "json",
...params,
});
},
/**
* No description
*
* @tags upload
* @name DelUpload
* @summary
* @request DELETE:/api/v1/upload/{id}
*/
delUpload: (id: string, params: RequestParams = {}) => {
return this.request<
Response & {
data?: string;
},
any
>({
path: `/api/v1/upload/${id}`,
method: "DELETE",
format: "json",
...params,
});
},
};
post = {
/**
* No description
*
* @tags post
* @name AddPost
* @summary
* @request POST:/api/v1/post
*/
addPost: (data: CreatePostDto, params: RequestParams = {}) => {
return this.request<
Response & {
data?: number;
},
any
>({
path: `/api/v1/post`,
method: "POST",
body: data,
type: ContentType.Json,
format: "json",
...params,
});
},
/**
* No description
*
* @tags post
* @name GetPosts
* @summary
* @request GET:/api/v1/post
*/
getPosts: (params: RequestParams = {}) => {
return this.request<void, any>({
path: `/api/v1/post`,
method: "GET",
...params,
});
},
/**
* No description
*
* @tags post
* @name GetPost
* @summary
* @request GET:/api/v1/post/{id}
*/
getPost: (id: string, params: RequestParams = {}) => {
return this.request<
Response & {
data?: Post;
},
any
>({
path: `/api/v1/post/${id}`,
method: "GET",
format: "json",
...params,
});
},
/**
* No description
*
* @tags post
* @name UpdatePost
* @summary
* @request PATCH:/api/v1/post/{id}
*/
updatePost: (id: string, data: UpdatePostDto, params: RequestParams = {}) => {
return this.request<void, any>({
path: `/api/v1/post/${id}`,
method: "PATCH",
body: data,
type: ContentType.Json,
...params,
});
},
/**
* No description
*
* @tags post
* @name DelPost
* @summary
* @request DELETE:/api/v1/post/{id}
*/
delPost: (id: string, params: RequestParams = {}) => {
return this.request<void, any>({
path: `/api/v1/post/${id}`,
method: "DELETE",
...params,
});
},
};
} }

View File

@ -25,7 +25,7 @@ export const FormModal = defineComponent({
* *
*/ */
trigger: { trigger: {
type: [Boolean, Object] as PropType< type: [Boolean, Function, Object] as PropType<
| boolean | boolean
| ((props: { model: any; items: any[] }) => VNode) | ((props: { model: any; items: any[] }) => VNode)
| { | {

View File

@ -49,7 +49,8 @@ export const config = {
}, },
columnButtonBase: { columnButtonBase: {
buttonProps: { buttonProps: {
type: "primary", // type: "text",
size: "mini",
}, },
}, },
columnButtonDelete: { columnButtonDelete: {

View File

@ -5,7 +5,6 @@ import { Form, FormInstance, FormModal, FormModalInstance, FormModalProps, FormP
import { config } from "./table.config"; import { config } from "./table.config";
type DataFn = (search: Record<string, any>, paging: { page: number; size: number }) => Promise<any>; type DataFn = (search: Record<string, any>, paging: { page: number; size: number }) => Promise<any>;
type Data = BaseData[] | DataFn;
/** /**
* *
@ -16,9 +15,10 @@ export const Table = defineComponent({
props: { props: {
/** /**
* *
* @description `{ data: BaseData[], total: number }`
*/ */
data: { data: {
type: [Array, Function] as PropType<Data>, type: [Array, Function] as PropType<BaseData[] | DataFn>,
}, },
/** /**
* *
@ -159,9 +159,16 @@ export const Table = defineComponent({
<div class={`mb-2 flex justify-between ${!this.inlined && "mt-2"}`}> <div class={`mb-2 flex justify-between ${!this.inlined && "mt-2"}`}>
<div class="flex-1 flex gap-2"> <div class="flex-1 flex gap-2">
{this.create && <FormModal ref="createRef" onSubmited={this.reloadData} {...(this.create as any)}></FormModal>} {this.create && (
<FormModal ref="createRef" onSubmited={this.reloadData} {...(this.create as any)}></FormModal>
)}
{this.modify && ( {this.modify && (
<FormModal ref="modifyRef" onSubmited={this.reloadData} trigger={false} {...(this.modify as any)}></FormModal> <FormModal
{...(this.modify as any)}
ref="modifyRef"
onSubmited={this.reloadData}
trigger={false}
></FormModal>
)} )}
{this.$slots.action?.()} {this.$slots.action?.()}
</div> </div>

View File

@ -20,72 +20,71 @@ interface UseColumnRenderOptions {
export interface TableColumnButton { export interface TableColumnButton {
/** /**
* button text *
*/ */
text?: string; text?: string;
/** /**
* button type *
*/ */
type?: "delete" | "modify"; type?: "delete" | "modify";
/** /**
* onClick callback *
*/ */
onClick?: (data: UseColumnRenderOptions, openModify?: (model: Record<string, any>) => void) => void; onClick?: (data: UseColumnRenderOptions, openModify?: (model: Record<string, any>) => void) => void;
/** /**
* disable button dynamicly *
*/ */
disabled?: (data: UseColumnRenderOptions) => boolean; disabled?: (data: UseColumnRenderOptions) => boolean;
/** /**
* show or hide button dynamicly *
*/ */
visible?: (data: UseColumnRenderOptions) => boolean; visible?: (data: UseColumnRenderOptions) => boolean;
/** /**
* props for `Button` * props
*/ */
buttonProps?: Partial<Omit<InstanceType<typeof Link>["$props"], "onClick" | "disabled">>; buttonProps?: Partial<Omit<InstanceType<typeof Link>["$props"], "onClick" | "disabled">>;
} }
export interface UseTableColumn extends TableColumnData { export interface UseTableColumn extends TableColumnData {
/** /**
* column type *
*/ */
type?: "index" | "button"; type?: "index" | "button";
/** /**
* only for `type: "button"` *
*/ */
buttons?: TableColumnButton[]; buttons?: TableColumnButton[];
} }
type ExtendableFormItem = (
| string
| ({
/**
* common.itemsfield
*/
extend?: string;
} & Partial<IFormItem>)
| IFormItem
)[];
export interface UseTableOptions extends Omit<TableProps, "search" | "create" | "modify" | "columns"> { export interface UseTableOptions extends Omit<TableProps, "search" | "create" | "modify" | "columns"> {
/** /**
* columns config, extends from `TableColumnData` *
* @see https://arco.design/web-vue/components/table/#tablecolumn * @see https://arco.design/web-vue/components/table/#tablecolumn
*/ */
columns: UseTableColumn[]; columns: UseTableColumn[];
/** /**
* search form config *
* @see FormProps * @see FormProps
*/ */
search?: Partial<{ search?: Partial<
[k in keyof FormProps]: k extends "items" ? ExtendableFormItem : FormProps[k]; Omit<FormProps, "items"> & {
}>; /**
*
*/
items?: (Partial<IFormItem> & {
/**
* common.itemsfield
*/
extend?: string;
})[];
}
>;
/** /**
* common props for `create` and `modify` modal * common props for `create` and `modify` modal
* @see FormModalProps * @see FormModalProps
@ -94,19 +93,22 @@ export interface UseTableOptions extends Omit<TableProps, "search" | "create" |
/** /**
* *
*/ */
create?: Partial< create?: FormModalProps;
{
[k in keyof FormModalProps]: k extends "items"
? (string | (IFormItem & { extend: string }))[]
: FormModalProps[k];
} & { extend: boolean }
>;
/** /**
* *
*/ */
modify?: Partial< modify?: Partial<
{ [k in keyof FormModalProps]: k extends "items" ? (string | IFormItem)[] : FormModalProps[k] } & { Omit<FormModalProps, "items"> & {
/**
* `create`
*/
extend: boolean; extend: boolean;
items?: (FormModalProps["items"][number] & {
/**
* `create`field
*/
extend?: string;
})[];
} }
>; >;
/** /**

View File

@ -1,38 +1,24 @@
import { Link, Message, Modal, TableColumnData } from "@arco-design/web-vue"; import { Link, Message, Modal, TableColumnData } from "@arco-design/web-vue";
import { defaultsDeep, isArray, isFunction, mergeWith } from "lodash-es"; import { defaultsDeep, isArray, isFunction, merge } from "lodash-es";
import { reactive } from "vue"; import { reactive } from "vue";
import { useFormModal } from "../form"; import { useFormModal } from "../form";
import { TableInstance } from "./table"; import { TableInstance } from "./table";
import { config } from "./table.config"; import { config } from "./table.config";
import { UseTableOptions } from "./use-interface"; import { UseTableOptions } from "./use-interface";
const merge = (...args: any[]) => {
return mergeWith({}, ...args, (obj: any, src: any) => {
if (Array.isArray(obj) && Array.isArray(src)) {
return obj.concat(src);
}
return undefined;
});
};
const has = (obj: any, key: string) => Object.prototype.hasOwnProperty.call(obj, key);
const propTruly = (obj: any, key: string) => !has(obj, key) || !!obj[key];
/** /**
* 便Table * hook
* @see src/components/table/use-table.tsx * @see src/components/table/use-table.tsx
*/ */
export const useTable = (optionsOrFn: UseTableOptions | (() => UseTableOptions)): any => { export const useTable = (optionsOrFn: UseTableOptions | (() => UseTableOptions)): any => {
const options: UseTableOptions = typeof optionsOrFn === "function" ? optionsOrFn() : optionsOrFn; const options: UseTableOptions = typeof optionsOrFn === "function" ? optionsOrFn() : optionsOrFn;
const columns: TableColumnData[] = []; const columns: TableColumnData[] = [];
const getTable = (): TableInstance => (columns as any).instance; const getTable = (): TableInstance => (columns as any).instance;
/** /**
* *
*/ */
options.columns.forEach((column) => { for (const column of options.columns) {
if (column.type === "index") { if (column.type === "index") {
defaultsDeep(column, config.columnIndex); defaultsDeep(column, config.columnIndex);
} }
@ -92,29 +78,29 @@ export const useTable = (optionsOrFn: UseTableOptions | (() => UseTableOptions))
} }
columns.push({ ...config.columnBase, ...column }); columns.push({ ...config.columnBase, ...column });
}); }
const itemsMap = options.common?.items?.reduce((map, item) => { /**
map[item.field] = item; *
return map; */
}, {} as any); if (options.create) {
options.create = useFormModal(options.create as any) as any;
}
/** /**
* *
*/ */
if (options.search && options.search.items) { if (options.search && options.search.items) {
const searchItems: any[] = []; const searchItems: any[] = [];
const createItems = options.create?.items ?? [];
for (const item of options.search.items) { for (const item of options.search.items) {
if (typeof item === "string") { if (item.extend) {
if (!itemsMap[item]) { const createItem = createItems.find((i) => i.field === item.extend);
throw new Error(`search item ${item} not found in common items`); if (createItem) {
const mergedItem = merge({}, createItem, item);
searchItems.push(mergedItem);
continue;
} }
searchItems.push(itemsMap[item]);
continue;
}
if ("extend" in item && item.extend && itemsMap[item.extend]) {
searchItems.push(merge({}, itemsMap[item.extend], item));
continue;
} }
searchItems.push(item); searchItems.push(item);
} }
@ -122,18 +108,25 @@ export const useTable = (optionsOrFn: UseTableOptions | (() => UseTableOptions))
options.search.items = searchItems; options.search.items = searchItems;
} }
/**
*
*/
if (options.create && propTruly(options.create, "extend")) {
options.create = useFormModal(merge(options.common, options.create)) as any;
}
/** /**
* *
*/ */
if (options.modify && propTruly(options.modify, "extend")) { if (options.modify) {
options.modify = useFormModal(merge(options.common, options.modify)) as any; if (options.modify.extend && options.create) {
const createItems = options.create.items;
const modifyItems = options.modify.items;
if (modifyItems && createItems) {
for (let i = 0; i < modifyItems.length; i++) {
if (modifyItems[i].extend) {
modifyItems[i] = merge({}, createItems[i], modifyItems[i]);
}
}
}
const merged = merge({}, options.create, options.modify);
options.modify = useFormModal(merged as any) as any;
} else {
options.modify = useFormModal(options.modify as any) as any;
}
} }
return reactive({ ...options, columns }); return reactive({ ...options, columns });

View File

@ -10,136 +10,142 @@ import { Table, useTable } from "@/components";
import { dayjs } from "@/plugins"; import { dayjs } from "@/plugins";
import { Avatar, Button } from "@arco-design/web-vue"; import { Avatar, Button } from "@arco-design/web-vue";
const table = useTable({ const table = useTable(() => {
data: async (model, paging) => { const nicknameRender = ({ record }: any) => {
return api.user.getUsers({ ...model, ...paging }); return (
}, <div class="flex items-center">
columns: [ <Avatar size={32}>
{ <img src={record.avatar} alt="" />
type: "index", </Avatar>
<span class="ml-2 flex-1 flex flex-col overflow-hidden">
<span>{record.nickname}</span>
<span class="text-gray-400 text-xs truncate">账号{record.username}</span>
</span>
</div>
);
};
return {
data: async (model, paging) => {
return api.user.getUsers({ ...model, ...paging });
}, },
{ columns: [
title: "用户昵称", {
dataIndex: "username", type: "index",
width: 200,
render({ record }) {
return (
<div class="flex items-center">
<Avatar size={32}>
<img src={record.avatar} alt="" />
</Avatar>
<span class="ml-2 flex-1 flex flex-col overflow-hidden">
<span>{record.nickname}</span>
<span class="text-gray-400 text-xs truncate">账号{record.username}</span>
</span>
</div>
);
}, },
}, {
{ title: "用户昵称",
title: "用户描述", dataIndex: "username",
dataIndex: "description", width: 200,
}, render: nicknameRender,
{ },
title: "用户邮箱", {
dataIndex: "email", title: "用户描述",
}, dataIndex: "description",
{ },
title: "创建时间", {
dataIndex: "createdAt", title: "用户邮箱",
width: 200, dataIndex: "email",
render: ({ record }) => dayjs(record.createdAt).format(), },
}, {
{ title: "创建时间",
title: "操作", dataIndex: "createdAt",
type: "button", width: 200,
width: 70, render: ({ record }) => dayjs(record.createdAt).format(),
buttons: [ },
{
title: "操作",
type: "button",
width: 148,
buttons: [
{
type: "modify",
text: "修改",
},
{
type: "delete",
text: "删除",
onClick: async (data) => {
return api.user.deleteUser(data.record.id);
},
},
],
},
],
search: {
items: [
{ {
type: "modify", extend: "username",
text: "修改", required: false,
}, },
], ],
}, },
], create: {
common: { title: "新建用户",
model: { trigger: () => (
avatarUrl: "",
},
items: [
{
field: "username",
label: "登录账号",
type: "input",
required: true,
},
{
field: "nickname",
label: "用户昵称",
type: "input",
},
{
field: "description",
label: "个人描述",
type: "input",
},
{
field: "password",
label: "密码",
type: "password",
},
{
label: "头像",
field: "avatar?avatarUrl",
type: "select",
},
{
field: "startTime:endTime",
label: "日期范围",
type: "dateRange",
nodeProps: {},
},
],
modalProps: {
width: 772,
maskClosable: false,
},
formProps: {
layout: "vertical",
class: "!grid grid-cols-2 gap-x-3",
},
},
search: {
items: [
{
extend: "username",
required: false,
},
],
},
create: {
title: "新建用户",
trigger: () => {
return (
<Button type="primary"> <Button type="primary">
{{ {{
icon: () => <i class="icon-park-outline-people-plus-one"></i>, icon: () => <i class="icon-park-outline-people-plus-one" />,
default: () => "添加", default: () => "添加",
}} }}
</Button> </Button>
); ),
modalProps: {
width: 772,
maskClosable: false,
},
formProps: {
layout: "vertical",
class: "!grid grid-cols-2 gap-x-3",
},
model: {
avatarUrl: "",
},
items: [
{
field: "username",
label: "登录账号",
type: "input",
required: true,
},
{
field: "nickname",
label: "用户昵称",
type: "input",
},
{
field: "description",
label: "个人描述",
type: "input",
},
{
field: "password",
label: "密码",
type: "password",
},
{
label: "头像",
field: "avatar?avatarUrl",
type: "select",
},
{
field: "startTime:endTime",
label: "日期范围",
type: "dateRange",
nodeProps: {},
},
],
submit: ({ model }) => {
console.log(model);
},
}, },
submit: ({ model }) => { modify: {
console.log(model); extend: true,
title: "修改用户",
submit: ({ model }) => {
return api.user.updateUser(model.id, model);
},
}, },
}, };
modify: {
extend: true,
title: "修改用户",
submit: ({ model }) => {
console.log(model);
},
},
}); });
</script> </script>

View File

@ -11,23 +11,16 @@ declare module '@vue/runtime-core' {
ABreadcrumb: typeof import('@arco-design/web-vue')['Breadcrumb'] ABreadcrumb: typeof import('@arco-design/web-vue')['Breadcrumb']
ABreadcrumbItem: typeof import('@arco-design/web-vue')['BreadcrumbItem'] ABreadcrumbItem: typeof import('@arco-design/web-vue')['BreadcrumbItem']
AButton: typeof import('@arco-design/web-vue')['Button'] AButton: typeof import('@arco-design/web-vue')['Button']
ACheckbox: typeof import('@arco-design/web-vue')['Checkbox']
AConfigProvider: typeof import('@arco-design/web-vue')['ConfigProvider'] AConfigProvider: typeof import('@arco-design/web-vue')['ConfigProvider']
ADoption: typeof import('@arco-design/web-vue')['Doption'] ADoption: typeof import('@arco-design/web-vue')['Doption']
ADrawer: typeof import('@arco-design/web-vue')['Drawer'] ADrawer: typeof import('@arco-design/web-vue')['Drawer']
ADropdown: typeof import('@arco-design/web-vue')['Dropdown'] ADropdown: typeof import('@arco-design/web-vue')['Dropdown']
AForm: typeof import('@arco-design/web-vue')['Form']
AFormItem: typeof import('@arco-design/web-vue')['FormItem']
AInput: typeof import('@arco-design/web-vue')['Input']
AInputPassword: typeof import('@arco-design/web-vue')['InputPassword']
ALayout: typeof import('@arco-design/web-vue')['Layout'] ALayout: typeof import('@arco-design/web-vue')['Layout']
ALayoutContent: typeof import('@arco-design/web-vue')['LayoutContent'] ALayoutContent: typeof import('@arco-design/web-vue')['LayoutContent']
ALayoutHeader: typeof import('@arco-design/web-vue')['LayoutHeader'] ALayoutHeader: typeof import('@arco-design/web-vue')['LayoutHeader']
ALayoutSider: typeof import('@arco-design/web-vue')['LayoutSider'] ALayoutSider: typeof import('@arco-design/web-vue')['LayoutSider']
ALink: typeof import('@arco-design/web-vue')['Link']
AMenu: typeof import('@arco-design/web-vue')['Menu'] AMenu: typeof import('@arco-design/web-vue')['Menu']
AMenuItem: typeof import('@arco-design/web-vue')['MenuItem'] AMenuItem: typeof import('@arco-design/web-vue')['MenuItem']
ASpace: typeof import('@arco-design/web-vue')['Space']
ASubMenu: typeof import('@arco-design/web-vue')['SubMenu'] ASubMenu: typeof import('@arco-design/web-vue')['SubMenu']
ATag: typeof import('@arco-design/web-vue')['Tag'] ATag: typeof import('@arco-design/web-vue')['Tag']
ATooltip: typeof import('@arco-design/web-vue')['Tooltip'] ATooltip: typeof import('@arco-design/web-vue')['Tooltip']