feat: 表格行增加下拉菜单功能

master
luoer 2023-08-08 17:32:41 +08:00
parent 8a2b29ef01
commit 0c9e19dc10
12 changed files with 243 additions and 125 deletions

View File

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

View File

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

View File

@ -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提示不是很友好。
### 最后 ### 最后
尽管看起来是低代码,但其实我更倾向于是业务组件。 尽管看起来是低代码,但其实我更倾向于是业务组件。

View File

@ -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',
* } * }
* ``` * ```
*/ */

View File

@ -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,10 +57,11 @@ const table = useTable({
}); });
</script> </script>
``` ```
以上就是一个CRUD表格的简单用法。参数描述
以上,就是一个 CRUD 表格的简单用法。参数描述:
| 参数 | 说明 | 类型 | | 参数 | 说明 | 类型 |
| :--- | :--- | :--- | | :--- | :--- | :--- |
| data | 表格数据,可为数组或函数(发起HTTP请求) | BaseData[] | ((search, paging) => Promise<any>) | | data | 表格数据,可为数组或函数(发起 HTTP 请求) | BaseData[] | ((search, paging) => Promise<any>) |
| columns | 表格列,参见 [TableColumnData](https://arco.design/vue/component/table#TableColumnData) 文档,增加和扩展部分属性,详见下文。 | TableColumnData[] | | columns | 表格列,参见 [TableColumnData](https://arco.design/vue/component/table#TableColumnData) 文档,增加和扩展部分属性,详见下文。 | TableColumnData[] |
| pagination | 分页参数,参见 [Pagination](https://arco.design/vue/component/pagination) 文档,默认 15/每页。| Pagination | | pagination | 分页参数,参见 [Pagination](https://arco.design/vue/component/pagination) 文档,默认 15/每页。| Pagination |
| search | 搜索表单的配置,参见 [Form]() 说明,其中 `submit` 参数不可用 | FormProps | | search | 搜索表单的配置,参见 [Form]() 说明,其中 `submit` 参数不可用 | FormProps |
@ -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' }`数据
- 问题:搜索表单、新增表单和修改表单通常用到同一表单项,如何避免重复定义 - 问题:搜索表单、新增表单和修改表单通常用到同一表单项,如何避免重复定义

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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