This commit is contained in:
wujinliang
2026-08-25 22:08:59 +08:00
parent f702d1b988
commit 57de615717
13 changed files with 199 additions and 15 deletions
@@ -5,6 +5,7 @@ import {
batchApprovePlatformUnifiedOrders,
addPlatformUnifiedOrderNote,
getPlatformUnifiedOrder,
downloadPlatformProductImage,
listPlatformUnifiedOrders,
listPlatformUnifiedOrderFeeDetails,
} from './unifiedOrders'
@@ -90,6 +91,14 @@ describe('unified orders API contract', () => {
expect(http.get).toHaveBeenCalledWith('/platform/v1/orders/90001', { params: { tenantId: '208' } })
})
it('downloads a tenant-scoped product image for platform order details', async () => {
const image = new Blob(['image'], { type: 'image/png' })
vi.mocked(http.get).mockResolvedValue(image)
await expect(downloadPlatformProductImage('208/1', '90001')).resolves.toBe(image)
expect(http.get).toHaveBeenCalledWith('/platform/v1/files/product-images/208%2F1/90001', { responseType: 'blob' })
})
it('uses the explicit first-leg approval boundary for pending review', async () => {
vi.mocked(http.post).mockResolvedValue({ data: null })
@@ -395,6 +395,13 @@ export async function getPlatformUnifiedOrder(tenantId: string, orderId: string)
return { ...result, summary: normalizeRow(result.summary) }
}
export function downloadPlatformProductImage(tenantId: string, fileId: string) {
return http.get<Blob, Blob>(
`/platform/v1/files/product-images/${encodeURIComponent(tenantId)}/${encodeURIComponent(fileId)}`,
{ responseType: 'blob' },
)
}
export function approvePlatformUnifiedOrder(tenantId: string, orderId: string) {
return http.post<ApiEnvelope<void>, ApiEnvelope<void>>(
`/platform/v1/orders/first-leg/${orderId}/approve`,
@@ -114,4 +114,20 @@ describe('UnifiedOrdersView list request coordination', () => {
expect(listPlatformUnifiedOrders).toHaveBeenCalledTimes(4)
expect(listPlatformUnifiedOrders).toHaveBeenLastCalledWith(expect.objectContaining({ page: 1, size: 50 }))
})
it('does not turn a cleared full-container filter into false', async () => {
const wrapper = mountView()
await flushPromises()
const toolbar = wrapper.getComponent({ name: 'QueryToolbar' })
const selected = { ...toolbar.props('modelValue'), fullContainer: 'true' }
toolbar.vm.$emit('update:modelValue', selected)
toolbar.vm.$emit('search')
await flushPromises()
expect(listPlatformUnifiedOrders).toHaveBeenLastCalledWith(expect.objectContaining({ fullContainer: true }))
toolbar.vm.$emit('update:modelValue', { ...selected, fullContainer: undefined })
toolbar.vm.$emit('search')
await flushPromises()
expect(listPlatformUnifiedOrders).toHaveBeenLastCalledWith(expect.objectContaining({ fullContainer: undefined }))
})
})
@@ -5,6 +5,7 @@ import { ArrowLeft, Box, ChevronDown, ChevronUp, CircleCheck, Eye, GitBranch, Hi
import { useRoute, useRouter } from 'vue-router'
import {
getPlatformUnifiedOrder,
downloadPlatformProductImage,
approvePlatformUnifiedOrder,
batchApprovePlatformUnifiedOrders,
addPlatformUnifiedOrderNote,
@@ -137,6 +138,7 @@ const measurementPreviewError = ref<ApiErrorDetails | null>(null)
const measurementPreviewUrl = ref('')
const measurementPreviewTitle = ref('测量图片')
const measurementCollapsed = reactive({ customer: false, outer: false, actual: false })
const cargoImagePreviewUrls = new Set<string>()
const measurementDialogOpen = ref(false)
const smartLoadingOpen = ref(false)
const measurementOrder = ref<UnifiedOrderResource | null>(null)
@@ -296,8 +298,10 @@ function trimmed(value: string) {
return value.trim() || undefined
}
function optionalBoolean(value: string) {
return value === '' ? undefined : value === 'true'
function optionalBoolean(value: string | null | undefined) {
if (value === 'true') return true
if (value === 'false') return false
return undefined
}
function selectStatus(status: UnifiedOrderMainStatus | '') {
@@ -449,6 +453,29 @@ function cargoImage(cargo: Record<string, unknown>) {
return typeof value === 'string' && value ? value : ''
}
function releaseCargoImagePreviews() {
cargoImagePreviewUrls.forEach((url) => URL.revokeObjectURL(url))
cargoImagePreviewUrls.clear()
}
async function loadCargoImagePreviews(resource: UnifiedOrderDetailResource, serial: number) {
const tenantId = String(resource.summary.tenantId || '')
if (!tenantId) return
await Promise.all((resource.order?.cargoes ?? []).map(async (cargo) => {
const fileId = cargo.productImageFileId
if (fileId === null || fileId === undefined || fileId === '') return
try {
const blob = await downloadPlatformProductImage(tenantId, String(fileId))
if (serial !== detailRequestSerial || detailRouteKey.value !== `${tenantId}:${orderId.value}`) return
const url = URL.createObjectURL(blob)
cargo.imageUrl = url
cargoImagePreviewUrls.add(url)
} catch {
// Historical files may have been removed; keep the remaining cargo fields visible.
}
}))
}
function feeReviewLabel(value: string | null | undefined) {
return unifiedOrderReviewStatusLabel(value)
}
@@ -823,6 +850,7 @@ function runMoreAction(command: string | number | object, row: UnifiedOrderResou
}
function closeDetail() {
releaseCargoImagePreviews()
detail.value = null
detailError.value = null
void router.push({ name: 'unified-orders' })
@@ -845,12 +873,14 @@ async function loadDetail(force = false) {
measurementLoadedKey.value = ''
measurementPreviewOpen.value = false
releaseMeasurementPreview()
releaseCargoImagePreviews()
detailLoading.value = true
detailError.value = null
try {
const response = await getPlatformUnifiedOrder(tenantId, orderId.value)
if (serial !== detailRequestSerial || requestKey !== detailRouteKey.value) return
detail.value = response
await loadCargoImagePreviews(response, serial)
} catch (error) {
if (serial !== detailRequestSerial || requestKey !== detailRouteKey.value) return
detail.value = null
@@ -886,6 +916,7 @@ onMounted(() => {
onBeforeUnmount(() => {
measurementRequestSerial += 1
releaseMeasurementPreview()
releaseCargoImagePreviews()
})
</script>