优化
This commit is contained in:
@@ -9,6 +9,7 @@ import {
|
||||
listConfirmedLoadingLists,
|
||||
listPreloadPlans,
|
||||
updatePreloadTransport,
|
||||
updateLoadingItemWarehouseRemark,
|
||||
} from './platformLoadingLists'
|
||||
|
||||
const preloadSummary = {
|
||||
@@ -49,7 +50,7 @@ const preloadSummary = {
|
||||
const detail = {
|
||||
summary: preloadSummary,
|
||||
items: [{
|
||||
itemId: 11, accountId: 12, orderId: 15, orderNo: 'ORD-001', inboundId: 13, inboundDetailId: 14, containerNo: 'MSCU1234567', productQuantity: 20,
|
||||
itemId: 11, itemVersion: 4, accountId: 12, orderId: 15, orderNo: 'ORD-001', inboundId: 13, inboundDetailId: 14, containerNo: 'MSCU1234567', productQuantity: 20,
|
||||
customerName: '测试客户', inboundCode: 'RK001', skuCode: 'SKU001', destinationWarehouse: 'ONT8', chineseName: '产品', englishName: null,
|
||||
goodsImageUrl: null, packageCount: 2, palletCount: 1, grossWeightKg: 10, netWeightKg: 9, volumeCbm: 1,
|
||||
dimensionalWeightKg: 2, unitsPerPackage: 10, fbaTrackingNo: null, shipmentNo: null, shippingMark: null,
|
||||
@@ -108,6 +109,22 @@ describe('platform loading lists API', () => {
|
||||
expect(loaded.summary.status).toBe('COMPLETED')
|
||||
})
|
||||
|
||||
it('updates one item warehouse remark with both optimistic-lock versions and operation id', async () => {
|
||||
const patch = vi.spyOn(http, 'patch').mockResolvedValue({ code: '200', message: 'ok', data: { id: 100, version: 4, items: [{ id: 11, warehouseRemark: '装柜时注意外箱', version: 5 }] } })
|
||||
const result = await updateLoadingItemWarehouseRemark('100', '11', {
|
||||
warehouseRemark: '装柜时注意外箱',
|
||||
expectedPlanVersion: 3,
|
||||
expectedItemVersion: 4,
|
||||
operationId: 'warehouse-remark-1',
|
||||
})
|
||||
|
||||
expect(patch).toHaveBeenCalledWith(
|
||||
'/platform/v1/first-leg/smart-loading/plans/100/items/11/warehouse-remark',
|
||||
{ warehouseRemark: '装柜时注意外箱', expectedPlanVersion: 3, expectedItemVersion: 4, operationId: 'warehouse-remark-1' },
|
||||
)
|
||||
expect(result).toMatchObject({ id: '100', version: 4, items: [{ id: '11', version: 5, warehouseRemark: '装柜时注意外箱' }] })
|
||||
})
|
||||
|
||||
it('confirms the whole preload plan with expectedVersion and the caller operation id', async () => {
|
||||
const post = vi.spyOn(http, 'post').mockResolvedValue({
|
||||
code: '200', message: 'ok', data: {
|
||||
|
||||
@@ -80,6 +80,7 @@ export type LoadingListSummary = PreloadSummary | ConfirmedSummary
|
||||
|
||||
export interface LoadingItem {
|
||||
itemId: string
|
||||
itemVersion: number
|
||||
accountId: string
|
||||
customerName: string | null
|
||||
orderId: string
|
||||
@@ -172,6 +173,19 @@ export interface TransportUpdateRequest {
|
||||
operationReason: string | null
|
||||
}
|
||||
|
||||
export interface WarehouseRemarkUpdateRequest {
|
||||
warehouseRemark: string | null
|
||||
expectedPlanVersion: number
|
||||
expectedItemVersion: number
|
||||
operationId: string
|
||||
}
|
||||
|
||||
export interface WarehouseRemarkUpdateResponse {
|
||||
id: string
|
||||
version: number
|
||||
items: Array<{ id: string; version: number; warehouseRemark: string | null }>
|
||||
}
|
||||
|
||||
export interface PreloadConfirmationResponse {
|
||||
planId: string
|
||||
containerBookingId: string
|
||||
@@ -239,6 +253,7 @@ function normalizeItem(raw: LoadingItem): LoadingItem {
|
||||
return {
|
||||
...raw,
|
||||
itemId: requiredId(raw.itemId),
|
||||
itemVersion: Number(raw.itemVersion),
|
||||
accountId: requiredId(raw.accountId),
|
||||
orderId: requiredId(raw.orderId),
|
||||
inboundId: requiredId(raw.inboundId),
|
||||
@@ -312,6 +327,19 @@ export async function updatePreloadTransport(planId: string, warehouseId: string
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateLoadingItemWarehouseRemark(planId: string, itemId: string, request: WarehouseRemarkUpdateRequest) {
|
||||
const response = await http.patch<ApiEnvelope<WarehouseRemarkUpdateResponse>, ApiEnvelope<WarehouseRemarkUpdateResponse>>(
|
||||
`${basePath}/smart-loading/plans/${encodeURIComponent(planId)}/items/${encodeURIComponent(itemId)}/warehouse-remark`,
|
||||
request,
|
||||
)
|
||||
const result = unwrapApiResponse(response)
|
||||
return {
|
||||
...result,
|
||||
id: requiredId(result.id),
|
||||
items: (result.items || []).map((item) => ({ ...item, id: requiredId(item.id), version: Number(item.version) })),
|
||||
}
|
||||
}
|
||||
|
||||
export async function confirmPreloadPlan(planId: string, expectedVersion: number, operationId: string) {
|
||||
const response = await http.post<ApiEnvelope<PreloadConfirmationResponse>, ApiEnvelope<PreloadConfirmationResponse>>(
|
||||
`${basePath}/loading-plans/${encodeURIComponent(planId)}/confirm`,
|
||||
|
||||
@@ -22,10 +22,11 @@ const props = defineProps<{
|
||||
customsSaving?: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{ retry: []; edit: []; confirmPreload: []; attachCustoms: []; completeCustoms: [] }>()
|
||||
const emit = defineEmits<{ retry: []; edit: []; editWarehouseRemark: [item: LoadingItem]; confirmPreload: []; attachCustoms: []; completeCustoms: [] }>()
|
||||
|
||||
const itemColumns: DataTableColumn[] = [
|
||||
{ key: 'sequence', prop: 'loadSequence', label: '装柜顺序', fixed: 'left', width: 92, hideable: false, movable: false },
|
||||
{ key: 'actions', label: '操作', fixed: 'left', width: 106, slotName: 'actions', hideable: false, movable: false },
|
||||
{ key: 'sequence', prop: 'loadSequence', label: '装柜顺序', width: 92 },
|
||||
{ key: 'inboundCode', prop: 'inboundCode', label: '入仓码', minWidth: 150 },
|
||||
{ key: 'orderNo', prop: 'orderNo', label: '订单号', minWidth: 150 },
|
||||
{ key: 'containerNo', prop: 'containerNo', label: '集装箱号', minWidth: 145, formatter: (row: LoadingItem) => row.containerNo || '—' },
|
||||
@@ -45,9 +46,9 @@ const itemColumns: DataTableColumn[] = [
|
||||
{ key: 'fbaTrackingNo', prop: 'fbaTrackingNo', label: 'FBA 追踪编码', minWidth: 155 },
|
||||
{ key: 'shipmentNo', prop: 'shipmentNo', label: '货件号', minWidth: 140 },
|
||||
{ key: 'shippingMark', prop: 'shippingMark', label: '唛头', minWidth: 130 },
|
||||
{ key: 'internalRemark', prop: 'internalRemark', label: '备注(内部)', minWidth: 160, showOverflowTooltip: true },
|
||||
{ key: 'customerRemark', prop: 'customerRemark', label: '备注(客户)', minWidth: 160, showOverflowTooltip: true },
|
||||
{ key: 'warehouseRemark', prop: 'warehouseRemark', label: '备注(仓库)', minWidth: 160, showOverflowTooltip: true },
|
||||
{ key: 'customerRemark', prop: 'customerRemark', label: '备注(客户)', minWidth: 190, slotName: 'customerRemark' },
|
||||
{ key: 'internalRemark', prop: 'internalRemark', label: '备注(内部)', minWidth: 190, slotName: 'internalRemark' },
|
||||
{ key: 'warehouseRemark', prop: 'warehouseRemark', label: '备注(仓库)', minWidth: 210, slotName: 'warehouseRemark' },
|
||||
{ key: 'customsDocument', label: '报关资料', minWidth: 170, slotName: 'customsDocument' },
|
||||
{ key: 'frozenAt', label: '冻结时间', minWidth: 150, slotName: 'frozenAt' },
|
||||
]
|
||||
@@ -143,7 +144,7 @@ function formatNumber(value?: number | null, digits = 3) {
|
||||
<div v-else-if="mode === 'confirmed'" class="detail-actions"><el-button v-if="canManageCustoms && !detail.customsDocument?.completedAt" @click="emit('attachCustoms')"><FileUp :size="16" />{{ detail.customsDocument ? '更换报关资料' : '上传报关资料' }}</el-button><el-button v-if="canManageCustoms && detail.customsDocument && !detail.customsDocument.completedAt" type="primary" :loading="customsSaving" @click="emit('completeCustoms')"><Check :size="16" />完成报关</el-button><StatusBadge v-if="detail.customsDocument?.completedAt" status="报关完成" /></div>
|
||||
|
||||
<section class="detail-section surface-panel">
|
||||
<header class="detail-section__header"><h2>装柜货物明细</h2><span>{{ detail.items.length }} 条</span></header>
|
||||
<header class="detail-section__header"><div><h2>装柜货物明细</h2><p>仓库备注将随装柜完成资料下发海外仓</p></div><span>{{ detail.items.length }} 条</span></header>
|
||||
<DataTable
|
||||
:data="detail.items"
|
||||
:columns="itemColumns"
|
||||
@@ -153,10 +154,17 @@ function formatNumber(value?: number | null, digits = 3) {
|
||||
empty-text="暂无装柜货物明细"
|
||||
aria-label="装柜货物明细"
|
||||
>
|
||||
<template #actions="{ row }">
|
||||
<el-button v-if="mode === 'preload' && canEdit && ['EDITING', 'LOADING'].includes(summary.status)" size="small" type="primary" plain :aria-label="`编辑 ${row.inboundCode || row.orderNo} 的仓库备注`" @click="emit('editWarehouseRemark', row as LoadingItem)"><Pencil :size="14" />编辑备注</el-button>
|
||||
<span v-else aria-label="只读">—</span>
|
||||
</template>
|
||||
<template #grossWeight="{ row }">{{ formatNumber(row.grossWeightKg) }}</template>
|
||||
<template #netWeight="{ row }">{{ formatNumber(row.netWeightKg) }}</template>
|
||||
<template #volume="{ row }">{{ formatNumber(row.volumeCbm) }}</template>
|
||||
<template #dimensionalWeight="{ row }">{{ formatNumber(row.dimensionalWeightKg) }}</template>
|
||||
<template #customerRemark="{ row }"><div class="remark-cell"><span>{{ row.customerRemark || '—' }}</span><small>来源:客户下单</small></div></template>
|
||||
<template #internalRemark="{ row }"><div class="remark-cell"><span>{{ row.internalRemark || '—' }}</span><small>来源:订单内部备注</small></div></template>
|
||||
<template #warehouseRemark="{ row }"><div class="remark-cell"><span>{{ row.warehouseRemark || '—' }}</span><small>{{ mode === 'preload' ? '由仓库维护' : '装柜完成时冻结' }}</small></div></template>
|
||||
<template #frozenAt="{ row }">{{ formatDateTime(row.frozenAt) }}</template>
|
||||
<template #customsDocument><span class="document-cell">{{ detail.customsDocument?.fileName || '未上传' }}</span></template>
|
||||
</DataTable>
|
||||
@@ -192,11 +200,13 @@ function formatNumber(value?: number | null, digits = 3) {
|
||||
.detail-summary__wide { grid-column: span 2; }
|
||||
.detail-actions { display: flex; justify-content: flex-end; }
|
||||
.detail-section, .history-section { min-width: 0; overflow: hidden; border: 1px solid var(--cc-border); background: #fff; }
|
||||
.detail-section__header { display: flex; align-items: baseline; gap: 10px; padding: 18px 18px 0; }
|
||||
.detail-section__header { display: flex; align-items: flex-start; gap: 10px; padding: 18px 18px 0; }
|
||||
.detail-section__header h2 { margin: 0; font-size: 16px; }
|
||||
.detail-section__header span { color: var(--cc-muted); font-size: 12px; }
|
||||
.detail-section__header p { margin: 4px 0 0; color: var(--cc-muted); font-size: 12px; }
|
||||
.detail-section :deep(.el-table) { width: 100%; }
|
||||
.detail-section small { display: block; margin-top: 3px; color: var(--cc-muted); }
|
||||
.remark-cell { min-width: 0; line-height: 1.45; overflow-wrap: anywhere; }
|
||||
.history-section { padding: 18px; }
|
||||
.history-section header { display: flex; align-items: baseline; gap: 10px; margin-bottom: 18px; }
|
||||
.history-section h2 { margin: 0; font-size: 16px; }
|
||||
|
||||
@@ -142,6 +142,8 @@ describe('LoadingListsView', () => {
|
||||
})
|
||||
|
||||
it('shows every prototype detail field that is backed by the loading item contract', () => {
|
||||
expect(detailSource.indexOf("key: 'actions'")).toBeLessThan(detailSource.indexOf("key: 'sequence'"))
|
||||
expect(detailSource).toContain("key: 'actions', label: '操作', fixed: 'left'")
|
||||
expect(detailSource.indexOf("key: 'orderNo'")).toBeLessThan(detailSource.indexOf("key: 'containerNo'"))
|
||||
expect(detailSource.indexOf("key: 'containerNo'")).toBeLessThan(detailSource.indexOf("key: 'skuCode'"))
|
||||
const fields = [
|
||||
@@ -158,8 +160,8 @@ describe('LoadingListsView', () => {
|
||||
'fbaTrackingNo',
|
||||
'shipmentNo',
|
||||
'shippingMark',
|
||||
'internalRemark',
|
||||
'customerRemark',
|
||||
'internalRemark',
|
||||
'warehouseRemark',
|
||||
'customsDocument',
|
||||
]
|
||||
@@ -173,6 +175,16 @@ describe('LoadingListsView', () => {
|
||||
expect(detailSource).not.toContain("label: '退税资料'")
|
||||
})
|
||||
|
||||
it('keeps customer and internal remarks read-only while exposing per-item warehouse remark editing', () => {
|
||||
expect(detailSource).toContain('来源:客户下单')
|
||||
expect(detailSource).toContain('来源:订单内部备注')
|
||||
expect(detailSource).toContain('仓库备注将随装柜完成资料下发海外仓')
|
||||
expect(detailSource).toContain("emit('editWarehouseRemark', row as LoadingItem)")
|
||||
expect(detailSource).toContain("mode === 'preload' && canEdit && ['EDITING', 'LOADING'].includes(summary.status)")
|
||||
expect(detailSource).not.toContain("emit('editCustomerRemark'")
|
||||
expect(detailSource).not.toContain("emit('editInternalRemark'")
|
||||
})
|
||||
|
||||
it('does not expose unknown history action codes', () => {
|
||||
expect(detailSource).toContain("CONFIRM_PRELOAD: '确认预装'")
|
||||
expect(detailSource).toContain("[action] || '状态更新'")
|
||||
|
||||
@@ -27,6 +27,28 @@ describe('PreloadPlanDetailView prototype contract', () => {
|
||||
expect(source).not.toContain('saveMfNote')
|
||||
})
|
||||
|
||||
it('edits only the selected item warehouse remark with optimistic locking', () => {
|
||||
expect(source).toContain('@edit-warehouse-remark="openWarehouseRemark"')
|
||||
expect(source).toContain('updateLoadingItemWarehouseRemark(current.summary.planId, target.itemId')
|
||||
expect(source).toContain('expectedPlanVersion: current.summary.version')
|
||||
expect(source).toContain('expectedItemVersion: target.itemVersion')
|
||||
expect(source).toContain('operationId: remarkOperationId.value')
|
||||
expect(source).toContain('maxlength="1000"')
|
||||
expect(source).toContain("warehouseRemark.value.trim() || null")
|
||||
expect(source).toContain('summary: { ...current.summary, version: result.version }')
|
||||
expect(source).toContain('updatedItem?.version')
|
||||
expect(source).not.toContain('plan.remark')
|
||||
})
|
||||
|
||||
it('preserves warehouse remark input and refreshes current data on conflicts', () => {
|
||||
expect(source).toContain("details.code === 'A0409'")
|
||||
expect(source).toContain('await loadDetail()')
|
||||
expect(source).toContain("detail.value?.items.find((item) => item.itemId === target.itemId) || target")
|
||||
expect(source).toContain('当前输入已保留')
|
||||
expect(source).toContain('计划状态或明细版本已变化')
|
||||
expect(source).toContain(':confirm-loading="remarkSaving"')
|
||||
})
|
||||
|
||||
it('is the formal container-number entry and does not imply that preload directly opens PDA', () => {
|
||||
expect(source).toContain("{ required: true, message: '请输入集装箱号'")
|
||||
expect(source).toContain("detail.value?.summary.containerNo ? '修改集装箱号 / 航次' : '录入集装箱号'")
|
||||
@@ -47,7 +69,7 @@ describe('PreloadPlanDetailView prototype contract', () => {
|
||||
|
||||
it('keeps backend confirmation errors visible and supports narrow dialogs', () => {
|
||||
expect(source).toContain("confirmError.value = getApiErrorDetails(cause, '确认预装失败,请稍后重试')")
|
||||
expect(source).toContain('<OperationErrorToast :details="editError || confirmError" />')
|
||||
expect(source).toContain('<OperationErrorToast :details="remarkError || editError || confirmError" />')
|
||||
expect(source).not.toContain(':message="confirmError.message"')
|
||||
expect(source).toContain('width="min(520px, calc(100vw - 28px))"')
|
||||
expect(source).toContain("label=\"柜次号\"")
|
||||
|
||||
@@ -11,7 +11,7 @@ import LoadErrorState from '@/shared/components/LoadErrorState.vue'
|
||||
import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http'
|
||||
import { useAccessPreviewStore } from '@/core/access/accessPreview'
|
||||
import LoadingListDetailDrawer from '../components/LoadingListDetailDrawer.vue'
|
||||
import { confirmPreloadPlan, getPreloadPlan, updatePreloadTransport, type LoadingDetail, type PreloadSummary } from '../api/platformLoadingLists'
|
||||
import { confirmPreloadPlan, getPreloadPlan, updateLoadingItemWarehouseRemark, updatePreloadTransport, type LoadingDetail, type LoadingItem, type PreloadSummary } from '../api/platformLoadingLists'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
@@ -28,6 +28,12 @@ const confirmError = ref<ApiErrorDetails | null>(null)
|
||||
const confirmOperationId = ref('')
|
||||
const editFormRef = ref<FormInstance>()
|
||||
const editForm = reactive({ containerNo: '', voyageNo: '', operationReason: '' })
|
||||
const remarkOpen = ref(false)
|
||||
const remarkSaving = ref(false)
|
||||
const remarkError = ref<ApiErrorDetails | null>(null)
|
||||
const remarkTarget = ref<LoadingItem | null>(null)
|
||||
const warehouseRemark = ref('')
|
||||
const remarkOperationId = ref('')
|
||||
const planId = computed(() => String(route.params.planId || ''))
|
||||
const warehouseId = computed(() => typeof route.query.warehouseId === 'string' ? route.query.warehouseId : '')
|
||||
const canEdit = computed(() => access.can('platform:firstleg:preload:edit'))
|
||||
@@ -101,6 +107,56 @@ async function saveTransport() {
|
||||
}
|
||||
}
|
||||
|
||||
function openWarehouseRemark(item: LoadingItem) {
|
||||
if (!canEdit.value || !detail.value || !['EDITING', 'LOADING'].includes(detail.value.summary.status)) return
|
||||
remarkTarget.value = item
|
||||
warehouseRemark.value = item.warehouseRemark || ''
|
||||
remarkError.value = null
|
||||
remarkOperationId.value = `admin-preload-warehouse-remark-${detail.value.summary.planId}-${item.itemId}-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`
|
||||
remarkOpen.value = true
|
||||
}
|
||||
|
||||
async function saveWarehouseRemark() {
|
||||
const target = remarkTarget.value
|
||||
const current = detail.value
|
||||
if (!target || !current || !canEdit.value || remarkSaving.value) return
|
||||
if (warehouseRemark.value.length > 1000) {
|
||||
remarkError.value = { code: 'WAREHOUSE_REMARK_TOO_LONG', message: '仓库备注不能超过 1000 个字符' }
|
||||
return
|
||||
}
|
||||
remarkSaving.value = true
|
||||
remarkError.value = null
|
||||
try {
|
||||
const result = await updateLoadingItemWarehouseRemark(current.summary.planId, target.itemId, {
|
||||
warehouseRemark: warehouseRemark.value.trim() || null,
|
||||
expectedPlanVersion: current.summary.version,
|
||||
expectedItemVersion: target.itemVersion,
|
||||
operationId: remarkOperationId.value,
|
||||
})
|
||||
const updatedItem = result.items.find((item) => item.id === target.itemId)
|
||||
detail.value = {
|
||||
...current,
|
||||
summary: { ...current.summary, version: result.version },
|
||||
items: current.items.map((item) => item.itemId === target.itemId
|
||||
? { ...item, itemVersion: updatedItem?.version ?? item.itemVersion, warehouseRemark: updatedItem?.warehouseRemark ?? null }
|
||||
: item),
|
||||
}
|
||||
remarkOpen.value = false
|
||||
remarkTarget.value = null
|
||||
remarkOperationId.value = ''
|
||||
ElMessage.success('仓库备注已更新')
|
||||
} catch (cause) {
|
||||
const details = getApiErrorDetails(cause, '仓库备注保存失败,请稍后重试')
|
||||
if (details.code === 'A0409' || /版本|并发|状态.*变化/.test(details.message)) {
|
||||
await loadDetail()
|
||||
remarkTarget.value = detail.value?.items.find((item) => item.itemId === target.itemId) || target
|
||||
remarkError.value = { ...details, message: '计划状态或明细版本已变化,系统已刷新最新数据;当前输入已保留,请核对后重新保存。' }
|
||||
} else remarkError.value = details
|
||||
} finally {
|
||||
remarkSaving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function openConfirmPreload() {
|
||||
const summary = detail.value?.summary
|
||||
if (!canEdit.value || !summary?.readiness.canConfirm) return
|
||||
@@ -133,7 +189,7 @@ onMounted(() => void loadDetail())
|
||||
<template>
|
||||
<AdminPage>
|
||||
<div class="preload-detail-page">
|
||||
<OperationErrorToast :details="editError || confirmError" />
|
||||
<OperationErrorToast :details="remarkError || editError || confirmError" />
|
||||
<el-button link type="primary" class="back-button" @click="goBack"><ArrowLeft :size="16" />返回列表</el-button>
|
||||
<el-alert
|
||||
v-if="detail"
|
||||
@@ -146,7 +202,7 @@ onMounted(() => void loadDetail())
|
||||
<LoadErrorState v-if="error && !detail" title="预装清单详情加载失败" :message="error.message" :loading="loading" @retry="loadDetail">
|
||||
<template #details><ApiErrorMeta :details="error" /></template>
|
||||
</LoadErrorState>
|
||||
<LoadingListDetailDrawer v-else :mode="'preload'" :loading="loading" :detail="detail" :error="error" :can-edit="canEdit" :confirm-saving="confirmSaving" @retry="loadDetail" @edit="openEdit" @confirm-preload="openConfirmPreload" />
|
||||
<LoadingListDetailDrawer v-else :mode="'preload'" :loading="loading" :detail="detail" :error="error" :can-edit="canEdit" :confirm-saving="confirmSaving" @retry="loadDetail" @edit="openEdit" @edit-warehouse-remark="openWarehouseRemark" @confirm-preload="openConfirmPreload" />
|
||||
</div>
|
||||
|
||||
<AppDialog v-model="editOpen" class="loading-transport-dialog" :title="editDialogTitle" width="min(620px, calc(100vw - 28px))" confirm-text="保存" :confirm-loading="editSaving" :confirm-disabled="!canEdit" @confirm="saveTransport">
|
||||
@@ -157,6 +213,16 @@ onMounted(() => void loadDetail())
|
||||
</el-form>
|
||||
</AppDialog>
|
||||
|
||||
<AppDialog v-model="remarkOpen" class="warehouse-remark-dialog" title="编辑仓库备注" width="min(560px, calc(100vw - 28px))" confirm-text="保存备注" cancel-text="取消" :confirm-loading="remarkSaving" :confirm-disabled="!canEdit" @confirm="saveWarehouseRemark">
|
||||
<el-alert type="info" :closable="false" show-icon title="仓库备注将随装柜完成资料下发海外仓" />
|
||||
<el-form label-position="top" class="warehouse-remark-form">
|
||||
<el-form-item label="客户备注(只读)"><el-input :model-value="remarkTarget?.customerRemark || '—'" disabled /><small>来源:客户下单</small></el-form-item>
|
||||
<el-form-item label="内部备注(只读)"><el-input :model-value="remarkTarget?.internalRemark || '—'" disabled /><small>来源:订单内部备注</small></el-form-item>
|
||||
<el-form-item label="仓库备注"><el-input v-model="warehouseRemark" type="textarea" :rows="5" maxlength="1000" show-word-limit placeholder="可填写、修改或清空仓库备注" /></el-form-item>
|
||||
</el-form>
|
||||
<LoadErrorState v-if="remarkError" compact title="仓库备注保存失败" :message="remarkError.message" :show-retry="false"><template #details><ApiErrorMeta :details="remarkError" /></template></LoadErrorState>
|
||||
</AppDialog>
|
||||
|
||||
<AppDialog v-model="confirmOpen" class="preload-confirm-dialog" title="确认整份预装计划" width="min(520px, calc(100vw - 28px))" confirm-text="确认预装" :confirm-loading="confirmSaving" :confirm-disabled="!detail?.summary.readiness.canConfirm" @confirm="submitConfirmPreload">
|
||||
<div class="confirm-content">
|
||||
<el-alert type="warning" :closable="false" show-icon title="本操作将确认整份预装计划" description="系统会原子确认本计划包含的全部入库预装票据;成功后由服务端按真实条件创建唯一 PDA 任务,当前页面不会伪造 PDA 成功。" />
|
||||
@@ -176,6 +242,8 @@ onMounted(() => void loadDetail())
|
||||
.transport-form { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 16px; }
|
||||
.transport-form :deep(.el-form-item:last-child) { grid-column: 1 / -1; }
|
||||
.confirm-content { display: grid; gap: 16px; }
|
||||
.warehouse-remark-form { margin-top: 14px; }
|
||||
.warehouse-remark-form small { display: block; margin-top: 4px; color: var(--cc-muted); }
|
||||
:global(.loading-transport-dialog.el-dialog) { max-height: 90vh; overflow: hidden; }
|
||||
@media (min-width: 1920px) { .preload-detail-page { width: 100%; max-width: none; gap: 12px; } }
|
||||
@media (max-width: 720px) { .transport-form { grid-template-columns: 1fr; } .transport-form :deep(.el-form-item:last-child) { grid-column: auto; } }
|
||||
|
||||
Reference in New Issue
Block a user