Files
admin-web/src/modules/unified-orders/pages/UnifiedOrdersView.fulfillment.test.ts
T

429 lines
23 KiB
TypeScript

import { flushPromises, shallowMount } from '@vue/test-utils'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { getLoadingAssociationSummary, getOverseasInboundCodeRelationship } from '@/shared/api/inboundCodeRelations'
import { downloadPlatformProductImage, getPlatformUnifiedOrder, listPlatformUnifiedOrders } from '@/modules/unified-orders/api/unifiedOrders'
import { downloadPlatformInboundHoldEvidence, getPlatformInboundHold, holdPlatformInbound, releasePlatformInbound, uploadPlatformInboundHoldEvidence } from '@/modules/inbounds'
import UnifiedOrdersView from './UnifiedOrdersView.vue'
vi.mock('vue-router', () => ({
useRoute: () => ({ params: { orderId: '5001' }, query: { tenantId: '6001' } }),
useRouter: () => ({ push: vi.fn() }),
}))
const { canPermission } = vi.hoisted(() => ({ canPermission: vi.fn((..._permissions: string[]) => true) }))
vi.mock('@/core/access/accessPreview', () => ({ useAccessPreviewStore: () => ({ can: canPermission }) }))
vi.mock('@/modules/iam', () => ({ listPlatformTenants: vi.fn().mockResolvedValue({ records: [], page: 1, size: 200, total: 0, pages: 0 }) }))
vi.mock('@/modules/warehouses', () => ({ listAllPlatformWarehouses: vi.fn().mockResolvedValue([]) }))
vi.mock('@/modules/inbounds', () => ({
downloadWeightImage: vi.fn(), getInboundMeasurement: vi.fn(),
downloadPlatformInboundHoldEvidence: vi.fn(), getPlatformInboundHold: vi.fn(), holdPlatformInbound: vi.fn(), releasePlatformInbound: vi.fn(), uploadPlatformInboundHoldEvidence: vi.fn(),
InboundFeePanel: { template: '<div />' }, InboundMeasurementDialog: { template: '<div />' }, MeasurementEvidenceThumbnail: { template: '<div />' },
}))
vi.mock('@/modules/unified-orders/workflows', () => ({ UnifiedOrderWorkflowPanel: { template: '<div />' } }))
vi.mock('@/modules/unified-orders/api/coLoadGroups', () => ({
createPlatformCoLoadGroup: vi.fn(), getPlatformCoLoadGroupByInboundCode: vi.fn(),
}))
vi.mock('@/shared/api/inboundCodeRelations', () => ({
getLoadingAssociationSummary: vi.fn(), getOverseasInboundCodeRelationship: vi.fn(),
}))
vi.mock('@/modules/unified-orders/api/unifiedOrders', () => ({
updatePlatformUnifiedOrderRemarks: vi.fn(), approvePlatformUnifiedOrder: vi.fn(), batchApprovePlatformUnifiedOrders: vi.fn(),
downloadPlatformProductImage: vi.fn(),
getPlatformUnifiedOrder: vi.fn(), listPlatformUnifiedOrderFeeDetails: vi.fn(), listPlatformUnifiedOrders: vi.fn(),
unifiedOrderStatusOptions: [],
}))
const detail = {
summary: {
orderId: '5001', tenantId: '6001', orderNo: 'ORD-HIDDEN', mainStatus: 'WAIT_SPLIT', nextAction: null, relation: null,
internalOrderStatus: 'WAIT_SPLIT', customerId: '6001', customerName: '测试客户', channelName: '纽约渠道',
receivingWarehouseId: '8', receivingWarehouseName: '义乌仓', destinationWarehouseId: '9', destinationWarehouseName: 'ONT8',
inboundId: '7001', inboundVersion: 1, inboundCode: 'NYEXX2608310001', expectedShipDate: null, totalPackageCount: 2,
holdFlag: false, currentHoldReason: null,
locationCodes: null, actualGrossWeightKg: null, actualVolumeCbm: null, measurement: null, preload: null, loading: null, delivery: null, transport: null,
coLoadGroup: null, createdAt: null, updatedAt: null, expectedArrivalAt: null, feeReviewStatus: null, requestedPickupAt: null,
totalPalletCount: null, chargeableVolumeCbm: null, volumetricWeightKg: null, totalNetWeightKg: null,
measurementCompletedAt: null, inboundCompletedAt: null, expressCompany: null, expressTrackingNo: null, skuNos: null,
totalQuantity: null, shipmentId: null, referenceId: null,
},
order: { order: null, cargoes: [], parties: [], internalNotes: [], statusHistory: [] },
timeline: [],
}
function mountDetail() {
return shallowMount(UnifiedOrdersView, {
global: {
directives: { loading: () => undefined },
stubs: {
AdminPage: { template: '<main><slot /></main>' },
DetailPageLayout: { template: '<section><slot /></section>' },
OrderFulfillmentTreePanel: { template: '<div data-testid="fulfillment-tree" />' },
LoadErrorState: { props: ['title', 'message'], template: '<div>{{ title }} {{ message }}<slot name="details" /></div>' },
ApiErrorMeta: true,
StatusBadge: true,
'el-button': { template: '<button @click="$emit(\'click\')"><slot /></button>' },
'el-table': { props: ['data'], template: '<div><slot /></div>' },
'el-table-column': { template: '<div />' },
'el-tag': { template: '<span><slot /></span>' },
'el-empty': { props: ['description'], template: '<div>{{ description }}</div>' },
'el-alert': { props: ['title'], template: '<div>{{ title }}</div>' },
},
},
})
}
describe('UnifiedOrdersView downstream fulfillment detail', () => {
beforeEach(() => {
vi.clearAllMocks()
canPermission.mockImplementation(() => true)
vi.mocked(getPlatformUnifiedOrder).mockResolvedValue(detail as never)
vi.mocked(downloadPlatformProductImage).mockResolvedValue(new Blob(['image'], { type: 'image/png' }))
vi.mocked(listPlatformUnifiedOrders).mockResolvedValue({ records: [], page: 1, size: 20, total: 0, pages: 0 })
vi.mocked(uploadPlatformInboundHoldEvidence).mockResolvedValue({
fileId: '9007199254740999', fileName: '扣货说明.pdf', contentType: 'application/pdf', sizeBytes: 8,
})
vi.mocked(downloadPlatformInboundHoldEvidence).mockResolvedValue(new Blob(['evidence'], { type: 'application/pdf' }))
vi.mocked(getPlatformInboundHold).mockResolvedValue({
inboundId: '7001', tenantId: '6001', holdFlag: false, currentHoldReason: null, version: 3,
canHold: true, canRelease: false, actionBlockReason: null,
history: [{ id: '8801', action: 'RELEASE', reason: '货款已核对', evidenceFileId: '9901', operatorName: '操作员A', operatedAt: '2026-09-07T10:00:00' }],
})
vi.mocked(holdPlatformInbound).mockResolvedValue({
inboundId: '7001', tenantId: '6001', holdFlag: true, currentHoldReason: '等待客户补款', version: 4,
canHold: false, canRelease: true, actionBlockReason: null, history: [],
})
vi.mocked(releasePlatformInbound).mockResolvedValue({
inboundId: '7001', tenantId: '6001', holdFlag: false, currentHoldReason: null, version: 4,
canHold: true, canRelease: false, actionBlockReason: null, history: [],
})
vi.mocked(getLoadingAssociationSummary).mockResolvedValue({
inboundId: '7001', inboundCode: 'NYEXX2608310001', loadingPlanId: null, loadingPlanNo: null,
containerBookingId: null, cabinetNo: null, containerNo: null, loadingStatus: null, taskId: null,
pdaTaskStatus: null, coLoadGroupId: null, coLoadGroupNo: null, items: [],
})
vi.mocked(getOverseasInboundCodeRelationship).mockResolvedValue({
inboundCode: 'NYEXX2608310001', found: false, customer: null, overseasWarehouse: null,
overseasReceivingStatus: null, taillegOrders: [], inboundPlans: [], palletCodes: [], inventory: [],
})
})
it('downloads one preview for cargo rows sharing the same product image', async () => {
vi.mocked(getPlatformUnifiedOrder).mockResolvedValue({
...detail,
order: {
...detail.order,
cargoes: [1, 2, 3, 4].map((itemNo) => ({ itemNo, productImageFileId: '9901' })),
},
} as never)
mountDetail()
await flushPromises()
expect(downloadPlatformProductImage).toHaveBeenCalledTimes(1)
expect(downloadPlatformProductImage).toHaveBeenCalledWith('6001', '9901')
})
it('shows a compact empty state in order detail when no downstream relationship exists', async () => {
const wrapper = mountDetail()
await flushPromises()
await wrapper.findAll('.detail-tabs button').find((button) => button.text().includes('拆分履约'))!.trigger('click')
await flushPromises()
expect(getLoadingAssociationSummary).toHaveBeenCalledWith('NYEXX2608310001')
expect(getOverseasInboundCodeRelationship).toHaveBeenCalledWith('NYEXX2608310001')
expect(wrapper.text()).toContain('暂无后续履约数据')
expect(wrapper.text()).not.toContain('新增关联')
})
it('renders actual loading and overseas business identifiers inside order detail', async () => {
vi.mocked(getLoadingAssociationSummary).mockResolvedValue({
inboundId: '7001', inboundCode: 'NYEXX2608310001', loadingPlanId: '9001', loadingPlanNo: 'LP-260831-001',
containerBookingId: '9002', cabinetNo: 'CAB-260831-001', containerNo: 'MSCU1234567', loadingStatus: 'LOADING',
taskId: '9003', pdaTaskStatus: 'IN_PROGRESS', coLoadGroupId: null, coLoadGroupNo: null, items: [],
})
vi.mocked(getOverseasInboundCodeRelationship).mockResolvedValue({
inboundCode: 'NYEXX2608310001', found: true, customer: null,
overseasWarehouse: { warehouseId: '9004', warehouseCode: 'LAX', warehouseName: '洛杉矶海外仓' },
overseasReceivingStatus: 'RECEIVING', taillegOrders: [{ orderNo: 'LL-260831-001' }],
inboundPlans: [{ planNo: 'IP-260831-001' }], palletCodes: ['PLT-LAX-001'],
inventory: [{ skuCode: 'SKU-001', packageCount: 2 }],
})
const wrapper = mountDetail()
await flushPromises()
await wrapper.findAll('.detail-tabs button').find((button) => button.text().includes('拆分履约'))!.trigger('click')
await flushPromises()
expect(wrapper.text()).toContain('LP-260831-001')
expect(wrapper.text()).toContain('MSCU1234567')
expect(wrapper.text()).toContain('LL-260831-001')
expect(wrapper.text()).toContain('IP-260831-001')
expect(wrapper.text()).toContain('PLT-LAX-001')
expect(wrapper.text()).not.toContain('9001')
expect(wrapper.text()).not.toContain('9004')
})
it('loads server hold capabilities and history only after opening the other tab', async () => {
const wrapper = mountDetail()
await flushPromises()
expect(getPlatformInboundHold).not.toHaveBeenCalled()
await wrapper.findAll('.detail-tabs button').find((button) => button.text().includes('其他'))!.trigger('click')
await flushPromises()
expect(getPlatformInboundHold).toHaveBeenCalledWith('6001', '7001')
expect(wrapper.text()).toContain('未扣货')
const loaded = (wrapper.vm as unknown as { holdDetail: { history: Array<{ reason: string; evidenceFileId: string }> } }).holdDetail
expect(loaded.history).toEqual([expect.objectContaining({ reason: '货款已核对', evidenceFileId: '9901' })])
expect(wrapper.findAll('button').map((button) => button.text())).toContain('扣货')
})
it('submits the required reason and server version, then refreshes detail and list', async () => {
const wrapper = mountDetail()
await flushPromises()
await wrapper.findAll('.detail-tabs button').find((button) => button.text().includes('其他'))!.trigger('click')
await flushPromises()
const vm = wrapper.vm as unknown as { holdReason: string; openHoldDialog(action: 'HOLD'): void; submitHoldAction(): Promise<void> }
vm.openHoldDialog('HOLD')
vm.holdReason = ' 等待客户补款 '
await vm.submitHoldAction()
expect(holdPlatformInbound).toHaveBeenCalledWith('6001', '7001', { expectedVersion: 3, reason: '等待客户补款' })
expect(getPlatformUnifiedOrder).toHaveBeenCalledTimes(2)
expect(listPlatformUnifiedOrders).toHaveBeenCalledTimes(1)
})
it('exposes loading, empty and error states for the lazy hold request', async () => {
let resolveHold!: (value: Awaited<ReturnType<typeof getPlatformInboundHold>>) => void
vi.mocked(getPlatformInboundHold).mockImplementationOnce(() => new Promise((resolve) => { resolveHold = resolve }))
const wrapper = mountDetail()
await flushPromises()
await wrapper.findAll('.detail-tabs button').find((button) => button.text().includes('其他'))!.trigger('click')
expect((wrapper.vm as unknown as { holdLoading: boolean }).holdLoading).toBe(true)
resolveHold({
inboundId: '7001', tenantId: '6001', holdFlag: false, currentHoldReason: null, version: 3,
canHold: true, canRelease: false, actionBlockReason: null, history: [],
})
await flushPromises()
const loadedVm = wrapper.vm as unknown as { holdLoading: boolean; holdDetail: { history: unknown[] } }
expect(loadedVm.holdLoading).toBe(false)
expect(loadedVm.holdDetail.history).toEqual([])
expect(wrapper.text()).toContain('扣货历史')
vi.mocked(getPlatformInboundHold).mockRejectedValueOnce(new Error('网络断开'))
await (wrapper.vm as unknown as { loadHoldDetail(force: boolean): Promise<void> }).loadHoldDetail(true)
expect(wrapper.text()).toContain('扣货状态与历史加载失败')
expect((wrapper.vm as unknown as { holdError: { message: string } }).holdError.message).toBe('扣货状态与历史加载失败')
})
it('rejects blank and over-limit reasons while accepting exactly 1000 characters', async () => {
const wrapper = mountDetail()
await flushPromises()
await wrapper.findAll('.detail-tabs button').find((button) => button.text().includes('其他'))!.trigger('click')
await flushPromises()
const vm = wrapper.vm as unknown as {
holdReason: string
openHoldDialog(action: 'HOLD'): void
submitHoldAction(): Promise<void>
}
vm.openHoldDialog('HOLD')
vm.holdReason = ' '
await vm.submitHoldAction()
vm.holdReason = 'a'.repeat(1001)
await vm.submitHoldAction()
expect(holdPlatformInbound).not.toHaveBeenCalled()
vm.holdReason = 'a'.repeat(1000)
await vm.submitHoldAction()
expect(holdPlatformInbound).toHaveBeenCalledOnce()
expect(holdPlatformInbound).toHaveBeenCalledWith('6001', '7001', {
expectedVersion: 3,
reason: 'a'.repeat(1000),
})
})
it('prevents duplicate submissions while the hold request is pending', async () => {
let resolveCommand!: (value: Awaited<ReturnType<typeof holdPlatformInbound>>) => void
vi.mocked(holdPlatformInbound).mockImplementationOnce(() => new Promise((resolve) => { resolveCommand = resolve }))
const wrapper = mountDetail()
await flushPromises()
await wrapper.findAll('.detail-tabs button').find((button) => button.text().includes('其他'))!.trigger('click')
await flushPromises()
const vm = wrapper.vm as unknown as {
holdReason: string
holdSaving: boolean
openHoldDialog(action: 'HOLD'): void
submitHoldAction(): Promise<void>
}
vm.openHoldDialog('HOLD')
vm.holdReason = '等待客户补款'
const first = vm.submitHoldAction()
const duplicate = vm.submitHoldAction()
await duplicate
expect(vm.holdSaving).toBe(true)
expect(holdPlatformInbound).toHaveBeenCalledOnce()
resolveCommand({
inboundId: '7001', tenantId: '6001', holdFlag: true, currentHoldReason: '等待客户补款', version: 4,
canHold: false, canRelease: true, actionBlockReason: null, history: [],
})
await first
expect(vm.holdSaving).toBe(false)
})
it('keeps the dialog and reason when a stale-version submission fails', async () => {
vi.mocked(holdPlatformInbound).mockRejectedValueOnce(Object.assign(new Error('扣货状态已变化,请刷新后重试'), { code: 'A0409' }))
const wrapper = mountDetail()
await flushPromises()
await wrapper.findAll('.detail-tabs button').find((button) => button.text().includes('其他'))!.trigger('click')
await flushPromises()
const vm = wrapper.vm as unknown as {
holdReason: string
holdDialogOpen: boolean
openHoldDialog(action: 'HOLD'): void
submitHoldAction(): Promise<void>
}
vm.openHoldDialog('HOLD')
vm.holdReason = '等待客户补款'
await vm.submitHoldAction()
expect(holdPlatformInbound).toHaveBeenCalledWith('6001', '7001', { expectedVersion: 3, reason: '等待客户补款' })
expect(vm.holdDialogOpen).toBe(true)
expect(vm.holdReason).toBe('等待客户补款')
expect(getPlatformUnifiedOrder).toHaveBeenCalledOnce()
expect(listPlatformUnifiedOrders).not.toHaveBeenCalled()
})
it('does not submit the command when evidence upload fails and preserves the form', async () => {
vi.mocked(uploadPlatformInboundHoldEvidence).mockRejectedValueOnce(new Error('文件服务不可用'))
const wrapper = mountDetail()
await flushPromises()
await wrapper.findAll('.detail-tabs button').find((button) => button.text().includes('其他'))!.trigger('click')
await flushPromises()
const evidence = new File(['evidence'], '扣货说明.pdf', { type: 'application/pdf' })
const vm = wrapper.vm as unknown as {
holdReason: string
holdDialogOpen: boolean
holdEvidenceFile: File | null
holdEvidenceError: { message: string } | null
openHoldDialog(action: 'HOLD'): void
selectHoldEvidenceFile(upload: { raw: File }): void
submitHoldAction(): Promise<void>
}
vm.openHoldDialog('HOLD')
vm.holdReason = '等待客户补款'
vm.selectHoldEvidenceFile({ raw: evidence })
await vm.submitHoldAction()
expect(uploadPlatformInboundHoldEvidence).toHaveBeenCalledWith('6001', '7001', evidence)
expect(holdPlatformInbound).not.toHaveBeenCalled()
expect(vm.holdDialogOpen).toBe(true)
expect(vm.holdReason).toBe('等待客户补款')
expect(vm.holdEvidenceFile).toBe(evidence)
expect(vm.holdEvidenceError?.message).toBe('关联文件上传失败,请稍后重试')
})
it('uploads optional evidence once and submits its long identifier', async () => {
const wrapper = mountDetail()
await flushPromises()
await wrapper.findAll('.detail-tabs button').find((button) => button.text().includes('其他'))!.trigger('click')
await flushPromises()
const evidence = new File(['evidence'], '扣货说明.pdf', { type: 'application/pdf' })
const vm = wrapper.vm as unknown as {
holdReason: string
openHoldDialog(action: 'HOLD'): void
selectHoldEvidenceFile(upload: { raw: File }): void
submitHoldAction(): Promise<void>
}
vm.openHoldDialog('HOLD')
vm.holdReason = '等待客户补款'
vm.selectHoldEvidenceFile({ raw: evidence })
await vm.submitHoldAction()
expect(uploadPlatformInboundHoldEvidence).toHaveBeenCalledOnce()
expect(holdPlatformInbound).toHaveBeenCalledWith('6001', '7001', {
expectedVersion: 3,
reason: '等待客户补款',
evidenceFileId: '9007199254740999',
})
})
it('rejects unsafe evidence extensions, empty files and files over 10MB', async () => {
const wrapper = mountDetail()
await flushPromises()
await wrapper.findAll('.detail-tabs button').find((button) => button.text().includes('其他'))!.trigger('click')
await flushPromises()
const vm = wrapper.vm as unknown as {
holdEvidenceFile: File | null
holdEvidenceError: { code: string } | null
openHoldDialog(action: 'HOLD'): void
selectHoldEvidenceFile(upload: { raw: File }): void
}
vm.openHoldDialog('HOLD')
vm.selectHoldEvidenceFile({ raw: new File(['run'], '恶意程序.exe') })
expect(vm.holdEvidenceFile).toBeNull()
expect(vm.holdEvidenceError?.code).toBe('FILE_TYPE_INVALID')
vm.selectHoldEvidenceFile({ raw: new File([], '空文件.txt', { type: 'text/plain' }) })
expect(vm.holdEvidenceError?.code).toBe('FILE_SIZE_INVALID')
const oversized = new File(['x'], '过大文件.png', { type: 'image/png' })
Object.defineProperty(oversized, 'size', { value: 10 * 1024 * 1024 + 1 })
vm.selectHoldEvidenceFile({ raw: oversized })
expect(vm.holdEvidenceError?.code).toBe('FILE_SIZE_INVALID')
expect(uploadPlatformInboundHoldEvidence).not.toHaveBeenCalled()
})
it('downloads associated evidence once while a repeated click is pending', async () => {
let resolveDownload!: (value: Blob) => void
vi.mocked(downloadPlatformInboundHoldEvidence).mockImplementationOnce(() => new Promise((resolve) => { resolveDownload = resolve }))
Object.defineProperty(URL, 'createObjectURL', { configurable: true, value: vi.fn(() => 'blob:hold-evidence') })
Object.defineProperty(URL, 'revokeObjectURL', { configurable: true, value: vi.fn() })
const click = vi.spyOn(HTMLAnchorElement.prototype, 'click').mockImplementation(() => undefined)
const wrapper = mountDetail()
await flushPromises()
const vm = wrapper.vm as unknown as { downloadHoldEvidence(fileId: string): Promise<void>; holdDownloadingFileId: string }
const first = vm.downloadHoldEvidence('9007199254740999')
const duplicate = vm.downloadHoldEvidence('9007199254740999')
await duplicate
expect(downloadPlatformInboundHoldEvidence).toHaveBeenCalledOnce()
expect(vm.holdDownloadingFileId).toBe('9007199254740999')
resolveDownload(new Blob(['evidence'], { type: 'application/pdf' }))
await first
expect(downloadPlatformInboundHoldEvidence).toHaveBeenCalledWith('6001', '7001', '9007199254740999')
expect(click).toHaveBeenCalledOnce()
expect(URL.revokeObjectURL).toHaveBeenCalledWith('blob:hold-evidence')
expect(vm.holdDownloadingFileId).toBe('')
})
it('submits release with the current server version and refreshes detail and list', async () => {
vi.mocked(getPlatformInboundHold).mockResolvedValueOnce({
inboundId: '7001', tenantId: '6001', holdFlag: true, currentHoldReason: '等待客户补款', version: 8,
canHold: false, canRelease: true, actionBlockReason: null, history: [],
})
const wrapper = mountDetail()
await flushPromises()
await wrapper.findAll('.detail-tabs button').find((button) => button.text().includes('其他'))!.trigger('click')
await flushPromises()
const vm = wrapper.vm as unknown as {
holdReason: string
openHoldDialog(action: 'RELEASE'): void
submitHoldAction(): Promise<void>
}
vm.openHoldDialog('RELEASE')
vm.holdReason = '货款已核对'
await vm.submitHoldAction()
expect(releasePlatformInbound).toHaveBeenCalledWith('6001', '7001', { expectedVersion: 8, reason: '货款已核对' })
expect(getPlatformUnifiedOrder).toHaveBeenCalledTimes(2)
expect(listPlatformUnifiedOrders).toHaveBeenCalledTimes(1)
})
it('stays read-only without the independent hold permission', async () => {
canPermission.mockImplementation((...permissions: string[]) => permissions[0] !== 'platform:firstleg:inbound:hold')
const wrapper = mountDetail()
await flushPromises()
await wrapper.findAll('.detail-tabs button').find((button) => button.text().includes('其他'))!.trigger('click')
await flushPromises()
expect(getPlatformInboundHold).toHaveBeenCalledWith('6001', '7001')
expect(wrapper.text()).toContain('未扣货')
expect(wrapper.findAll('button').map((button) => button.text())).not.toEqual(expect.arrayContaining(['扣货', '解除扣货']))
})
it('stays read-only and does not request hold detail before an inbound exists', async () => {
vi.mocked(getPlatformUnifiedOrder).mockResolvedValue({ ...detail, summary: { ...detail.summary, inboundId: null, inboundCode: null, holdFlag: null, currentHoldReason: null } } as never)
const wrapper = mountDetail()
await flushPromises()
await wrapper.findAll('.detail-tabs button').find((button) => button.text().includes('其他'))!.trigger('click')
await flushPromises()
expect(getPlatformInboundHold).not.toHaveBeenCalled()
expect(wrapper.text()).toContain('订单尚未生成入库单,扣货状态仅可查看')
expect(wrapper.findAll('button').map((button) => button.text())).not.toEqual(expect.arrayContaining(['扣货', '解除扣货']))
})
})