diff --git a/src/components/a-drop-table/ADropTable.vue b/src/components/a-drop-table/ADropTable.vue index 6d271a4..905414c 100644 --- a/src/components/a-drop-table/ADropTable.vue +++ b/src/components/a-drop-table/ADropTable.vue @@ -209,6 +209,10 @@ const displayData = computed(() => { diff --git a/src/components/a-page/a-table-page/ATablePage.tsx b/src/components/a-page/a-table-page/ATablePage.tsx new file mode 100644 index 0000000..c11063c --- /dev/null +++ b/src/components/a-page/a-table-page/ATablePage.tsx @@ -0,0 +1,405 @@ +/* @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, + 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 { DefaultRow } from 'element-plus/es/components/table/src/table/defaults' +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' + +interface ColumnScopeType { + row: T, + column: TableColumnCtx, + $index: number +} + +export interface TableActionType { + icon?: ElIconType + type?: 'text' | 'default' | 'primary' | 'success' | 'warning' | 'info' | 'danger' + label?: string | ((data: ColumnScopeType) => string) + tooltip?: string | ((data: ColumnScopeType) => string) + loading?: boolean + textBtn?: boolean + show?: (data: ColumnScopeType) => boolean + action: (data: ColumnScopeType) => Promise | void + confirm?: { + title: string | ((data: ColumnScopeType) => string) + width?: string + confirmButtonText?: string + cancelButtonText?: string + } +} + +export interface ActionColumnType { + width?: string + tableActions: TableActionType[] +} + +export interface ToolType { + icon?: ElIconType + type?: 'text' | 'default' | 'primary' | 'success' | 'warning' | 'info' | 'danger' + label?: string + action: () => void +} + +export interface ToolBarType { + leftTools: ToolType[] + rightTools: Required>[] +} + +export type TablePropsType = Omit, 'data' | 'headerRowClassName' | 'cellClassName' | 'context'> & { + treeLoad?: (param: F, row: T, expanded: any, resolve?: (data: T[]) => void) => void + actionColumn: ActionColumnType +} +export type FormPropsType = { + paging?: (param: P) => Promise>> + export?: (param: P) => Promise> + defaultData: P + highForm?: Partial & { colCount?: number, vgap?: string, hgap?: string } + simpleForm?: Partial +} + +interface PageLayoutType { + /* columns: string[] + rows: string[] */ + dataListHeight: string +} + +export interface ATablePageType

{ + pageLayout: PageLayoutType + searchForm: FormPropsType + toolBar: ToolBarType + table: TablePropsType +} + +function buildSearchForm(searchForm: Partial> = {}) { + if (searchForm.defaultData == null) { + searchForm.defaultData = {} as P + } + return searchForm +} + + +function buildPageLayout(pageLayout: Partial = {}) { + /* if (pageLayout.columns == null) { + pageLayout.columns = [ '1fr' ] + } + + if (pageLayout.rows == null) { + pageLayout.rows = [ '1fr', '9fr' ] + } */ + if (pageLayout.dataListHeight == null) { + pageLayout.dataListHeight = '9fr' + } + + return pageLayout +} + +function buildTable

(table: Partial> = {}) { + if (table.actionColumn == null) { + table.actionColumn = { + tableActions: [], + } + } + if (table.actionColumn.tableActions == null) { + table.actionColumn.tableActions = [] + } + + return table +} + +function buildToolBar(toolBar: Partial = {}) { + if (toolBar.leftTools == null) toolBar.leftTools = [] + if (toolBar.rightTools == null) toolBar.rightTools = [] + + return toolBar +} + +export interface PropsTplType

{ + pageLayout?: Partial + searchForm?: Partial> + toolBar?: Partial + table?: Partial> +} + +export function buildProps

({pageLayout, searchForm, toolBar, table}: PropsTplType) { + return reactive({ + pageLayout: buildPageLayout(pageLayout), + searchForm: buildSearchForm(searchForm), + toolBar: buildToolBar(toolBar), + table: buildTable(table), + }) +} + +interface Exposed extends Record { + doSearch: () => void +} + +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(true) + + 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(() => { + return { + '--col-count': props.searchForm.highForm?.colCount ?? 3, + '--vgap': props.searchForm.highForm?.vgap ?? '0', + '--hgap': props.searchForm.highForm?.hgap ?? '0', + } + }) + + const pageCssParam = computed(() => { + return { + '--data-list-height': props.pageLayout.dataListHeight, + } + }) + const actionColumnBtnRender = (scope: ColumnScopeType, tableAction: TableActionType) => { + const Btn = + {tableAction.label} + + const tooltipTxt = tableAction.tooltip == null ? '' : (typeof tableAction.tooltip === 'function' ? tableAction.tooltip(scope) : tableAction.tooltip) + if (Strings.isBlank(tooltipTxt)) { + return ( + {{ + default: () => Btn, + }} + ) + } + return Btn + } + const actionColumnRender = () => { + const actionColumn = props.table?.actionColumn + return Colls.isEmpty(actionColumn?.tableActions) ? <> + : ( +

+ {{ + default: (scope: ColumnScopeType) => (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))) + ), + }} +
+ ) + } + onMounted(doSearch) + return () => ( + {/*@ts-ignore*/} +
+ + {/*@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" + > + { + 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, Exposed { +} + +export default component diff --git a/src/components/a-page/a-table-page/a-table-page.d.ts b/src/components/a-page/a-table-page/a-table-page.d.ts new file mode 100644 index 0000000..bf5b637 --- /dev/null +++ b/src/components/a-page/a-table-page/a-table-page.d.ts @@ -0,0 +1,5 @@ +export {} +declare global { + interface ATablePageInstance extends InstanceType, ATablePageExposed { + } +} diff --git a/src/components/a-page/a-table-page/a-table-page.module.styl b/src/components/a-page/a-table-page/a-table-page.module.styl new file mode 100644 index 0000000..378c975 --- /dev/null +++ b/src/components/a-page/a-table-page/a-table-page.module.styl @@ -0,0 +1,197 @@ +.table-page { + grid-template-columns 1fr + grid-template-rows: minmax(0, 1fr) minmax(0, var(--data-list-height)); + grid-auto-rows: minmax(0, auto); + gap 10px + + &.folder { + grid-template-rows: 0 minmax(0, 3fr); + gap: 0; + + .search-form { + padding 0 + border-width 0 + height 0 + } + } + + .search-form-wrapper { + border: 1px solid #EAEBF1; + padding: 20px; + border-radius: 8px; + background-color: white; + height 100% + width 100%; + box-sizing: border-box; + + & { + + } + + & > div { + max-height 168px + width 100%; + } + + .search-form { + display grid + grid-template-columns repeat(var(--col-count), 1fr) + row-gap var(--vgap); + column-gap var(--hgap); + + :global(.el-form-item) { + margin 0 + } + + :global(.el-form-item).form-action-btn { + grid-column: 1 / -1; + + .el-form-item__content { + justify-content end + } + } + } + } + + .data-list { + box-sizing: border-box; + display grid + grid-template-columns 1fr + grid-template-rows 50px minmax(0, 1fr) 50px + border: 1px solid #EAEBF1; + padding: 15px 20px 20px 15px; + border-radius: 8px; + background-color: white; + height 100% + width 100%; + gap 20px + + .tool-bar { + display flex + justify-content space-between + box-sizing: border-box; + + .tool-bar-left { + flex 1 + } + + .tool-bar-right { + flex 1 + display flex + gap 10px + justify-content end + + :global(.el-form) { + display flex + gap 10px + flex-shrink 0 + + .el-form-item { + margin: 0; + flex 1 + } + } + + :global(.el-button--default) { + width 32px + color #4D5875 + background-color #F1F4F4 + border-color #F1F4F4 + margin 0 + + &:hover { + color var(--main-color) + background-color #E6EAEB; + border-color #E6EAEB; + } + } + + + :global(.el-dropdown) { + flex-shrink 0 + } + + :global(.el-button--default).filter-btn-active { + color var(--main-color) + } + } + } + + :global(.el-table) { + width 100% + height: 100%; + + :global(.table-header) { + color #454C59 + + th { + background-color #EDF1F7 + font-weight 500 + position relative + + & > div { + display flex + gap 5px + align-items center + } + + &:not(:first-child) > div::before { + position: absolute; + top: 50%; + left: 1px; + width: 1px; + background-color: #D3D7DE; + transform: translateY(-50%); + content: ""; + height 50% + } + } + } + + :global(.table-cell) { + color #2F3540 + } + + .action-btn { + width 100% + display flex + flex-wrap wrap + gap: 10px; + + .text-btn { + margin 0 + } + + .icon-btn { + margin 0 + padding: 8px; + justify-content: center; + align-items: center; + //#ebf0ff + } + + :global(.el-button--default).icon-btn { + color oklch(72% .19 231.6) + background-color oklch(0.96 0.03 224.26) + border-color oklch(0.96 0.03 224.26) + } + + :global(.el-button--danger).icon-btn { + color oklch(73% .15 25.3) + background-color oklch(0.96 0.02 22.09) + border-color oklch(0.96 0.02 22.09) + } + } + } + + :global(.el-pagination) { + justify-content center + + :global(.btn-next), + :global(.btn-prev), + :global(.el-pager) li { + border-radius: 6px !important; + } + } + } +} diff --git a/src/components/page/FormPage.vue b/src/components/page/FormPage.vue index cbffe75..bb07ab3 100644 --- a/src/components/page/FormPage.vue +++ b/src/components/page/FormPage.vue @@ -1,6 +1,6 @@