This commit is contained in:
2026-08-12 18:00:26 +08:00
parent 2a889a7d91
commit 9d9b8943b8
16 changed files with 114 additions and 16 deletions
@@ -180,4 +180,13 @@ describe('ContainersView', () => {
previous = current
}
})
it('renders container business actions as buttons without changing the transport gate', () => {
const source = readFileSync(resolve(process.cwd(), 'src/modules/container-loading/pages/ContainersView.vue'), 'utf8')
expect(source).toContain('size="small" type="primary" plain :aria-label="containerViewLabel')
expect(source).toContain("row.planStatus === 'LOADING' ? '录入集装箱号' : '编辑'")
expect(source).toContain("row.planStatus === 'EDITING' && canSmartLoading")
expect(source).not.toContain('canSmartLoading && row.containerNo')
expect(source).not.toContain('link type="primary" :aria-label="containerViewLabel')
})
})
@@ -325,7 +325,7 @@ onMounted(() => { void Promise.all([loadWarehouses(), loadBusinessOptions(), loa
<section v-if="!loadError" class="surface-panel table-panel">
<DataTable v-model:page="page" v-model:page-size="pageSize" :data="rows" :columns="columns" :total="total" :loading="loading" preference-key="container-loading-table" empty-text="暂无货柜">
<template #toolbar-actions><el-button v-if="canCreate" type="primary" @click="openCreate"><Plus :size="16" />新增货柜</el-button></template>
<template #actions="{ row }"><div class="row-actions"><el-button v-if="canView" link type="primary" :aria-label="containerViewLabel(row as ContainerBookingResource)" @click="openDetail(row)"><Eye :size="15" />查看</el-button><el-button v-if="canView && canEdit && row.planStatus !== 'COMPLETED'" link type="primary" @click="openEdit(row)"><Pencil :size="15" />编辑</el-button><el-button v-if="row.planStatus === 'EDITING' && canSmartLoading" link type="primary" @click="goContainerProcess(row)"><Sparkles :size="15" />排柜</el-button><el-button v-else-if="row.planStatus === 'LOADING' && canViewPreload" link type="primary" @click="goContainerProcess(row)"><ListChecks :size="15" />查看预装清单</el-button><el-button v-else-if="row.planStatus === 'COMPLETED' && canViewLoadingList" link type="primary" @click="goContainerProcess(row)"><ListChecks :size="15" />查看装柜清单</el-button></div></template>
<template #actions="{ row }"><div class="row-actions"><el-button v-if="canView" size="small" type="primary" plain :aria-label="containerViewLabel(row as ContainerBookingResource)" @click="openDetail(row)"><Eye :size="15" />查看</el-button><el-button v-if="canView && canEdit && row.planStatus !== 'COMPLETED'" size="small" @click="openEdit(row)"><Pencil :size="15" />{{ row.planStatus === 'LOADING' ? '录入集装箱号' : '编辑' }}</el-button><el-button v-if="row.planStatus === 'EDITING' && canSmartLoading" size="small" type="primary" @click="goContainerProcess(row)"><Sparkles :size="15" />排柜</el-button><el-button v-else-if="row.planStatus === 'LOADING' && canViewPreload" size="small" type="primary" plain @click="goContainerProcess(row)"><ListChecks :size="15" />查看预装清单</el-button><el-button v-else-if="row.planStatus === 'COMPLETED' && canViewLoadingList" size="small" type="primary" plain @click="goContainerProcess(row)"><ListChecks :size="15" />查看装柜清单</el-button></div></template>
<template #cabinet="{ row }"><strong>{{ row.cabinetNo }}</strong><small>{{ row.bookingNo }}</small></template>
<template #containerNo="{ row }">{{ row.containerNo || '待录入' }}</template>
<template #status="{ row }"><el-tag :type="row.planStatus === 'COMPLETED' ? 'success' : row.planStatus === 'LOADING' ? 'warning' : 'info'">{{ statusText(row.planStatus) }}</el-tag></template>
@@ -3,6 +3,8 @@ import { AxiosError } from 'axios'
import { createPinia, setActivePinia } from 'pinia'
import { flushPromises, mount } from '@vue/test-utils'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { readFileSync } from 'node:fs'
import { resolve } from 'node:path'
import QueryToolbar from '@/shared/components/QueryToolbar.vue'
import GlobalSmartLoadingView from './GlobalSmartLoadingView.vue'
import {
@@ -213,4 +215,12 @@ describe('GlobalSmartLoadingView', () => {
expect(errorMessage).toHaveBeenCalledWith('该订单在拣货中不许删除')
expect(wrapper.text()).toContain('该订单在拣货中不许删除')
})
it('uses bordered assignment and adjustment actions at narrow widths', () => {
const source = readFileSync(resolve(process.cwd(), 'src/modules/container-loading/pages/GlobalSmartLoadingView.vue'), 'utf8')
expect(source).toContain('size="small"')
expect(source).toContain('type="danger" plain')
expect(source).not.toContain('link type="danger"')
expect(source).toContain('@media (max-width: 720px)')
})
})
@@ -369,8 +369,9 @@ onMounted(load)
<template #actions="{ row }">
<el-button
v-if="canAssign && editable"
link
size="small"
type="primary"
plain
:disabled="!routeMatches(row) || assignedDetailIds.has(row.inboundDetailId) || row.availablePackageCount < 1"
@click="openAssign(row)"
><Plus :size="15" />加入</el-button>
@@ -436,7 +437,7 @@ onMounted(load)
<div><div class="meter-title"><span>重量</span><strong>{{ formatNumber(totalWeight, 1) }} / {{ formatNumber(weightLimit, 1) }} kg</strong></div><el-progress :percentage="Math.min(displayedWeightPercent, 100)" :status="weightPercent > 100 ? 'exception' : undefined" /></div>
</div>
<DataTable :data="plan.items" :columns="itemColumns" :loading="selectionLoading || mutating" preference-key="global-smart-loading-items" column-settings-mode="hide" empty-text="当前柜次暂无已装货物">
<template #actions="{ row }"><div class="row-actions"><el-button v-if="canAssign && editable" link type="primary" :disabled="quantityDrafts[row.id] === row.packageCount" @click="adjust(row)"><Save :size="15" />保存</el-button><el-button v-if="canDelete && editable" link type="danger" @click="remove(row)"><Trash2 :size="15" />移除</el-button></div></template>
<template #actions="{ row }"><div class="row-actions"><el-button v-if="canAssign && editable" size="small" type="primary" plain :disabled="quantityDrafts[row.id] === row.packageCount" @click="adjust(row)"><Save :size="15" />保存</el-button><el-button v-if="canDelete && editable" size="small" type="danger" plain @click="remove(row)"><Trash2 :size="15" />移除</el-button></div></template>
<template #order="{ row }"><strong>{{ row.orderNo || '—' }}</strong><small>{{ row.inboundCode || '—' }}</small></template>
<template #goods="{ row }"><strong>{{ row.productNameZh || '未命名货物' }}</strong><small>{{ row.skuCode || '无 SKU' }}</small></template>
<template #packageCount="{ row }"><el-input-number v-model="quantityDrafts[row.id]" :min="1" :step="1" :disabled="!canAssign || !editable" controls-position="right" /></template>
@@ -4,6 +4,8 @@ import { createPinia, setActivePinia } from 'pinia'
import { createMemoryHistory, createRouter } from 'vue-router'
import { flushPromises, mount } from '@vue/test-utils'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { readFileSync } from 'node:fs'
import { resolve } from 'node:path'
import SmartLoadingWorkbenchView from './SmartLoadingWorkbenchView.vue'
import { getContainer, getSmartLoadingPlanByBooking, listSmartLoadingCandidates, recommendSmartLoading } from '../api/platformContainerLoading'
@@ -82,4 +84,13 @@ describe('SmartLoadingWorkbenchView', () => {
expect(wrapper.text()).toContain('已刷新最新版本')
expect(wrapper.text()).toContain('request-1')
})
it('uses bordered row actions and keeps push independent from containerNo', () => {
const source = readFileSync(resolve(process.cwd(), 'src/modules/container-loading/pages/SmartLoadingWorkbenchView.vue'), 'utf8')
expect(source).toContain('size="small" type="primary" plain')
expect(source).toContain('size="small" type="danger" plain')
expect(source).toContain("canPush && plan?.status === 'EDITING'")
expect(source).not.toContain("canPush && plan?.status === 'EDITING' && container?.containerNo")
expect(source).not.toContain('link type="primary" :disabled="!routeMatches(row)')
})
})
@@ -229,7 +229,7 @@ onMounted(load)
<template v-else-if="plan">
<DataTable :data="plan.items" :columns="itemColumns" :loading="loading || mutating" preference-key="smart-loading-items" column-settings-mode="hide" empty-text="暂无已分配货物">
<template #toolbar-start><div><h2>装柜方案</h2><p>计划 {{ plan.planNo }} · 版本 {{ plan.version }};智能建议必须经人工核对。</p></div></template>
<template #actions="{ row }"><div class="row-actions"><el-button v-if="canAssign && editable" link type="primary" :disabled="quantityDrafts[row.id] === row.packageCount" @click="adjust(row)"><Save :size="15" />保存箱数</el-button><el-button v-if="canDelete && editable" link type="danger" @click="remove(row)"><Trash2 :size="15" />移除</el-button></div></template>
<template #actions="{ row }"><div class="row-actions"><el-button v-if="canAssign && editable" size="small" type="primary" plain :disabled="quantityDrafts[row.id] === row.packageCount" @click="adjust(row)"><Save :size="15" />保存箱数</el-button><el-button v-if="canDelete && editable" size="small" type="danger" plain @click="remove(row)"><Trash2 :size="15" />移除</el-button></div></template>
<template #order="{ row }"><strong>{{ row.orderNo || '—' }}</strong><small>{{ row.inboundCode || '—' }}</small></template>
<template #goods="{ row }"><strong>{{ row.productNameZh || '未命名货物' }}</strong><small>{{ row.skuCode || '无 SKU' }}</small></template>
<template #packageCount="{ row }"><el-input-number v-model="quantityDrafts[row.id]" :min="1" :step="1" :disabled="!canAssign || !editable" controls-position="right" /></template>
@@ -238,7 +238,7 @@ onMounted(load)
</DataTable>
<DataTable :data="candidates" :columns="candidateColumns" :loading="loading || mutating" preference-key="smart-loading-candidates" column-settings-mode="hide" empty-text="当前仓库暂无可排柜货物">
<template #toolbar-start><div><h2>候选货物</h2><p>系统基于仓库、目的仓、可用箱数及 110% 容量上限校验。</p></div></template>
<template #actions="{ row }"><el-button v-if="canAssign && editable" link type="primary" :disabled="!routeMatches(row) || assignedDetailIds.has(row.inboundDetailId) || row.availablePackageCount < 1" @click="openAssign(row)"><Plus :size="15" />分配</el-button></template>
<template #actions="{ row }"><el-button v-if="canAssign && editable" size="small" type="primary" plain :disabled="!routeMatches(row) || assignedDetailIds.has(row.inboundDetailId) || row.availablePackageCount < 1" @click="openAssign(row)"><Plus :size="15" />分配</el-button></template>
<template #order="{ row }"><strong>{{ row.orderNo || '—' }}</strong><small>{{ row.inboundCode || '—' }}</small></template>
<template #unitVolume="{ row }">{{ formatNumber(Number(row.unitVolumeCbm || 0)) }} m³</template><template #unitWeight="{ row }">{{ formatNumber(Number(row.unitWeightKg || 0),1) }} kg</template>
<template #route="{ row }"><el-tag :type="routeMatches(row) ? 'success' : 'danger'">{{ routeMatches(row) ? '匹配' : '冲突' }}</el-tag><small>{{ explainCandidate(row) }}</small></template>
@@ -87,6 +87,20 @@ describe('CarrierRatesView price-book source contract', () => {
expect(source).toContain('LoadErrorState')
expect(queryToolbarSource).toContain('flex-wrap: wrap')
expect(source).toContain('@media (max-width: 720px)')
expect(source).toContain('.table-actions :deep(.el-button) { min-height: 40px; }')
expect(source).toContain('@media (max-width: 420px)')
expect(source).toContain('.detail-grid { grid-template-columns: 1fr; }')
})
it('uses bordered striped tables and explicit semantic business buttons', () => {
expect(source).toContain('row-key="id"\n border\n stripe')
expect(source).toContain('<el-button size="small" plain type="primary" @click="openDetail')
expect(source).toContain('size="small"\n plain\n :loading="downloadingIds.has')
expect(source).toContain('<Eye :size="15" />{{ detailActionLabel')
expect(source).toContain('<Download :size="15" />原文件')
expect(source).toContain('<CircleCheck :size="16" />确认并立即生效')
expect(source).toContain('.price-book-footer :deep(.el-button) { min-height: 44px; }')
expect(source).not.toContain('<el-button link type="primary" @click="openDetail')
})
it('keeps upload actions in the table toolbar instead of the query toolbar', () => {
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { computed, onMounted, ref, watch } from 'vue'
import { AlertTriangle, Download, Eye, FileSpreadsheet, FileUp, RefreshCw, Ship, Warehouse } from '@lucide/vue'
import { AlertTriangle, CircleCheck, Download, Eye, FileSpreadsheet, FileUp, RefreshCw, Ship, Warehouse } from '@lucide/vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { useAccessPreviewStore } from '@/core/access/accessPreview'
import { listAllPlatformWarehouses, type PlatformWarehouseResource } from '@/modules/warehouses'
@@ -631,16 +631,18 @@ onMounted(() => {
:total="rows.length"
:page-sizes="[10, 20, 50]"
row-key="id"
border
stripe
aria-label="船期价格表版本列表"
empty-text="暂无船期价格表版本"
>
<template #toolbar-actions><el-button v-if="canEdit" type="primary" @click="openUpload"><FileUp :size="16" />上传价格表</el-button></template>
<template #actions="{ row }">
<div class="table-actions">
<el-button link type="primary" @click="openDetail(row as PlatformPriceBookVersion)"><Eye :size="15" />{{ detailActionLabel(row as PlatformPriceBookVersion) }}</el-button>
<el-button size="small" plain type="primary" @click="openDetail(row as PlatformPriceBookVersion)"><Eye :size="15" />{{ detailActionLabel(row as PlatformPriceBookVersion) }}</el-button>
<el-button
link
type="primary"
size="small"
plain
:loading="downloadingIds.has((row as PlatformPriceBookVersion).id)"
@click="downloadSource(row as PlatformPriceBookVersion)"
><Download :size="15" />原文件</el-button>
@@ -692,6 +694,12 @@ onMounted(() => {
@confirm="confirmPriceBook"
@cancel="closeDetail"
>
<template #footer="{ close, submitting }">
<div class="price-book-footer">
<el-button :disabled="submitting" @click="close">取消</el-button>
<el-button type="primary" :loading="publishing" :disabled="detailLoading || !canConfirmDetail" @click="confirmPriceBook"><CircleCheck :size="16" />确认并立即生效</el-button>
</div>
</template>
<section v-if="detailError" class="detail-load-error" role="alert">
<strong>价格表详情暂时无法加载</strong>
<p>{{ detailError.message }}</p>
@@ -804,6 +812,8 @@ onMounted(() => {
.price-book-page { min-width: 0; }
.table-actions { display: flex; min-height: 32px; align-items: center; gap: 8px; }
.table-actions :deep(.el-button + .el-button) { margin-left: 0; }
.price-book-footer { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 10px; }
.price-book-footer :deep(.el-button) { margin-left: 0; }
.version-cell { display: flex; min-width: 0; flex-direction: column; gap: 3px; }
.version-cell strong { color: var(--cc-text); }
.version-cell span, .metric-cell span { color: var(--cc-muted); font-size: 11px; }
@@ -858,6 +868,7 @@ onMounted(() => {
.channel-meta { display: flex; flex-wrap: wrap; gap: 6px 14px; margin: -4px 0 8px; color: var(--cc-muted); font-size: 12px; }
@media (max-width: 720px) {
.table-actions :deep(.el-button) { min-height: 40px; }
.price-book-footer :deep(.el-button) { min-height: 44px; }
.upload-file-control, .detail-summary, .mapping-load-error { align-items: stretch; flex-direction: column; }
.detail-summary__actions { justify-content: flex-start; }
.detail-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
@@ -110,4 +110,13 @@ describe('FeeItemsView public master data and access', () => {
expect(source).not.toContain("access.can('firstleg:fee-item:edit')\" link")
expect(source).not.toContain("access.can('firstleg:fee-item:delete')\" link")
})
it('keeps the prototype table treatment and mobile form fallback without inventing a pricing mode', () => {
expect(source).toContain(':page-sizes="[20, 50, 100]"\n border\n stripe')
expect(source).toContain('@media (max-width: 720px)')
expect(source).toContain('.direction-grid, .form-grid.two-cols { grid-template-columns: 1fr; }')
expect(source).not.toContain('pricingMode')
expect(source).not.toContain('计价模式')
expect(source).not.toContain('固定费用')
})
})
@@ -357,6 +357,8 @@ onMounted(() => {
:loading="loading"
:total="total"
:page-sizes="[20, 50, 100]"
border
stripe
preference-key="fee-items-table"
aria-label="费用项目列表"
row-key="id"
@@ -88,4 +88,15 @@ describe('InboundWorkflowWorkspace', () => {
expect(source).not.toContain('reserveLocation')
expect(source).not.toContain('allocatePallet')
})
it('uses semantic outlined or solid buttons for workflow launches and decisions', () => {
expect(source).toContain('<el-button type="primary" @click="measurementDialogOpen = true">')
expect(source).toContain('<el-button type="primary" @click="reviewDialogOpen = true">')
expect(source).toContain('type="primary" plain')
expect(source).toContain('type="danger" plain')
expect(source).toContain('type="warning" plain')
expect(source).toContain('<Pencil :size="16" />编辑测量数据')
expect(source).toContain('<ClipboardCheck :size="16" />审核测量数据')
expect(source).toContain('@media (max-width: 360px)')
})
})
@@ -334,5 +334,10 @@ describe('InboundOrdersView', () => {
expect(source).toContain('preference-key="inbound-orders-table"')
expect(columnSource.match(/defaultVisible: false/g)?.length).toBeGreaterThanOrEqual(25)
expect(toolbarSource).toMatch(/@media \(max-width: 720px\)[\s\S]*width: 100%/)
expect(source).toContain('<el-button size="small" type="primary" @click="openDetail')
expect(source).toContain('<ClipboardCheck :size="15" />处理')
expect(source).toContain('<el-button size="small" plain type="primary"')
expect(source).not.toContain('<el-button link type="primary" @click="openDetail(row as InboundOrderSummary)">')
expect(source).toContain('@media (max-width: 360px) { .inbound-tab { padding-inline: 12px; } .row-actions { align-items: stretch; flex-direction: column; }')
})
})
@@ -411,7 +411,7 @@ onBeforeUnmount(() => { disposed = true; loadRequestId += 1; customerRequestId +
</header>
<DataTable v-model:page="page" v-model:page-size="pageSize" :data="result.records" :columns="columns" preference-key="inbound-orders-table" :loading="loading" :total="result.total" :page-sizes="[20, 50, 100]" row-key="id" :empty-text="listEmptyText" aria-label="入库单列表">
<template #pagination-left>当前显示 {{ currentPageCount }} 条 · 共 {{ result.total }} 条</template>
<template #actions="{ row }"><div v-if="access.can('platform:firstleg:inbound:view')" class="row-actions"><el-button link type="primary" @click="openDetail(row as InboundOrderSummary)"><ClipboardCheck :size="15" />处理</el-button><el-button link :loading="printingInboundId === (row as InboundOrderSummary).id" :disabled="Boolean(printingInboundId)" @click="printCartonLabels(row as InboundOrderSummary)"><Printer :size="15" />打印箱唛</el-button><el-button link @click="openDetail(row as InboundOrderSummary)"><Eye :size="15" />查看</el-button></div><span v-else class="muted-action">无查看权限</span></template>
<template #actions="{ row }"><div v-if="access.can('platform:firstleg:inbound:view')" class="row-actions"><el-button size="small" type="primary" @click="openDetail(row as InboundOrderSummary)"><ClipboardCheck :size="15" />处理</el-button><el-button size="small" plain type="primary" :loading="printingInboundId === (row as InboundOrderSummary).id" :disabled="Boolean(printingInboundId)" @click="printCartonLabels(row as InboundOrderSummary)"><Printer :size="15" />打印箱唛</el-button><el-button size="small" plain type="primary" @click="openDetail(row as InboundOrderSummary)"><Eye :size="15" />查看</el-button></div><span v-else class="muted-action">无查看权限</span></template>
<template #inbound="{ row }"><div class="primary-cell"><strong>{{ text((row as InboundOrderSummary).inboundCode) }}</strong><span>订单 {{ text((row as InboundOrderSummary).orderNo) }}</span></div></template>
<template #processStatus="{ row }"><el-tag :type="statusType((row as InboundOrderSummary).processStatus)" effect="light">{{ statusLabel((row as InboundOrderSummary).processStatus) }}</el-tag></template>
<template #orderStatus="{ row }">{{ codeLabel((row as InboundOrderSummary).orderStatusSnapshot, inboundOrderStatusLabels) }}</template>
@@ -485,5 +485,5 @@ onBeforeUnmount(() => { disposed = true; loadRequestId += 1; customerRequestId +
.primary-cell span, .status-stack span, .status-stack small { color: var(--cc-muted); font-size: 12px; }
@media (max-width: 900px) { .inbound-tabs { width: 100%; } }
@media (max-width: 720px) { .inbound-tab { padding-inline: 14px; } .table-tabs-row { padding: 0 10px; } .inbound-stats-row { min-height: 0; align-items: stretch; flex-direction: column; gap: 10px; padding: 10px; } .inbound-stats-row .total-info { justify-content: flex-start; } }
@media (max-width: 360px) { .inbound-tab { padding-inline: 12px; } .row-actions { align-items: flex-start; flex-direction: column; } }
@media (max-width: 360px) { .inbound-tab { padding-inline: 12px; } .row-actions { align-items: stretch; flex-direction: column; } .row-actions :deep(.el-button) { width: 100%; min-height: 40px; } }
</style>
@@ -412,7 +412,7 @@ defineExpose({ reload: loadWorkspace })
/>
<el-tooltip v-if="canEdit && editable" content="删除图片" placement="top">
<el-button
text
plain
type="danger"
class="weight-delete"
:aria-label="`删除称重图片 ${index + 1}`"
@@ -91,6 +91,17 @@ describe('LoadingListsView', () => {
expect(detailSource).toContain('<StatusBadge :status="loadingListStatusLabel(summary.status)" />')
})
it('uses bordered list actions with a stable 320px layout', () => {
expect(source).toContain('size="small" type="primary" plain')
expect(source).toContain('size="small" @click="openEditFromRow(row)"')
expect(source).toContain('编辑运输信息')
expect(source).not.toContain('<el-button v-if="canView" link')
expect(source).toContain("key: 'actions', label: '操作', fixed: 'left', width: 230")
expect(source).toContain('flex-wrap: wrap')
expect(source).toContain('@media (max-width: 720px)')
expect(source).toContain('.row-actions { gap: 4px; }')
})
it('does not expose unknown history action codes', () => {
expect(detailSource).toContain("[action] || '状态更新'")
expect(detailSource).not.toContain('[action] || action')
@@ -65,7 +65,7 @@ const fields = computed<QueryField[]>(() => [
])
const columns = computed<DataTableColumn[]>(() => isPreload.value ? [
{ key: 'actions', label: '操作', fixed: 'left', width: 110, slotName: 'actions', hideable: false, movable: false },
{ key: 'actions', label: '操作', fixed: 'left', width: 230, slotName: 'actions', hideable: false, movable: false },
{ key: 'preloadNo', prop: 'preloadNo', label: '预装单号', minWidth: 165 },
{ key: 'containerNo', prop: 'containerNo', label: '集装箱号', minWidth: 145 },
{ key: 'internalContainerNo', prop: 'internalContainerNo', label: '柜号', minWidth: 130 },
@@ -307,8 +307,10 @@ onMounted(() => {
:aria-label="title"
>
<template #actions="{ row }">
<el-button v-if="canView" link type="primary" @click="openDetail(row)"><Eye :size="15" />查看</el-button>
<el-button v-if="isPreload && canEdit && ['EDITING', 'LOADING'].includes(row.status)" link type="primary" @click="openEditFromRow(row)"><Pencil :size="15" />编辑</el-button>
<div class="row-actions">
<el-button v-if="canView" size="small" type="primary" plain @click="openDetail(row)"><Eye :size="15" />查看</el-button>
<el-button v-if="isPreload && canEdit && ['EDITING', 'LOADING'].includes(row.status)" size="small" @click="openEditFromRow(row)"><Pencil :size="15" />编辑运输信息</el-button>
</div>
</template>
<template #customers="{ row }">{{ (row as PreloadSummary).customers.map((item) => item.customerName || '客户信息缺失').join('、') || '—' }}</template>
<template #vesselVoyage="{ row }">{{ (row as PreloadSummary).vesselName || '—' }} / {{ (row as PreloadSummary).voyageNo || '—' }}</template>
@@ -363,6 +365,8 @@ onMounted(() => {
.confirmed-inline-detail { padding: 20px; border: 1px solid var(--cc-border); background: #fff; }
.transport-form { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 16px; margin-top: 16px; }
.transport-form :deep(.el-form-item:last-child) { grid-column: 1 / -1; }
.row-actions { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; }
.row-actions :deep(.el-button) { margin-left: 0; }
:global(.loading-transport-dialog.el-dialog) { max-height: 90vh; overflow: hidden; }
@media (max-width: 720px) { .confirmed-inline-detail { padding: 12px; } .transport-form { grid-template-columns: 1fr; } .transport-form :deep(.el-form-item:last-child) { grid-column: auto; } }
@media (max-width: 720px) { .confirmed-inline-detail { padding: 12px; } .transport-form { grid-template-columns: 1fr; } .transport-form :deep(.el-form-item:last-child) { grid-column: auto; } .row-actions { gap: 4px; } }
</style>