/* @jsxImportSource vue */ import APage from '@/components/a-page/APage.vue' import styles from '@/components/a-page/a-table-page/a-table-page.module.styl' import { ElButton, ElDropdown, ElDropdownItem, ElDropdownMenu, ElForm, ElLoading, ElMessageBox, ElPagination, ElPopconfirm, ElScrollbar, ElTable, ElTableColumn, ElTooltip, type TableColumnCtx, type TableProps, } from 'element-plus' import { defineComponent, withDirectives, withModifiers, } from 'vue' import type { R } from '@/common/utils/http-util.ts' import { elIcons, type ElIconType, } from '@/common/element/element.ts' import type { FormProps } from 'element-plus/es/components/form/src/form' import Utils, { type ResetAble } from '@/common/utils' import Strings from '@/common/utils/strings.ts' import { saveFile } from '@/common/app' import Colls from '@/common/utils/colls.ts' import type { SetupContext } from '@vue/runtime-core' import Types from '@/common/utils/types.ts' import { type IconName, iconNames, } from '@/components/a-icon/iconfont.ts' import AIcon from '@/components/a-icon/AIcon.vue' import type { DefaultRow } from 'element-plus/es/components/table/src/table/defaults' interface ColumnScopeType { row: T, column: TableColumnCtx, $index: number } interface TableActionType { icon?: ElIconType | IconName type?: 'text' | 'default' | 'primary' | 'success' | 'warning' | 'info' | 'danger' tooltip: string | ((data: ColumnScopeType) => string) loading?: boolean show?: (data: ColumnScopeType) => boolean action: (data: ColumnScopeType) => Promise | void confirm?: { title: string | ((data: ColumnScopeType) => string) width?: string confirmButtonText?: string cancelButtonText?: string } } interface ActionColumnType { /** * 列宽,默认:80px */ width: number foldLimit: number /** * 按钮列表 */ tableActions: TableActionType[] } interface ToolType { icon?: ElIconType type?: 'text' | 'default' | 'primary' | 'success' | 'warning' | 'info' | 'danger' label?: string action: () => void } interface ToolBarType { leftTools: ToolType[] rightTools: Required>[] } interface TablePropsType extends Omit, 'data' | 'headerRowClassName' | 'cellClassName' | 'context'> { treeLoad?: (param: F, row: T, expanded: any, resolve?: (data: T[]) => void) => void /** * 操作列配置 */ actionColumn: ActionColumnType } interface FormPropsType { /** * 分页函数 * @param param 查询条件 */ paging: (param: P) => Promise>> /** * 导出函数 * @param param 查询条件 */ export?: (param: P) => Promise> /** * 表单默认值 */ defaultData: P /** * 高级查询表单配置 */ highForm: Partial> & { /** * 标签宽度,单位:px,默认:90px */ labelWidth: number, /** * 输入框宽度,单位:px,默认:200px */ contentWidth: number, /** * 行间隔,单位:px,默认:10px */ rgap: number, /** * 列间隔,单位:px,默认:10px */ cgap: number } /** * 简单查询表单配置 */ simpleForm: Partial & { /** * 列数,默认:1 */ colCount: number, /** * 输入框宽度,单位:px,默认:200px */ contentWidth: number, /** * 行间隔,单位:px,默认:10px */ // rgap: number, /** * 列间隔,单位:px,默认:10px */ // cgap: number } } /** * 页布局配置 */ interface PageLayoutType { /** * 查询表单高度,传入数字时,单位:fr,传入字符串时不会而外添加单位,,默认:1fr */ searchFormHeight: number | string /** * 数据表格高度,传入数字时,单位:fr,传入字符串时不会而外添加单位,,默认:9fr */ dataListHeight: number | string showHighForm: boolean enableHighForm: boolean } interface ATablePageType

{ /** * 页布局配置 */ pageLayout: PageLayoutType /** * 查询表单配置 */ searchForm: FormPropsType /** * 工具栏配置 */ toolBar: ToolBarType /** * 表格配置 */ table: TablePropsType } function buildSearchForm(searchForm: DeepPartial> = {}) { if (searchForm.defaultData == null) { searchForm.defaultData = {} as DeepPartial

} if (searchForm.highForm == null) { searchForm.highForm = { labelWidth: 90, contentWidth: 200, rgap: 10, cgap: 10, } } if (searchForm.highForm.labelWidth == null) searchForm.highForm.labelWidth = 90 if (searchForm.highForm.contentWidth == null) searchForm.highForm.contentWidth = 200 if (searchForm.highForm.rgap == null) searchForm.highForm.rgap = 10 if (searchForm.highForm.cgap == null) searchForm.highForm.cgap = 10 if (searchForm.simpleForm == null) { searchForm.simpleForm = { contentWidth: 200, colCount: 1, // rgap: 10, // cgap: 10, } } if (searchForm.simpleForm.colCount == null) searchForm.simpleForm.colCount = 1 if (searchForm.simpleForm.contentWidth == null) searchForm.simpleForm.contentWidth = 200 // if (searchForm.simpleForm.rgap == null) searchForm.simpleForm.rgap = 10 // if (searchForm.simpleForm.cgap == null) searchForm.simpleForm.cgap = 10 return searchForm } function buildPageLayout(pageLayout: DeepPartial = {}) { if (pageLayout.searchFormHeight == null) { pageLayout.searchFormHeight = 1 } if (pageLayout.dataListHeight == null) { pageLayout.dataListHeight = 9 } if (pageLayout.showHighForm == null) { pageLayout.showHighForm = false } if (pageLayout.enableHighForm == null) { pageLayout.enableHighForm = true } return pageLayout } function buildTable

(table: DeepPartial> = {}) { if (table.actionColumn == null) { table.actionColumn = { width: 80, foldLimit: 3, tableActions: [], } } if (table.actionColumn.tableActions == null) { table.actionColumn.tableActions = [] } for (let tableAction of table.actionColumn.tableActions) { if (tableAction.confirm != null) { if (tableAction.confirm.cancelButtonText == null) tableAction.confirm.cancelButtonText = '否' if (tableAction.confirm.confirmButtonText == null) tableAction.confirm.confirmButtonText = '是' if (tableAction.confirm.width == null) tableAction.confirm.width = '180' } if (tableAction.type == null) { tableAction.type = 'primary' } } if (table.actionColumn.foldLimit == null) { table.actionColumn.foldLimit = 3 } if (table.actionColumn.width == null) { table.actionColumn.width = Math.min(table.actionColumn.tableActions.length, table.actionColumn.foldLimit) * 50 + 10 } if (table.emptyText == null) { table.emptyText = '暂无数据' } if (table.rowKey == null) { table.rowKey = 'id' } return table } function buildToolBar(toolBar: DeepPartial = {}) { if (toolBar.leftTools == null) toolBar.leftTools = [] for (let leftTool of toolBar.leftTools) { if (leftTool.type == null) { leftTool.type = 'primary' } } if (toolBar.rightTools == null) toolBar.rightTools = [] return toolBar } export function buildTablePageProps

({pageLayout, searchForm, toolBar, table}: DeepPartial>) { return reactive({ pageLayout: buildPageLayout(pageLayout), searchForm: buildSearchForm(searchForm), toolBar: buildToolBar(toolBar), table: buildTable(table), }) } const component = defineComponent(

(props: ATablePageType, {slots, expose}: SetupContext) => { const formData = Utils.resetAble(reactive

({ ...(props.searchForm.defaultData), current: 1, size: 20, })) as ResetAble

const tableData = Utils.resetAble(reactive([])) as ResetAble const totalCount = ref(0) const loading = ref(false) const showHighForm = ref(props.pageLayout.showHighForm) const doSearch = () => { loading.value = true if (props.searchForm.paging != null) { props.searchForm.paging(formData.$clone() as P) .then(res => { totalCount.value = res.data?.total ?? 0 const records = res.data?.records ?? ([] as T[]) tableData.$reset(records) }) .finally(() => { loading.value = false }) return } } expose({doSearch}) const showHighFormHandle = () => { showHighForm.value = !showHighForm.value formData.$reset() } const doExport = () => { if (props.searchForm.export == null) { return } loading.value = true saveFile(props.searchForm.export(formData.$clone() as P)) .finally(() => { loading.value = false }) } const rowAction = (data: ColumnScopeType, action: TableActionType) => { if (action.loading != null) { action.loading = true } const result = action.action(data) if (result instanceof Promise) { result .then(reload => { if (reload) { doSearch() } }) .finally(() => { if (action.loading != null) { action.loading = false } }) } } function doReset() { formData.$reset() doSearch() } const highFormCssParam = computed(() => { const labelWidth = props.searchForm.highForm.labelWidth const contentWidth = props.searchForm.highForm.contentWidth return { '--item-min-width': (labelWidth + contentWidth) + 'px', '--rgap': props.searchForm.highForm.rgap + 'px', '--cgap': props.searchForm.highForm.cgap + 'px', } }) const toolBarRightCssParam = computed(() => { const colCount = props.searchForm.simpleForm.colCount const contentWidth = props.searchForm.simpleForm.contentWidth const btnCount = props.toolBar.rightTools.length + (props.searchForm.export == null ? 0 : 1) + (props.pageLayout.enableHighForm ? 1 : 0) return { '--form-item-count': colCount, '--form-item-width': contentWidth + 'px', // '--rgap': props.searchForm.simpleForm.rgap + 'px', // '--cgap': props.searchForm.simpleForm.cgap + 'px', '--btn-count': btnCount, } }) const pageCssParam = computed(() => { const dataListHeight = props.pageLayout.dataListHeight const searchFormHeight = props.pageLayout.searchFormHeight return { '--data-list-height': Types.isString(dataListHeight) ? dataListHeight : dataListHeight + 'fr', '--search-form-height': Types.isString(searchFormHeight) ? searchFormHeight : searchFormHeight + 'fr', } }) const actionColumnBtnRender = (scope: ColumnScopeType, tableAction: TableActionType) => { let elIcon: any | undefined = undefined if (iconNames.includes(tableAction.icon as IconName)) { elIcon = } else if (Object.keys(elIcons).includes(tableAction.icon as unknown as string)) { elIcon = elIcons[tableAction.icon as ElIconType] } const tooltipTxt = tableAction.tooltip == null ? '' : (typeof tableAction.tooltip === 'function' ? tableAction.tooltip(scope) : tableAction.tooltip) if (!Strings.isBlank(tooltipTxt)) { return ( rowAction(scope, tableAction) : undefined} plain/> ) } return ( rowAction(scope, tableAction) : undefined} text/>) } const actionColumnRender = () => { const actionColumn = props.table.actionColumn if (Colls.isEmpty(actionColumn.tableActions)) return (<>) return ( {{ default: (scope: ColumnScopeType) => { const len = actionColumn.tableActions.filter(it => it.show == null || it.show(scope)).length let btns: any[] if (len <= actionColumn.foldLimit) { btns = (actionColumn.tableActions .filter(it => (it.show == null ? true : it.show(scope))) .map((tableAction, i) => (tableAction.confirm != null ? ( rowAction(scope, tableAction)}> {{ reference: () => (

{actionColumnBtnRender(scope, tableAction)}
), }} ) : actionColumnBtnRender(scope, tableAction))) ) } else { btns = (actionColumn.tableActions .filter(it => (it.show == null ? true : it.show(scope))) .filter((_, i) => i < actionColumn.foldLimit - 1) .map((tableAction, i) => (tableAction.confirm != null ? ( rowAction(scope, tableAction)}> {{ reference: () => (
{actionColumnBtnRender(scope, tableAction)}
), }}
) : actionColumnBtnRender(scope, tableAction))) ) btns.push( { const tableAction = actionColumn.tableActions[cmd] if (tableAction.confirm != null) { ElMessageBox.confirm( typeof tableAction.confirm.title === 'function' ? tableAction.confirm.title(scope) : tableAction.confirm.title, '操作提示', { confirmButtonText: tableAction.confirm.confirmButtonText, cancelButtonText: tableAction.confirm.cancelButtonText, confirmButtonType: 'primary', cancelButtonType: 'danger', type: 'warning', }, ) .then(() => { rowAction(scope, tableAction) }) .catch(() => { }) return } rowAction(scope, tableAction) }}> {{ default: () => (), dropdown: () => ( { actionColumn.tableActions .filter(it => (it.show == null ? true : it.show(scope))) .filter((_, i) => i >= actionColumn.foldLimit - 1) .map((it, i) => { let elIcon: any | undefined = undefined if (iconNames.includes(it.icon as IconName)) { elIcon = } else if (Object.keys(elIcons).includes(it.icon as unknown as string)) { elIcon = elIcons[it.icon as ElIconType] } return ( {typeof it.tooltip === 'function' ? it.tooltip(scope) : it.tooltip} ) }) } ), }} ) } return (
{btns}
) }, }} ) } const sortChangeHandler = ({prop, order}: { prop: string, order: 'ascending' | 'descending' | null }) => { formData.orders = order == null ? undefined : (prop + ':' + (order == 'ascending' ? 'asc' : 'desc')) doSearch() } onMounted(doSearch) return () => (
{props.pageLayout.enableHighForm ? ( {/*@ts-ignore*/} { slots.highFormItem?.(formData) }
{ props.toolBar.leftTools.map((tool, i) => ( {tool.label} )) }
{/*@ts-ignore*/} { slots.simpleFormItem?.(formData) }
{ withDirectives( props.table.treeLoad!(formData as P, row, expanded, resolve)) : undefined} onExpand-change={props.table.treeLoad ? ((row: any, expandedRows: any) => props.table.treeLoad!(formData as P, row, expandedRows, undefined)) : undefined} cell-class-name="table-cell" header-row-class-name="table-header" class="data-table" onSort-change={sortChangeHandler} > { slots.columns?.() } { actionColumnRender() } , [ [ ElLoading.directive, loading.value ] ]) } (formData as G.PageParam).current = val} onUpdate:page-size={(val) => (formData as G.PageParam).size = val} hide-on-single-page={false} page-sizes={[ 10, 20, 50, 100, 500 ]} teleported={false} total={totalCount.value} background={true} layout="total, prev, pager, next, sizes, jumper" onChange={doSearch}/> { slots?.default?.() }
) }, { name: 'ATablePage', props: [ 'pageLayout', 'searchForm', 'toolBar', 'table' ], }, ) export interface ATablePageInstance extends InstanceType { doSearch: () => void } export default component