From fdaf98499ffe45b255c1ce532db6e975299fcb59 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Mon, 7 Sep 2026 19:04:00 +0800 Subject: [PATCH] fix: optimize table overflow and action widths --- .../layoutAcceptance.test.ts | 10 +++--- .../pages/DispatchesView.vue | 2 +- .../pages/LastMileOrdersView.vue | 2 +- .../pages/OutboundsView.vue | 2 +- .../overseas-warehouse/pages/PalletsView.vue | 2 +- .../pages/RelabelOrdersView.vue | 2 +- src/shared/components/DataTable.test.ts | 34 +++++++++++++++++-- src/shared/components/DataTable.vue | 27 ++++++++++++++- 8 files changed, 68 insertions(+), 13 deletions(-) diff --git a/src/modules/overseas-warehouse/layoutAcceptance.test.ts b/src/modules/overseas-warehouse/layoutAcceptance.test.ts index 01120efa..60363d97 100644 --- a/src/modules/overseas-warehouse/layoutAcceptance.test.ts +++ b/src/modules/overseas-warehouse/layoutAcceptance.test.ts @@ -8,13 +8,13 @@ const component = (name: string) => readFileSync(resolve(process.cwd(), 'src/mod describe('overseas warehouse 1920px layout acceptance', () => { it('keeps action columns first and wide enough for every visible action', () => { const widths: Record = { - 'LastMileOrdersView.vue': 270, + 'LastMileOrdersView.vue': 110, 'InboundOrdersView.vue': 260, - 'DispatchesView.vue': 240, - 'OutboundsView.vue': 190, + 'DispatchesView.vue': 270, + 'OutboundsView.vue': 180, 'DeliveryOrdersView.vue': 300, - 'RelabelOrdersView.vue': 190, - 'PalletsView.vue': 250, + 'RelabelOrdersView.vue': 180, + 'PalletsView.vue': 180, 'InventoryView.vue': 110, } for (const [name, width] of Object.entries(widths)) { diff --git a/src/modules/overseas-warehouse/pages/DispatchesView.vue b/src/modules/overseas-warehouse/pages/DispatchesView.vue index 7f33e963..f13727b2 100644 --- a/src/modules/overseas-warehouse/pages/DispatchesView.vue +++ b/src/modules/overseas-warehouse/pages/DispatchesView.vue @@ -74,7 +74,7 @@ const fields = computed(() => [ ]) const columns: DataTableColumn[] = [ - { key: 'actions', label: '操作', width: 240, fixed: 'left', slotName: 'actions' }, + { key: 'actions', label: '操作', width: 270, fixed: 'left', slotName: 'actions' }, { key: 'dispatchNo', prop: 'dispatchNo', label: '排车单号', minWidth: 170, fixed: 'left' }, { key: 'vehiclePlate', prop: 'vehiclePlate', label: '车牌号', minWidth: 130 }, { key: 'driverName', prop: 'driverName', label: '司机', minWidth: 120 }, diff --git a/src/modules/overseas-warehouse/pages/LastMileOrdersView.vue b/src/modules/overseas-warehouse/pages/LastMileOrdersView.vue index 9b53f9c6..482767bb 100644 --- a/src/modules/overseas-warehouse/pages/LastMileOrdersView.vue +++ b/src/modules/overseas-warehouse/pages/LastMileOrdersView.vue @@ -132,7 +132,7 @@ function newReceivingWarehouseIdempotencyKey() { return `container-receiving-warehouse-${Date.now()}-${Math.random().toString(36).slice(2, 10)}` } const columns: DataTableColumn[] = [ - { key: 'actions', label: '操作', width: 270, fixed: 'left', slotName: 'actions' }, + { key: 'actions', label: '操作', width: 110, fixed: 'left', slotName: 'actions' }, { key: 'selection', type: 'selection', width: 48, fixed: 'left' }, { key: 'orderNo', prop: 'orderNo', label: '尾程单号', minWidth: 170, slotName: 'orderNo' }, { key: 'consolidated', prop: 'consolidated', label: '合托标记', width: 100, hideable: false }, diff --git a/src/modules/overseas-warehouse/pages/OutboundsView.vue b/src/modules/overseas-warehouse/pages/OutboundsView.vue index 6d798c40..03edeb48 100644 --- a/src/modules/overseas-warehouse/pages/OutboundsView.vue +++ b/src/modules/overseas-warehouse/pages/OutboundsView.vue @@ -28,7 +28,7 @@ const customerOptions = computed(() => { return [...customers].map(([value, label]) => ({ value, label })) }) const availablePositionOptions = computed(() => positionOptions.value.filter(({ balance }) => balance.customerId === selectedCustomerId.value)) -const columns: DataTableColumn[] = [{ key: 'actions', label: '操作', width: 190, fixed: 'left', slotName: 'actions' }, { key: 'outboundNo', prop: 'outboundNo', label: '出库单号', minWidth: 180 }, { key: 'lxCodes', prop: 'lxCodes', label: 'LX代码', minWidth: 180 }, { key: 'dispatchNo', prop: 'dispatchNo', label: '关联排车单', minWidth: 160 }, { key: 'status', prop: 'status', label: '状态', width: 130, slotName: 'status' }, { key: 'palletCount', prop: 'palletCount', label: '托盘数', width: 100 }, { key: 'packageCount', prop: 'packageCount', label: '箱数', width: 100 }, { key: 'createdAt', prop: 'createdAt', label: '创建时间', minWidth: 180 }] +const columns: DataTableColumn[] = [{ key: 'actions', label: '操作', width: 180, fixed: 'left', slotName: 'actions' }, { key: 'outboundNo', prop: 'outboundNo', label: '出库单号', minWidth: 180 }, { key: 'lxCodes', prop: 'lxCodes', label: 'LX代码', minWidth: 180 }, { key: 'dispatchNo', prop: 'dispatchNo', label: '关联排车单', minWidth: 160 }, { key: 'status', prop: 'status', label: '状态', width: 130, slotName: 'status' }, { key: 'palletCount', prop: 'palletCount', label: '托盘数', width: 100 }, { key: 'packageCount', prop: 'packageCount', label: '箱数', width: 100 }, { key: 'createdAt', prop: 'createdAt', label: '创建时间', minWidth: 180 }] const fields: QueryField[] = [{ key: 'documentNo', type: 'input', label: '出库单号', placeholder: '请输入出库单号', width: 220 }, { key: 'lxCode', type: 'input', label: 'LX代码', placeholder: '请输入领星入库单代码', width: 220 }, { key: 'warehouseId', type: 'input', label: '目的仓', placeholder: '请输入目的仓', width: 220 }, { key: 'status', type: 'select', label: '状态', placeholder: '全部状态', width: 160, options: [{ label: '全部', value: '' }, { label: '待出库', value: 'WAIT_OUTBOUND' }, { label: '已出库', value: 'OUTBOUNDED' }] }] async function load(model: QueryModel = filters.value) { Object.assign(filters.value, model); loading.value = true; error.value = null; try { const result = await listOutbounds({ page: page.value, size: pageSize.value, documentNo: String(filters.value.documentNo || '').trim() || undefined, lxCode: String(filters.value.lxCode || '').trim() || undefined, warehouseId: String(filters.value.warehouseId || '').trim() || undefined, status: String(filters.value.status || '').trim() || undefined }); rows.value = result.records.map((row) => ({ ...row, createdAt: formatOverseasDateTime(row.createdAt) })); total.value = result.total } catch (cause) { error.value = getApiErrorDetails(cause, '出库单列表加载失败') } finally { loading.value = false } } diff --git a/src/modules/overseas-warehouse/pages/PalletsView.vue b/src/modules/overseas-warehouse/pages/PalletsView.vue index ebd21216..9120d74c 100644 --- a/src/modules/overseas-warehouse/pages/PalletsView.vue +++ b/src/modules/overseas-warehouse/pages/PalletsView.vue @@ -41,7 +41,7 @@ const fields = computed(() => [ { key: 'status', type: 'select', label: '状态', placeholder: '全部状态', width: 140, options: [{ label: '闲置', value: 'IDLE' }, { label: '占用', value: 'OCCUPIED' }, { label: '已报废', value: 'SCRAPPED' }] }, ]) const columns: DataTableColumn[] = [ - { key: 'actions', label: '操作', width: 250, fixed: 'left', slotName: 'actions' }, + { key: 'actions', label: '操作', width: 180, fixed: 'left', slotName: 'actions' }, { key: 'code', prop: 'code', label: '托盘编码', minWidth: 160 }, { key: 'type', prop: 'type', label: '托盘类型', minWidth: 130 }, { key: 'warehouse', prop: 'warehouse', label: '仓库', minWidth: 180 }, { key: 'volume', prop: 'volume', label: '容积(m³)', width: 110, align: 'right' }, { key: 'purchasePrice', prop: 'purchasePrice', label: '采购价格', width: 120, align: 'right' }, { key: 'fbaWarehouse', prop: 'fbaWarehouse', label: '所属FBA仓', minWidth: 140 }, diff --git a/src/modules/overseas-warehouse/pages/RelabelOrdersView.vue b/src/modules/overseas-warehouse/pages/RelabelOrdersView.vue index 30b88b51..f5c1af0c 100644 --- a/src/modules/overseas-warehouse/pages/RelabelOrdersView.vue +++ b/src/modules/overseas-warehouse/pages/RelabelOrdersView.vue @@ -37,7 +37,7 @@ const fields = computed(() => [ { key: 'status', type: 'select', label: '状态', placeholder: '全部状态', width: 160, options: [{ label: '待处理', value: 'PENDING_PROCESSING' }, { label: '处理中', value: 'PROCESSING' }, { label: '已完成', value: 'COMPLETED' }, { label: '已驳回', value: 'REJECTED' }, { label: '已取消', value: 'CANCELLED' }] }, ]) const columns: DataTableColumn[] = [ - { key: 'actions', label: '操作', width: 190, fixed: 'left', slotName: 'actions' }, + { key: 'actions', label: '操作', width: 180, fixed: 'left', slotName: 'actions' }, { key: 'orderNo', label: '换标单号', minWidth: 170 }, { key: 'sku', label: '商品SKU', minWidth: 150 }, { key: 'productName', label: '中文品名', minWidth: 180 }, diff --git a/src/shared/components/DataTable.test.ts b/src/shared/components/DataTable.test.ts index 0a8c1f87..5b81f500 100644 --- a/src/shared/components/DataTable.test.ts +++ b/src/shared/components/DataTable.test.ts @@ -12,8 +12,8 @@ const TableStub = defineComponent({ }) const ColumnStub = defineComponent({ inheritAttrs: false, - props: { label: String, prop: String, fixed: [String, Boolean], showOverflowTooltip: Boolean }, - template: '
', + props: { label: String, prop: String, fixed: [String, Boolean], showOverflowTooltip: Boolean, className: String }, + template: '
', }) const ButtonStub = defineComponent({ template: '' }) const PopoverStub = defineComponent({ template: '
' }) @@ -152,6 +152,36 @@ describe('DataTable column preferences', () => { expect(wrapper.get('.data-table__pagination').text()).toContain('共 0 条') }) + it('keeps ordinary cells on one line with overflow tooltips and exempts system columns', () => { + const wrapper = shallowMount(DataTable, { + props: { + data: [], + columnSettingsMode: 'hide', + columns: [ + { key: 'actions', label: '操作', fixed: 'left', slotName: 'actions' }, + { key: 'customerName', label: '客户' }, + { key: 'remark', label: '备注', singleLine: false }, + ], + }, + global: { + stubs: { ElTable: TableStub, ElTableColumn: ColumnStub, ElButton: ButtonStub, ElPopover: PopoverStub, ElSelect: true, ElOption: true }, + directives: { loading: {} }, + }, + }) + + const columns = wrapper.findAll('.column-stub') + expect(columns[0].attributes()).toMatchObject({ + 'data-overflow-tooltip': 'undefined', + 'data-class-name': 'data-table__system-cell', + }) + expect(columns[1].attributes()).toMatchObject({ + 'data-overflow-tooltip': 'true', + 'data-class-name': 'data-table__single-line-cell', + }) + expect(columns[2].attributes('data-overflow-tooltip')).toBe('undefined') + expect(columns[2].attributes('data-class-name')).toBe('') + }) + it('uses key as the data property for ordinary columns only', () => { const wrapper = shallowMount(DataTable, { props: { diff --git a/src/shared/components/DataTable.vue b/src/shared/components/DataTable.vue index 38d81ccd..273fce06 100644 --- a/src/shared/components/DataTable.vue +++ b/src/shared/components/DataTable.vue @@ -10,6 +10,9 @@ export interface DataTableColumn { hideable?: boolean movable?: boolean defaultVisible?: boolean + singleLine?: boolean + showOverflowTooltip?: boolean + className?: string [attribute: string]: unknown } @@ -241,7 +244,7 @@ watch([page, pageCount], ([value, count]) => { if (value !== nextValue) page.value = nextValue }, { immediate: true }) -function columnAttrs({ key, slotName, headerSlotName, hideable, movable, defaultVisible, ...attrs }: DataTableColumn) { +function columnAttrs({ key, slotName, headerSlotName, hideable, movable, defaultVisible, singleLine, ...attrs }: DataTableColumn) { const hasDataProperty = attrs.prop !== undefined || attrs.property !== undefined const isSpecialColumn = slotName || ['selection', 'expand', 'index'].includes(String(attrs.type || '')) if (hasDataProperty || isSpecialColumn || key === undefined || key === null || key === '') return attrs @@ -257,6 +260,22 @@ function columnAttrs({ key, slotName, headerSlotName, hideable, movable, default } } +function singleLineColumn(column: DataTableColumn) { + return column.singleLine !== false && !systemColumn(column) +} + +function columnOverflowTooltip(column: DataTableColumn) { + return singleLineColumn(column) ? column.showOverflowTooltip ?? true : column.showOverflowTooltip +} + +function columnClassName(column: DataTableColumn) { + return [ + column.className, + systemColumn(column) ? 'data-table__system-cell' : '', + singleLineColumn(column) ? 'data-table__single-line-cell' : '', + ].filter(Boolean).join(' ') +} + function columnRenderKey(column: DataTableColumn, index: number) { return String(column.key ?? column.columnKey ?? column.prop ?? column.property ?? index) } @@ -309,6 +328,8 @@ function setPage(value: number) { v-for="(column, index) in visibleColumns" :key="columnRenderKey(column, index)" v-bind="columnAttrs(column)" + :class-name="columnClassName(column)" + :show-overflow-tooltip="columnOverflowTooltip(column)" >