feat(phase3): complete loading workbenches

This commit is contained in:
2026-08-10 17:42:17 +08:00
parent 820432a381
commit 1595fbcae2
34 changed files with 1352 additions and 159 deletions
@@ -25,13 +25,36 @@ describe('PlatformFirstLegOrdersPanel review queue', () => {
expect(source).toContain('await nextTick()')
})
it('keeps the fixed action column compact by grouping secondary actions', () => {
it('shows up to three row actions directly and overflows only the fourth action onward', () => {
expect(source).toContain('class="table-actions order-row-actions"')
expect(source).toContain('更多订单操作')
expect(source).toContain('directSecondaryActionCount')
expect(source).toContain('Math.max(0, 3 - primaryActionCount)')
expect(source).toContain("isDirectSecondaryAction(row as PlatformFirstLegOrderResource, 'express')")
expect(source).toContain("isDirectSecondaryAction(row as PlatformFirstLegOrderResource, 'note')")
expect(source).toContain("isDirectSecondaryAction(row as PlatformFirstLegOrderResource, 'cancel')")
expect(source).toContain('v-if="hasOverflowActions(row as PlatformFirstLegOrderResource)"')
expect(source).toContain(':aria-label="moreOrderActionsLabel(row as PlatformFirstLegOrderResource)"')
expect(source).toContain(':title="moreOrderActionsLabel(row as PlatformFirstLegOrderResource)"')
expect(source).toContain('维护快递')
expect(source).toContain('内部备注')
expect(source).toContain('取消订单')
expect(source).toContain("minWidth: 224, fixed: 'left'")
expect(source).toContain("minWidth: 288, fixed: 'left'")
})
it('distinguishes overflow action buttons by order and keeps a stable missing-field fallback', () => {
expect(source).toContain('function moreOrderActionsLabel(order: PlatformFirstLegOrderResource)')
expect(source).toContain('[compatibleOrder.orderNo, compatibleOrder.id, compatibleOrder.orderId]')
expect(source).toContain("identifier || '当前订单'")
expect(source).not.toContain('aria-label="更多订单操作"')
})
it('uses structured list regions so toolbar and table keep one layout gap', () => {
expect(source).toContain('<ListPageLayout class="first-leg-order-list"')
expect(source).toContain('<template #toolbar>')
expect(source).toContain('<template v-if="tenantLoadError || loadError" #state>')
expect(source).toContain('<template #default>')
expect(source).toContain('<section v-if="!loadError" class="surface-panel table-panel">')
expect(source).toContain('.first-leg-order-list { gap: 16px; }')
})
it('prioritizes order status and removes noisy customer and time metadata', () => {
@@ -2,7 +2,7 @@
import { computed, nextTick, onMounted, reactive, ref, watch } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import type { FormInstance, FormRules } from 'element-plus'
import { ArrowLeft, CircleX, Eye, FilePenLine, MoreHorizontal, PackageCheck } from '@lucide/vue'
import { ArrowLeft, CircleCheckBig, CircleX, Eye, FilePenLine, MoreHorizontal, PackageCheck } from '@lucide/vue'
import { useRoute, useRouter } from 'vue-router'
import {
acceptPlatformFirstLegOrder,
@@ -97,7 +97,7 @@ const queryFields = computed<QueryField[]>(() => [
])
const orderColumns: DataTableColumn[] = [
{ key: 'actions', label: '操作', minWidth: 224, fixed: 'left', slotName: 'actions' },
{ key: 'actions', label: '操作', minWidth: 288, fixed: 'left', slotName: 'actions' },
{ key: 'status', label: '状态', width: 108, slotName: 'status' },
{ key: 'orderInfo', label: '订单信息', minWidth: 210, slotName: 'orderInfo' },
{ key: 'customer', label: '客户', minWidth: 190, showOverflowTooltip: true, slotName: 'customer' },
@@ -202,8 +202,41 @@ function canMaintainExpress(order: PlatformFirstLegOrderResource) {
return can('platform:order:fulfillment') && !['DRAFT', 'REJECTED', 'CANCELLED'].includes(order.status)
}
function hasSecondaryActions(order: PlatformFirstLegOrderResource) {
return canCancel(order) || canMaintainExpress(order) || can('platform:order:note')
type SecondaryAction = 'express' | 'note' | 'cancel'
function secondaryActions(order: PlatformFirstLegOrderResource): SecondaryAction[] {
const actions: SecondaryAction[] = []
if (canMaintainExpress(order)) actions.push('express')
if (can('platform:order:note')) actions.push('note')
if (canCancel(order)) actions.push('cancel')
return actions
}
function directSecondaryActionCount(order: PlatformFirstLegOrderResource) {
const primaryActionCount = actionLabel(order) ? 2 : 1
return Math.max(0, 3 - primaryActionCount)
}
function isDirectSecondaryAction(order: PlatformFirstLegOrderResource, action: SecondaryAction) {
const index = secondaryActions(order).indexOf(action)
return index >= 0 && index < directSecondaryActionCount(order)
}
function isOverflowSecondaryAction(order: PlatformFirstLegOrderResource, action: SecondaryAction) {
const index = secondaryActions(order).indexOf(action)
return index >= directSecondaryActionCount(order)
}
function hasOverflowActions(order: PlatformFirstLegOrderResource) {
return secondaryActions(order).length > directSecondaryActionCount(order)
}
function moreOrderActionsLabel(order: PlatformFirstLegOrderResource) {
const compatibleOrder = order as PlatformFirstLegOrderResource & { orderId?: string | number | null }
const identifier = [compatibleOrder.orderNo, compatibleOrder.id, compatibleOrder.orderId]
.map((value) => String(value ?? '').trim())
.find(Boolean)
return `更多订单操作:${identifier || '当前订单'}`
}
function runSecondaryAction(command: string | number | object, order: PlatformFirstLegOrderResource) {
@@ -511,7 +544,7 @@ onMounted(() => void loadTenants())
</template>
<template v-else>
<ListPageLayout ariaLabel="头程订单列表">
<ListPageLayout class="first-leg-order-list" ariaLabel="头程订单列表">
<template #toolbar>
<QueryToolbar
v-model="filters"
@@ -525,18 +558,18 @@ onMounted(() => void loadTenants())
/>
</template>
<el-alert v-if="tenantLoadError" type="warning" :closable="false" show-icon role="alert">
<template #title><div class="api-error-alert-title"><span>{{ tenantLoadError.message }}</span><ApiErrorMeta :details="tenantLoadError" /></div></template>
<el-button link type="warning" @click="loadTenants">重新加载企业列表</el-button>
</el-alert>
<template v-if="loadError" #state>
<el-alert type="error" :closable="false" show-icon role="alert">
<template v-if="tenantLoadError || loadError" #state>
<el-alert v-if="tenantLoadError" type="warning" :closable="false" show-icon role="alert">
<template #title><div class="api-error-alert-title"><span>{{ tenantLoadError.message }}</span><ApiErrorMeta :details="tenantLoadError" /></div></template>
<el-button link type="warning" @click="loadTenants">重新加载企业列表</el-button>
</el-alert>
<el-alert v-if="loadError" type="error" :closable="false" show-icon role="alert">
<template #title><div class="api-error-alert-title"><span>{{ loadError.message }}</span><ApiErrorMeta :details="loadError" /></div></template>
<el-button link type="danger" @click="loadOrders">重新加载</el-button>
</el-alert>
</template>
<template #default>
<section v-if="!loadError" class="surface-panel table-panel">
<DataTable
v-model:page="page"
@@ -554,14 +587,17 @@ onMounted(() => void loadTenants())
<template #actions="{ row }">
<div class="table-actions order-row-actions">
<el-button link type="primary" :disabled="actionLoadingId === String((row as PlatformFirstLegOrderResource).id)" @click="openDetail(row as PlatformFirstLegOrderResource)"><Eye :size="15" />详情</el-button>
<el-button v-if="actionLabel(row as PlatformFirstLegOrderResource)" link type="success" :loading="actionLoadingId === String((row as PlatformFirstLegOrderResource).id)" @click="runLifecycleAction(row as PlatformFirstLegOrderResource)">{{ actionLabel(row as PlatformFirstLegOrderResource) }}</el-button>
<el-dropdown v-if="hasSecondaryActions(row as PlatformFirstLegOrderResource)" trigger="click" popper-class="first-leg-action-menu" :disabled="actionLoadingId === String((row as PlatformFirstLegOrderResource).id)" @command="runSecondaryAction($event, row as PlatformFirstLegOrderResource)">
<el-button link type="primary" aria-label="更多订单操作" title="更多订单操作"><MoreHorizontal :size="17" /></el-button>
<el-button v-if="actionLabel(row as PlatformFirstLegOrderResource)" link type="success" :loading="actionLoadingId === String((row as PlatformFirstLegOrderResource).id)" @click="runLifecycleAction(row as PlatformFirstLegOrderResource)"><CircleCheckBig :size="15" />{{ actionLabel(row as PlatformFirstLegOrderResource) }}</el-button>
<el-button v-if="isDirectSecondaryAction(row as PlatformFirstLegOrderResource, 'express')" link type="primary" :disabled="actionLoadingId === String((row as PlatformFirstLegOrderResource).id)" @click="openExpress(row as PlatformFirstLegOrderResource)"><PackageCheck :size="15" />维护快递</el-button>
<el-button v-if="isDirectSecondaryAction(row as PlatformFirstLegOrderResource, 'note')" link type="primary" :disabled="actionLoadingId === String((row as PlatformFirstLegOrderResource).id)" @click="openNote(row as PlatformFirstLegOrderResource)"><FilePenLine :size="15" />内部备注</el-button>
<el-button v-if="isDirectSecondaryAction(row as PlatformFirstLegOrderResource, 'cancel')" link type="danger" :disabled="actionLoadingId === String((row as PlatformFirstLegOrderResource).id)" @click="cancelOrder(row as PlatformFirstLegOrderResource)"><CircleX :size="15" />取消订单</el-button>
<el-dropdown v-if="hasOverflowActions(row as PlatformFirstLegOrderResource)" trigger="click" popper-class="first-leg-action-menu" :disabled="actionLoadingId === String((row as PlatformFirstLegOrderResource).id)" @command="runSecondaryAction($event, row as PlatformFirstLegOrderResource)">
<el-button link type="primary" :aria-label="moreOrderActionsLabel(row as PlatformFirstLegOrderResource)" :title="moreOrderActionsLabel(row as PlatformFirstLegOrderResource)"><MoreHorizontal :size="17" /></el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item v-if="canMaintainExpress(row as PlatformFirstLegOrderResource)" command="express"><PackageCheck :size="15" />维护快递</el-dropdown-item>
<el-dropdown-item v-if="can('platform:order:note')" command="note"><FilePenLine :size="15" />内部备注</el-dropdown-item>
<el-dropdown-item v-if="canCancel(row as PlatformFirstLegOrderResource)" command="cancel" divided><CircleX :size="15" />取消订单</el-dropdown-item>
<el-dropdown-item v-if="isOverflowSecondaryAction(row as PlatformFirstLegOrderResource, 'express')" command="express"><PackageCheck :size="15" />维护快递</el-dropdown-item>
<el-dropdown-item v-if="isOverflowSecondaryAction(row as PlatformFirstLegOrderResource, 'note')" command="note"><FilePenLine :size="15" />内部备注</el-dropdown-item>
<el-dropdown-item v-if="isOverflowSecondaryAction(row as PlatformFirstLegOrderResource, 'cancel')" command="cancel" divided><CircleX :size="15" />取消订单</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
@@ -576,6 +612,7 @@ onMounted(() => void loadTenants())
<template #express="{ row }"><div class="primary-cell"><strong>{{ (row as PlatformFirstLegOrderResource).expressCompany || '未维护' }}</strong><span>{{ (row as PlatformFirstLegOrderResource).expressTrackingNo || '—' }}</span></div></template>
</DataTable>
</section>
</template>
</ListPageLayout>
</template>
@@ -595,6 +632,7 @@ onMounted(() => void loadTenants())
<style scoped>
.detail-page { overflow: hidden; }
.first-leg-order-list { gap: 16px; }
.detail-page-header { display: flex; min-height: 88px; align-items: center; justify-content: space-between; gap: 18px; padding: 20px 24px; border-bottom: 1px solid var(--cc-border); }
.detail-page-header h1 { margin: 0; font-size: 20px; line-height: 1.35; }
.detail-page-header p { margin: 5px 0 0; color: var(--cc-muted); font-size: 12px; line-height: 1.5; }