Release/test #4

Merged
admin merged 3 commits from release/test into main 2026-09-20 11:11:23 +08:00
4 changed files with 85 additions and 5 deletions
-2
View File
@@ -23,8 +23,6 @@ declare module 'vue' {
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
ElDialog: typeof import('element-plus/es')['ElDialog']
ElDrawer: typeof import('element-plus/es')['ElDrawer']
ElDropdown: typeof import('element-plus/es')['ElDropdown']
+1 -1
View File
@@ -19,7 +19,7 @@ export const firstLegModule = {
name: 'first-leg-order-create',
component: () => import('./views/FirstLegOrderCreateHubView.vue'),
release: 'active',
meta: { title: '创建订单', workspaceTitle: '头程订单 · 新建', description: '填写发货、目的地和货物信息', permission: 'order:first-leg:create' },
meta: { title: '创建订单', workspaceTitle: '头程订单 · 新建', description: '填写发货、目的地和货物信息', permission: 'order:first-leg:create', keepAlive: false },
},
{
path: 'orders/first-leg/:orderId/detail',
@@ -19,7 +19,7 @@ test('routes the confirmed first-leg path to the implemented order view', () =>
})
test('opens order creation as a dedicated protected page while keeping draft editing in a large dialog', () => {
assert.match(routerSource, /path: 'orders\/first-leg\/create'[\s\S]*?name: 'first-leg-order-create'[\s\S]*?release: 'active'[\s\S]*?permission: 'order:first-leg:create'/)
assert.match(routerSource, /path: 'orders\/first-leg\/create'[\s\S]*?name: 'first-leg-order-create'[\s\S]*?release: 'active'[\s\S]*?permission: 'order:first-leg:create'[\s\S]*?keepAlive: false/)
assert.match(routerSource, /name: 'first-leg-order-create'[\s\S]*?FirstLegOrderCreateHubView\.vue/)
assert.match(createHubSource, /<FirstLegOrdersView v-if="activeMode === 'standard'" create-page \/>/)
assert.match(source, /router\.push\(\{ name: 'first-leg-order-create' \}\)/)
@@ -48,6 +48,17 @@ test('exposes real list, draft, submit, and detail workflow entries without canc
assert.doesNotMatch(source, /cancelOrder|取消订单/)
})
test('copies an order into the dedicated create page without reusing order identity', () => {
assert.match(source, /key: 'copy', label: '一键复制'/)
assert.match(source, /name: 'first-leg-order-create'[\s\S]*copyFrom: order\.id/)
assert.match(source, /order\.status === 'DRAFT' \? \{ copyDraft: '1' \}/)
assert.match(source, /sourceIsDraft \? await store\.loadDraftDetail\(id\) : await store\.loadDetail\(id\)/)
assert.match(source, /const draft = draftFromOrder\(order\)[\s\S]*inboundCode: ''/)
assert.match(source, /id: `copied-cargo-\$\{\+\+cargoSequence\}`/)
assert.match(source, /editingId\.value = undefined/)
assert.match(source, /订单数据已复制,请核对后提交/)
})
test('keeps create-page support errors scoped to create-page requests', () => {
assert.match(source, /if \(!isCreatePage\.value && store\.draftLoadError\)/)
})
@@ -71,6 +71,8 @@ const isCreatePage = computed(() => props.createPage)
const isDetailPage = computed(() => props.detailPage)
const routeDraftId = computed(() => typeof route.params.draftId === 'string' ? route.params.draftId : '')
const routeDetailId = computed(() => typeof route.params.orderId === 'string' ? route.params.orderId : '')
const routeCopySourceId = computed(() => typeof route.query.copyFrom === 'string' ? route.query.copyFrom : '')
const routeCopySourceIsDraft = computed(() => route.query.copyDraft === '1')
const canViewCompanyProfile = computed(() => auth.hasPermission('company:profile:view'))
const companyAuditLoaded = ref(false)
const companySubmissionBlocked = computed(() => companyAuditLoaded.value && tenantStore.company.auditStatus !== 'ACTIVE')
@@ -197,6 +199,7 @@ const editorOpen = ref(false)
const orderFormRef = ref<FormInstance>()
const editingId = ref<string>()
const openingEditor = ref(false)
const copyingOrderId = ref('')
const detailId = ref<string>()
const saving = ref(false)
const detailLoading = ref(false)
@@ -616,6 +619,7 @@ async function loadPageData() {
if (addressResult.status === 'rejected') supportingDataErrors.value.push(`企业地址簿加载失败:${errorMessage(addressResult.reason)}`)
await autofillWarehouseSelections()
if (isCreatePage.value && routeDraftId.value) await loadRouteDraft(routeDraftId.value)
else if (isCreatePage.value && routeCopySourceId.value) await loadCopiedOrder(routeCopySourceId.value, routeCopySourceIsDraft.value)
if (isDetailPage.value && routeDetailId.value) await loadDetailPage(routeDetailId.value)
supportingDataLoading.value = false
}
@@ -633,6 +637,39 @@ async function loadRouteDraft(id: string) {
markEditorClean()
}
async function loadCopiedOrder(id: string, sourceIsDraft: boolean) {
try {
const detailedOrder = sourceIsDraft ? await store.loadDraftDetail(id) : await store.loadDetail(id)
const draft = copyDraftFromOrder(detailedOrder)
if (draft.destinationType !== 'PRIVATE') await loadDestinationWarehouses(draft.destinationType)
const warehouseAvailable = availableWarehouses.value.some((item) => item.id === draft.receivingWarehouseId)
if (!warehouseAvailable) {
draft.receivingWarehouseId = ''
draft.channelCode = ''
draft.channelName = ''
}
if (draft.destinationType !== 'PRIVATE') {
const destinationAvailable = store.warehouses.some((item) => item.id === draft.destinationWarehouseId && item.warehouseType === draft.destinationType)
if (!destinationAvailable) {
draft.destinationWarehouseId = ''
draft.destinationWarehouseName = ''
draft.amazonWarehouseCode = ''
}
}
editingId.value = undefined
replaceForm(draft)
await Promise.all(form.items.map(ensureProductImagePreview))
if (warehouseAvailable) await loadSelectedWarehouse(draft.receivingWarehouseId)
resetEditorValidation()
markEditorClean()
ElMessage.success('订单数据已复制,请核对后提交')
} catch (error) {
supportingDataErrors.value.push(`复制订单数据失败:${errorMessage(error)}`)
}
}
async function loadDestinationWarehouses(type: 'FBA' | 'OVERSEAS' | 'PRIVATE') {
if (type === 'PRIVATE') return
destinationWarehousesLoading.value = true
@@ -833,6 +870,21 @@ function draftFromOrder(order: FirstLegOrder): FirstLegOrderDraft {
}
}
function copyDraftFromOrder(order: FirstLegOrder): FirstLegOrderDraft {
const draft = draftFromOrder(order)
return {
...draft,
inboundCode: '',
items: draft.items.map((item) => ({
...item,
id: `copied-cargo-${++cargoSequence}`,
productImagePreviewUrl: '',
productImageUploading: false,
productImageError: '',
})),
}
}
function selectChannel(code: string) {
const channel = channelOptions.value.find((item) => item.channelCode === code)
form.channelName = channel?.channelName ?? ''
@@ -1225,6 +1277,9 @@ function getOrderRowActions(row: object): TableAction[] {
const actions: TableAction[] = [
{ key: 'detail', label: '详情', run: () => openDetail(order) },
]
if (canCreateOrder.value) {
actions.push({ key: 'copy', label: '一键复制', loading: copyingOrderId.value === order.id, run: () => copyOrder(order) })
}
if (canConfirmInboundFee.value && order.businessSummary.customerFeeConfirmationRequired) {
actions.push({ key: 'fee', label: '确认计费重', run: () => openFeeConfirmation(order) })
}
@@ -1237,6 +1292,22 @@ function getOrderRowActions(row: object): TableAction[] {
return actions
}
async function copyOrder(order: FirstLegOrder) {
if (copyingOrderId.value || !canCreateOrder.value) return
copyingOrderId.value = order.id
try {
await router.push({
name: 'first-leg-order-create',
query: {
copyFrom: order.id,
...(order.status === 'DRAFT' ? { copyDraft: '1' } : {}),
},
})
} finally {
copyingOrderId.value = ''
}
}
async function openCreate() {
if (availableWarehouses.value.length === 0) {
ElMessage.warning(supportingDataLoading.value
@@ -2061,7 +2132,7 @@ async function searchOrders(model: QueryModel) {
</div>
<el-alert v-if="isCreatePage && !supportingDataLoading && availableWarehouses.length === 0" title="当前没有可用的平台收货仓" description="请联系平台维护并启用收货仓后再创建订单。" type="warning" :closable="false" show-icon />
<FirstLegOrderEditorShell v-model="editorOpen" :page="isCreatePage" :title="editingId ? '编辑订单草稿' : '创建订单'" :before-close="requestCloseEditor" @back="closeEditor">
<FirstLegOrderEditorShell v-model="editorOpen" :page="isCreatePage" :title="editingId ? '编辑订单草稿' : (routeCopySourceId ? '复制订单' : '创建订单')" :before-close="requestCloseEditor" @back="closeEditor">
<el-form ref="orderFormRef" v-loading="isCreatePage && supportingDataLoading" :model="form" :rules="orderFormRules" label-position="top" class="order-form prototype-order-form" :class="{ 'is-dialog-order-form': !isCreatePage }" scroll-to-error>
<section class="order-form-section prototype-section">
<div class="order-section-heading"><div><h3>客户下单</h3><p>先选择货物如何送到国内收货仓,并填写本次运输安排。</p></div></div>