feat(claim-skin): 买家皮肤领取页(路径式 /claim/<token>)

买家从卡券/订单消息里的链接打开,看到物品图片与信息,填游戏账号即可领取。

- **路径式无 `#`**:新增独立 BrowserRouter 分支(照 `/redeem` 的做法),
  `isClaimSkinPath` 只认 `/claim/<token>` 两段精确匹配;
  打手领取页仍是主站 HashRouter 的 `#/claim/:token`(其 pathname 为 `/`),两者互不影响
- **物品卡片**:商品图(映射不到或加载失败都降级为占位块,不出现破图)、
  内部 SKU、数量 ×N、订单号、引流标题(灰色副信息)
- **领取表单**:按接口下发的 `claimFields` 动态渲染(游戏账号/角色ID),
  提交前二次确认让买家核对账号
- **状态**:待领取展示表单;已领取展示成功态(优先于 tokenStatus);
  token 失效/过期/关闭/已用分别给中文提示与联系客服引导
- 纯函数抽到 `utils/claim-skin.ts` 并补 20 个单测(路由判定、状态映射、字段校验、格式化)
This commit is contained in:
yml2213
2026-10-09 15:06:56 +08:00
parent b70741daec
commit eacca11768
9 changed files with 1214 additions and 0 deletions
+254
View File
@@ -472,6 +472,260 @@
margin-bottom: 0;
}
/* ===== 买家皮肤领取页 (Skin Claim Page) =====
路径式入口 `/claim/<token>`:按竞品的深色玻璃质感卡片做,
但主按钮沿用仓库主题色 `--theme-primary`(橙色渐变),与 /redeem 的主色保持协调。
所有覆盖都挂在 `.claim-skin-page` 下,保证优先级高于 antd 的运行时样式。 */
.claim-skin-page {
min-height: 100vh;
display: flex;
justify-content: center;
padding: 28px 16px 40px;
background: radial-gradient(120% 70% at 50% 0%, #27334f 0%, #141b2d 52%, #0b1020 100%);
color: #e2e8f0;
}
.claim-skin-shell {
width: min(480px, 100%);
display: flex;
flex-direction: column;
gap: 14px;
}
.claim-skin-head {
text-align: center;
}
.claim-skin-page .claim-skin-title {
margin: 0 !important;
color: #f8fafc;
font-size: 22px;
letter-spacing: 0.5px;
}
.claim-skin-subtitle {
margin: 8px 0 0;
color: rgba(226, 232, 240, 0.72);
font-size: 13px;
line-height: 1.6;
}
.claim-skin-page .claim-skin-card {
border: 1px solid rgba(148, 163, 184, 0.22);
border-radius: 16px;
background: rgba(15, 23, 42, 0.66);
box-shadow: 0 18px 40px rgba(2, 6, 23, 0.45);
backdrop-filter: blur(12px);
}
.claim-skin-card .ant-card-body {
display: flex;
flex-direction: column;
gap: 14px;
padding: 20px 18px;
}
/* --- 物品卡片 --- */
.claim-skin-item-image-wrap {
display: flex;
justify-content: center;
}
.claim-skin-item-image {
width: 160px;
height: 160px;
border-radius: 16px;
object-fit: cover;
background: rgba(148, 163, 184, 0.12);
border: 1px solid rgba(148, 163, 184, 0.24);
}
.claim-skin-item-image-fallback {
display: flex;
align-items: center;
justify-content: center;
font-size: 46px;
color: rgba(148, 163, 184, 0.55);
border-style: dashed;
}
.claim-skin-item-name {
display: flex;
align-items: baseline;
justify-content: center;
gap: 8px;
color: #f1f5f9;
font-size: 19px;
font-weight: 700;
line-height: 1.4;
text-align: center;
overflow-wrap: anywhere;
}
.claim-skin-item-qty {
flex: 0 0 auto;
color: #ffb27a;
font-size: 16px;
}
.claim-skin-item-meta {
margin: 0;
color: rgba(148, 163, 184, 0.92);
font-size: 12px;
line-height: 1.6;
text-align: center;
overflow-wrap: anywhere;
}
.claim-skin-item-meta-sub {
color: rgba(148, 163, 184, 0.62);
font-size: 11px;
}
/* --- 表单卡片 --- */
.claim-skin-page .claim-skin-form-title {
margin: 0 !important;
color: #f8fafc;
font-size: 16px;
}
.claim-skin-form-card .ant-form {
display: flex;
flex-direction: column;
gap: 12px;
}
.claim-skin-form-card .ant-form-item {
margin-bottom: 0;
}
.claim-skin-form-card .ant-form-item-label > label {
color: rgba(226, 232, 240, 0.86);
font-size: 14px;
font-weight: 600;
}
.claim-skin-field .ant-input {
background: rgba(8, 13, 26, 0.6);
border-color: rgba(148, 163, 184, 0.3);
color: #f1f5f9;
}
.claim-skin-field .ant-input::placeholder {
color: rgba(148, 163, 184, 0.62);
}
.claim-skin-field .ant-input-affix-wrapper {
background: rgba(8, 13, 26, 0.6);
border-color: rgba(148, 163, 184, 0.3);
}
.claim-skin-field .ant-input-affix-wrapper > input.ant-input {
background: transparent;
}
.claim-skin-field .ant-input-affix-wrapper .anticon {
color: rgba(148, 163, 184, 0.72);
}
.claim-skin-form-hint {
margin: 0;
color: rgba(253, 186, 116, 0.9);
font-size: 12px;
line-height: 1.6;
}
.claim-skin-page .claim-skin-submit.ant-btn {
height: 48px;
border: none;
border-radius: 14px;
font-size: 16px;
font-weight: 700;
background: linear-gradient(135deg, #ff8a3d 0%, var(--theme-primary) 100%);
box-shadow: 0 12px 24px var(--theme-primary-shadow);
}
.claim-skin-page .claim-skin-submit.ant-btn:hover:not(:disabled) {
background: linear-gradient(135deg, #ff9a55 0%, var(--theme-primary-hover) 100%);
}
.claim-skin-page .claim-skin-submit.ant-btn:disabled {
opacity: 0.6;
}
/* --- 提示 / 成功态 --- */
.claim-skin-state .ant-result {
padding: 8px 0 0;
}
.claim-skin-state .ant-result-title {
color: #f8fafc;
font-size: 19px;
}
.claim-skin-state .ant-result-subtitle {
color: rgba(226, 232, 240, 0.72);
font-size: 13px;
line-height: 1.6;
}
.claim-skin-state-desc {
margin: 0;
color: rgba(226, 232, 240, 0.72);
font-size: 13px;
line-height: 1.6;
text-align: center;
}
.claim-skin-contact-tip {
margin: 0;
color: rgba(253, 186, 116, 0.9);
font-size: 12px;
line-height: 1.6;
text-align: center;
}
.claim-skin-account-line {
display: grid;
gap: 4px;
padding: 12px;
border-radius: 12px;
background: rgba(8, 13, 26, 0.55);
border: 1px solid rgba(148, 163, 184, 0.22);
text-align: center;
}
.claim-skin-account-line span {
color: rgba(148, 163, 184, 0.9);
font-size: 12px;
}
.claim-skin-account-line strong {
color: #f8fafc;
font-size: 18px;
overflow-wrap: anywhere;
}
@media (max-width: 480px) {
.claim-skin-page {
padding: 18px 12px 32px;
}
.claim-skin-card .ant-card-body {
gap: 12px;
padding: 16px 14px;
}
.claim-skin-item-image {
width: 140px;
height: 140px;
}
}
/* ===== 买家页面响应式断点 ===== */
@media (max-width: 720px) {