From 771c2dff8f7f80da9a760ff2454c1fc6909d92ec Mon Sep 17 00:00:00 2001 From: lzq <2495532633@qq.com> Date: Tue, 27 Jan 2026 23:58:17 +0800 Subject: [PATCH] 1 --- src/common/utils/objects.ts | 4 +- src/common/utils/times.ts | 14 +- src/common/utils/types.ts | 6 +- .../a-page/a-table-page/ATablePage.tsx | 279 ++++++++++++------ .../a-table-page/a-table-page.module.styl | 76 ++--- src/pages/order/book/Book.vue | 11 +- 6 files changed, 249 insertions(+), 141 deletions(-) diff --git a/src/common/utils/objects.ts b/src/common/utils/objects.ts index c8b15fc..72b01da 100644 --- a/src/common/utils/objects.ts +++ b/src/common/utils/objects.ts @@ -1,5 +1,5 @@ import { TypeTag } from '@/common/utils/index.ts' -import { isString } from '@/common/utils/types.ts' +import Types from '@/common/utils/types.ts' /** * 检查是否为普通对象 @@ -36,7 +36,7 @@ export function isArrayLike(obj: any) { * @param obj 待检查对象 */ export function isNumStr(obj: any) { - return isString(obj) && !isNaN(Number(obj)) + return Types.isString(obj) && !isNaN(Number(obj)) } type tags = 'error' | 'info' | 'warning' | 'risk' | 'error' | 'fatal' | 'success' | undefined; diff --git a/src/common/utils/times.ts b/src/common/utils/times.ts index 121c714..b266042 100644 --- a/src/common/utils/times.ts +++ b/src/common/utils/times.ts @@ -1,8 +1,4 @@ -import { - isDate, - isNumber, - isString, -} from '@/common/utils/types' +import Types from '@/common/utils/types' import { isNumStr } from '@/common/utils/objects.ts' import { type DateObjectUnits, @@ -44,11 +40,11 @@ function now() { * @return luxon 库的时间对象 */ function parse(date: Date | number | string | DateObjectUnits, fmt: string = FMT.date_time_sec) { - if (isDate(date)) { + if (Types.isDate(date)) { return DateTime.fromJSDate(date as Date) - } else if (isNumber(date) || isNumStr(date)) { + } else if (Types.isNumber(date) || isNumStr(date)) { return DateTime.fromMillis(Number(date)) - } else if (isString(date)) { + } else if (Types.isString(date)) { return DateTime.fromFormat(date as string, fmt) } else { return DateTime.fromObject(date as DateObjectUnits) @@ -63,7 +59,7 @@ function parse(date: Date | number | string | DateObjectUnits, fmt: string = FMT */ function format(date?: DateTime | Date, fmt: string = FMT.date_time_sec) { if (date == null) return '' - if (isDate(date)) { + if (Types.isDate(date)) { return DateTime.fromJSDate(date as Date).toFormat(fmt) } else { return (date as DateTime).toFormat(fmt) diff --git a/src/common/utils/types.ts b/src/common/utils/types.ts index 7000f43..cb8dfd2 100644 --- a/src/common/utils/types.ts +++ b/src/common/utils/types.ts @@ -4,7 +4,7 @@ import { TypeTag } from '@/common/utils/index.ts' * 检查给定的值是否为字符串 * @param obj 待检查的对象 */ -export function isString(obj: any) { +function isString(obj: any) { return obj != null && (typeof obj === 'string' || (typeof obj === 'object' && @@ -16,7 +16,7 @@ export function isString(obj: any) { * 检查给定的值是否为 JS 日期(Date) * @param obj 待检查的对象 */ -export function isDate(obj: any) { +function isDate(obj: any) { return obj != null && (typeof obj === 'object' && Object.prototype.toString.call(obj) === TypeTag.DATE) @@ -26,7 +26,7 @@ export function isDate(obj: any) { * 检查给定的值是否为数字 * @param obj 待检查的对象 */ -export function isNumber(obj: any) { +function isNumber(obj: any) { return obj != null && (typeof obj === 'number' || (typeof obj === 'object' && diff --git a/src/components/a-page/a-table-page/ATablePage.tsx b/src/components/a-page/a-table-page/ATablePage.tsx index c11063c..7e88d9c 100644 --- a/src/components/a-page/a-table-page/ATablePage.tsx +++ b/src/components/a-page/a-table-page/ATablePage.tsx @@ -34,6 +34,15 @@ 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' + +type DeepPartial = T extends Function + ? T + : T extends Array + ? Array> + : T extends object + ? { [K in keyof T]?: DeepPartial } + : T; interface ColumnScopeType { row: T, @@ -41,13 +50,12 @@ interface ColumnScopeType { $index: number } -export interface TableActionType { +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?: { @@ -58,57 +66,130 @@ export interface TableActionType { } } -export interface ActionColumnType { - width?: string +interface ActionColumnType { + /** + * 列宽,默认:80px + */ + width: string + /** + * 按钮列表 + */ tableActions: TableActionType[] } -export interface ToolType { +interface ToolType { icon?: ElIconType type?: 'text' | 'default' | 'primary' | 'success' | 'warning' | 'info' | 'danger' label?: string action: () => void } -export interface ToolBarType { +interface ToolBarType { leftTools: ToolType[] rightTools: Required>[] } -export type TablePropsType = Omit, 'data' | 'headerRowClassName' | 'cellClassName' | 'context'> & { +interface TablePropsType extends 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>> + +interface FormPropsType { + /** + * 分页函数 + * @param param 查询条件 + */ + paging: (param: P) => Promise>> + /** + * 导出函数 + * @param param 查询条件 + */ export?: (param: P) => Promise> + /** + * 表单默认值 + */ defaultData: P - highForm?: Partial & { colCount?: number, vgap?: string, hgap?: string } - simpleForm?: Partial + /** + * 高级查询表单配置 + */ + highForm: Partial> & { + /** + * 标签宽度,单位:px,默认:90px + */ + labelWidth: number, + /** + * 输入框宽度,单位:px,默认:190px + */ + contentWidth: number, + /** + * 行间隔,单位:px,默认:10px + */ + rgap: number, + /** + * 列间隔,单位:px,默认:10px + */ + cgap: number + } + /** + * 简单查询表单配置 + */ + simpleForm: Partial } +/** + * 页布局配置 + */ interface PageLayoutType { /* columns: string[] rows: string[] */ - dataListHeight: string + /** + * 数据表格高度,传入数字时,单位:fr,传入字符串时不会而外添加单位,,默认:9fr + */ + dataListHeight: number | string } -export interface ATablePageType

{ +interface ATablePageType

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

} + if (searchForm.highForm == null) { + searchForm.highForm = { + labelWidth: 90, + contentWidth: 190, + rgap: 10, + cgap: 10, + } + } + if (searchForm.highForm.labelWidth == null) searchForm.highForm.labelWidth = 90 + if (searchForm.highForm.contentWidth == null) searchForm.highForm.contentWidth = 90 + if (searchForm.highForm.rgap == null) searchForm.highForm.rgap = 10 + if (searchForm.highForm.cgap == null) searchForm.highForm.cgap = 10 return searchForm } - -function buildPageLayout(pageLayout: Partial = {}) { +function buildPageLayout(pageLayout: DeepPartial = {}) { /* if (pageLayout.columns == null) { pageLayout.columns = [ '1fr' ] } @@ -117,40 +198,55 @@ function buildPageLayout(pageLayout: Partial = {}) { pageLayout.rows = [ '1fr', '9fr' ] } */ if (pageLayout.dataListHeight == null) { - pageLayout.dataListHeight = '9fr' + pageLayout.dataListHeight = 9 } return pageLayout } -function buildTable

(table: Partial> = {}) { +function buildTable

(table: DeepPartial> = {}) { if (table.actionColumn == null) { table.actionColumn = { + width: '80px', 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 (table.actionColumn.width == null) { + table.actionColumn.width = '80px' + } + if (table.emptyText == null) { + table.emptyText = '暂无数据' + } + if (table.rowKey == null) { + table.emptyText = 'id' + } return table } -function buildToolBar(toolBar: Partial = {}) { +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 interface PropsTplType

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

({pageLayout, searchForm, toolBar, table}: PropsTplType) { +export function buildProps

({pageLayout, searchForm, toolBar, table}: DeepPartial>) { return reactive({ pageLayout: buildPageLayout(pageLayout), searchForm: buildSearchForm(searchForm), @@ -159,10 +255,6 @@ export function buildProps

({pageLay }) } -interface Exposed extends Record { - doSearch: () => void -} - const component = defineComponent(

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

({ @@ -197,11 +289,11 @@ const component = defineComponent( formData.$reset() } const doExport = () => { - if (props?.searchForm?.export == null) { + if (props.searchForm.export == null) { return } loading.value = true - saveFile(props?.searchForm?.export(formData.$clone() as P)) + saveFile(props.searchForm.export(formData.$clone() as P)) .finally(() => { loading.value = false }) @@ -232,16 +324,19 @@ const component = defineComponent( } const highFormCssParam = computed(() => { + const labelWidth = props.searchForm.highForm.labelWidth + const contentWidth = props.searchForm.highForm.contentWidth return { - '--col-count': props.searchForm.highForm?.colCount ?? 3, - '--vgap': props.searchForm.highForm?.vgap ?? '0', - '--hgap': props.searchForm.highForm?.hgap ?? '0', + '--item-min-width': (labelWidth + contentWidth) + 'px', + '--rgap': props.searchForm.highForm.rgap + 'px', + '--cgap': props.searchForm.highForm.cgap + 'px', } }) const pageCssParam = computed(() => { + const dataListHeight = props.pageLayout.dataListHeight return { - '--data-list-height': props.pageLayout.dataListHeight, + '--data-list-height': Types.isString(dataListHeight) ? dataListHeight : dataListHeight + 'fr', } }) const actionColumnBtnRender = (scope: ColumnScopeType, tableAction: TableActionType) => { @@ -249,7 +344,7 @@ const component = defineComponent( icon={tableAction.icon == null ? undefined : elIcons[tableAction.icon]} loading={tableAction.loading} type={tableAction.type} - class={tableAction.textBtn ? 'text-btn' : 'icon-btn'} + class={styles.iconBtn} text> {tableAction.label} @@ -264,20 +359,20 @@ const component = defineComponent( return Btn } const actionColumnRender = () => { - const actionColumn = props.table?.actionColumn - return Colls.isEmpty(actionColumn?.tableActions) ? <> - : ( + const actionColumn = props.table.actionColumn + return Colls.isEmpty(actionColumn.tableActions) ? <> + : (

{{ - default: (scope: ColumnScopeType) => (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)}> @@ -292,11 +387,10 @@ const component = defineComponent( } onMounted(doSearch) return () => ( - {/*@ts-ignore*/}
{/*@ts-ignore*/} - + { slots.highFormItem?.(formData) } @@ -308,10 +402,10 @@ const component = defineComponent(
{ - props.toolBar?.leftTools?.map((tool, i) => ( + props.toolBar.leftTools.map((tool, i) => ( {tool.label} @@ -319,51 +413,57 @@ const component = defineComponent( }
- {/*@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} + lazy={props.table.treeLoad == null ? undefined : true} + load={props.table.treeLoad ? ((row, expanded, resolve) => 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" > @@ -399,7 +499,8 @@ const component = defineComponent( }, ) -export interface ATablePageInstance extends InstanceType, Exposed { +export interface ATablePageInstance extends InstanceType { + doSearch: () => void } export default component 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 index 378c975..1da20a5 100644 --- 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 @@ -1,6 +1,6 @@ .table-page { grid-template-columns 1fr - grid-template-rows: minmax(0, 1fr) minmax(0, var(--data-list-height)); + grid-template-rows: minmax(74px, 1fr) minmax(277px, var(--data-list-height)); grid-auto-rows: minmax(0, auto); gap 10px @@ -35,9 +35,9 @@ .search-form { display grid - grid-template-columns repeat(var(--col-count), 1fr) - row-gap var(--vgap); - column-gap var(--hgap); + grid-template-columns repeat(auto-fit, minmax(var(--item-min-width), 1fr)) + row-gap var(--rgap); + column-gap var(--cgap); :global(.el-form-item) { margin 0 @@ -67,53 +67,59 @@ gap 20px .tool-bar { - display flex - justify-content space-between + display: grid; + grid-template-columns: minmax(260px, 1fr) minmax(428px, 1fr); + grid-template-rows: 50px; box-sizing: border-box; .tool-bar-left { - flex 1 + //flex 1 } .tool-bar-right { - flex 1 - display flex + display grid + grid-template-columns: minmax(200px, 1fr) 150px; + grid-auto-rows: 32px; gap 10px - justify-content end - :global(.el-form) { - display flex - gap 10px - flex-shrink 0 + & > div:first-child { + :global(.el-form) { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); + gap 10px - .el-form-item { - margin: 0; - flex 1 + :global(.el-form-item) { + margin: 0; + } } } - :global(.el-button--default) { - width 32px - color #4D5875 - background-color #F1F4F4 - border-color #F1F4F4 - margin 0 + & > div:last-child { + display: flex; + justify-content: space-evenly; - &:hover { + :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) - background-color #E6EAEB; - border-color #E6EAEB; } } - - - :global(.el-dropdown) { - flex-shrink 0 - } - - :global(.el-button--default).filter-btn-active { - color var(--main-color) - } } } diff --git a/src/pages/order/book/Book.vue b/src/pages/order/book/Book.vue index 9c89afb..2e00ee7 100644 --- a/src/pages/order/book/Book.vue +++ b/src/pages/order/book/Book.vue @@ -72,6 +72,7 @@ + y @@ -98,6 +99,9 @@ function research() { } const tablePageProps = buildProps({ + pageLayout: { + dataListHeight: 4, + }, table: { actionColumn: { tableActions: [ @@ -111,10 +115,13 @@ const tablePageProps = buildProps({ }, }, searchForm: { + highForm: { + contentWidth: 342, + }, paging(param: OrderTypes.SearchOrderParam) { return OrderApi.paging(param).then(res => { res.data.records = [] - for (let i = 0; i < 5; i++) { + for (let i = 0; i < 20; i++) { res.data.records.push({}) } return res @@ -122,6 +129,4 @@ const tablePageProps = buildProps({ }, }, }) - -