feat: 表格行增加下拉菜单功能
parent
8a2b29ef01
commit
0c9e19dc10
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="mt-4 mx-5">
|
<div class="mt-3 mx-5">
|
||||||
<a-breadcrumb>
|
<a-breadcrumb>
|
||||||
<a-breadcrumb-item>
|
<a-breadcrumb-item>
|
||||||
<i class="icon-park-outline-all-application text-gray-400"></i>
|
<i class="icon-park-outline-all-application text-gray-400"></i>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
<div>
|
<div>
|
||||||
<BreadCrumb></BreadCrumb>
|
<BreadCrumb></BreadCrumb>
|
||||||
<slot name="content">
|
<slot name="content">
|
||||||
<div class="mx-4 mt-4 p-4 bg-white">
|
<div class="mx-4 mt-3 p-4 bg-white">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
</slot>
|
</slot>
|
||||||
|
|
|
||||||
|
|
@ -48,19 +48,19 @@ const form = useForm({
|
||||||
| formProps | 传递给`AForm`组件的参数(可选),具体可参考`Arco-Design`的`Form`组件,部分参数不可用,如`model`等。 | `FormInstance['$props']` |
|
| formProps | 传递给`AForm`组件的参数(可选),具体可参考`Arco-Design`的`Form`组件,部分参数不可用,如`model`等。 | `FormInstance['$props']` |
|
||||||
|
|
||||||
### 表单数据
|
### 表单数据
|
||||||
`model`表示当前表单的数据,当使用`useForm`时,将从`items`中每一项的`field`和`initialValue`生成。如果`model`中的属性与`field`值同名,且`initialValue`值不为空,则原`model`中的同名属性值将被覆盖。
|
`model`表示当前表单的数据,可为空。当使用`useForm`时,将从`items`中每一项的`field`和`initialValue`生成。如果`model`中的属性与`field`值同名,且`initialValue`值不为空,则原`model`中的同名属性值将被覆盖。
|
||||||
|
|
||||||
对于日期范围框、级联选择器等值为数组的组件,提供有一份便捷的语法,请看如下示例:
|
对于日期范围框、级联选择器等值为数组的组件,提供有一份便捷的语法,请看如下示例:
|
||||||
```typescript
|
```typescript
|
||||||
const form = useForm({
|
const form = useForm({
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
field: `startDate:endDate`,
|
field: `[startDate, endDate]`,
|
||||||
label: '日期范围',
|
label: '日期范围',
|
||||||
type: 'dateRange',
|
type: 'dateRange',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'provice:city:town',
|
field: '[provice: number, city: number, town: number]',
|
||||||
label: '省市区',
|
label: '省市区',
|
||||||
type: 'cascader',
|
type: 'cascader',
|
||||||
options: []
|
options: []
|
||||||
|
|
@ -68,14 +68,14 @@ const form = useForm({
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
以上,`field`可通过`:`分隔的语法,指定提交表单时,将数组值划分到指定的属性上,最终提交的数据如下:
|
以上,`field` 使用的是类似Typescript元组的写法,类型目前支持 number 和 boolean,在提交时将得到如下数据:
|
||||||
```typescript
|
```typescript
|
||||||
{
|
{
|
||||||
startDate: '',
|
startDate: '2023',
|
||||||
endDate: '',
|
endDate: '2024',
|
||||||
province: '',
|
province: 1,
|
||||||
city: '',
|
city: 2,
|
||||||
town: ''
|
town: 3
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -229,9 +229,9 @@ const form = useForm({
|
||||||
|
|
||||||
### 常见问题
|
### 常见问题
|
||||||
- Q:为什么不是模板形式?
|
- Q:为什么不是模板形式?
|
||||||
- A:配置式更易于描述逻辑,模板介入和引入的组件比较多,且对于做typescript类型提示不是很方便。
|
- A:状态驱动,配置式更易于描述逻辑,模板介入和引入的组件比较多,且对于做typescript类型提示不是很方便。
|
||||||
- Q:为什么不是JSON形式?
|
- Q:为什么不是JSON形式?
|
||||||
- A:对于自定义组件支持、联动等不是非常友好,尽管可以通过解析字符串执行等方式实现,对typescript提示也不是很友好。
|
- A:对于自定义组件支持、联动等不是非常友好,尽管可以通过解析字符串执行等方式实现,对typescript提示不是很友好。
|
||||||
|
|
||||||
### 最后
|
### 最后
|
||||||
尽管看起来是低代码,但其实我更倾向于是业务组件。
|
尽管看起来是低代码,但其实我更倾向于是业务组件。
|
||||||
|
|
@ -79,18 +79,17 @@ export const FormItem = (props: any, { emit }: any) => {
|
||||||
|
|
||||||
type FormItemBase = {
|
type FormItemBase = {
|
||||||
/**
|
/**
|
||||||
* 字段名,用于表单数据、表单校验和输入框值绑定,支持特殊语法。
|
* 字段名,用于表单、校验和输入框绑定,支持特殊语法。
|
||||||
* @example
|
* @example
|
||||||
* ```typescript
|
* ```typescript
|
||||||
* // 1. 以:分隔的字段名,将用作数组值解构。例如:
|
|
||||||
* {
|
* {
|
||||||
* field: 'v1:v2',
|
* field: '[v1,v2]',
|
||||||
* type: 'dateRange',
|
* type: 'dateRange',
|
||||||
* }
|
* }
|
||||||
* // 将得到
|
* // 将得到
|
||||||
* {
|
* {
|
||||||
* v1: '2021-01-01',
|
* v1: '2021',
|
||||||
* v2: '2021-01-02',
|
* v2: '2021',
|
||||||
* }
|
* }
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,7 @@ const table = useTable({
|
||||||
username: '用户A'
|
username: '用户A'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
meta: {
|
|
||||||
total: 30
|
total: 30
|
||||||
}
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
columns: [
|
columns: [
|
||||||
|
|
@ -32,13 +30,12 @@ const table = useTable({
|
||||||
search: {
|
search: {
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
field: "username",
|
extend: "username",
|
||||||
label: "用户名称",
|
|
||||||
type: "input",
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
common: {
|
create: {
|
||||||
|
title: "新建用户",
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
field: "username",
|
field: "username",
|
||||||
|
|
@ -46,15 +43,13 @@ const table = useTable({
|
||||||
type: "input",
|
type: "input",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
|
||||||
create: {
|
|
||||||
title: "新建用户",
|
|
||||||
submit: async ({ model }) => {
|
submit: async ({ model }) => {
|
||||||
return api.xx(model);
|
return api.xx(model);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
modify: {
|
modify: {
|
||||||
title: "修改用户",
|
title: "修改用户",
|
||||||
|
extend: true,
|
||||||
submit: async ({ model }) => {
|
submit: async ({ model }) => {
|
||||||
return api.xx(model);
|
return api.xx(model);
|
||||||
},
|
},
|
||||||
|
|
@ -62,6 +57,7 @@ const table = useTable({
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
以上,就是一个 CRUD 表格的简单用法。参数描述:
|
以上,就是一个 CRUD 表格的简单用法。参数描述:
|
||||||
| 参数 | 说明 | 类型 |
|
| 参数 | 说明 | 类型 |
|
||||||
| :--- | :--- | :--- |
|
| :--- | :--- | :--- |
|
||||||
|
|
@ -75,81 +71,91 @@ const table = useTable({
|
||||||
| tableProps | 传递给`Table`组件的参数,参见 [Table](https://arco.design/vue/component/table) 文档,其中`columns`参数不可用。| TableProps |
|
| tableProps | 传递给`Table`组件的参数,参见 [Table](https://arco.design/vue/component/table) 文档,其中`columns`参数不可用。| TableProps |
|
||||||
|
|
||||||
### 表格数据
|
### 表格数据
|
||||||
|
|
||||||
`data`定义表格数据,可以是数组或函数。
|
`data`定义表格数据,可以是数组或函数。
|
||||||
|
|
||||||
- 当是数组时,直接用作数据源。
|
- 当是数组时,直接用作数据源。
|
||||||
- 当是函数时,传入查询参数和分页参数,可返回数组或对象,返回数组作用同上,返回对象时需遵循`{ data: [], meta: { total: number } }`格式,用于分页处理。
|
- 当是函数时,传入查询参数和分页参数,可返回数组或对象,返回数组作用同上,返回对象时需遵循`{ data: [], total: number }`格式,用于分页处理。
|
||||||
|
|
||||||
用法示例:
|
用法示例:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
const table = useTable({
|
const table = useTable({
|
||||||
data: async (search, paging) {
|
data: async (search, paging) {
|
||||||
const { page, size: pageSize } = paging
|
const res = await api.xx({ ...search, ...paging });
|
||||||
const res = await api.xx({ ...search, page, pageSize });
|
|
||||||
return {
|
return {
|
||||||
data: res.data,
|
data: res.data,
|
||||||
meta: {
|
|
||||||
total: res.total
|
total: res.total
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
### 表格列
|
### 表格列
|
||||||
|
|
||||||
`columns`定义表格列,并在原本基础上增加默认值并扩展部分属性。增加和扩展的属性如下:
|
`columns`定义表格列,并在原本基础上增加默认值并扩展部分属性。增加和扩展的属性如下:
|
||||||
|
|
||||||
| 参数 | 说明 | 类型 |
|
| 参数 | 说明 | 类型 |
|
||||||
| :--- | :--- | :--- |
|
| :------ | :--------------------------------------------------------------------------------------------------- | :------- | -------- |
|
||||||
| type | 特殊类型, 目前支持`index`(表示行数)、`button`(行操作按钮) | 'index' | 'button' |
|
| type | 特殊类型, 目前支持`index`(表示行数)、`button`(行操作按钮) | 'index' | 'button' |
|
||||||
| buttons | 当`type`为`button`时的按钮数组,如果子项是对象则为`Button`组件的参数,如果为函数则为自定义渲染函数。 | Button[]
|
| buttons | 当`type`为`button`时的按钮数组,如果子项是对象则为`Button`组件的参数,如果为函数则为自定义渲染函数。 | Button[] |
|
||||||
|
|
||||||
### 表格分页
|
### 表格分页
|
||||||
|
|
||||||
`pagination`定义分页行为,具体参数可参考 [Pagination](https://arco.design/vue/component/pagination) 文档。当`data`为数组时,将作为数据源进行分页;当`data`为函数且返回值为对象时,则根据`total`值进行分页。
|
`pagination`定义分页行为,具体参数可参考 [Pagination](https://arco.design/vue/component/pagination) 文档。当`data`为数组时,将作为数据源进行分页;当`data`为函数且返回值为对象时,则根据`total`值进行分页。
|
||||||
|
|
||||||
### 搜索表单
|
### 搜索表单
|
||||||
|
|
||||||
参阅
|
参阅
|
||||||
|
|
||||||
### 公共参数
|
### 公共参数
|
||||||
|
|
||||||
参数为`FormModal`的参数,主要作为新增和修改的公共参数。在大多数情况,新增和修改的配置大多是相似的,没必要写两份,把相同的参数写在这里即可,不同的参数在`create`和`modify`中单独配置。
|
参数为`FormModal`的参数,主要作为新增和修改的公共参数。在大多数情况,新增和修改的配置大多是相似的,没必要写两份,把相同的参数写在这里即可,不同的参数在`create`和`modify`中单独配置。
|
||||||
|
|
||||||
注意,这里的`items`也可以被搜索表单复用,搜索表单可通过`extends: <field>`继承`common.items`中对应的字段配置。使用示例如下:
|
注意,这里的`items`也可以被搜索表单复用,搜索表单可通过`extends: <field>`继承`common.items`中对应的字段配置。使用示例如下:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
const table = useTable({
|
const table = useTable({
|
||||||
common: {
|
common: {
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
field: 'username',
|
field: "username",
|
||||||
label: '用户名称',
|
label: "用户名称",
|
||||||
type: 'input',
|
type: "input",
|
||||||
required: true,
|
required: true,
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
search: {
|
search: {
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
extend: 'usernam',
|
extend: "usernam",
|
||||||
required: false,
|
required: false,
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
### 新增弹窗
|
### 新增弹窗
|
||||||
|
|
||||||
`create`为新增表单弹窗的参数,即`useFormModal`对应的参数。参阅。当指定该参数时,会在表格左上添加新建按钮,如需自定义按钮样式或自定义渲染,可通过`create.trigger`参数配置。
|
`create`为新增表单弹窗的参数,即`useFormModal`对应的参数。参阅。当指定该参数时,会在表格左上添加新建按钮,如需自定义按钮样式或自定义渲染,可通过`create.trigger`参数配置。
|
||||||
|
|
||||||
### 修改弹窗
|
### 修改弹窗
|
||||||
|
|
||||||
`modify`为新增表单弹窗的参数,即`useFormModal`对应的参数。参阅。当指定该参数时,会在表格行添加修改按钮。
|
`modify`为新增表单弹窗的参数,即`useFormModal`对应的参数。参阅。当指定该参数时,会在表格行添加修改按钮。
|
||||||
|
|
||||||
### 表格参数
|
### 表格参数
|
||||||
|
|
||||||
`tableProps`为传递给`Table`组件的额外参数,其中部分参数不可用,如`data`和`columns`等。此外,部分参数有默认值,具体参数可查看`src/components/table/table.config.ts`文件。
|
`tableProps`为传递给`Table`组件的额外参数,其中部分参数不可用,如`data`和`columns`等。此外,部分参数有默认值,具体参数可查看`src/components/table/table.config.ts`文件。
|
||||||
|
|
||||||
### 插槽
|
### 插槽
|
||||||
|
|
||||||
- `Table`组件的插槽可正常使用
|
- `Table`组件的插槽可正常使用
|
||||||
- `action`插槽用作表格左上方的操作区。
|
- `action`插槽用作表格左上方的操作区。
|
||||||
|
|
||||||
## 问题
|
## 问题
|
||||||
|
|
||||||
- 问题:日期范围框值为数组,处理不方便
|
- 问题:日期范围框值为数组,处理不方便
|
||||||
- 解决:字段名使用`v1:v2`格式,提交时会生成`{ v1: '00:00:01', v2: '00:00:02' }`数据
|
- 解决:字段名使用`v1:v2`格式,提交时会生成`{ v1: '00:00:01', v2: '00:00:02' }`数据
|
||||||
- 问题:搜索表单、新增表单和修改表单通常用到同一表单项,如何避免重复定义
|
- 问题:搜索表单、新增表单和修改表单通常用到同一表单项,如何避免重复定义
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { Button } from "@arco-design/web-vue";
|
||||||
import { IconRefresh, IconSearch } from "@arco-design/web-vue/es/icon";
|
import { IconRefresh, IconSearch } from "@arco-design/web-vue/es/icon";
|
||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
|
searchInlineCount: 3,
|
||||||
searchFormProps: {
|
searchFormProps: {
|
||||||
labelAlign: "left",
|
labelAlign: "left",
|
||||||
autoLabelWidth: true,
|
autoLabelWidth: true,
|
||||||
|
|
@ -18,7 +19,7 @@ export const config = {
|
||||||
const tableRef = inject<any>("ref:table");
|
const tableRef = inject<any>("ref:table");
|
||||||
return (
|
return (
|
||||||
<div class="w-full flex gap-x-2 justify-end">
|
<div class="w-full flex gap-x-2 justify-end">
|
||||||
{(tableRef.search?.items?.length || 0) > 3 && (
|
{(tableRef.search?.items?.length || 0) > config.searchInlineCount && (
|
||||||
<Button disabled={tableRef?.loading.value} onClick={() => tableRef?.reloadData()}>
|
<Button disabled={tableRef?.loading.value} onClick={() => tableRef?.reloadData()}>
|
||||||
{{ icon: () => <IconRefresh></IconRefresh>, default: () => "重置" }}
|
{{ icon: () => <IconRefresh></IconRefresh>, default: () => "重置" }}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -55,7 +56,7 @@ export const config = {
|
||||||
columnButtonBase: {
|
columnButtonBase: {
|
||||||
buttonProps: {
|
buttonProps: {
|
||||||
// type: "text",
|
// type: "text",
|
||||||
size: "mini",
|
// size: "mini",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
columnButtonDelete: {
|
columnButtonDelete: {
|
||||||
|
|
@ -65,6 +66,10 @@ export const config = {
|
||||||
hideCancel: false,
|
hideCancel: false,
|
||||||
maskClosable: false,
|
maskClosable: false,
|
||||||
},
|
},
|
||||||
|
columnDropdownModify: {
|
||||||
|
text: "修改",
|
||||||
|
icon: "icon-park-outline-edit",
|
||||||
|
},
|
||||||
getApiErrorMessage(error: any): string {
|
getApiErrorMessage(error: any): string {
|
||||||
const message = error?.response?.data?.message || error?.message || "请求失败";
|
const message = error?.response?.data?.message || error?.message || "请求失败";
|
||||||
return message;
|
return message;
|
||||||
|
|
|
||||||
|
|
@ -76,9 +76,9 @@ export const Table = defineComponent({
|
||||||
const createRef = ref<FormModalInstance>();
|
const createRef = ref<FormModalInstance>();
|
||||||
const modifyRef = ref<FormModalInstance>();
|
const modifyRef = ref<FormModalInstance>();
|
||||||
const renderData = ref<BaseData[]>([]);
|
const renderData = ref<BaseData[]>([]);
|
||||||
const inlined = computed(() => (props.search?.items?.length ?? 0) < 4);
|
const inlined = computed(() => (props.search?.items?.length ?? 0) <= config.searchInlineCount);
|
||||||
const reloadData = () => loadData({ current: 1, pageSize: 10 });
|
const reloadData = () => loadData({ current: 1, pageSize: 10 });
|
||||||
const openModifyModal = (data: any) => modifyRef.value?.open(data.record);
|
const openModifyModal = (data: any) => modifyRef.value?.open(data);
|
||||||
|
|
||||||
const loadData = async (pagination: Partial<any> = {}) => {
|
const loadData = async (pagination: Partial<any> = {}) => {
|
||||||
const merged = { ...props.pagination, ...pagination };
|
const merged = { ...props.pagination, ...pagination };
|
||||||
|
|
@ -185,7 +185,7 @@ export const Table = defineComponent({
|
||||||
|
|
||||||
<BaseTable
|
<BaseTable
|
||||||
row-key="id"
|
row-key="id"
|
||||||
bordered={false}
|
bordered={true}
|
||||||
{...this.tableProps}
|
{...this.tableProps}
|
||||||
loading={this.loading}
|
loading={this.loading}
|
||||||
pagination={this.pagination}
|
pagination={this.pagination}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
import { Link, TableColumnData, TableData } from "@arco-design/web-vue";
|
import { Doption, Link, TableColumnData, TableData } from "@arco-design/web-vue";
|
||||||
import { FormModalProps, FormProps } from "../form";
|
import { FormModalProps, FormProps } from "../form";
|
||||||
import { IFormItem } from "../form/form-item";
|
import { IFormItem } from "../form/form-item";
|
||||||
import { TableProps } from "./table";
|
import { TableProps } from "./table";
|
||||||
|
import { RenderFunction } from "vue";
|
||||||
|
|
||||||
interface UseColumnRenderOptions {
|
interface UseColumnRenderOptions {
|
||||||
/**
|
/**
|
||||||
|
|
@ -46,15 +47,50 @@ export interface TableColumnButton {
|
||||||
buttonProps?: Partial<Omit<InstanceType<typeof Link>["$props"], "onClick" | "disabled">>;
|
buttonProps?: Partial<Omit<InstanceType<typeof Link>["$props"], "onClick" | "disabled">>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface TableColumnDropdown {
|
||||||
|
/**
|
||||||
|
* 特殊类型
|
||||||
|
*/
|
||||||
|
type?: "modify" | "delete";
|
||||||
|
/**
|
||||||
|
* 下拉菜单文本
|
||||||
|
*/
|
||||||
|
text?: string;
|
||||||
|
/**
|
||||||
|
* 下拉菜单图标
|
||||||
|
*/
|
||||||
|
icon?: string | RenderFunction;
|
||||||
|
/**
|
||||||
|
* 是否禁用
|
||||||
|
*/
|
||||||
|
disabled?: (data: UseColumnRenderOptions) => boolean;
|
||||||
|
/**
|
||||||
|
* 是否显示
|
||||||
|
*/
|
||||||
|
visibled?: (data: UseColumnRenderOptions) => boolean;
|
||||||
|
/**
|
||||||
|
* 处理事件
|
||||||
|
*/
|
||||||
|
onClick?: (data: UseColumnRenderOptions) => any;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
doptionProps?: Partial<InstanceType<typeof Doption> & Record<string, any>>;
|
||||||
|
}
|
||||||
|
|
||||||
export interface UseTableColumn extends TableColumnData {
|
export interface UseTableColumn extends TableColumnData {
|
||||||
/**
|
/**
|
||||||
* 表格列类型
|
* 表格列类型
|
||||||
*/
|
*/
|
||||||
type?: "index" | "button";
|
type?: "index" | "button" | "dropdown";
|
||||||
/**
|
/**
|
||||||
* 按钮配置列表
|
* 按钮配置列表
|
||||||
*/
|
*/
|
||||||
buttons?: TableColumnButton[];
|
buttons?: TableColumnButton[];
|
||||||
|
/**
|
||||||
|
* 下拉菜单配置列表
|
||||||
|
*/
|
||||||
|
dropdowns?: TableColumnDropdown[];
|
||||||
}
|
}
|
||||||
|
|
||||||
type ExtendedFormItem = Partial<IFormItem> & {
|
type ExtendedFormItem = Partial<IFormItem> & {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { Link, Message, TableColumnData } from "@arco-design/web-vue";
|
import { Doption, Dropdown, Link, Message, TableColumnData } from "@arco-design/web-vue";
|
||||||
import { defaultsDeep, isArray, merge } from "lodash-es";
|
import { isArray, 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";
|
||||||
|
|
@ -7,6 +7,32 @@ import { config } from "./table.config";
|
||||||
import { UseTableOptions } from "./use-interface";
|
import { UseTableOptions } from "./use-interface";
|
||||||
import { modal } from "@/utils/modal";
|
import { modal } from "@/utils/modal";
|
||||||
|
|
||||||
|
const onClick = async (item: any, columnData: any, getTable: any) => {
|
||||||
|
if (item.type === "modify") {
|
||||||
|
const data = (await item.onClick?.(columnData)) ?? columnData.record;
|
||||||
|
getTable()?.openModifyModal(data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (item.type === "delete") {
|
||||||
|
await modal.delConfirm();
|
||||||
|
try {
|
||||||
|
const resData: any = await item?.onClick?.(columnData);
|
||||||
|
const message = resData?.data?.message;
|
||||||
|
if (message) {
|
||||||
|
Message.success(`提示:${message}`);
|
||||||
|
}
|
||||||
|
getTable()?.loadData();
|
||||||
|
} catch (error: any) {
|
||||||
|
const message = error.response?.data?.message;
|
||||||
|
if (message) {
|
||||||
|
Message.warning(`提示:${message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
item.onClick?.(columnData);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 表格组件hook
|
* 表格组件hook
|
||||||
* @see `src/components/table/use-table.tsx`
|
* @see `src/components/table/use-table.tsx`
|
||||||
|
|
@ -19,60 +45,50 @@ export const useTable = (optionsOrFn: UseTableOptions | (() => UseTableOptions))
|
||||||
/**
|
/**
|
||||||
* 表格列处理
|
* 表格列处理
|
||||||
*/
|
*/
|
||||||
for (const column of options.columns) {
|
for (let column of options.columns) {
|
||||||
|
/**
|
||||||
|
* 索引列处理
|
||||||
|
*/
|
||||||
if (column.type === "index") {
|
if (column.type === "index") {
|
||||||
defaultsDeep(column, config.columnIndex);
|
column = merge({}, config.columnIndex, column);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按钮列处理
|
||||||
|
*/
|
||||||
if (column.type === "button" && isArray(column.buttons)) {
|
if (column.type === "button" && isArray(column.buttons)) {
|
||||||
if (options.modify) {
|
const buttons = column.buttons;
|
||||||
const modifyAction = column.buttons.find((i) => i.type === "modify");
|
let hasModify = false;
|
||||||
if (modifyAction) {
|
let hasDelete = false;
|
||||||
const { onClick } = modifyAction;
|
for (let i = 0; i < buttons.length; i++) {
|
||||||
modifyAction.onClick = async (columnData) => {
|
let btn = merge({}, config.columnButtonBase);
|
||||||
const result = (await onClick?.(columnData)) || columnData;
|
if (buttons[i].type === "modify") {
|
||||||
getTable()?.openModifyModal(result);
|
btn = merge(btn, buttons[i]);
|
||||||
};
|
hasModify = true;
|
||||||
} else {
|
|
||||||
column.buttons.unshift({
|
|
||||||
text: "修改",
|
|
||||||
onClick: (data) => getTable()?.openModifyModal(data),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
if (buttons[i].type === "delete") {
|
||||||
|
btn = merge(btn, buttons[i]);
|
||||||
|
hasDelete = true;
|
||||||
}
|
}
|
||||||
|
buttons[i] = merge(btn, buttons[i]);
|
||||||
column.buttons = column.buttons?.map((action) => {
|
|
||||||
let onClick = action?.onClick;
|
|
||||||
if (action.type === "delete") {
|
|
||||||
onClick = async (data) => {
|
|
||||||
await modal.delConfirm();
|
|
||||||
try {
|
|
||||||
const resData: any = await action?.onClick?.(data);
|
|
||||||
const message = resData?.data?.message;
|
|
||||||
if (message) {
|
|
||||||
Message.success(`提示:${message}`);
|
|
||||||
}
|
}
|
||||||
getTable()?.loadData();
|
if (!hasModify) {
|
||||||
} catch (error: any) {
|
buttons.push(merge({}, config.columnButtonBase));
|
||||||
const message = error.response?.data?.message;
|
|
||||||
if (message) {
|
|
||||||
Message.warning(`提示:${message}`);
|
|
||||||
}
|
}
|
||||||
|
if (!hasDelete) {
|
||||||
|
buttons.push(merge({}, config.columnButtonBase));
|
||||||
}
|
}
|
||||||
};
|
|
||||||
}
|
|
||||||
return { ...config.columnButtonBase, ...action, onClick } as any;
|
|
||||||
});
|
|
||||||
|
|
||||||
column.render = (columnData) => {
|
column.render = (columnData) => {
|
||||||
return column.buttons?.map((btn) => {
|
return column.buttons?.map((btn) => {
|
||||||
const onClick = () => btn.onClick?.(columnData);
|
if (btn.visible?.(columnData) === false) {
|
||||||
const disabled = () => btn.disabled?.(columnData);
|
|
||||||
if (btn.visible && !btn.visible(columnData)) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Link onClick={onClick} disabled={disabled()} {...btn.buttonProps}>
|
<Link
|
||||||
|
{...btn.buttonProps}
|
||||||
|
onClick={() => onClick(btn, columnData, getTable)}
|
||||||
|
disabled={btn.disabled?.(columnData)}
|
||||||
|
>
|
||||||
{btn.text}
|
{btn.text}
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
|
|
@ -80,6 +96,53 @@ export const useTable = (optionsOrFn: UseTableOptions | (() => UseTableOptions))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 菜单列处理
|
||||||
|
*/
|
||||||
|
if (column.type === "dropdown" && Array.isArray(column.dropdowns)) {
|
||||||
|
if (options.modify) {
|
||||||
|
const index = column.dropdowns?.findIndex((i) => i.type === "modify");
|
||||||
|
if (index !== undefined) {
|
||||||
|
column.dropdowns[index] = merge({}, config.columnDropdownModify, column.dropdowns[index]);
|
||||||
|
} else {
|
||||||
|
column.dropdowns?.unshift(merge({}, config.columnDropdownModify));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
column.render = (columnData) => {
|
||||||
|
const content = column.dropdowns?.map((dropdown) => {
|
||||||
|
const { text, icon, disabled, visibled, doptionProps } = dropdown;
|
||||||
|
if (visibled?.(columnData) === false) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Doption
|
||||||
|
{...doptionProps}
|
||||||
|
onClick={() => onClick(dropdown, columnData, getTable)}
|
||||||
|
disabled={disabled?.(columnData)}
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
icon: typeof icon === "function" ? icon() : () => <i class={icon} />,
|
||||||
|
default: text,
|
||||||
|
}}
|
||||||
|
</Doption>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
const trigger = () => (
|
||||||
|
<span class="inline-flex p-1 hover:bg-slate-200 rounded cursor-pointer">
|
||||||
|
<i class="icon-park-outline-more"></i>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<Dropdown position="br">
|
||||||
|
{{
|
||||||
|
default: trigger,
|
||||||
|
content: content,
|
||||||
|
}}
|
||||||
|
</Dropdown>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
columns.push({ ...config.columnBase, ...column });
|
columns.push({ ...config.columnBase, ...column });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-4 text-gray-500">
|
<div class="flex items-center gap-4 text-gray-500">
|
||||||
<ADropdown>
|
<ADropdown>
|
||||||
<span class="cursor-pointer">
|
<span class="cursor-pointer hover:text-gray-900">
|
||||||
上传者
|
上传者
|
||||||
<i class="icon-park-outline-down"></i>
|
<i class="icon-park-outline-down"></i>
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -31,7 +31,7 @@
|
||||||
</template>
|
</template>
|
||||||
</ADropdown>
|
</ADropdown>
|
||||||
<ADropdown>
|
<ADropdown>
|
||||||
<span class="cursor-pointer">
|
<span class="cursor-pointer hover:text-gray-900">
|
||||||
排序:默认
|
排序:默认
|
||||||
<i class="icon-park-outline-down"></i>
|
<i class="icon-park-outline-down"></i>
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -67,17 +67,17 @@
|
||||||
</ADropdown>
|
</ADropdown>
|
||||||
<div class="space-x-1">
|
<div class="space-x-1">
|
||||||
<span
|
<span
|
||||||
class="inline-flex p-1 hover:bg-slate-100 rounded cursor-pointer text-gray-400 hover:text-gray-700"
|
class="inline-flex p-1 hover:bg-slate-200 rounded cursor-pointer text-gray-400 hover:text-gray-700 bg-slate-200 text-slate-700"
|
||||||
>
|
>
|
||||||
<i class="icon-park-outline-list"></i>
|
<i class="icon-park-outline-list"></i>
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
class="inline-flex p-1 hover:bg-slate-100 rounded cursor-pointer text-gray-400 hover:text-gray-700"
|
class="inline-flex p-1 hover:bg-slate-200 rounded cursor-pointer text-gray-400 hover:text-gray-700"
|
||||||
>
|
>
|
||||||
<i class="icon-park-outline-insert-table"></i>
|
<i class="icon-park-outline-insert-table"></i>
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
class="inline-flex p-1 hover:bg-slate-100 rounded cursor-pointer text-gray-400 hover:text-gray-700"
|
class="inline-flex p-1 hover:bg-slate-200 rounded cursor-pointer text-gray-400 hover:text-gray-700"
|
||||||
>
|
>
|
||||||
<i class="icon-park-outline-refresh"></i>
|
<i class="icon-park-outline-refresh"></i>
|
||||||
</span>
|
</span>
|
||||||
|
|
|
||||||
|
|
@ -34,19 +34,25 @@ const table = useTable({
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "操作",
|
title: "操作",
|
||||||
type: "button",
|
type: "dropdown",
|
||||||
width: 136,
|
width: 60,
|
||||||
buttons: [
|
align: "center",
|
||||||
|
dropdowns: [
|
||||||
{
|
{
|
||||||
type: "modify",
|
type: "modify",
|
||||||
text: "修改",
|
text: "修改",
|
||||||
|
icon: "icon-park-outline-edit",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "delete",
|
type: "delete",
|
||||||
text: "删除",
|
text: "删除",
|
||||||
|
icon: "icon-park-outline-delete",
|
||||||
onClick: ({ record }) => {
|
onClick: ({ record }) => {
|
||||||
return api.post.delPost(record.id);
|
return api.post.delPost(record.id);
|
||||||
},
|
},
|
||||||
|
doptionProps: {
|
||||||
|
class: "!text-red-500 !hover-bg-red-50",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,7 @@ const table = useTable({
|
||||||
title: "用户昵称",
|
title: "用户昵称",
|
||||||
dataIndex: "username",
|
dataIndex: "username",
|
||||||
width: 200,
|
width: 200,
|
||||||
render: ({ record }) => {
|
render: ({ record }) => (
|
||||||
return (
|
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<Avatar size={32}>
|
<Avatar size={32}>
|
||||||
<img src={record.avatar} alt="" />
|
<img src={record.avatar} alt="" />
|
||||||
|
|
@ -33,8 +32,7 @@ const table = useTable({
|
||||||
<span class="text-gray-400 text-xs truncate">账号:{record.username}</span>
|
<span class="text-gray-400 text-xs truncate">账号:{record.username}</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
),
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "用户描述",
|
title: "用户描述",
|
||||||
|
|
@ -63,7 +61,7 @@ const table = useTable({
|
||||||
type: "delete",
|
type: "delete",
|
||||||
text: "删除",
|
text: "删除",
|
||||||
onClick: async ({ record }) => {
|
onClick: async ({ record }) => {
|
||||||
return api.user.delUser(record.id);
|
return api.user.delUser(record.id, { toast: true });
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
@ -72,7 +70,7 @@ const table = useTable({
|
||||||
search: {
|
search: {
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
extend: "username",
|
extend: "nickname",
|
||||||
required: false,
|
required: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
@ -151,6 +149,11 @@ const table = useTable({
|
||||||
"sort": 10301,
|
"sort": 10301,
|
||||||
"title": "用户管理",
|
"title": "用户管理",
|
||||||
"icon": "icon-park-outline-user"
|
"icon": "icon-park-outline-user"
|
||||||
|
},
|
||||||
|
"parentMeta": {
|
||||||
|
"title": "系统管理",
|
||||||
|
"icon": "icon-park-outline-setting",
|
||||||
|
"sort": 20000
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</route>
|
</route>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue