fix: display measurements throughout overseas fulfillment

This commit is contained in:
2026-09-18 14:59:14 +08:00
parent d77d145405
commit 0ab2302720
21 changed files with 222 additions and 33 deletions
+1
View File
@@ -11,6 +11,7 @@
- 查询与筛选使用 `QueryToolbar.vue`,通过 `fields`、`v-model`、`search`、`reset` 接入;查询即刷新当前结果,不另设独立刷新按钮。
- 数据列表使用 `DataTable.vue`,通过 `columns`、命名插槽、`v-model:page`、`v-model:page-size` 和 `total` 接入,不重复拼装 `el-table` 与分页器。
- “操作”列放在第一列,需要固定时固定在左侧。单行操作不超过 3 个时直接显示“图标 + 文案”;第 4 个起使用 `MoreHorizontal` 菜单,并优先直接保留详情、编辑、下载等高频操作。
- 列表操作统一使用 `RowActions.vue`:默认直接展示 3 个,通过 `priority` 决定高频或关键操作的展示资格,其余自动进入带可访问名称的“更多”菜单;权限和状态由业务页面生成 `actions` 时决定,组件不推导业务资格。
- 新增、导入、导出和批量操作放入 `DataTable` 的 `toolbar-actions`;不放入查询栏,不用 `toolbar-start` 重复列表标题、数量或说明,数量统一放在 `pagination-left`。
- 路由标题和说明默认由 `AppLayout` 的 `page-heading` 根据路由元数据展示;标准列表正文不重复标题。只有明确关闭路由页头的详情页或工作台才自带独立页头。
+61
View File
@@ -0,0 +1,61 @@
import ElementPlus from 'element-plus'
import { mount } from '@vue/test-utils'
import { describe, expect, it } from 'vitest'
import RowActions, { type RowActionItem } from './RowActions.vue'
const actions: RowActionItem[] = [
{ key: 'view', label: '查看', priority: 300 },
{ key: 'print', label: '打印', priority: 200 },
{ key: 'edit', label: '编辑', priority: 100 },
{ key: 'transfer', label: '转派送', priority: 250, tone: 'success' },
{ key: 'cancel', label: '取消', tone: 'danger' },
]
describe('RowActions', () => {
it('shows at most three prioritized actions and moves the rest into more', () => {
const wrapper = mount(RowActions, {
props: { actions, moreLabel: '更多操作 · PC-1' },
global: { plugins: [ElementPlus] },
})
expect(wrapper.findAll('.row-actions > .el-button').map(button => button.text().trim()))
.toEqual(['查看', '打印', '转派送'])
expect(wrapper.get('.el-dropdown .el-button').attributes('aria-label')).toBe('更多操作 · PC-1')
})
it('keeps input order when priorities choose the visible actions', () => {
const wrapper = mount(RowActions, {
props: { actions, maxVisible: 2 },
global: { plugins: [ElementPlus] },
})
expect(wrapper.findAll('.row-actions > .el-button').map(button => button.text().trim()))
.toEqual(['查看', '转派送'])
})
it('emits enabled direct actions and ignores loading actions', async () => {
const wrapper = mount(RowActions, {
props: {
actions: [
{ key: 'view', label: '查看' },
{ key: 'print', label: '打印', loading: true },
],
},
global: { plugins: [ElementPlus] },
})
await wrapper.findAll('.row-actions > .el-button')[0].trigger('click')
await wrapper.findAll('.row-actions > .el-button')[1].trigger('click')
expect(wrapper.emitted('action')).toEqual([['view']])
})
it('omits hidden actions without rendering an empty more menu', () => {
const wrapper = mount(RowActions, {
props: { actions: [{ key: 'edit', label: '编辑', visible: false }] },
global: { plugins: [ElementPlus] },
})
expect(wrapper.find('.el-dropdown').exists()).toBe(false)
expect(wrapper.text()).toBe('')
})
})
+84
View File
@@ -0,0 +1,84 @@
<script setup lang="ts">
import { computed } from 'vue'
import { MoreHorizontal } from '@lucide/vue'
export type RowActionTone = 'neutral' | 'warning' | 'purple' | 'primary' | 'success' | 'danger'
export interface RowActionItem {
key: string
label: string
visible?: boolean
disabled?: boolean
loading?: boolean
tone?: RowActionTone
priority?: number
divided?: boolean
}
const props = withDefaults(defineProps<{
actions: RowActionItem[]
maxVisible?: number
moreLabel?: string
}>(), {
maxVisible: 3,
moreLabel: '更多操作',
})
const emit = defineEmits<{
action: [key: string]
}>()
const availableActions = computed(() => props.actions.filter(action => action.visible !== false))
const directKeys = computed(() => new Set(
availableActions.value
.map((action, index) => ({ action, index }))
.sort((left, right) => (right.action.priority ?? 0) - (left.action.priority ?? 0) || left.index - right.index)
.slice(0, Math.max(0, props.maxVisible))
.map(({ action }) => action.key),
))
const directActions = computed(() => availableActions.value.filter(action => directKeys.value.has(action.key)))
const overflowActions = computed(() => availableActions.value.filter(action => !directKeys.value.has(action.key)))
function select(action: RowActionItem) {
if (action.disabled || action.loading) return
emit('action', action.key)
}
</script>
<template>
<div class="density-actions row-actions">
<el-button
v-for="action in directActions"
:key="action.key"
class="density-action"
:data-tone="action.tone || 'neutral'"
size="small"
:disabled="action.disabled"
:loading="action.loading"
@click="select(action)"
>{{ action.label }}</el-button>
<el-dropdown v-if="overflowActions.length" trigger="click">
<el-button
class="density-action"
data-tone="neutral"
size="small"
:aria-label="moreLabel"
:title="moreLabel"
><MoreHorizontal :size="15" aria-hidden="true" /></el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
v-for="action in overflowActions"
:key="action.key"
:class="{ 'danger-menu-item': action.tone === 'danger' }"
:disabled="action.disabled || action.loading"
:divided="action.divided"
@click="select(action)"
>{{ action.label }}</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</template>