feat(order-service): 优化客户订单状态与验收展示

This commit is contained in:
yml2213
2026-11-04 16:36:29 +08:00
parent c31fa7db76
commit e3a66309a0
5 changed files with 517 additions and 74 deletions
@@ -19,7 +19,6 @@ import {
Select,
Space,
Spin,
Steps,
Tag,
Typography,
} from 'antd'
@@ -42,14 +41,15 @@ import { useOrderServicePageMeta } from './use-order-service-page-meta'
const { Text, Title } = Typography
const NOT_FOUND_MESSAGE = '订单号无效,请确认订单号是否正确'
const STATUS_REFRESH_INTERVAL_MS = 15_000
const STATUS_STEP_INDEX: Record<string, number> = {
pending_material: 0,
unassigned: 1,
open: 2,
in_progress: 2,
pending_acceptance: 3,
accepted: 3,
problem: 2,
const STATUS_STEP_ORDER = ['submitted', 'scheduled', 'processing', 'completed'] as const
const STATUS_STEP_KEY: Record<string, (typeof STATUS_STEP_ORDER)[number]> = {
pending_material: 'submitted',
unassigned: 'scheduled',
open: 'processing',
in_progress: 'processing',
pending_acceptance: 'completed',
accepted: 'completed',
problem: 'processing',
}
const DEFAULT_STATUS_STEPS: OrderServiceItem['statusSteps'] = {
submitted: { title: '提交资料', description: '' },
@@ -292,6 +292,10 @@ export default function OrderServicePage() {
const pageConfig = info?.items[0]
const pendingItems = (info?.items || []).filter((item) => item.canSubmit)
const purchaseSummary =
[
...new Set(info?.items.map((item) => item.productName || item.categoryName).filter(Boolean)),
].join('、') || '订单商品'
return (
<main className="collect-link-page">
@@ -313,9 +317,14 @@ export default function OrderServicePage() {
<div className="collect-link-heading">
<div>
<Title level={4}>{pageConfig?.title || '订单服务'}</Title>
<Text type="secondary">订单号:{info.platformOrderId}</Text>
<div className="collect-link-order-meta">
<span>订单号:{info.platformOrderId}</span>
<span>
购买物品:<strong>{purchaseSummary}</strong>
</span>
</div>
{pageConfig?.description ? (
<div style={{ marginTop: 6 }}>
<div className="collect-link-description">
<Text type="secondary">{pageConfig.description}</Text>
</div>
) : null}
@@ -387,9 +396,7 @@ export default function OrderServicePage() {
}
function OrderServiceStatusCard({ item }: { item: OrderServiceItem }) {
const currentStep = STATUS_STEP_INDEX[item.state] ?? 0
const times = [
item.submittedAt ? { label: '资料提交时间', value: item.submittedAt } : null,
item.state === 'pending_acceptance' && item.acceptanceSubmittedAt
? { label: '凭证提交时间', value: item.acceptanceSubmittedAt }
: null,
@@ -398,8 +405,12 @@ function OrderServiceStatusCard({ item }: { item: OrderServiceItem }) {
: null,
].filter((row): row is { label: string; value: string } => Boolean(row?.value))
const acceptanceImageUrls = item.acceptanceImageUrls || []
const showSteps = item.state !== 'cancelled'
const acceptanceVideo = item.acceptanceVideo
const statusSteps = item.statusSteps || DEFAULT_STATUS_STEPS
const statusStepKey = STATUS_STEP_KEY[item.state]
const currentStepIndex = statusStepKey ? STATUS_STEP_ORDER.indexOf(statusStepKey) : -1
const currentStep = statusStepKey ? statusSteps[statusStepKey] : null
const currentStatusText = currentStep?.description?.trim() || item.message
return (
<Card size="small" className="collect-link-status-card">
@@ -409,37 +420,24 @@ function OrderServiceStatusCard({ item }: { item: OrderServiceItem }) {
<Tag color={STATUS_TAG_COLORS[item.state] || 'default'}>{item.stateLabel}</Tag>
</div>
{showSteps ? (
<Steps
className="collect-link-status-steps"
current={currentStep}
items={[
{
title: statusSteps.submitted.title,
description: statusSteps.submitted.description || undefined,
},
{
title: statusSteps.scheduled.title,
description: statusSteps.scheduled.description || undefined,
},
{
title: statusSteps.processing.title,
description: statusSteps.processing.description || undefined,
},
{
title: statusSteps.completed.title,
description: statusSteps.completed.description || undefined,
},
]}
responsive
size="small"
status={
item.state === 'accepted' ? 'finish' : item.state === 'problem' ? 'error' : 'process'
}
/>
) : null}
<SubmittedMaterialSummary item={item} />
<div className={`collect-link-status-message is-${item.state}`}>{item.message}</div>
{currentStep && currentStepIndex >= 0 ? (
<div className={`collect-link-current-status is-${item.state}`}>
<span className="collect-link-current-status-index">{currentStepIndex + 1}</span>
<div className="collect-link-current-status-copy">
<div>
<Text strong>{currentStep.title}</Text>
<Text type="secondary">
当前进度 {currentStepIndex + 1}/{STATUS_STEP_ORDER.length}
</Text>
</div>
<p>{currentStatusText}</p>
</div>
</div>
) : (
<div className={`collect-link-status-message is-${item.state}`}>{item.message}</div>
)}
{times.length > 0 ? (
<div className="collect-link-status-times">
@@ -451,19 +449,60 @@ function OrderServiceStatusCard({ item }: { item: OrderServiceItem }) {
</div>
) : null}
{acceptanceImageUrls.length > 0 ? (
{acceptanceImageUrls.length > 0 || acceptanceVideo ? (
<div className="collect-link-acceptance">
<div>
<Text strong>交付验收图片</Text>
<Text type="secondary">点击图片可查看大图</Text>
<Text strong>交付验收</Text>
<Text type="secondary">
{acceptanceVideo && acceptanceImageUrls.length > 0
? '图片可点击查看大图,视频可直接播放'
: acceptanceVideo
? '可直接播放并确认交付内容'
: '点击图片可查看大图'}
</Text>
</div>
<Image.PreviewGroup>
<div className="collect-link-acceptance-images">
{acceptanceImageUrls.map((url, index) => (
<Image key={url} alt={`验收图片 ${index + 1}`} src={url} width={92} height={92} />
))}
{acceptanceVideo ? (
<div className="collect-link-acceptance-video">
<video
controls
playsInline
preload="metadata"
poster={acceptanceVideo.thumbnailUrl || undefined}
src={acceptanceVideo.url}
>
您的浏览器暂不支持视频播放,请
<a href={acceptanceVideo.url} rel="noreferrer" target="_blank">
点击下载验收视频
</a>
</video>
<div>
<Text type="secondary">
{acceptanceVideo.filename || '验收视频'}
{acceptanceVideo.durationSeconds
? ` · ${Math.ceil(acceptanceVideo.durationSeconds)} 秒`
: ''}
</Text>
<a href={acceptanceVideo.url} rel="noreferrer" target="_blank">
打开原视频
</a>
</div>
</div>
</Image.PreviewGroup>
) : null}
{acceptanceImageUrls.length > 0 ? (
<Image.PreviewGroup>
<div className="collect-link-acceptance-images">
{acceptanceImageUrls.map((url, index) => (
<Image
key={url}
alt={`验收图片 ${index + 1}`}
src={url}
width={92}
height={92}
/>
))}
</div>
</Image.PreviewGroup>
) : null}
</div>
) : null}
</Space>
@@ -471,6 +510,72 @@ function OrderServiceStatusCard({ item }: { item: OrderServiceItem }) {
)
}
function SubmittedMaterialSummary({ item }: { item: OrderServiceItem }) {
const values = item.values || {}
const rows =
item.fields && item.fields.length > 0
? item.fields.map((field) => ({
key: field.key,
label: field.label,
value: String(values[field.key] || '').trim(),
}))
: Object.entries(values).map(([key, value]) => ({
key,
label: key,
value: String(value || '').trim(),
}))
const filledRows = rows.filter((row) => row.value)
const screenshots = item.screenshots || []
if (filledRows.length === 0 && screenshots.length === 0) return null
return (
<section className="collect-link-submitted">
<div className="collect-link-submitted-head">
<span>
<Text strong>买家提交的资料</Text>
<Text type="secondary">交付时会按以下信息核对</Text>
</span>
{item.submittedAt ? (
<Text type="secondary">提交于 {formatOrderServiceTime(item.submittedAt)}</Text>
) : null}
</div>
{filledRows.length > 0 ? (
<div className="collect-link-submitted-fields">
{filledRows.map((row) => (
<div key={row.key}>
<span>{row.label}</span>
<strong>{row.value}</strong>
</div>
))}
</div>
) : null}
{screenshots.length > 0 ? (
<div className="collect-link-submitted-screenshots">
<span>
{item.screenshotsRule?.label || '买家截图'}:{screenshots.length} 张
</span>
<Image.PreviewGroup>
<div>
{screenshots.map((file, index) => (
<Image
key={file.objectKey || file.url}
alt={file.filename || `买家截图 ${index + 1}`}
src={file.thumbnailUrl || file.url}
width={68}
height={68}
/>
))}
</div>
</Image.PreviewGroup>
</div>
) : null}
</section>
)
}
function OrderServiceForm({
item,
code,