feat(collect): 补资料查询仅限待完善并优化页面展示

- 公开查询只返回待完善工单,待分配/已验收等状态提示「该订单当前无需补充资料」
- 404 失败查询按 IP 冷却 2 秒,成功查询不计数;前端显示「查询过于频繁」
- 提交成功后本地展示成功页,刷新后按严格模式提示无需补充资料
- 收敛补资料页重复提示,移除状态标签与工单号,商品与订单号合并展示
- 修复 React StrictMode 下刷新重复请求查询接口的问题
This commit is contained in:
2026-11-02 10:38:09 +08:00
parent db73e8f820
commit a9e2de443e
7 changed files with 391 additions and 77 deletions
@@ -1,4 +1,4 @@
import { LoadingOutlined } from '@ant-design/icons'
import { CheckCircleFilled, LoadingOutlined } from '@ant-design/icons'
import {
Alert,
App,
@@ -47,33 +47,28 @@ function resolveErrorTitle(code: string): string {
if (code === 'material_link_not_found' || code === 'material_link_work_order_invalid') {
return '订单号无效'
}
if (code === 'material_link_not_pending') return '该订单当前无需补充资料'
if (code === 'rate_limited') return '查询过于频繁'
if (code === 'material_link_work_order_required') return '请选择要补充的商品'
if (code === 'material_link_order_status_invalid') return '订单状态已变化'
return '暂时无法打开页面'
}
const STATUS_TAG_COLORS: Record<string, string> = {
pending_material: 'orange',
unassigned: 'blue',
open: 'cyan',
in_progress: 'processing',
pending_acceptance: 'purple',
problem: 'red',
accepted: 'green',
cancelled: 'default',
}
function resolveStatusColor(status: string): string {
return STATUS_TAG_COLORS[status] || 'default'
/** 公开页不展示「待分配」字样:已提交统一用「资料已提交」表达。 */
function resolveMaterialOrderMessage(order: CollectMaterialLinkOrder): string {
if (order.blockedReason === 'already_submitted') {
return '资料已提交;如需修改请联系客服。'
}
return order.message || '当前不能修改资料,如需修改请联系客服。'
}
/**
* 客户侧补资料页。
*
* 只要知道平台订单号就能打开:一个订单号下可能有多个商品工单,页面按 orders
* 渲染多张卡片。只有「待完善 + 未指派 + 无份额」的工单返回表单,其余工单只展示
* 商品、工单号、状态与客服提示,不暴露已填资料和截图。
* 提交成功后工单进入「待分配」,同一工单不能再提交,需联系客服重置。
* 只查询「待完善」工单;其他状态(待分配、已验收等)统一提示无需补充资料,
* 不展示商品、订单号等信息。页面按 orders 渲染多张卡片,只有未指派、无份额的
* 待完善工单返回表单。提交成功后本地展示成功页;刷新后因工单已变为待分配,
* 会提示「该订单当前无需补充资料」。
*/
export default function CollectMaterialLinkPage() {
const { message } = App.useApp()
@@ -88,7 +83,11 @@ export default function CollectMaterialLinkPage() {
const [submittingWorkOrderId, setSubmittingWorkOrderId] = useState(0)
const submittingRef = useRef(false)
const requestSeqRef = useRef(0)
/** React StrictMode 开发环境会重复执行 mount effect,这里按订单号去重,避免刷新时打两次查询接口。 */
const loadedCodeRef = useRef<string | null>(null)
const [screenshotsByOrder, setScreenshotsByOrder] = useState<Record<number, UploadedFile[]>>({})
const [submittedSuccess, setSubmittedSuccess] = useState(false)
const [submittedNames, setSubmittedNames] = useState<string[]>([])
/**
* 读取订单资料状态。
@@ -101,6 +100,13 @@ export default function CollectMaterialLinkPage() {
const seq = requestSeqRef.current + 1
requestSeqRef.current = seq
if (!options.silent) {
// 新订单号/刷新页面时清空上一次提交成功状态;提交后不再回查
// (严格模式只允许查询待完善订单,回查会变成「无需补充资料」)。
setSubmittedSuccess(false)
setSubmittedNames([])
}
if (!code) {
setInfo(null)
setLoadError({ code: 'material_link_not_found', message: NOT_FOUND_MESSAGE })
@@ -145,8 +151,10 @@ export default function CollectMaterialLinkPage() {
)
useEffect(() => {
if (loadedCodeRef.current === code) return
loadedCodeRef.current = code
void loadInfo()
}, [loadInfo])
}, [code, loadInfo])
async function handleSubmit(order: CollectMaterialLinkOrder, values: Record<string, string>) {
if (!code) return
@@ -160,8 +168,19 @@ export default function CollectMaterialLinkPage() {
fields: values,
screenshots: screenshotsByOrder[order.workOrderId] ?? order.screenshots ?? [],
})
message.success('资料已提交,订单已进入待分配')
await loadInfo({ silent: true })
message.success('提交成功')
const submittedName = String(order.productName || '').trim() || '订单商品'
setSubmittedNames((previous) => [...previous, submittedName])
const remainingOrders = (info?.orders || []).filter(
(item) => item.workOrderId !== order.workOrderId,
)
if (remainingOrders.length > 0) {
await loadInfo({ silent: true })
} else {
// 严格模式只查询待完善订单:最后一个商品提交后不再回查,
// 直接本地展示成功页,避免刷新成「无需补充资料」。
setSubmittedSuccess(true)
}
} catch (error) {
const errorCode = resolveErrorCode(error)
message.error(resolveErrorMessage(error))
@@ -181,6 +200,9 @@ export default function CollectMaterialLinkPage() {
}
}
const emptyStateMessage = '该订单当前不可修改资料,如需修改请联系客服。'
const submittedProductNames = submittedNames.join('、')
return (
<main className="collect-link-page">
<Card className="collect-link-card" bordered={false}>
@@ -191,56 +213,63 @@ export default function CollectMaterialLinkPage() {
</div>
) : loadError ? (
<Result
status="warning"
status={loadError.code === 'material_link_not_pending' ? 'info' : 'warning'}
title={resolveErrorTitle(loadError.code)}
subTitle={loadError.message || NOT_FOUND_MESSAGE}
subTitle={
loadError.code === 'material_link_not_pending'
? undefined
: loadError.code === 'rate_limited'
? '请稍后再试'
: loadError.message || NOT_FOUND_MESSAGE
}
extra={
<Link to="/supplement">
<Button>重新输入订单号</Button>
</Link>
}
/>
) : submittedSuccess ? (
<div className="collect-link-success">
<CheckCircleFilled className="collect-link-success-icon" />
<Title level={4} className="collect-link-success-title">
提交成功
</Title>
<Text type="secondary">资料已提交;如需修改请联系客服。</Text>
<div className="collect-link-success-products">
<span>本次提交</span>
<strong>{submittedProductNames || '订单资料'}</strong>
</div>
<Text type="secondary" className="collect-link-footnote">
本页面仅用于补充订单资料,请妥善保管订单号。
</Text>
</div>
) : info ? (
<Space direction="vertical" size={16} className="full-width">
<div>
<Title level={3} className="collect-link-title">
订单资料补充
</Title>
<Text type="secondary">
{info.pendingCount > 0
? '请补充以下资料,提交后客服会尽快处理。'
: '以下为该订单当前的资料状态,如需修改请联系客服。'}
</Text>
</div>
{info.pendingCount > 0 ? (
<Text type="secondary">请补充以下资料,提交后客服会尽快处理。</Text>
) : null}
{info.pendingCount > 1 ? (
<Alert
type="info"
showIcon
message={`当前订单有 ${info.pendingCount} 个待完善商品,请逐个提交`}
description="每个商品的资料仅可提交一次,提交成功后该商品进入待分配。"
message={`当前订单有 ${info.pendingCount} 个商品待补充资料,请逐个提交`}
description="每个商品的资料仅可提交一次,提交成功后不能再修改。"
/>
) : null}
{info.pendingCount === 0 ? (
<Alert type="info" showIcon message="该订单当前不可修改资料,如需修改请联系客服" />
<Alert type="warning" showIcon message={emptyStateMessage} />
) : null}
{info.orders.map((order) => (
<Card
key={order.workOrderId}
className="collect-link-order-card"
size="small"
title={order.productName || '订单商品'}
extra={<Tag color={resolveStatusColor(order.status)}>{order.statusLabel}</Tag>}
>
<Card key={order.workOrderId} className="collect-link-order-card" size="small">
<Space direction="vertical" size={12} className="full-width">
<div className="collect-link-meta">
<div>
<span>工单号</span>
<strong>{order.workOrderNo || '-'}</strong>
<div className="collect-link-order-head">
<div className="collect-link-order-name">
<span>{order.productName || '订单商品'}</span>
</div>
<div>
<div className="collect-link-order-no">
<span>订单号</span>
<strong>{order.platformOrderId || '-'}</strong>
</div>
@@ -253,7 +282,7 @@ export default function CollectMaterialLinkPage() {
type="warning"
showIcon
message="该商品资料仅可提交一次"
description="提交成功后该商品进入待分配,不能再修改;如需修改请联系客服。"
description="提交成功后不能再修改;如需修改请联系客服。"
/>
) : null}
@@ -274,13 +303,13 @@ export default function CollectMaterialLinkPage() {
onSubmit={(values) => handleSubmit(order, values)}
/>
</>
) : (
) : info.pendingCount > 0 ? (
<Alert
type={order.blockedReason === 'already_submitted' ? 'info' : 'warning'}
showIcon
message={order.message || '当前不能修改资料,如需修改请联系客服。'}
message={resolveMaterialOrderMessage(order)}
/>
)}
) : null}
</Space>
</Card>
))}
+87 -17
View File
@@ -508,30 +508,42 @@
margin: 0 0 4px !important;
}
.collect-link-meta {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px 20px;
padding: 14px 16px;
background: #f8fafc;
border-radius: 10px;
/* 商品名与订单号放在同一个信息头里,避免标题和灰色信息块分离 */
.collect-link-order-head {
display: flex;
align-items: baseline;
justify-content: space-between;
flex-wrap: wrap;
gap: 6px 20px;
padding-bottom: 12px;
border-bottom: 1px solid #eef2f7;
}
.collect-link-meta > div {
.collect-link-order-name {
min-width: 0;
display: flex;
flex-direction: column;
gap: 2px;
align-items: center;
flex-wrap: wrap;
gap: 8px;
color: #0f172a;
font-size: 16px;
font-weight: 700;
overflow-wrap: anywhere;
}
.collect-link-meta span {
.collect-link-order-no {
min-width: 0;
display: flex;
align-items: baseline;
gap: 6px;
color: #64748b;
font-size: 12px;
}
.collect-link-meta strong {
.collect-link-order-no strong {
color: #0f172a;
font-size: 14px;
font-weight: 600;
overflow-wrap: anywhere;
}
@@ -544,6 +556,56 @@
gap: 16px;
}
/* 提交成功:居中的成功状态,不再展示订单号 */
.collect-link-success {
min-height: 380px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 10px;
padding: 24px 12px 8px;
text-align: center;
}
.collect-link-success-icon {
font-size: 64px;
color: #22c55e;
filter: drop-shadow(0 10px 20px rgba(34, 197, 94, 0.18));
}
.collect-link-success-title {
margin: 2px 0 0 !important;
color: #0f172a;
}
.collect-link-success-products {
max-width: 100%;
margin-top: 10px;
padding: 12px 22px;
border: 1px solid #dcfce7;
border-radius: 12px;
background: #f0fdf4;
display: flex;
flex-direction: column;
gap: 4px;
}
.collect-link-success-products span {
color: #16a34a;
font-size: 12px;
}
.collect-link-success-products strong {
color: #166534;
font-size: 15px;
overflow-wrap: anywhere;
}
.collect-link-success .collect-link-footnote {
margin-top: 18px;
}
.collect-link-actions {
display: flex;
justify-content: flex-end;
@@ -569,11 +631,6 @@
padding: 18px;
}
.collect-link-meta {
grid-template-columns: 1fr;
gap: 8px;
}
.collect-link-actions {
justify-content: stretch;
}
@@ -591,6 +648,19 @@
.collect-link-card .ant-card-body {
padding: 14px;
}
.collect-link-success {
min-height: 320px;
padding: 16px 4px 4px;
}
.collect-link-success-icon {
font-size: 52px;
}
.collect-link-success-products {
padding: 10px 16px;
}
}
/* ===== 兑换中心 (Redeem Page) ===== */