diff --git a/docs/src/document/zh-CN/components/table.md b/docs/src/document/zh-CN/components/table.md index b1e46ca59c677863c4129e22818d637230307b2c..f704a26014f2b94c11757c597b4d32dad8f873c4 100644 --- a/docs/src/document/zh-CN/components/table.md +++ b/docs/src/document/zh-CN/components/table.md @@ -235,11 +235,11 @@ const clearSelectedKeys3 = () => { const getSelectedKeys3 = () => { layer.msg(selectedKeys3.value); -} +} const getCheckData3 = () => { layer.msg(JSON.stringify(tableRef3.value.getCheckData())); -} +} const columns3 = [ { fixed: "left", type: "checkbox", title: "复选"}, @@ -1020,6 +1020,7 @@ interface TableDefaultToolbarComplex { title: string; icon: string; onClick?: () => void; + render?: () => VNode; } ``` diff --git a/packages/component/component/table/__tests__/table.test.tsx b/packages/component/component/table/__tests__/table.test.tsx index fd33112f556b20334a5f124f75d9cb525c6fa00d..bb9501c7993c367c90b3ea11c334cac75536045c 100644 --- a/packages/component/component/table/__tests__/table.test.tsx +++ b/packages/component/component/table/__tests__/table.test.tsx @@ -1,9 +1,10 @@ import { DOMWrapper, type VueWrapper, mount } from "@vue/test-utils"; import LayTable from "../index.vue"; import LayCheckboxV2 from "@layui/component/component/checkboxV2/index.vue"; +import Button from '@layui/component/component/button/index.vue' import { describe, expect, test, vi } from "vitest"; -import { nextTick, reactive, ref } from "vue"; +import {h, nextTick, reactive, ref} from "vue"; import { sleep } from "../../../test-utils"; describe("LayTable", () => { @@ -492,17 +493,77 @@ describe("LayTable", () => { }, }); const iconBoxs = wrapper.findAll( - ".layui-table-view .layui-table-tool-self .layui-inline" + ".layui-table-view .layui-table-tool-self .layui-space-item" ); expect(iconBoxs.length).toBe(3); - expect(iconBoxs[1].attributes().title).toBe("刷新"); + expect(iconBoxs[1].find('div').attributes().title).toBe("刷新"); - await iconBoxs[1].trigger("click"); + await iconBoxs[1].find('div').trigger("click"); expect(value).toBe(2); }); + test("default-toolbar 渲染自定义组件", async () => { + const columns = [ + { + fixed: "left" as const, + type: "checkbox", + title: "复选", + key: "checkbox", + }, + { + title: "编号", + width: "100px", + key: "id", + }, + ]; + + const dataSource = ref([ + { + id: "1", + }, + { + id: "2", + }, + ]); + + let value = 1; + + const defaultToolbars = [ + "filter", + { + render: () => h(Button, { + onClick: () => { + value++; + }, + }) + }, + ]; + + const wrapper = mount({ + setup() { + return () => ( + + ); + }, + }); + + const iconBoxs = wrapper.findAll( + ".layui-table-view .layui-table-tool-self .layui-space-item" + ); + + expect(iconBoxs[1].find('.layui-btn').attributes().type).toBe('button') + + await iconBoxs[1].find('.layui-btn').trigger("click"); + + expect(value).toBe(2) + }); + test("page change", async () => { const columns = [ { diff --git a/packages/component/component/table/components/TableToolbar.tsx b/packages/component/component/table/components/TableToolbar.tsx new file mode 100644 index 0000000000000000000000000000000000000000..85209d4c88d6dd668c6fe0be6b2303f9a091e1f4 --- /dev/null +++ b/packages/component/component/table/components/TableToolbar.tsx @@ -0,0 +1,176 @@ +import type { PropType } from "vue"; +import type { LayTableContextType } from "../constant"; +import type { TableColumn, TableDefaultToolbar } from "../typing"; +import type { TableToolBarType } from "./types"; + +import LayRender from "@layui/component/component/_components/render"; +import LayCheckboxV2 from "@layui/component/component/checkboxV2"; +import LayDropdown from "@layui/component/component/dropdown"; +import LayIcon from "@layui/component/component/icon"; +import LaySpace from "@layui/component/component/space"; +import { isValueArray } from "@layui/component/utils"; +import { defineComponent, h, inject } from "vue"; +import { LAY_TABLE_CONTEXT } from "../constant"; +import { useToolBar } from "../hooks/useToolbar"; + +const clsPrefix = (cls: string) => `layui-${cls}`; + +function handleCheckChange(column: TableColumn) { + column.hide = !column.hide; +} + +const ToolbarItem = defineComponent({ + name: "ToolbarItem", + + props: { + resetParams: { + required: true, + type: Object as PropType< + Omit, "showToolbars"> & { + hierarchicalColumns: TableToolBarType["hierarchicalColumns"]; + } + >, + }, + + toolbar: { + required: true, + type: [String, Object] as PropType, + }, + }, + + setup(props) { + return () => { + const { toolbar, resetParams: { t, exportData, hierarchicalColumns } } = props; + + // filter + if (toolbar === "filter") { + return h(LayDropdown, { placement: "bottom-end" }, { + default: () => h( + "div", + { + class: clsPrefix("table-toolbar-item"), + title: t("table.filter"), + }, + h(LayIcon, { type: clsPrefix("icon-slider") }), + ), + + content: () => h( + "div", + { class: clsPrefix("table-tool-checkbox") }, + hierarchicalColumns[0].map((column, columnIndex) => h(LayCheckboxV2, { + skin: "primary", + key: column.key || column.type || columnIndex, + value: columnIndex, + modelValue: !column.hide, + disabled: isValueArray(column.children), + + onChange: () => handleCheckChange(column), + }, () => column.title)), + ), + }); + } + + // export + if (toolbar === "export") { + return h("div", { + title: t("table.export"), + class: clsPrefix("table-toolbar-item"), + + onClick: exportData, + }, h(LayIcon, { type: clsPrefix("icon-export") })); + } + + // print + if (toolbar === "print") { + return h("div", { + title: t("table.print"), + class: clsPrefix("table-toolbar-item"), + + onClick: print, + }, h(LayIcon, { type: clsPrefix("icon-print") })); + } + + // render + if (toolbar?.render) { + return toolbar.render(); + } + + return h("div", { + title: toolbar.title, + class: clsPrefix("table-toolbar-item"), + + onClick: toolbar.onClick, + }, h(LayIcon, { type: toolbar.icon })); + }; + }, +}); + +const TableToolbar = defineComponent({ + name: "TableToolbar", + + props: { + defaultToolbar: { + required: true, + type: Array as PropType, + }, + + hierarchicalColumns: { + required: true, + type: Array as PropType, + }, + + spanMethod: { + required: true, + type: Function as PropType, + }, + + lastLevelAllColumns: { + required: true, + type: Array as PropType, + }, + + tableDataSource: { + required: true, + type: Array as PropType, + }, + + tableRef: { + required: true, + type: Object as PropType, + }, + }, + + setup(props) { + const { tableSlots } = inject(LAY_TABLE_CONTEXT) as LayTableContextType; + const { showToolbars, ...resetToolBar } = useToolBar(props); + + return () => { + if (!isValueArray(showToolbars.value) && !tableSlots.toolbar) { + return null; + } + + return h("div", { class: clsPrefix("table-tool") }, [ + h("div", { class: clsPrefix("table-tool-temp") }, [ + h(LayRender, { slots: tableSlots, render: "toolbar" }), + ]), + + isValueArray(showToolbars.value) && h( + "div", + { class: clsPrefix("table-tool-self") }, + h(LaySpace, () => showToolbars.value.map((toolbar, index) => { + return h(ToolbarItem, { + toolbar, + key: index, + resetParams: { + ...resetToolBar, + hierarchicalColumns: props.hierarchicalColumns, + }, + }); + })), + ), + ]); + }; + }, +}); + +export default TableToolbar; diff --git a/packages/component/component/table/components/TableToolbar.vue b/packages/component/component/table/components/TableToolbar.vue deleted file mode 100644 index e549a200ba43b9d28ada9ed4ee95e53f3170c28b..0000000000000000000000000000000000000000 --- a/packages/component/component/table/components/TableToolbar.vue +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - - - - - - - - - - - - - handleCheckChange(column)" - > - {{ column.title }} - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/component/component/table/constant.ts b/packages/component/component/table/constant.ts index 0cf5c6dabfa128c1c0ef669251a5826814c2859b..1feb894dcda66f5747d7ae95964175bf709d5190 100644 --- a/packages/component/component/table/constant.ts +++ b/packages/component/component/table/constant.ts @@ -8,7 +8,7 @@ import type { RequiredTableProps, TableEmit, TableProps } from "./typing"; export const columnsTypeList = ["radio", "checkbox", "number"]; -interface LayTableContextType { +export interface LayTableContextType { tableEmits: TableEmit; tableProps: RequiredTableProps; tableSlots: Slots; diff --git a/packages/component/component/table/index.less b/packages/component/component/table/index.less index 77c3ef59196f34cbf103f8a0115b834f84af06c7..3832042ff260ccf75130e7e63478099aae195801 100644 --- a/packages/component/component/table/index.less +++ b/packages/component/component/table/index.less @@ -287,6 +287,25 @@ } } +.layui-table-toolbar-item { + position: relative; + width: 26px; + height: 26px; + line-height: 26px; + border-radius: var(--global-border-radius); + text-align: center; + color: #333; + border: 1px solid #ccc; + cursor: pointer; + .layui-icon { + font-size: 15px; + } +} + +.layui-table-toolbar-item:hover { + border: 1px solid #999; +} + .layui-table-tool .layui-inline[lay-event]:hover { border: 1px solid #999; } diff --git a/packages/component/component/table/index.vue b/packages/component/component/table/index.vue index b0697e32b9052010cce224a2a36839cb787095e1..df5f0787c0e1cb92620cdd927e7f248a258d5e53 100644 --- a/packages/component/component/table/index.vue +++ b/packages/component/component/table/index.vue @@ -19,7 +19,7 @@ import LayEmpty from "../empty/index.vue"; import TableHeader from "./components/TableHeader.vue"; import TableMain from "./components/TableMain"; import TablePage from "./components/TablePage.vue"; -import TableToolbar from "./components/TableToolbar.vue"; +import TableToolbar from "./components/TableToolbar"; import TableTotal from "./components/TableTotal.vue"; import { LAY_TABLE_CONTEXT } from "./constant"; diff --git a/packages/component/component/table/typing.ts b/packages/component/component/table/typing.ts index 888b95560f03a03ee6c8b3449cf9f489235fe27b..dd2414bb6b1be041b9e156ad98fe8d1186ddf80e 100644 --- a/packages/component/component/table/typing.ts +++ b/packages/component/component/table/typing.ts @@ -2,6 +2,7 @@ import type { RenderProps } from "@layui/component/component/_components/render" import type { PageProps } from "@layui/component/component/page/interface"; // import type { TooltipProps } from "@layui/component/component/tooltip/types"; import type { CommonAlign, Recordable } from "@layui/component/types"; +import type { VNode } from "vue"; export interface TableProps { id?: string; @@ -121,4 +122,5 @@ export interface TableDefaultToolbarComplex { title: string; icon: string; onClick?: () => void; + render?: () => VNode; } diff --git a/play/main.ts b/play/main.ts index c1868e7847b1b03924fe22d83541937656f83ea3..a5789235d89ceb7ec91c54420e342b0bc975e6c3 100644 --- a/play/main.ts +++ b/play/main.ts @@ -1,12 +1,11 @@ import { createApp } from "vue"; import layui from "../packages/component/index"; -import layer from "../packages/layer/src/index"; // import LayJsonSchemaForm from "../packages/json-schema-form/src/index"; // import LayJsonSchemaForm from "../packages/json-schema-form/lib/json-schema-form.es.js"; // import "../packages/component/lib/index.css"; (async () => { - const apps = import.meta.glob("./src/*.vue"); + const apps = import.meta.glob(["./src/*.vue", "./src/*.tsx"]); const name = location.pathname.replace(/^\//, "") || "App"; const file = apps[`./src/${name}.vue`]; if (!file) {