Files
order_site/apps/frontend/src/utils/consignment-evidence.ts
T
yml2213 ca04e09deb feat(consignment): 客服端凭证链可视化(提报凭证 + 赠送凭证)
问题:一件寄卖有两类凭证,客服界面只看得见第一类,还是 48x48 一张缩略图,
看不出是主页还是物品;第二类(证明"这件货真送出去了"的赠送完成截图)只在工单验收界面,
客服要证明赠送是否完成得自己跨模块翻,没有任何地方把两类凭证并排呈现。

- 两类凭证分别在哪里:
  提报凭证 = 提报表 screenshots_json(卖家提报时的游戏主页截图,含物品/仓库详情)
  赠送凭证 = 绑定工单的验收资料 work_orders.acceptance_json(冷却结束后上传、客服凭此验收)
- 后端把凭证链从客服两个列表/详情接口一起下发:提报表/库存 → 结果库存 → 绑定工单,
  新增 submissionScreenshots / giftEvidenceImages / giftEvidenceNote / giftEvidenceSubmittedAt /
  giftWorkOrderNo / giftWorkOrderStatus / giftEvidenceReviewStatus(none|pending|accepted|submitted);
  赠送凭证图从 imageUrls 与 files[].url|mediumUrl|thumbnailUrl 两处取并去重
- 提报凭证口径统一:有来源提报单用提报单主页截图,客服手工入库现货回退库存自身截图,
  前端只看 submissionScreenshots 一个字段,不会拿错
- 前端新增共享组件 ConsignmentEvidenceGallery(缩略图 + 大图预览 + 共 N 张,
  预览用 Image.PreviewGroup items,被折叠的图在大图里仍能翻看);审核面板把「凭据截图」
  拆成「提报凭证(游戏主页截图)」与「赠送凭证」(带待验收/已验收 Tag + 关联工单 Tooltip),
  仓库面板新增「凭证」列并分组展示,两个详情弹窗同口径(含备注/提交时间/工单号/一句话说明)

真实库验证:赠送凭证解析在构造的工单验收资料上取到 3 张图(imageUrls 1 + files 2)并识别
pending_acceptance → 待客服验收;提报凭证在真实数据上取到 1 张。临时数据已清理。
2026-09-17 11:22:59 +08:00

98 lines
3.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 寄卖凭证(提报凭证 / 赠送凭证)的纯展示逻辑。
*
* 客服要在列表和详情里一句话看清「赠送凭证交没交、交了几张、验没验」,
* 所以把文案规则集中在这里。刻意写成纯函数:不依赖 React / antd,
* 也不使用 `@/` 别名,方便 `node --experimental-strip-types --test` 直接跑单测。
*/
import type { GiftEvidenceReviewStatus } from '../types/consignment'
export type { GiftEvidenceReviewStatus }
export type GiftEvidenceBadge = {
/** 标签文案 */
text: string
/** antd Tag 的 color 取值 */
color: string
}
export type GiftEvidenceSummaryInput = {
status: GiftEvidenceReviewStatus
/** 赠送凭证图片(赠送完成截图) */
images: string[]
/** 凭证提交时间(ISO 字符串,可能为空) */
submittedAt?: string
}
/**
* 赠送凭证状态 → 客服标签。
* 后端以后新增状态时不能崩:未知状态一律回退成「未提交」。
*/
export function resolveGiftEvidenceBadge(status: GiftEvidenceReviewStatus): GiftEvidenceBadge {
switch (status) {
case 'pending':
return { text: '待客服验收', color: 'gold' }
case 'accepted':
return { text: '已验收', color: 'green' }
case 'submitted':
return { text: '已提交', color: 'blue' }
default:
return { text: '未提交', color: 'default' }
}
}
/**
* 给客服看的一句话赠送凭证说明:
* - `未提交`
* - `已提交 3 张,待客服验收`
* - `已验收(3 张)· 2026-09-17 10:00`
* - 有图但没时间 / 时间非法:省略 ` · 时间` 部分
* - 已提交但 0 张图:`已提交,无截图`(带状态后缀时如 `已提交,无截图,待客服验收`)
* - `none`(以及未来新增状态)只看成「还没交」:即使残留了时间也不拼进文案,避免自相矛盾
*/
export function describeGiftEvidence(input: GiftEvidenceSummaryInput): string {
const count = countEvidenceImages(input?.images)
const time = formatEvidenceTime(input?.submittedAt)
switch (input?.status) {
case 'pending':
return appendEvidenceTime(
count > 0 ? `已提交 ${count} 张,待客服验收` : '已提交,无截图,待客服验收',
time,
)
case 'accepted':
return appendEvidenceTime(count > 0 ? `已验收(${count} 张)` : '已验收,无截图', time)
case 'submitted':
return appendEvidenceTime(count > 0 ? `已提交 ${count} 张` : '已提交,无截图', time)
default:
return '未提交'
}
}
/** 追加提交时间;没有有效时间就原样返回(不留下孤零零的分隔符) */
function appendEvidenceTime(text: string, time: string): string {
if (!time) return text
// 以全角右括号收尾的短句(如「已验收(3 张)」)直接紧凑接 ·,其余用空格分隔更易读
return text.endsWith(')') ? `${text}· ${time}` : `${text} · ${time}`
}
/** 统计有效图片张数:空字符串 / 空白串不算凭证 */
function countEvidenceImages(images?: string[] | null): number {
if (!Array.isArray(images)) return 0
return images.filter((src) => String(src ?? '').trim()).length
}
/** 凭证提交时间 → `YYYY-MM-DD HH:mm`;空值或非法时间返回空串(调用方直接省略这段) */
function formatEvidenceTime(submittedAt?: string | null): string {
const normalized = String(submittedAt ?? '').trim()
if (!normalized) return ''
const date = new Date(normalized)
if (Number.isNaN(date.getTime())) return ''
const pad = (value: number) => String(value).padStart(2, '0')
const datePart = `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`
const timePart = `${pad(date.getHours())}:${pad(date.getMinutes())}`
return `${datePart} ${timePart}`
}