@seasonkoh/webaz 0.1.28 → 0.1.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/README.md +3 -2
  2. package/README.zh-CN.md +7 -6
  3. package/dist/currency.js +16 -0
  4. package/dist/deposit-rails.js +52 -0
  5. package/dist/direct-pay-aml-monitor.js +67 -0
  6. package/dist/direct-pay-aml-review.js +40 -0
  7. package/dist/direct-pay-base-bond-entry.js +5 -0
  8. package/dist/direct-pay-bond-rail-clearance.js +94 -0
  9. package/dist/direct-pay-compliance-ingress.js +145 -0
  10. package/dist/direct-pay-controls.js +136 -0
  11. package/dist/direct-pay-create.js +162 -0
  12. package/dist/direct-pay-deferral-quota.js +43 -0
  13. package/dist/direct-pay-disclosures.js +44 -0
  14. package/dist/direct-pay-eligibility.js +46 -0
  15. package/dist/direct-pay-fee-ar.js +241 -0
  16. package/dist/direct-pay-launch-readiness.js +108 -0
  17. package/dist/direct-pay-launch-summary.js +68 -0
  18. package/dist/direct-pay-ledger.js +94 -0
  19. package/dist/direct-receive-account-qr.js +77 -0
  20. package/dist/direct-receive-accounts.js +82 -0
  21. package/dist/direct-receive-deferral.js +142 -0
  22. package/dist/direct-receive-deposits.js +260 -0
  23. package/dist/direct-receive-payment-instruction.js +26 -0
  24. package/dist/fx-rates.js +71 -0
  25. package/dist/layer0-foundation/L0-1-database/schema.js +531 -1
  26. package/dist/layer0-foundation/L0-2-state-machine/genuine-sale.js +15 -5
  27. package/dist/layer0-foundation/L0-2-state-machine/transitions.js +41 -0
  28. package/dist/layer0-foundation/L0-5-manifest/manifest.js +1 -1
  29. package/dist/layer1-agent/L1-1-mcp-server/cli.js +64 -0
  30. package/dist/layer1-agent/L1-1-mcp-server/network-mode.js +11 -0
  31. package/dist/layer1-agent/L1-1-mcp-server/server.js +18 -16
  32. package/dist/layer4-economics/L4-4-skill-market/skill-engine.js +4 -4
  33. package/dist/ledger.js +12 -3
  34. package/dist/mcp.js +23 -4
  35. package/dist/merchant-bond-domain.js +64 -0
  36. package/dist/merchant-bond-exposure.js +78 -0
  37. package/dist/merchant-bond-watcher.js +26 -0
  38. package/dist/payment-rails.js +29 -0
  39. package/dist/product-verification.js +93 -0
  40. package/dist/pwa/acp-feed.js +3 -2
  41. package/dist/pwa/contract-fingerprint.js +3 -0
  42. package/dist/pwa/direct-pay-guards.js +20 -0
  43. package/dist/pwa/direct-pay-order-redaction.js +24 -0
  44. package/dist/pwa/endpoint-actions.js +3 -0
  45. package/dist/pwa/public/app-ai.js +10 -10
  46. package/dist/pwa/public/app-chat-poll.js +29 -0
  47. package/dist/pwa/public/app-create-kinds.js +17 -0
  48. package/dist/pwa/public/app-direct-pay-accounts.js +141 -0
  49. package/dist/pwa/public/app-direct-pay-buyer.js +72 -0
  50. package/dist/pwa/public/app-direct-pay-compliance.js +67 -0
  51. package/dist/pwa/public/app-direct-pay-deferral-admin.js +72 -0
  52. package/dist/pwa/public/app-direct-pay-deferral.js +61 -0
  53. package/dist/pwa/public/app-direct-pay-fee-center.js +33 -0
  54. package/dist/pwa/public/app-direct-pay-fee-ops.js +112 -0
  55. package/dist/pwa/public/app-direct-pay-product-verify.js +103 -0
  56. package/dist/pwa/public/app-direct-pay-readiness.js +38 -0
  57. package/dist/pwa/public/app-direct-pay-store-verify.js +100 -0
  58. package/dist/pwa/public/app-direct-pay.js +227 -0
  59. package/dist/pwa/public/app-discover.js +20 -20
  60. package/dist/pwa/public/app-external-links.js +32 -0
  61. package/dist/pwa/public/app-listings.js +4 -4
  62. package/dist/pwa/public/app-prelaunch-waz.js +39 -0
  63. package/dist/pwa/public/app-price.js +55 -0
  64. package/dist/pwa/public/app-product-media.js +15 -0
  65. package/dist/pwa/public/app-profile.js +6 -6
  66. package/dist/pwa/public/app-seller.js +5 -5
  67. package/dist/pwa/public/app-shop.js +19 -19
  68. package/dist/pwa/public/app.js +142 -146
  69. package/dist/pwa/public/i18n.js +398 -197
  70. package/dist/pwa/public/index.html +17 -0
  71. package/dist/pwa/public/openapi.json +495 -1
  72. package/dist/pwa/routes/admin-analytics.js +4 -2
  73. package/dist/pwa/routes/admin-direct-receive-deposits.js +321 -0
  74. package/dist/pwa/routes/buyer-feeds.js +2 -1
  75. package/dist/pwa/routes/chat.js +6 -1
  76. package/dist/pwa/routes/dashboards.js +2 -1
  77. package/dist/pwa/routes/direct-pay-availability.js +196 -0
  78. package/dist/pwa/routes/direct-pay-disclosure-acks.js +74 -0
  79. package/dist/pwa/routes/direct-pay-timeouts.js +71 -0
  80. package/dist/pwa/routes/direct-receive-accounts.js +155 -0
  81. package/dist/pwa/routes/direct-receive-payment-instructions.js +45 -0
  82. package/dist/pwa/routes/fx.js +12 -0
  83. package/dist/pwa/routes/leaderboard.js +47 -9
  84. package/dist/pwa/routes/listings.js +2 -2
  85. package/dist/pwa/routes/logistics.js +4 -0
  86. package/dist/pwa/routes/manifests.js +38 -0
  87. package/dist/pwa/routes/me-data.js +5 -2
  88. package/dist/pwa/routes/orders-action.js +117 -9
  89. package/dist/pwa/routes/orders-create.js +4 -0
  90. package/dist/pwa/routes/orders-read.js +36 -0
  91. package/dist/pwa/routes/p2p-products.js +2 -2
  92. package/dist/pwa/routes/products-create.js +5 -3
  93. package/dist/pwa/routes/products-links.js +34 -0
  94. package/dist/pwa/routes/products-list.js +2 -1
  95. package/dist/pwa/routes/products-update.js +22 -2
  96. package/dist/pwa/routes/promoter.js +3 -0
  97. package/dist/pwa/routes/referral.js +4 -0
  98. package/dist/pwa/routes/returns.js +26 -1
  99. package/dist/pwa/routes/rewards-apply.js +10 -5
  100. package/dist/pwa/routes/rewards-clearing-mature.js +96 -0
  101. package/dist/pwa/routes/rewards-escrow-expire.js +6 -2
  102. package/dist/pwa/routes/shops.js +2 -1
  103. package/dist/pwa/routes/url-claim.js +2 -2
  104. package/dist/pwa/routes/users-public.js +8 -0
  105. package/dist/pwa/routes/wallet-read.js +3 -0
  106. package/dist/pwa/routes/webauthn.js +1 -1
  107. package/dist/pwa/server.js +59 -102
  108. package/dist/runtime/webaz-schema-helpers.js +104 -0
  109. package/dist/store-verification.js +77 -0
  110. package/dist/version.js +1 -1
  111. package/package.json +71 -2
@@ -721,7 +721,7 @@ async function render(page, params) {
721
721
  if (params[0] === 'public-ideas') return renderAdminPublicIdeas(app)
722
722
  if (params[0] === 'task-proposals') return renderAdminTaskProposals(app)
723
723
  if (params[0] === 'quota-requests') return renderAdminBuildTaskQuota(app)
724
- if (params[0] === 'operator-claims') return renderAdminOperatorClaims(app)
724
+ if (params[0] === 'operator-claims') return renderAdminOperatorClaims(app); if (params[0] === 'deferrals') return renderAdminDirectPayDeferrals(app); if (params[0] === 'product-verifications') return renderAdminProductVerifications(app); if (params[0] === 'store-verifications') return renderAdminStoreVerifications(app); if (params[0] === 'compliance') return renderAdminDirectReceiveCompliance(app); if (params[0] === 'dp-ops') return renderAdminDirectPayHub(app); if (params[0] === 'dp-fee') return renderAdminDirectPayFeeOps(app) // PR-②c 缓交 / PR-④b 逐品 / PR-⑤b 店铺 / PR-⑧ 合规录入 / PR-B 商户运营 hub + 平台服务费
725
725
  if (params[0] === 'params') return renderAdminParams(app)
726
726
  if (params[0] === 'timeline' && params[1]) return renderAdminUserTimeline(app, params[1])
727
727
  if (params[0] === 'timeline') return renderAdminUserTimelinePicker(app)
@@ -1017,7 +1017,7 @@ function roleHome(role) {
1017
1017
  function preLaunchBannerHTML() {
1018
1018
  if (window._protocolPhase && window._protocolPhase !== 'pre_launch') return ''
1019
1019
  return `<div style="background:#fef3c7;border:1px solid #fde68a;border-radius:8px;padding:10px 14px;font-size:12px;color:#92400e;text-align:center;line-height:1.5;margin:8px 12px 12px">
1020
- ⚠️ <strong>${t('协议尚未公开上线 · 数据为测试 / demo · 请勿据此投资或承诺第三方')}</strong>
1020
+ ⚠️ <strong>${t('直付(direct pay)已上线,是你与卖家的真实场外付款;WebAZ 非托管——不代持、不担保、不退款 · 平台托管(escrow)尚未上线(模拟测试币)· 价格按 USDC 计价仅供展示 · 邀请制预发布,勿据此投资或向第三方承诺')}</strong>
1021
1021
  <a href="#welcome" style="margin-left:8px;color:#7c2d12;text-decoration:underline;font-weight:600">${t('详情')}</a>
1022
1022
  </div>`
1023
1023
  }
@@ -2045,7 +2045,7 @@ window.adminUserBatch = async (action) => {
2045
2045
  reason = prompt(t('暂停原因(必填,会写入审计)')) || ''
2046
2046
  if (!reason.trim()) return alert(t('需填写原因'))
2047
2047
  }
2048
- if (!confirm(t('确认') + label + ' ' + ids.length + ' ' + t('个用户?'))) return
2048
+ if (!confirm((action === 'suspend' ? t('确认暂停') : t('确认恢复')) + ' ' + ids.length + ' ' + t('个用户?'))) return
2049
2049
  let ok = 0, fail = 0
2050
2050
  for (const uid of ids) {
2051
2051
  const path = action === 'suspend' ? `/admin/users/${uid}/suspend` : `/admin/users/${uid}/resume`
@@ -3378,7 +3378,7 @@ window.openResignModal = (role) => {
3378
3378
  <li>${t('已 assigned 但未完成的 case 必须先完成 / 转交,否则无法卸任')}</li>
3379
3379
  </ul>
3380
3380
  </div>
3381
- <div style="font-size:13px;color:#374151;margin-bottom:8px">${t('输入')} <code style="background:#fee2e2;padding:2px 6px;border-radius:4px;color:#991b1b">${expected}</code> ${t('确认')}:</div>
3381
+ <div style="font-size:13px;color:#374151;margin-bottom:8px">${t('输入')} <code style="background:#fee2e2;padding:2px 6px;border-radius:4px;color:#991b1b">${expected}</code> ${t('以确认')}:</div>
3382
3382
  <input id="resign-confirm-input" type="text" placeholder="${expected}" style="width:100%;padding:8px;border:1px solid #d1d5db;border-radius:4px;font-family:monospace" oninput="document.getElementById('resign-submit-btn').disabled = this.value !== '${expected}'">
3383
3383
  <div id="resign-msg" style="margin-top:12px"></div>
3384
3384
  <div style="margin-top:16px;display:flex;gap:8px;justify-content:flex-end">
@@ -3505,7 +3505,7 @@ async function renderAdminGovernance(app) {
3505
3505
  <div style="display:flex;justify-content:space-between;align-items:flex-start;gap:10px">
3506
3506
  <div style="flex:1">
3507
3507
  <div style="font-weight:600;font-size:14px">${a.role === 'arbitrator' ? '⚖' : '🛡'} ${escHtml(a.user_name || '')} <span style="color:#6b7280;font-size:12px">(${a.handle ? '@' + escHtml(a.handle) : a.user_id})</span></div>
3508
- <div style="font-size:12px;color:#6b7280;margin-top:4px">${t('申请')}: ${roleLabel} · ${t('Region')}: ${escHtml(a.region || '—')} · ${t('Email')}: ${a.email ? '✓' : '✗'}</div>
3508
+ <div style="font-size:12px;color:#6b7280;margin-top:4px">${t('角色')}: ${roleLabel} · ${t('Region')}: ${escHtml(a.region || '—')} · ${t('Email')}: ${a.email ? '✓' : '✗'}</div>
3509
3509
  <div style="font-size:12px;margin-top:6px;display:flex;gap:12px">
3510
3510
  <span style="color:${quizOk ? '#16a34a' : '#dc2626'}">${quizOk ? '✓' : '✗'} ${t('题目')} ${a.quiz_score != null ? a.quiz_score + '%' : '—'}</span>
3511
3511
  <span style="color:${caseOk ? '#16a34a' : '#dc2626'}">${caseOk ? '✓' : '✗'} ${t('案例 review')}</span>
@@ -3970,7 +3970,7 @@ async function renderAdminQuotaApps(app) {
3970
3970
  <div style="font-weight:600">${escHtml(a.user_name)} <span style="font-size:11px;color:#6b7280">${a.email || ''}</span></div>
3971
3971
  <div style="font-size:11px;color:#9ca3af;margin-top:2px">${a.user_id} · ${fmtTime(a.applied_at)}</div>
3972
3972
  <div style="font-size:13px;margin-top:8px">
3973
- ${t('当前配额')}: <strong>${a.current_quota}</strong> → ${t('申请')}: <strong>${a.requested_quota}</strong>
3973
+ ${t('当前配额')}: <strong>${a.current_quota}</strong> → ${t('申请配额')}: <strong>${a.requested_quota}</strong>
3974
3974
  </div>
3975
3975
  ${a.reason ? `<div style="font-size:12px;color:#6b7280;margin-top:6px;padding:6px;background:#f9fafb;border-radius:4px">${escHtml(a.reason)}</div>` : ''}
3976
3976
  <div style="display:flex;gap:6px;margin-top:10px">
@@ -4392,7 +4392,7 @@ function renderWelcome(app) {
4392
4392
  <div style="min-height:100vh;background:#FFFFFF;font-family:-apple-system,BlinkMacSystemFont,'SF Pro Display','SF Pro Text','PingFang SC',sans-serif;-webkit-font-smoothing:antialiased;position:relative">
4393
4393
  <!-- Top nav — frosted glass,左返回,右语言切换(当前语言高亮) -->
4394
4394
  <div style="position:sticky;top:0;z-index:10;display:flex;justify-content:space-between;align-items:center;padding:14px 18px;background:rgba(255,255,255,0.85);-webkit-backdrop-filter:saturate(180%) blur(20px);backdrop-filter:saturate(180%) blur(20px);border-bottom:1px solid #E4E4E7">
4395
- <button class="w-nav-back" onclick="navigate('#login')" style="background:none;border:none;cursor:pointer;font-size:14px;color:#3F3F46;padding:6px 10px;font-weight:500;transition:color 0.15s;font-family:inherit">← ${T('返回首页', 'Back to Home')}</button>
4395
+ <button class="w-nav-back" onclick="navigate('#buy')" style="background:none;border:none;cursor:pointer;font-size:14px;color:#3F3F46;padding:6px 10px;font-weight:500;transition:color 0.15s;font-family:inherit">← ${T('返回首页', 'Back to Home')}</button>
4396
4396
  <span class="w-lang" style="font-size:13px;font-weight:500">
4397
4397
  <span class="${en ? 'w-lang-inactive' : 'w-lang-active'}" ${en ? `onclick="toggleLang();renderWelcome(document.getElementById('app'))"` : ''}>中文</span>
4398
4398
  <span class="w-lang-sep">/</span>
@@ -5473,7 +5473,7 @@ async function renderPromoter(app) {
5473
5473
  <div class="card" style="text-align:center;padding:10px 6px">
5474
5474
  <div style="font-size:11px;color:#6b7280">💰 ${t('累计收益')}</div>
5475
5475
  <div style="font-size:17px;font-weight:700;color:#059669;margin-top:2px">${totalEarnings.toFixed(2)}</div>
5476
- <div style="font-size:10px;color:#9ca3af">WAZ</div>
5476
+ <div style="font-size:10px;color:#9ca3af">WAZ</div>${(data.earnings?.clearing_total||0)>0?`<div style="font-size:9px;color:#9ca3af;margin-top:1px" title="${t('清算中:退货期满后到账')}">${t('清算中')} ${Number(data.earnings.clearing_total).toFixed(2)}</div>`:''}
5477
5477
  </div>
5478
5478
  <div class="card" style="text-align:center;padding:10px 6px">
5479
5479
  <div style="font-size:11px;color:#6b7280">📊 ${t('近 30 日')}</div>
@@ -5558,9 +5558,7 @@ async function renderPromoter(app) {
5558
5558
  const ratioPct = cs.clicks.raw > 0 ? Math.round((cs.clicks.unique / cs.clicks.raw) * 100) : null
5559
5559
  // 我买过的(合并进"我的产品分享"作为商品来源子列表)
5560
5560
  const bought = shareHub.bought_products || []
5561
- const buildProductImg = (images) => {
5562
- try { const arr = typeof images === 'string' ? JSON.parse(images) : (images || []); return arr[0] || '' } catch { return '' }
5563
- }
5561
+ const buildProductImg = (images) => window.productThumbSrc ? window.productThumbSrc(images) : ''
5564
5562
  const renderBoughtRow = (p) => {
5565
5563
  const img = buildProductImg(p.images)
5566
5564
  const rate = Number(p.commission_rate || 0)
@@ -5586,7 +5584,7 @@ async function renderPromoter(app) {
5586
5584
 
5587
5585
  return `
5588
5586
  <div id="bought-${orderId || p.id}" style="display:flex;gap:8px;align-items:center;padding:8px 10px;border:${isHighlight ? '2px solid #4f46e5' : '1px solid #f3f4f6'};border-radius:8px;margin-bottom:6px;background:${isHighlight ? '#eef2ff' : '#fff'}">
5589
- ${img ? `<img src="${escHtml(img)}" style="width:36px;height:36px;border-radius:6px;object-fit:cover;flex-shrink:0">` : `<div style="width:36px;height:36px;background:#f3f4f6;border-radius:6px;display:flex;align-items:center;justify-content:center;font-size:16px;flex-shrink:0">📦</div>`}
5587
+ ${img ? `<img src="${escHtml(img)}" onerror="this.outerHTML='📦'" style="width:36px;height:36px;border-radius:6px;object-fit:cover;flex-shrink:0">` : `<div style="width:36px;height:36px;background:#f3f4f6;border-radius:6px;display:flex;align-items:center;justify-content:center;font-size:16px;flex-shrink:0">📦</div>`}
5590
5588
  <div style="flex:1;min-width:0">
5591
5589
  <div style="font-size:12px;font-weight:600;color:#1f2937;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${escHtml(p.title)}</div>
5592
5590
  <div style="font-size:10px;color:#9ca3af;margin-top:1px">
@@ -6325,8 +6323,6 @@ async function renderReviewsFeed(app) {
6325
6323
  const rows = items.length === 0
6326
6324
  ? `<div style="text-align:center;padding:40px;color:#9ca3af"><div style="font-size:48px">📺</div><div style="font-size:13px;margin-top:8px">${t('暂无评测内容')}</div></div>`
6327
6325
  : items.map(s => {
6328
- let prodImg = ''
6329
- try { const imgs = typeof s.product_images === 'string' ? JSON.parse(s.product_images) : s.product_images; if (Array.isArray(imgs) && imgs[0]) prodImg = imgs[0] } catch {}
6330
6326
  return `
6331
6327
  <div class="card" style="padding:10px 12px;margin-bottom:8px;cursor:pointer" onclick="openExternalReview('${s.id}', '${escHtml(s.external_url || '').replace(/'/g, "\\'")}')">
6332
6328
  <div style="display:flex;gap:10px;align-items:center">
@@ -6334,7 +6330,7 @@ async function renderReviewsFeed(app) {
6334
6330
  <div style="flex:1;min-width:0">
6335
6331
  <div style="font-size:13px;font-weight:600;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical">${escHtml(s.title || s.external_url || '(无标题)')}</div>
6336
6332
  <div style="font-size:11px;color:#9ca3af;margin-top:2px">@${escHtml(s.owner_handle || s.owner_name || '?')} · ${s.click_count} 👁 · ❤ ${s.like_count || 0} · ${fmtTime(s.created_at)}</div>
6337
- ${s.product_title ? `<div style="font-size:11px;color:#6366f1;margin-top:2px">📦 ${escHtml(s.product_title)} · ${s.product_price} WAZ</div>` : ''}
6333
+ ${s.product_title ? `<div style="font-size:11px;color:#6366f1;margin-top:2px">📦 ${escHtml(s.product_title)} · ${window.fmtPrice(s.product_price)}</div>` : ''}
6338
6334
  </div>
6339
6335
  </div>
6340
6336
  </div>`
@@ -6448,20 +6444,16 @@ window.lookupAnchorAction = async () => {
6448
6444
  // 商品区块
6449
6445
  const productBlock = (r.product) ? (() => {
6450
6446
  const p = r.product
6451
- let img = ''
6452
- try {
6453
- const imgs = typeof p.images === 'string' ? JSON.parse(p.images) : p.images
6454
- if (Array.isArray(imgs) && imgs[0]) img = String(imgs[0])
6455
- } catch {}
6447
+ let img = window.productThumbSrc(p.images)
6456
6448
  const idx = calcIndex(p)
6457
6449
  const idxColor = idx == null ? '#9ca3af' : idx >= 8 ? '#dc2626' : idx >= 6 ? '#f59e0b' : '#6b7280'
6458
6450
  return `
6459
6451
  <div style="border:1px solid #e5e7eb;border-radius:8px;padding:12px;margin-bottom:10px">
6460
6452
  <div style="display:flex;gap:10px;margin-bottom:10px">
6461
- ${img ? `<img src="${img}" style="width:64px;height:64px;object-fit:cover;border-radius:6px;flex-shrink:0">` : `<div style="width:64px;height:64px;background:#f3f4f6;border-radius:6px;display:flex;align-items:center;justify-content:center;font-size:24px;color:#9ca3af;flex-shrink:0">📦</div>`}
6453
+ ${img ? `<img src="${escHtml(img)}" onerror="this.outerHTML='📦'" style="width:64px;height:64px;object-fit:cover;border-radius:6px;flex-shrink:0">` : `<div style="width:64px;height:64px;background:#f3f4f6;border-radius:6px;display:flex;align-items:center;justify-content:center;font-size:24px;color:#9ca3af;flex-shrink:0">📦</div>`}
6462
6454
  <div style="flex:1;min-width:0">
6463
6455
  <div style="font-size:14px;font-weight:600;color:#1f2937;line-height:1.4;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden">${escHtml(p.title)}</div>
6464
- <div style="font-size:17px;font-weight:800;color:#dc2626;margin-top:4px">${Number(p.price).toFixed(2)} <span style="font-size:11px;color:#9ca3af;font-weight:500">WAZ</span></div>
6456
+ <div style="font-size:17px;font-weight:800;color:#dc2626;margin-top:4px">${window.fmtPrice(p.price)}</div>
6465
6457
  <div style="font-size:11px;color:#6b7280;margin-top:2px">@${escHtml(p.seller_handle || p.seller_name?.slice(0,8) || '?')}</div>
6466
6458
  </div>
6467
6459
  </div>
@@ -6514,17 +6506,17 @@ async function renderGroupBuysLive(app) {
6514
6506
  ? `<div style="text-align:center;padding:40px;color:#9ca3af"><div style="font-size:48px">👥</div><div style="font-size:13px;margin-top:8px">${t('暂无进行中的团购')}</div></div>`
6515
6507
  : items.map(it => {
6516
6508
  let imageUrl = ''
6517
- try { const imgs = typeof it.images === 'string' ? JSON.parse(it.images) : it.images; if (Array.isArray(imgs) && imgs[0]) imageUrl = imgs[0] } catch {}
6509
+ imageUrl = window.productThumbSrc(it.images) || imageUrl
6518
6510
  const pct = (Number(it.discount_pct) * 100).toFixed(0)
6519
6511
  const final = (Number(it.original_price) * (1 - Number(it.discount_pct))).toFixed(2)
6520
6512
  const progress = Math.min(100, (Number(it.joined_count) / Number(it.target_count)) * 100)
6521
6513
  return `
6522
6514
  <div class="card" style="padding:12px;margin-bottom:8px;cursor:pointer;border-left:3px solid #16a34a" onclick="location.hash='#group-buy/${it.id}'">
6523
6515
  <div style="display:flex;gap:10px;align-items:center;margin-bottom:8px">
6524
- <div style="font-size:32px;flex-shrink:0">${imageUrl ? `<img src="${escHtml(imageUrl)}" style="width:48px;height:48px;border-radius:6px;object-fit:cover">` : getCategoryIcon(it.category) || '📦'}</div>
6516
+ <div style="font-size:32px;flex-shrink:0">${imageUrl ? `<img src="${escHtml(imageUrl)}" onerror="this.outerHTML='📦'" style="width:48px;height:48px;border-radius:6px;object-fit:cover">` : getCategoryIcon(it.category) || '📦'}</div>
6525
6517
  <div style="flex:1;min-width:0">
6526
6518
  <div style="font-size:13px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${escHtml(it.product_title)}</div>
6527
- <div style="font-size:12px;margin-top:2px"><span style="color:#16a34a;font-weight:700">${final}</span> WAZ <span style="font-size:10px;color:#9ca3af;text-decoration:line-through margin-left:4px">${it.original_price}</span> <span style="font-size:10px;color:#16a34a;background:#dcfce7;padding:1px 5px;border-radius:99px;margin-left:4px;font-weight:600">-${pct}%</span></div>
6519
+ <div style="font-size:12px;margin-top:2px"><span style="color:#16a34a;font-weight:700">${window.fmtPrice(final)}</span> <span style="font-size:10px;color:#9ca3af;text-decoration:line-through margin-left:4px">${it.original_price} USDC</span> <span style="font-size:10px;color:#16a34a;background:#dcfce7;padding:1px 5px;border-radius:99px;margin-left:4px;font-weight:600">-${pct}%</span></div>
6528
6520
  <div style="font-size:10px;color:#9ca3af;margin-top:2px">@${escHtml(it.seller_handle || '')} · ${t('截止')} ${fmtTime(it.ends_at)}</div>
6529
6521
  </div>
6530
6522
  </div>
@@ -6562,7 +6554,7 @@ async function renderGroupBuyDetail(app, id) {
6562
6554
  <h1 class="page-title">👥 ${escHtml(r.product_title)}</h1>
6563
6555
  <div class="card" style="background:linear-gradient(135deg,#ecfdf5,#fff);padding:14px;margin-bottom:10px">
6564
6556
  <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px">
6565
- <div><div style="font-size:24px;font-weight:800;color:#16a34a">${final} WAZ</div><div style="font-size:11px;color:#9ca3af;text-decoration:line-through">${r.original_price} WAZ</div></div>
6557
+ <div><div style="font-size:24px;font-weight:800;color:#16a34a">${window.fmtPrice(final)}</div><div style="font-size:11px;color:#9ca3af;text-decoration:line-through">${r.original_price} USDC</div></div>
6566
6558
  <div style="font-size:18px;font-weight:700;color:#16a34a;background:#dcfce7;padding:6px 12px;border-radius:8px">-${pct}%</div>
6567
6559
  </div>
6568
6560
  <div style="font-size:12px;color:#374151;margin-bottom:4px">${t('已成团')} <strong>${joined}/${r.target_count}</strong> · ${t('剩余时间')} ${remainingStr}</div>
@@ -6572,7 +6564,7 @@ async function renderGroupBuyDetail(app, id) {
6572
6564
  : expired ? `<div style="color:#9ca3af">${t('已结束,等待结算')}</div>`
6573
6565
  : isOwn ? `<div style="color:#9ca3af">${t('自己开的团,等待成团')}</div>`
6574
6566
  : isParticipant ? `<button class="btn btn-outline btn-sm" style="width:100%;color:#dc2626;border-color:#fca5a5" onclick="leaveGroupBuy('${r.id}')">${t('退出团购')}</button>`
6575
- : state.user?.role === 'buyer' ? `<button class="btn btn-primary" style="width:100%" onclick="openJoinGroupBuy('${r.id}')">+ ${t('加入团购')} (${r.original_price} WAZ ${t('预付')})</button>`
6567
+ : state.user?.role === 'buyer' ? `<button class="btn btn-primary" style="width:100%" onclick="openJoinGroupBuy('${r.id}')">+ ${t('加入团购')} (${window.fmtPrice(r.original_price)} ${t('预付')})</button>`
6576
6568
  : `<div style="color:#9ca3af">${t('仅买家可加入')}</div>`}
6577
6569
  </div>
6578
6570
  <div style="font-size:13px;font-weight:600;margin:14px 0 8px">${t('已加入')} (${joined})</div>
@@ -6647,7 +6639,7 @@ async function renderSellerFlashSales(app) {
6647
6639
  <div style="font-size:13px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:60%">${escHtml(it.product_title)}</div>
6648
6640
  <span style="font-size:10px;font-weight:600;color:#fff;background:${phaseColor};padding:2px 8px;border-radius:99px">${phaseLabel}</span>
6649
6641
  </div>
6650
- <div style="font-size:12px;color:#374151">${it.sale_price} WAZ <span style="font-size:11px;color:#9ca3af;text-decoration:line-through;margin-left:4px">${it.original_price}</span></div>
6642
+ <div style="font-size:12px;color:#374151">${window.fmtPrice(it.sale_price)} <span style="font-size:11px;color:#9ca3af;text-decoration:line-through;margin-left:4px">${it.original_price} USDC</span></div>
6651
6643
  <div style="font-size:11px;color:#9ca3af;margin-top:2px">${fmtTime(it.starts_at)} → ${fmtTime(it.ends_at)}${it.max_qty > 0 ? ' · ' + t('已售') + ' ' + it.sold_count + '/' + it.max_qty : ''}</div>
6652
6644
  ${phase === 'scheduled' ? `<button class="btn btn-outline btn-sm" style="margin-top:6px;font-size:10px;padding:3px 8px;color:#dc2626;border-color:#fca5a5" onclick="deleteFlashSale('${it.id}')">${t('取消')}</button>` : ''}
6653
6645
  </div>`
@@ -6810,7 +6802,7 @@ window.aiSuggestPrice = async () => {
6810
6802
  if (res.error) { if (hint) hint.innerHTML = '<span style="color:#dc2626">' + escHtml(res.error) + '</span>'; return }
6811
6803
  const priceInp = document.getElementById('ep-price')
6812
6804
  if (hint) {
6813
- hint.innerHTML = `💡 ${t('建议')} <strong style="color:#16a34a;cursor:pointer;text-decoration:underline" onclick="document.getElementById('ep-price').value=${res.suggested_price}">${res.suggested_price}</strong> WAZ · ${t('区间')} ${res.low_price}-${res.high_price} · ${escHtml(res.reasoning || '')}`
6805
+ hint.innerHTML = `💡 ${t('建议价')} <strong style="color:#16a34a;cursor:pointer;text-decoration:underline" onclick="document.getElementById('ep-price').value=${res.suggested_price}">${res.suggested_price}</strong> WAZ · ${t('区间')} ${res.low_price}-${res.high_price} · ${escHtml(res.reasoning || '')}`
6814
6806
  }
6815
6807
  }
6816
6808
 
@@ -6904,10 +6896,10 @@ async function renderReferral(app) {
6904
6896
 
6905
6897
  <div class="card" style="background:linear-gradient(135deg,#ecfdf5,#fff);padding:14px;margin-bottom:10px">
6906
6898
  <div style="font-size:13px;font-weight:600;margin-bottom:8px">💰 ${t('邀请收益')}</div>
6907
- <div style="display:grid;grid-template-columns:repeat(3,1fr);gap:8px;text-align:center">
6899
+ <div style="display:grid;grid-template-columns:repeat(${Number(e.clearing_waz||0)>0?4:3},1fr);gap:8px;text-align:center">
6908
6900
  <div><div style="font-size:18px;font-weight:700;color:#16a34a">${Number(e.total_waz || 0).toFixed(2)}</div><div style="font-size:10px;color:#9ca3af">${t('累计收益')}</div></div>
6909
6901
  <div><div style="font-size:18px;font-weight:700;color:#4f46e5">${Number(e.month_waz || 0).toFixed(2)}</div><div style="font-size:10px;color:#9ca3af">${t('近 30 天')}</div></div>
6910
- <div><div style="font-size:18px;font-weight:700;color:#d97706">${Number(e.today_waz || 0).toFixed(2)}</div><div style="font-size:10px;color:#9ca3af">${t('今日')}</div></div>
6902
+ <div><div style="font-size:18px;font-weight:700;color:#d97706">${Number(e.today_waz || 0).toFixed(2)}</div><div style="font-size:10px;color:#9ca3af">${t('今日')}</div></div>${Number(e.clearing_waz||0)>0?`<div><div style="font-size:18px;font-weight:700;color:#9ca3af" title="${t('清算中:退货期满后到账')}">${Number(e.clearing_waz).toFixed(2)}</div><div style="font-size:10px;color:#9ca3af">${t('清算中')}</div></div>`:''}
6911
6903
  </div>
6912
6904
  <div style="font-size:10px;color:#9ca3af;margin-top:6px;text-align:center">${e.total_records} ${t('条记录')} · WAZ</div>
6913
6905
  </div>
@@ -8065,7 +8057,7 @@ async function renderFollowFeed(app) {
8065
8057
  : items.map(it => {
8066
8058
  const meta = TYPE_META[it.type] || { icon: '·', label: it.type, color: '#6b7280' }
8067
8059
  let imageUrl = ''
8068
- try { const imgs = typeof it.images === 'string' ? JSON.parse(it.images) : it.images; if (Array.isArray(imgs) && imgs[0]) imageUrl = imgs[0] } catch {}
8060
+ imageUrl = window.productThumbSrc(it.images) || imageUrl
8069
8061
  return `
8070
8062
  <div class="card" style="padding:10px 12px;margin-bottom:8px;cursor:pointer" onclick="location.hash='#order-product/${it.product_id}'">
8071
8063
  <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:6px">
@@ -8076,10 +8068,10 @@ async function renderFollowFeed(app) {
8076
8068
  <span style="font-size:10px;color:#9ca3af">${fmtTime(it.ts)}</span>
8077
8069
  </div>
8078
8070
  <div style="display:flex;gap:10px;align-items:center">
8079
- <div style="font-size:28px;flex-shrink:0">${imageUrl ? `<img src="${escHtml(imageUrl)}" style="width:48px;height:48px;border-radius:6px;object-fit:cover">` : getCategoryIcon(it.category) || '📦'}</div>
8071
+ <div style="font-size:28px;flex-shrink:0">${imageUrl ? `<img src="${escHtml(imageUrl)}" onerror="this.outerHTML='📦'" style="width:48px;height:48px;border-radius:6px;object-fit:cover">` : getCategoryIcon(it.category) || '📦'}</div>
8080
8072
  <div style="flex:1;min-width:0">
8081
8073
  <div style="font-size:14px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${escHtml(it.title)}</div>
8082
- <div style="font-size:13px;color:#4f46e5;margin-top:2px;font-weight:600">${it.price} WAZ <span style="font-size:11px;color:#9ca3af;font-weight:400">· ${stockBadgeHtml(it)}</span></div>
8074
+ <div style="font-size:13px;color:#4f46e5;margin-top:2px;font-weight:600">${window.fmtPrice(it.price)} <span style="font-size:11px;color:#9ca3af;font-weight:400">· ${stockBadgeHtml(it)}</span></div>
8083
8075
  </div>
8084
8076
  </div>
8085
8077
  </div>`
@@ -9176,7 +9168,7 @@ function renderShareBanner(variant) {
9176
9168
  const tgtKind = ctx.target_kind
9177
9169
  const tgtLine = tgt
9178
9170
  ? (tgtKind === 'product'
9179
- ? `🎁 <strong>${escHtml(tgt.title)}</strong> · ${tgt.price} WAZ <span style="color:#9ca3af">@ ${escHtml(tgt.seller_name)}</span>`
9171
+ ? `🎁 <strong>${escHtml(tgt.title)}</strong> · ${window.fmtPrice(tgt.price)} <span style="color:#9ca3af">@ ${escHtml(tgt.seller_name)}</span>`
9180
9172
  : tgtKind === 'user'
9181
9173
  ? `👤 <strong>${escHtml(tgt.name)}</strong>${tgt.bio ? ` · ${escHtml(tgt.bio).slice(0, 40)}` : ''}`
9182
9174
  : '')
@@ -10137,7 +10129,7 @@ async function renderBuyPage(app, productId) {
10137
10129
  const bottomBar = canBuy ? `
10138
10130
  <div style="display:flex;gap:8px;align-items:center">
10139
10131
  <div style="flex:0 0 auto">
10140
- <div style="font-size:18px;font-weight:800;color:#dc2626;line-height:1">${livePrice} <span style="font-size:11px;font-weight:600">WAZ</span></div>
10132
+ <div style="font-size:18px;font-weight:800;color:#dc2626;line-height:1">${window.fmtPrice(livePrice)}</div>
10141
10133
  ${state._flashSale ? `<div style="font-size:10px;color:#9ca3af;text-decoration:line-through">${p.price}</div>` : ''}
10142
10134
  </div>
10143
10135
  <button class="btn btn-outline" onclick="addToCart('${p.id}')" style="width:auto;padding:11px 14px;font-size:13px" title="${t('加入购物车')}">🛒+</button>
@@ -10151,18 +10143,18 @@ async function renderBuyPage(app, productId) {
10151
10143
  ${originBadge}
10152
10144
  <div class="card">
10153
10145
  ${productImageGallery(p)}
10154
- <h2 style="font-size:18px;font-weight:700;margin-bottom:6px">${escHtml(p.title)}</h2>
10146
+ <h2 style="font-size:18px;font-weight:700;margin-bottom:6px">${escHtml(p.title)}</h2>${window.extLinksBarHtml ? window.extLinksBarHtml(productId) : ''}
10155
10147
  ${state._flashSale ? `
10156
10148
  <div style="background:linear-gradient(135deg,#dc2626,#f59e0b);color:#fff;border-radius:8px;padding:8px 12px;margin-bottom:8px;display:flex;justify-content:space-between;align-items:center">
10157
10149
  <div>
10158
10150
  <div style="font-size:10px;font-weight:600;opacity:0.9">⚡ ${t('限时')}</div>
10159
- <div style="font-size:18px;font-weight:800">${state._flashSale.sale_price} WAZ <span style="font-size:11px;font-weight:400;text-decoration:line-through;opacity:0.7">${p.price}</span></div>
10151
+ <div style="font-size:18px;font-weight:800">${window.fmtPrice(state._flashSale.sale_price)} <span style="font-size:11px;font-weight:400;text-decoration:line-through;opacity:0.7">${p.price} USDC</span></div>
10160
10152
  </div>
10161
10153
  <div style="text-align:right;font-size:10px;opacity:0.9">${t('截止')}<br><span style="font-size:11px;font-weight:600">${fmtTime(state._flashSale.ends_at)}</span></div>
10162
10154
  </div>` : `
10163
10155
  <div style="display:flex;align-items:baseline;gap:8px;margin-bottom:8px">
10164
10156
  <span style="font-size:22px;font-weight:800;color:#dc2626">${p.price}</span>
10165
- <span style="font-size:12px;color:#6b7280">WAZ${usdHint(p.price)}</span>
10157
+ <span style="font-size:12px;color:#6b7280" data-usdc-local="${p.price}">USDC${window._fxLocal(p.price) ? ' ≈ ' + window._fxLocal(p.price) : ''}</span>
10166
10158
  </div>`}
10167
10159
  ${Number(p.trial_quota_remaining) > 0 ? `
10168
10160
  <div style="background:linear-gradient(135deg,#faf5ff,#f3e8ff);border:1px solid #e9d5ff;border-radius:8px;padding:10px 12px;margin-bottom:10px">
@@ -10322,7 +10314,7 @@ async function renderBuyPage(app, productId) {
10322
10314
  offers: {
10323
10315
  '@type': 'Offer',
10324
10316
  price: p.price,
10325
- priceCurrency: p.currency || 'WAZ', // 不再硬编码,读表里的 currency
10317
+ priceCurrency: 'USDC', // display/pricing unit (USDC≈USD); matches the human USDC display (PR-1c)
10326
10318
  availability: (Number(p.stock) || 0) > 0 ? 'https://schema.org/InStock' : 'https://schema.org/OutOfStock',
10327
10319
  url: location.origin + '/#order-product/' + p.id,
10328
10320
  seller: { '@type': 'Organization', '@id': location.origin + '/#u/' + p.seller_id, name: p.seller_name || p.seller_handle || p.seller_id },
@@ -10366,14 +10358,14 @@ window.openBuySheet = function(productId) {
10366
10358
  const summary = hasDefault ? buildAddrString(defaultAddr) : ''
10367
10359
  const max = buyMaxFor(prod)
10368
10360
  const variants = state._variants || []
10369
- const firstImg = (() => { try { const arr = typeof prod.images === 'string' ? JSON.parse(prod.images) : (prod.images || []); return arr[0] || '' } catch { return '' } })()
10361
+ const firstImg = window.productThumbSrc(prod.images)
10370
10362
 
10371
10363
  const html = `
10372
10364
  <div style="display:flex;gap:10px;align-items:center;padding-bottom:12px;border-bottom:1px solid #f3f4f6">
10373
- ${firstImg ? `<img src="${escHtml(firstImg)}" style="width:64px;height:64px;border-radius:8px;object-fit:cover;flex-shrink:0">` : `<div style="width:64px;height:64px;background:#f3f4f6;border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:24px;flex-shrink:0">📦</div>`}
10365
+ ${firstImg ? `<img src="${escHtml(firstImg)}" onerror="this.outerHTML='📦'" style="width:64px;height:64px;border-radius:8px;object-fit:cover;flex-shrink:0">` : `<div style="width:64px;height:64px;background:#f3f4f6;border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:24px;flex-shrink:0">📦</div>`}
10374
10366
  <div style="flex:1;min-width:0">
10375
10367
  <div style="font-size:13px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${escHtml(prod.title)}</div>
10376
- <div style="font-size:18px;font-weight:800;color:#dc2626;margin-top:2px">${livePrice} <span style="font-size:11px;font-weight:600">WAZ</span></div>
10368
+ <div style="font-size:18px;font-weight:800;color:#dc2626;margin-top:2px">${window.fmtPrice(livePrice)}</div>
10377
10369
  </div>
10378
10370
  ${variants.length === 0 && max > 0 ? `
10379
10371
  <div style="display:flex;align-items:center;gap:6px;flex-shrink:0">
@@ -10471,11 +10463,12 @@ window.openBuySheet = function(productId) {
10471
10463
  </div>
10472
10464
  </details>
10473
10465
 
10466
+ ${window.dpRailSelectorHtml ? window.dpRailSelectorHtml(prod.id, prod.price) : ''}
10474
10467
  <div id="buy-msg" style="margin-top:10px"></div>
10475
10468
  <div id="tax-preview-slot" style="margin-top:8px"></div>
10476
10469
 
10477
10470
  <div style="position:sticky;bottom:0;left:0;right:0;background:#fff;padding-top:12px;margin-top:12px;border-top:1px solid #f3f4f6">
10478
- <button class="btn btn-primary" id="btn-doBuy" onclick="doBuy('${prod.id}', ${livePrice}); closeSheet()" style="width:100%;padding:14px;font-size:15px;font-weight:700">${t('确认下单')} · ${livePrice} WAZ</button>
10471
+ <button class="btn btn-primary" id="btn-doBuy" onclick="doBuy('${prod.id}', ${livePrice})" style="width:100%;padding:14px;font-size:15px;font-weight:700">${t('确认下单')} · ${window.fmtPrice(livePrice)}</button>
10479
10472
  </div>
10480
10473
  `
10481
10474
  openSheet(html, { title: t('下单') })
@@ -11246,6 +11239,7 @@ window.qtyStep = (delta, max) => {
11246
11239
  const cur = Math.floor(Number(inp.value) || 1)
11247
11240
  const next = Math.max(1, Math.min(max, cur + delta))
11248
11241
  inp.value = next
11242
+ if (window.dpRenderAccountFx) window.dpRenderAccountFx() // D3:数量变→刷新分账号 FX(按订单总额,非单价)
11249
11243
  }
11250
11244
 
11251
11245
  // B5 捐赠 chip 选择 — 高亮当前选中 + 更新提示文案
@@ -11275,6 +11269,7 @@ window.qtyClamp = (max) => {
11275
11269
  if (!Number.isFinite(v) || v < 1) v = 1
11276
11270
  if (v > max) v = max
11277
11271
  inp.value = v
11272
+ if (window.dpRenderAccountFx) window.dpRenderAccountFx() // D3:数量变→刷新分账号 FX(按订单总额,非单价)
11278
11273
  }
11279
11274
 
11280
11275
  window.doBuy = async (productId, price) => {
@@ -11315,9 +11310,11 @@ window.doBuy = async (productId, price) => {
11315
11310
  // 数量(默认 1)— 服务端会再校验 stock + MAX_PER_ORDER
11316
11311
  const qtyInp = document.getElementById('inp-qty')
11317
11312
  const quantity = qtyInp ? Math.max(1, Math.min(Number(qtyInp.max) || 1, Math.floor(Number(qtyInp.value) || 1))) : 1
11318
- const res = await POST('/orders', { product_id: productId, shipping_address: addr, notes, sponsor_hint, coupon_code, delivery_window, variant_id, expected_price, buy_insurance, anonymous_recipient, donation_pct, quantity, ...giftPayload })
11313
+ const payment_rail = window.dpSelectedRail ? window.dpSelectedRail() : 'escrow'; if (!payment_rail) { const _dp=document.querySelector('input[name="dp-rail"]:checked')?.value==='direct_p2p'; const _m=document.getElementById('buy-msg'); if(_m) _m.innerHTML = alert$('error', _dp ? t('直付暂未就绪或不可用,请稍候重试或改选支付方式') : t('请选择支付方式')); const _b=document.getElementById('dp-rail-block'); if(_b) _b.open=true; return } if (window.closeSheet) window.closeSheet() // #28 空 rail 一律拦(永不静默落 escrow);[PRELAUNCH-WAZ-SIM] 未选→空;校验通过才关 sheet(块内 return 不关→提示+展开可见,修 P2)
11314
+ const res = await POST('/orders', { product_id: productId, shipping_address: addr, notes, sponsor_hint, coupon_code, delivery_window, variant_id, expected_price, buy_insurance, anonymous_recipient, donation_pct, quantity, payment_rail, direct_receive_account_id: (payment_rail === 'direct_p2p' && window.dpSelectedAccountId) ? (window.dpSelectedAccountId() || undefined) : undefined, ...giftPayload })
11315
+ if (payment_rail === 'direct_p2p') return void (window.dpAfterCreate && window.dpAfterCreate(res))
11319
11316
  if (res.error_code === 'PRICE_CHANGED') {
11320
- document.getElementById('buy-msg').innerHTML = alert$('error', `${t('价格已变动')}:${res.old_price} → ${res.new_price} WAZ · ${t('请刷新页面')}`)
11317
+ document.getElementById('buy-msg').innerHTML = alert$('error', `${t('价格已变动')}:${window.fmtPrice(res.old_price)} → ${window.fmtPrice(res.new_price)} · ${t('请刷新页面')}`)
11321
11318
  return
11322
11319
  }
11323
11320
  if (res.error) { document.getElementById('buy-msg').innerHTML = alert$('error', res.error); return }
@@ -11339,11 +11336,11 @@ window.checkCoupon = async (productId, basePrice) => {
11339
11336
  const r = await GET(`/coupons/preview?code=${encodeURIComponent(code)}&product_id=${productId}`)
11340
11337
  if (!r.ok) {
11341
11338
  resultEl.innerHTML = `<span style="color:#dc2626">✗ ${escHtml(r.error || t('优惠码无效'))}</span>`
11342
- if (btnEl) btnEl.innerHTML = `${t('立即下单')} · ${basePrice} WAZ`
11339
+ if (btnEl) btnEl.innerHTML = `${t('立即下单')} · ${window.fmtPrice(basePrice)}`
11343
11340
  return
11344
11341
  }
11345
- resultEl.innerHTML = `<span style="color:#16a34a">✓ ${t('已优惠')} ${r.discount} WAZ · ${t('最终')} ${r.final_price} WAZ</span>`
11346
- if (btnEl) btnEl.innerHTML = `${t('立即下单')} · ${r.final_price} WAZ <span style="font-size:10px;text-decoration:line-through;opacity:0.7">${basePrice}</span>`
11342
+ resultEl.innerHTML = `<span style="color:#16a34a">✓ ${t('已优惠')} ${window.fmtPrice(r.discount)} · ${t('最终')} ${window.fmtPrice(r.final_price)}</span>`
11343
+ if (btnEl) btnEl.innerHTML = `${t('立即下单')} · ${window.fmtPrice(r.final_price)} <span style="font-size:10px;text-decoration:line-through;opacity:0.7">${basePrice} USDC</span>`
11347
11344
  }
11348
11345
 
11349
11346
  // ─── P13: 购物车页 ────────────────────────────────────────────
@@ -12441,9 +12438,10 @@ async function renderOrderDetail(app, orderId) {
12441
12438
 
12442
12439
  // 操作按钮
12443
12440
  const actions = getActions(order, isBuyer, isSeller, isLogistic)
12441
+ window._dpOrderRail = order.payment_rail // direct_p2p:路由 mark_paid/confirm 到 Passkey 门(动作在渲染后触发,早设即可);hydrate 移到最终 app.innerHTML 之后(见下)——在此调度会撞 claim-task 的 await 间隙、#dp-order-instr 尚未渲染而 if(!box) 静默 bail → 框永久"加载中"
12444
12442
 
12445
12443
  const STATUS_ZH = {
12446
- created:'待付款', paid:'待接单', accepted:'待发货', shipped:'已发货',
12444
+ created:'待付款', direct_pay_window:'直付待付款', direct_expired_unconfirmed:'直付超时未确认', paid:'待接单', accepted:'待发货', shipped:'已发货',
12447
12445
  picked_up:'已揽收', in_transit:'运输中', delivered:'待确认',
12448
12446
  confirmed:'已确认', completed:'已完成', disputed:'争议中',
12449
12447
  cancelled:'已取消', fault_seller:'卖家违约', fault_buyer:'买家违约', fault_logistics:'物流违约',
@@ -12451,7 +12449,7 @@ async function renderOrderDetail(app, orderId) {
12451
12449
 
12452
12450
  const historyHtml = (history || []).map(h => `
12453
12451
  <div class="timeline-item">
12454
- <div><span class="timeline-status">${STATUS_ZH[h.from_status] || h.from_status} → ${STATUS_ZH[h.to_status] || h.to_status}</span></div>
12452
+ <div><span class="timeline-status">${t(STATUS_ZH[h.from_status] || h.from_status)} → ${t(STATUS_ZH[h.to_status] || h.to_status)}</span></div>
12455
12453
  <div class="timeline-actor">${h.actor_name}(${h.actor_role_name || h.actor_role})</div>
12456
12454
  <div class="timeline-time">${fmtTime(h.created_at)}</div>
12457
12455
  ${h.notes ? `<div class="timeline-evidence" style="color:#6b7280">💬 ${h.notes}</div>` : ''}
@@ -12501,7 +12499,7 @@ async function renderOrderDetail(app, orderId) {
12501
12499
  </div>
12502
12500
  </div>
12503
12501
 
12504
- ${orderStageTimeline(order, history)}
12502
+ ${window.wazEscrowOrderBanner ? window.wazEscrowOrderBanner(order, isBuyer) : ''}${orderStageTimeline(order, history)}
12505
12503
  ${nextActionCard(order, isBuyer, isSeller, activeDeadline, isOverdue)}
12506
12504
 
12507
12505
  <div class="card">
@@ -12525,6 +12523,7 @@ async function renderOrderDetail(app, orderId) {
12525
12523
  ${order.content_hash_at_order ? `<div class="detail-row"><span class="detail-label">🔒 ${t('P2P 内容哈希')}</span><span class="detail-value" style="font-family:monospace;font-size:11px;word-break:break-all">${escHtml(order.content_hash_at_order)}</span></div>` : ''}
12526
12524
  ${activeDeadline?.deadline ? `<div class="detail-row"><span class="detail-label">${t('截止')}</span><span class="detail-value" style="color:${isOverdue ? '#dc2626' : '#6b7280'};font-size:12px">${fmtTime(activeDeadline.deadline)}</span></div>` : ''}
12527
12525
  </div>
12526
+ ${order.payment_rail === 'direct_p2p' && window.dpOrderDisclosureHtml ? window.dpOrderDisclosureHtml(order) : ''}
12528
12527
 
12529
12528
  ${trackingHtml}
12530
12529
  ${disputeHtml}
@@ -12591,7 +12590,7 @@ async function renderOrderDetail(app, orderId) {
12591
12590
  <div class="action-title">${t('完整状态历史')}</div>
12592
12591
  <div class="timeline">${historyHtml || `<div style="color:#6b7280;font-size:13px">${t('暂无记录')}</div>`}</div>
12593
12592
  </div>
12594
- `, 'orders')
12593
+ `, 'orders'); if (order.payment_rail === 'direct_p2p' && window.dpHydrateOrderDisclosure) window.dpHydrateOrderDisclosure(order.id) // direct_p2p:最终 DOM 已就位,#dp-order-instr 必命中(both-acked→收款说明快照;否则 D1/D2 门;非买家→错误提示)。退货/评价 widget 注入各自子容器,不动此框
12595
12594
 
12596
12595
  // Wave B-3: 退货 widget — 异步加载(仅 completed 订单可退)
12597
12596
  // 买家:有退货窗口可申请/查看;卖家:有退货申请时内联查看+处理(accept/reject/received),无申请则隐藏卡
@@ -13214,10 +13213,10 @@ window.selectVariantOption = (dim, val, basePrice) => {
13214
13213
  const stockTag = Number(match.stock) > 0
13215
13214
  ? `<span style="color:#16a34a">● ${t('有货')}</span>`
13216
13215
  : `<span style="color:#dc2626">${t('缺货')}</span>`
13217
- summary.innerHTML = `✓ ${price} WAZ · ${stockTag}`
13216
+ summary.innerHTML = `✓ ${window.fmtPrice(price)} · ${stockTag}`
13218
13217
  if (buyBtn) {
13219
13218
  buyBtn.disabled = Number(match.stock) <= 0
13220
- buyBtn.innerHTML = `${t('立即下单')} · ${price} WAZ`
13219
+ buyBtn.innerHTML = `${t('立即下单')} · ${window.fmtPrice(price)}`
13221
13220
  }
13222
13221
  }
13223
13222
 
@@ -13422,37 +13421,26 @@ function getActions(order, isBuyer, isSeller, isLogistic) {
13422
13421
  const s = order.status
13423
13422
  const isInPerson = order.fulfillment_mode === 'in_person'
13424
13423
  const isSelfFulfillSeller = isSeller && !order.logistics_id
13424
+ // 直付(direct_p2p):买家在 direct_pay_window 标记"我已付款"(Passkey 门) 或取消(不门控);其余阶段沿用通用规则
13425
+ if (order.payment_rail === 'direct_p2p' && isBuyer && s === 'direct_pay_window')
13426
+ return [{ action: 'mark_paid', label: '✅ 我已付款', style: 'success' }, { action: 'cancel', label: '取消订单', style: 'secondary' }]
13425
13427
  // M8 面交订单:买家在 paid / accepted 都可"面交完成"直接结算
13426
- if (isInPerson && isBuyer && (s === 'paid' || s === 'accepted')) {
13428
+ if (isInPerson && isBuyer && (s === 'paid' || s === 'accepted'))
13427
13429
  return [{ action: 'confirm_in_person', label: '🤝 面交完成 / 确认收货', style: 'success' }]
13428
- }
13429
13430
  if (isSeller && s === 'paid')
13430
- return [
13431
- { action: 'accept', label: '接单', style: 'success' },
13432
- { action: 'decline', label: '拒绝接单', style: 'danger', custom: 'decline' },
13433
- ]
13431
+ return [{ action: 'accept', label: '接单', style: 'success' }, { action: 'decline', label: '拒绝接单', style: 'danger', custom: 'decline' }]
13434
13432
  if (isSeller && s === 'accepted' && !isInPerson)
13435
- return [{ action: 'ship', label: '确认发货', style: 'success', logisticsSelector: true,
13436
- trackingInput: true,
13437
- evidencePlaceholder: '包装状态描述 / 货物说明(可选)' }]
13433
+ return [{ action: 'ship', label: '确认发货', style: 'success', logisticsSelector: true, trackingInput: true, evidencePlaceholder: '包装状态描述 / 货物说明(可选)' }]
13438
13434
  if (isSeller && s === 'accepted' && isInPerson)
13439
13435
  return [{ action: 'noop_in_person', label: '🤝 面交中(等待买家确认)', style: 'secondary', disabled: true }]
13440
13436
  if ((isLogistic || isSelfFulfillSeller) && s === 'shipped')
13441
- return [{ action: 'pickup', label: '✅ 确认揽收', style: 'success', needsEvidence: true,
13442
- noteLabel: '快递单号 *', evidencePlaceholder: '如:SF1234567890',
13443
- helperText: isSelfFulfillSeller ? '自履约订单:你负责回传揽收/单号,超时仍按卖家责任处理。' : '' }]
13437
+ return [{ action: 'pickup', label: '✅ 确认揽收', style: 'success', needsEvidence: true, noteLabel: '快递单号 *', evidencePlaceholder: '如:SF1234567890', helperText: isSelfFulfillSeller ? '自履约订单:你负责回传揽收/单号,超时仍按卖家责任处理。' : '' }]
13444
13438
  if ((isLogistic || isSelfFulfillSeller) && s === 'picked_up')
13445
13439
  return [{ action: 'transit', label: '🚛 开始运输', style: 'primary' }]
13446
13440
  if ((isLogistic || isSelfFulfillSeller) && s === 'in_transit')
13447
- return [{ action: 'deliver', label: '📬 确认投递', style: 'success', needsEvidence: true,
13448
- noteLabel: '投递凭证', evidencePlaceholder: '门牌照片描述 / 收件人姓名 / 签收时间',
13449
- helperText: isSelfFulfillSeller ? '自履约投递需留存签收/门牌/交付说明,买家确认后才结算。' : '' }]
13441
+ return [{ action: 'deliver', label: '📬 确认投递', style: 'success', needsEvidence: true, noteLabel: '投递凭证', evidencePlaceholder: '门牌照片描述 / 收件人姓名 / 签收时间', helperText: isSelfFulfillSeller ? '自履约投递需留存签收/门牌/交付说明,买家确认后才结算。' : '' }]
13450
13442
  if (isBuyer && s === 'delivered')
13451
- return [
13452
- { action: 'confirm', label: '确认收货', style: 'success' },
13453
- { action: 'dispute', label: '发起争议', style: 'danger', needsEvidence: true,
13454
- noteLabel: '争议理由', evidencePlaceholder: '描述问题(货不对版/货损/未收到等)' },
13455
- ]
13443
+ return [{ action: 'confirm', label: '确认收货', style: 'success' }, { action: 'dispute', label: '发起争议', style: 'danger', needsEvidence: true, noteLabel: '争议理由', evidencePlaceholder: '描述问题(货不对版/货损/未收到等)' }]
13456
13444
  return null
13457
13445
  }
13458
13446
 
@@ -13507,6 +13495,8 @@ function renderActions(orderId, actions, order, logisticsCompanies = []) {
13507
13495
 
13508
13496
  window.handleAction = async (orderId, action, idx, needsEvidence, hasLogisticsSelector) => {
13509
13497
  const msgEl = document.getElementById('action-msg')
13498
+ // 直付:买家 mark_paid/confirm/confirm_in_person 是 RISK 动作 → 走两次披露 + Passkey 门(后端硬强制)
13499
+ if (window._dpOrderRail === 'direct_p2p' && ['mark_paid', 'confirm', 'confirm_in_person'].includes(action)) return void (window.dpHandleAction && window.dpHandleAction(orderId, action))
13510
13500
 
13511
13501
  // M8 面交确认:走专用端点,跳过物流状态机
13512
13502
  if (action === 'confirm_in_person') {
@@ -13757,13 +13747,13 @@ function evidenceRequestCard(req, currentUserId) {
13757
13747
  // evidence_types 存为 JSON 数组字符串
13758
13748
  let types = []
13759
13749
  try { types = typeof req.evidence_types === 'string' ? JSON.parse(req.evidence_types) : (req.evidence_types || []) } catch(e) {}
13760
- const typeLabels = types.map(t => `${EVIDENCE_TYPE_ICONS[t] || ''}${EVIDENCE_TYPE_LABELS[t] || t}`).join(' ')
13750
+ const typeLabels = types.map(et => `${EVIDENCE_TYPE_ICONS[et] || ''}${t(EVIDENCE_TYPE_LABELS[et] || et)}`).join(' ')
13761
13751
 
13762
13752
  const submittedHtml = (req.submitted_items || []).length > 0 ? `
13763
13753
  <div style="margin-top:8px">
13764
13754
  ${req.submitted_items.map(it => `
13765
13755
  <div style="background:#f0fdf4;border-radius:6px;padding:6px 8px;margin-top:4px;font-size:12px">
13766
- <span style="color:#6b7280">${EVIDENCE_TYPE_ICONS[it.type] || ''} ${EVIDENCE_TYPE_LABELS[it.type] || escHtml(it.type)}</span>
13756
+ <span style="color:#6b7280">${EVIDENCE_TYPE_ICONS[it.type] || ''} ${EVIDENCE_TYPE_LABELS[it.type] ? t(EVIDENCE_TYPE_LABELS[it.type]) : escHtml(it.type)}</span>
13767
13757
  <div style="margin-top:2px">${escHtml(it.description)}</div>
13768
13758
  ${it.file_hash ? `<div style="margin-top:3px;font-family:monospace;font-size:10px;color:#9ca3af" title="Phase 0 模拟锚点,Phase 2 替换为 IPFS CID / 链上 TX">🔒 ${escHtml(it.file_hash)}</div>` : ''}
13769
13759
  </div>`).join('')}
@@ -13771,26 +13761,26 @@ function evidenceRequestCard(req, currentUserId) {
13771
13761
 
13772
13762
  const submitForm = isMe && req.status === 'pending' ? `
13773
13763
  <div id="er-form-${req.id}" style="margin-top:10px;background:#fffbeb;border-radius:8px;padding:10px">
13774
- <div style="font-size:12px;font-weight:600;color:#92400e;margin-bottom:6px">提交所需证据</div>
13764
+ <div style="font-size:12px;font-weight:600;color:#92400e;margin-bottom:6px">${t('提交所需证据')}</div>
13775
13765
  <div id="er-msg-${req.id}"></div>
13776
13766
  <select class="form-control" id="er-type-${req.id}" style="margin-bottom:6px;font-size:13px">
13777
- <option value="">— 选择证据类型 —</option>
13778
- ${types.map(t => `<option value="${t}">${EVIDENCE_TYPE_ICONS[t]} ${EVIDENCE_TYPE_LABELS[t] || t}</option>`).join('')}
13767
+ <option value="">${t('— 选择证据类型 —')}</option>
13768
+ ${types.map(et => `<option value="${et}">${EVIDENCE_TYPE_ICONS[et]} ${t(EVIDENCE_TYPE_LABELS[et] || et)}</option>`).join('')}
13779
13769
  </select>
13780
13770
  <textarea class="form-control" id="er-desc-${req.id}" placeholder="${t('详细描述内容(如图片描述、文字陈述、链上 TX hash 等)')}" style="margin-bottom:6px;font-size:13px"></textarea>
13781
- <input class="form-control" id="er-hash-${req.id}" placeholder="(可选)文件哈希 / IPFS CID / 链上 TX ID" style="margin-bottom:6px;font-size:12px;font-family:monospace">
13782
- <button class="btn btn-primary btn-sm" style="width:auto" onclick="handleSubmitEvidence('${req.id}','${req.dispute_id}')">提交证据</button>
13771
+ <input class="form-control" id="er-hash-${req.id}" placeholder="${t('(可选)文件哈希 / IPFS CID / 链上 TX ID')}" style="margin-bottom:6px;font-size:12px;font-family:monospace">
13772
+ <button class="btn btn-primary btn-sm" style="width:auto" onclick="handleSubmitEvidence('${req.id}','${req.dispute_id}')">${t('提交证据')}</button>
13783
13773
  </div>` : ''
13784
13774
 
13785
13775
  return `
13786
13776
  <div style="border:1px solid ${isMe && req.status === 'pending' ? '#f59e0b' : '#e5e7eb'};border-radius:8px;padding:10px;margin-top:8px;background:${isMe && req.status === 'pending' ? '#fffdf0' : '#fff'}">
13787
13777
  <div style="display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:4px">
13788
- <div style="font-size:13px;font-weight:600">${isMe ? '👤 需要你提供' : `请求 → ${req.requested_from_name || '对方'}(${req.requested_from_role || ''})`}</div>
13778
+ <div style="font-size:13px;font-weight:600">${isMe ? t('👤 需要你提供') : `${t('请求')} → ${req.requested_from_name || t('对方')}(${req.requested_from_role || ''})`}</div>
13789
13779
  ${erStatusBadge(req.status)}
13790
13780
  </div>
13791
- <div style="font-size:12px;color:#6b7280;margin-bottom:4px">类型:${typeLabels}</div>
13781
+ <div style="font-size:12px;color:#6b7280;margin-bottom:4px">${t('类型:')}${typeLabels}</div>
13792
13782
  ${req.description ? `<div style="font-size:13px;margin-bottom:4px">${req.description}</div>` : ''}
13793
- <div style="font-size:11px;color:#9ca3af">截止:${fmtTime(req.deadline)}</div>
13783
+ <div style="font-size:11px;color:#9ca3af">${t('截止:')}${fmtTime(req.deadline)}</div>
13794
13784
  ${submittedHtml}
13795
13785
  ${submitForm}
13796
13786
  </div>`
@@ -13811,7 +13801,7 @@ const TL_ROLE_META = {
13811
13801
  }
13812
13802
  const TL_TYPE_META = {
13813
13803
  open: { icon: '🚩', title: '发起争议', border: '#dc2626' },
13814
- evidence: { icon: '📎', title: '提交证据', border: '#3b82f6' },
13804
+ evidence: { icon: '📎', title: '证据提交', border: '#3b82f6' },
13815
13805
  response: { icon: '📝', title: '被告反驳', border: '#f59e0b' },
13816
13806
  evidence_request: { icon: '📋', title: '仲裁员索证', border: '#a855f7' },
13817
13807
  ruling: { icon: '🏛️', title: '仲裁裁决', border: '#6b21a8' },
@@ -13849,7 +13839,7 @@ function buildTimelineEvent(ev, dispute, user, actors) {
13849
13839
  } else if (ev.type === 'evidence') {
13850
13840
  const meta = ev.meta || {}
13851
13841
  const typeIcon = EVIDENCE_TYPE_ICONS[meta.evidence_type] || '📎'
13852
- const typeLabel = EVIDENCE_TYPE_LABELS[meta.evidence_type] || meta.evidence_type
13842
+ const typeLabel = t(EVIDENCE_TYPE_LABELS[meta.evidence_type] || meta.evidence_type)
13853
13843
  const sizeKb = meta.size ? `${(Number(meta.size)/1024).toFixed(1)} KB` : ''
13854
13844
  const blobChip = meta.has_blob ? `<a href="/api/evidence/${escHtml(meta.evidence_id)}/blob" target="_blank" style="display:inline-block;background:#eff6ff;color:#1d4ed8;padding:2px 8px;border-radius:99px;font-size:11px;text-decoration:none;margin-right:4px">📥 ${t('打开附件')}${sizeKb ? ' · '+sizeKb : ''}</a>` : ''
13855
13845
  const hashChip = meta.file_hash ? `<span style="font-family:monospace;font-size:10px;color:#9ca3af;background:#f3f4f6;padding:1px 6px;border-radius:4px" title="${escHtml(meta.file_hash)}">🔒 ${escHtml(String(meta.file_hash).slice(0,12))}…</span>` : ''
@@ -13886,7 +13876,7 @@ function buildTimelineEvent(ev, dispute, user, actors) {
13886
13876
  } else if (ev.type === 'ruling' || ev.type === 'resolved') {
13887
13877
  const meta = ev.meta || {}
13888
13878
  const rulingLabel = meta.ruling || meta.ruling_type
13889
- const rulingText = rulingLabel ? (RULING_LABELS[rulingLabel] || rulingLabel) : ''
13879
+ const rulingText = rulingLabel ? t(RULING_LABELS[rulingLabel] || rulingLabel) : ''
13890
13880
  let liability = []
13891
13881
  try { liability = typeof meta.liability_parties === 'string' ? JSON.parse(meta.liability_parties) : (meta.liability_parties || []) } catch {}
13892
13882
  bodyHtml = `
@@ -15391,7 +15381,7 @@ async function renderSeller(app) {
15391
15381
  <div style="flex:1;min-width:0;cursor:pointer" onclick="navigate('#edit-product/${p.id}')">
15392
15382
  <div style="font-size:13px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">${escHtml(p.title)}</div>
15393
15383
  <div style="font-size:11px;color:#6b7280;margin-top:2px;display:flex;gap:8px;flex-wrap:wrap">
15394
- <span><strong style="color:#374151">${p.price}</strong> WAZ</span>
15384
+ <span><strong style="color:#374151">${window.fmtPrice(p.price)}</strong></span>
15395
15385
  <span style="color:${outOfStock?'#dc2626':lowStock?'#d97706':'#6b7280'}${outOfStock||lowStock?';font-weight:600':''}">${t('库存')} ${p.stock}${outOfStock?' ⛔':lowStock?' ⚠':''}</span>
15396
15386
  ${p.completion_count > 0 ? `<span>🛒 ${p.completion_count}</span>` : ''}
15397
15387
  </div>
@@ -15424,7 +15414,7 @@ async function renderSeller(app) {
15424
15414
  <div style="display:flex;justify-content:space-between;align-items:flex-start;gap:8px">
15425
15415
  <div style="flex:1;min-width:0">
15426
15416
  <div style="font-weight:600">${escHtml(p.title)}</div>
15427
- <div style="font-size:13px;color:#6b7280;margin-top:2px">${p.price} WAZ · ${t('库存')} ${p.stock}</div>
15417
+ <div style="font-size:13px;color:#6b7280;margin-top:2px">${window.fmtPrice(p.price)} · ${t('库存')} ${p.stock}</div>
15428
15418
  ${autoDelistedRecently ? `<div style="font-size:11px;color:#92400e;margin-top:2px;padding:4px 8px;background:#fef3c7;border-radius:6px;display:inline-block">📦 ${t('售罄自动下架')} · ${new Date(p.auto_delisted_at).toLocaleDateString()}</div>` : ''}
15429
15419
  ${p.has_pending_task ? `<div style="font-size:11px;color:#d97706;margin-top:2px">⏳ ${t('链接核验中,请等待验证结果')}</div>` : ''}
15430
15420
  ${p.all_links_revoked ? `<div style="font-size:11px;color:#ef4444;margin-top:2px">❌ ${t('所有链接已失效,请先添加新链接')}</div>` : ''}
@@ -15445,7 +15435,7 @@ async function renderSeller(app) {
15445
15435
  <div style="display:flex;justify-content:space-between;align-items:flex-start;gap:8px">
15446
15436
  <div style="flex:1;min-width:0">
15447
15437
  <div style="font-weight:600;text-decoration:line-through;color:#9ca3af">${escHtml(p.title)}</div>
15448
- <div style="font-size:13px;color:#d1d5db;margin-top:2px">${p.price} WAZ</div>
15438
+ <div style="font-size:13px;color:#d1d5db;margin-top:2px">${window.fmtPrice(p.price)}</div>
15449
15439
  </div>
15450
15440
  <div style="display:flex;gap:6px;flex-shrink:0;flex-wrap:wrap;justify-content:flex-end">
15451
15441
  <span class="badge badge-red">${t('回收箱')}</span>
@@ -15516,8 +15506,10 @@ async function renderSeller(app) {
15516
15506
  ${subTabBtn('products', '📦 ' + t('商品'))}
15517
15507
  ${subTabBtn('marketing', '💎 ' + t('营销'))}
15518
15508
  ${subTabBtn('skills', '⚡ ' + t('Skill'))}
15509
+ ${subTabBtn('settings', '⚙️ ' + t('设置'))}
15519
15510
  </div>
15520
15511
  `
15512
+ const settingsSection = sellerSubTab === 'settings' ? ((window.dpSellerReadinessSection ? window.dpSellerReadinessSection() : '') + (window.dpSellerFeeSection ? window.dpSellerFeeSection() : '') + (window.dpSellerDeferralSection ? window.dpSellerDeferralSection() : '') + (window.dpSellerProductVerifySection ? window.dpSellerProductVerifySection() : '') + (window.dpSellerStoreVerifySection ? window.dpSellerStoreVerifySection() : '') + (window.dpSellerInstructionSection ? window.dpSellerInstructionSection() : '') + (window.draAccountsSection ? window.draAccountsSection() : '')) : ''
15521
15513
 
15522
15514
  // 数据中心区块(30 天聚合 — 销售曲线 / Top 商品 / 客户洞察 / 状态分布)
15523
15515
  const insightsBlock = insights ? renderInsightsBlock(insights) : ''
@@ -15600,6 +15592,7 @@ async function renderSeller(app) {
15600
15592
  ${marketingSection}
15601
15593
  ${skillsSection}
15602
15594
  ${productsSection}
15595
+ ${settingsSection}
15603
15596
 
15604
15597
  <!-- 商品分类标签页(仅 products sub-tab 下显示)-->
15605
15598
  ${sellerSubTab === 'products' ? `
@@ -15657,7 +15650,7 @@ async function renderSeller(app) {
15657
15650
  <div class="form-group"><label class="form-label">${t('规格参数')}<span style="font-size:11px;color:#9ca3af;font-weight:400;margin-left:6px">${t('每行:材质: 陶瓷')}</span></label>
15658
15651
  <textarea class="form-control" id="imp-specs" rows="3"></textarea></div>
15659
15652
  <div style="display:flex;gap:12px">
15660
- <div class="form-group" style="flex:1"><label class="form-label">${t('价格(WAZ)')}</label><input class="form-control" id="imp-price" type="number"></div>
15653
+ <div class="form-group" style="flex:1"><label class="form-label">${t('价格(USDC)')}</label><input class="form-control" id="imp-price" type="number"></div>
15661
15654
  <div class="form-group" style="flex:1"><label class="form-label">${t('库存')}</label><input class="form-control" id="imp-stock" type="number" value="1"></div>
15662
15655
  </div>
15663
15656
  <div id="imp-price-hint" style="font-size:12px;color:#059669;margin:-8px 0 12px;padding:8px 12px;background:#f0fdf4;border-radius:6px;display:none"></div>
@@ -15688,7 +15681,7 @@ async function renderSeller(app) {
15688
15681
  <div id="add-product-form" style="display:none">
15689
15682
  <div class="divider"></div>
15690
15683
  <div class="card">
15691
- <div style="font-weight:700;margin-bottom:16px">${t('上架新商品')}</div>
15684
+ ${window.createKindChooserHtml ? window.createKindChooserHtml('new') : ''}<div style="font-weight:700;margin-bottom:16px">${t('上架新商品')}</div>
15692
15685
  <div id="add-msg"></div>
15693
15686
  <div id="seo-score-bar-prd"></div> <!-- #1052 SEO 友好度评分,setupSeoScoreBar('prd') 启用 -->
15694
15687
 
@@ -15711,7 +15704,7 @@ async function renderSeller(app) {
15711
15704
  <div class="form-group"><label class="form-label">${t('商品描述')} *<span style="font-size:11px;color:#9ca3af;font-weight:400;margin-left:6px">${t('面向 Agent 检索,写核心参数而非营销语言')}</span></label>
15712
15705
  <textarea class="form-control" id="prd-desc" rows="3" placeholder="${t('材质、尺寸、颜色、适用场景...')}"></textarea></div>
15713
15706
  <div style="display:flex;gap:12px">
15714
- <div class="form-group" style="flex:1"><label class="form-label">${t('价格(WAZ)')} *</label><input class="form-control" id="prd-price" type="number" placeholder="199" oninput="updateTrialCost()"></div>
15707
+ <div class="form-group" style="flex:1"><label class="form-label">${t('价格(USDC)')} *</label><input class="form-control" id="prd-price" type="number" placeholder="199" oninput="updateTrialCost()"></div>
15715
15708
  <div class="form-group" style="flex:1"><label class="form-label">${t('库存')}</label><input class="form-control" id="prd-stock" type="number" value="1"></div>
15716
15709
  </div>
15717
15710
 
@@ -15726,7 +15719,7 @@ async function renderSeller(app) {
15726
15719
 
15727
15720
  <div class="form-group"><label class="form-label">${t('规格参数')}<span style="font-size:11px;color:#9ca3af;font-weight:400;margin-left:6px">${t('每行一个,格式:材质: 陶瓷')}</span></label>
15728
15721
  <textarea class="form-control" id="prd-specs" rows="3" placeholder="${t('材质: 陶瓷&#10;容量: 350ml&#10;颜色: 白色')}"></textarea></div>
15729
-
15722
+ <div class="form-group"><label class="form-label">${t('商品类型')}</label><select class="form-control" id="prd-type"><option value="retail">${t('零售')}</option><option value="wholesale">${t('批发')}</option><option value="service">${t('服务')}</option><option value="digital">${t('数字')}</option></select></div>
15730
15723
  <div style="display:flex;gap:12px">
15731
15724
  <div class="form-group" style="flex:1"><label class="form-label">${t('分类')}</label>
15732
15725
  <select class="form-control" id="prd-cat">
@@ -15739,7 +15732,7 @@ async function renderSeller(app) {
15739
15732
  <div class="form-group" style="flex:1"><label class="form-label">${t('备货时间(小时)')}</label>
15740
15733
  <input class="form-control" id="prd-handling" type="number" value="24" min="1"></div>
15741
15734
  </div>
15742
-
15735
+ <div style="display:flex;gap:12px"><div class="form-group" style="flex:1"><label class="form-label">${t('品牌')}</label><input class="form-control" id="prd-brand"></div><div class="form-group" style="flex:1"><label class="form-label">${t('型号')}</label><input class="form-control" id="prd-model"></div></div>
15743
15736
  <details style="margin-bottom:16px">
15744
15737
  <summary style="font-size:13px;color:#6b7280;cursor:pointer;padding:4px 0">${t('售后与物流(填写越完整,Agent 越优先选择你)')}</summary>
15745
15738
  <div style="margin-top:12px;display:flex;gap:12px">
@@ -15759,7 +15752,7 @@ async function renderSeller(app) {
15759
15752
  <label for="prd-fragile" style="font-size:13px">${t('易碎品,需特殊包装')}</label>
15760
15753
  </div>
15761
15754
  </details>
15762
-
15755
+ <div style="display:flex;gap:8px;align-items:center;margin-bottom:12px"><label class="form-label" style="margin:0;font-size:13px">${t('低库存阈值')}</label><input class="form-control" id="prd-low-stock" type="number" value="3" min="0" style="width:90px"><input type="checkbox" id="prd-auto-delist" checked style="width:16px;height:16px"><label for="prd-auto-delist" style="font-size:13px">${t('售罄自动下架')}</label></div>
15763
15756
  <!-- 2026-05-24 #981:测评免单 (Trial Review Refund) -->
15764
15757
  <details style="margin-bottom:16px" id="prd-trial-section">
15765
15758
  <summary style="font-size:13px;color:#9333ea;cursor:pointer;padding:6px 0;font-weight:600">🎁 ${t('测评免单(新品冷启动)')} <span style="font-size:11px;color:#9ca3af;font-weight:400">${t('(买家原价下单 → 笔记达 reach 阈值后系统自动退款)')}</span></summary>
@@ -15851,6 +15844,7 @@ async function renderSeller(app) {
15851
15844
  </div>
15852
15845
  </div>
15853
15846
  `, 'seller')
15847
+ if (sellerSubTab === 'settings') { window.dpHydrateInstruction && window.dpHydrateInstruction(); window.dpHydrateSellerReadiness && window.dpHydrateSellerReadiness(); window.dpHydrateSellerFee && window.dpHydrateSellerFee(); window.dpHydrateSellerDeferral && window.dpHydrateSellerDeferral(); window.dpHydrateSellerProductVerify && window.dpHydrateSellerProductVerify(); window.dpHydrateSellerStoreVerify && window.dpHydrateSellerStoreVerify(); window.draHydrateAccounts && window.draHydrateAccounts() }
15854
15848
 
15855
15849
  // 智能下单"我也要上架"跳过来时:自动切到商品 tab + 展开手工上架表单 + 预填标题
15856
15850
  // hashchange 可能多次触发 renderSeller — 用 window cache 保证每次重渲都能应用
@@ -16124,13 +16118,13 @@ window.doAddProduct = async () => {
16124
16118
  const desc = document.getElementById('prd-desc').value.trim()
16125
16119
  const price = Number(document.getElementById('prd-price').value)
16126
16120
  const stock = Number(document.getElementById('prd-stock').value) || 1
16127
- const category = document.getElementById('prd-cat').value
16121
+ const category = document.getElementById('prd-cat').value; const productType = document.getElementById('prd-type')?.value || 'retail'
16128
16122
  const msgEl = document.getElementById('add-msg')
16129
16123
 
16130
16124
  if (!title || !desc || !price) { msgEl.innerHTML = alert$('error', t('请填写商品名、描述、价格')); return }
16131
16125
 
16132
- const extTitle = (document.getElementById('prd-ext-title')?.value || '').trim()
16133
- const commissionPct = Number(document.getElementById('prd-commission')?.value || 10)
16126
+ const extTitle = (document.getElementById('prd-ext-title')?.value || '').trim(); const brand = (document.getElementById('prd-brand')?.value || '').trim(); const model = (document.getElementById('prd-model')?.value || '').trim()
16127
+ const commissionPct = Number(document.getElementById('prd-commission')?.value || 10); const lowStock = document.getElementById('prd-low-stock')?.value; const autoDelist = document.getElementById('prd-auto-delist')?.checked ? 1 : 0
16134
16128
  if (!(commissionPct >= 1 && commissionPct <= 50)) {
16135
16129
  msgEl.innerHTML = alert$('error', t('佣金比例需在 1%-50% 之间')); return
16136
16130
  }
@@ -16138,7 +16132,7 @@ window.doAddProduct = async () => {
16138
16132
  const checkedAliases = Array.from(document.querySelectorAll('#prd-alias-candidates input[type="checkbox"]:checked'))
16139
16133
  .map(cb => ({ type: cb.dataset.type, value: cb.dataset.value }))
16140
16134
  const payload = {
16141
- title, description: desc, price, stock, category,
16135
+ title, description: desc, price, stock, category, product_type: productType, brand, model,
16142
16136
  specs: parseSpecs(document.getElementById('prd-specs').value),
16143
16137
  handling_hours: Number(document.getElementById('prd-handling').value) || 24,
16144
16138
  ship_regions: document.getElementById('prd-ship-regions').value.trim() || '全国',
@@ -16146,7 +16140,7 @@ window.doAddProduct = async () => {
16146
16140
  return_days: Number(document.getElementById('prd-return').value) ?? 7,
16147
16141
  return_condition: document.getElementById('prd-return-cond').value.trim(),
16148
16142
  warranty_days: Number(document.getElementById('prd-warranty').value) ?? 0,
16149
- fragile: document.getElementById('prd-fragile').checked ? 1 : 0,
16143
+ fragile: document.getElementById('prd-fragile').checked ? 1 : 0, low_stock_threshold: lowStock === '' || lowStock == null ? undefined : Number(lowStock), auto_delist_on_zero: autoDelist,
16150
16144
  commission_rate: commissionPct / 100,
16151
16145
  ...(extTitle ? { external_title: extTitle } : {}),
16152
16146
  ...(checkedAliases.length ? { aliases: checkedAliases } : {}),
@@ -16604,7 +16598,7 @@ async function renderEditProduct(app, productId) {
16604
16598
  <div style="display:flex;gap:12px">
16605
16599
  <div class="form-group" style="flex:1">
16606
16600
  <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:4px">
16607
- <label class="form-label" style="margin:0">${t('价格(WAZ)')}</label>
16601
+ <label class="form-label" style="margin:0">${t('价格(USDC)')}</label>
16608
16602
  <button class="btn btn-outline btn-sm" style="font-size:10px;padding:3px 8px;color:#16a34a;border-color:#86efac" onclick="aiSuggestPrice()">💰 ${t('AI 建议')}</button>
16609
16603
  </div>
16610
16604
  <input class="form-control" id="ep-price" type="number" value="${p.price || ''}">
@@ -17433,7 +17427,7 @@ async function doBatchBuy(urls, addr, auto) {
17433
17427
  <span style="font-size:12px;font-weight:700;color:${recColor};white-space:nowrap">${recLabel}</span>
17434
17428
  </div>
17435
17429
  ${res.best_product ? `<div style="margin-top:6px;font-size:12px;color:#16a34a;display:flex;align-items:center;gap:6px;flex-wrap:wrap">
17436
- <span>${t('最佳替代')}:${escHtml(res.best_product.title)} · <strong>${res.best_product.price} WAZ</strong></span>
17430
+ <span>${t('最佳替代')}:${escHtml(res.best_product.title)} · <strong>${window.fmtPrice(res.best_product.price)}</strong></span>
17437
17431
  <button class="btn btn-primary btn-sm" style="width:auto;padding:3px 10px;font-size:11px" onclick="navigate('#order-product/${res.best_product.id}')">${t('下单')}</button>
17438
17432
  </div>` : ''}
17439
17433
  ${res.auto_bought ? `<div style="margin-top:6px;font-size:12px;color:#16a34a">✅ ${t('已自动下单')} <a href="#order/${res.order_id}" style="color:#16a34a;font-weight:600">${res.order_id}</a></div>` : ''}`
@@ -17648,7 +17642,7 @@ window.doAgentBuy = async () => {
17648
17642
  const bestCard = res.best_product ? `
17649
17643
  <div style="background:#f0fdf4;border:1px solid #bbf7d0;border-radius:10px;padding:12px;margin:12px 0">
17650
17644
  <div style="font-weight:600;font-size:14px;margin-bottom:4px">${res.best_product.title}</div>
17651
- <div style="font-size:18px;font-weight:700;color:#16a34a;margin-bottom:4px">${res.best_product.price} WAZ</div>
17645
+ <div style="font-size:18px;font-weight:700;color:#16a34a;margin-bottom:4px">${window.fmtPrice(res.best_product.price)}</div>
17652
17646
  <div style="font-size:12px;color:#6b7280;margin-bottom:8px">${res.best_product.agent_summary || ''}</div>
17653
17647
  ${!res.auto_bought ? `<button class="btn btn-primary btn-sm" style="width:auto" onclick="navigate('#order-product/${res.best_product.id}')">${t('查看并下单')}</button>` : ''}
17654
17648
  </div>` : ''
@@ -17668,7 +17662,7 @@ window.doAgentBuy = async () => {
17668
17662
  <div style="font-size:13px;font-weight:500">${p.url_match ? '🎯 ' : ''}${p.title}</div>
17669
17663
  <div style="font-size:11px;color:#6b7280">${p.agent_summary || ''}${p.url_match ? ` · <span style="color:#16a34a">${t('同款商品')}</span>` : ''}</div>
17670
17664
  </div>
17671
- <div style="font-weight:700;color:#1d4ed8;white-space:nowrap;margin-left:8px">${p.price} WAZ</div>
17665
+ <div style="font-weight:700;color:#1d4ed8;white-space:nowrap;margin-left:8px">${window.fmtPrice(p.price)}</div>
17672
17666
  </div>`).join('')}
17673
17667
  </div>` : ''
17674
17668
 
@@ -18349,7 +18343,7 @@ async function renderWallet(app) {
18349
18343
  <div style="background:#ecfdf5;border-radius:6px;padding:8px">
18350
18344
  <div style="font-size:9px;color:#065f46">📡 ${t('推荐分润')}</div>
18351
18345
  <div style="font-size:14px;font-weight:700;color:#059669;margin-top:2px">${((inc.commissions?.l1?.total||0)+(inc.commissions?.l2?.total||0)+(inc.commissions?.l3?.total||0)).toFixed(2)}</div>
18352
- <div style="font-size:9px;color:#9ca3af">${((inc.commissions?.l1?.count||0)+(inc.commissions?.l2?.count||0)+(inc.commissions?.l3?.count||0))} ${t('单')}</div>
18346
+ <div style="font-size:9px;color:#9ca3af">${((inc.commissions?.l1?.count||0)+(inc.commissions?.l2?.count||0)+(inc.commissions?.l3?.count||0))} ${t('单')}</div>${(inc.commission_clearing||0)>0?`<div style="font-size:8px;color:#9ca3af" title="${t('清算中:退货期满后到账')}">${t('清算中')} ${Number(inc.commission_clearing).toFixed(2)}</div>`:''}
18353
18347
  </div>
18354
18348
  <div style="background:#fef3c7;border-radius:6px;padding:8px">
18355
18349
  <div style="font-size:9px;color:#92400e">⏳ ${t('待结算')}</div>
@@ -19848,7 +19842,7 @@ async function shInjectStrip(containerId, opts = {}) {
19848
19842
  ${it.cover ? `<img src="${escAttr(it.cover)}" style="width:100%;height:100%;object-fit:cover">` : `<div style="font-size:28px">${shCatIcon(it.category)}</div>`}
19849
19843
  </div>
19850
19844
  <div style="padding:6px">
19851
- <div style="font-size:13px;font-weight:700;color:#dc2626">${Number(it.price).toFixed(0)} WAZ</div>
19845
+ <div style="font-size:13px;font-weight:700;color:#dc2626">${window.fmtPrice(it.price)}</div>
19852
19846
  <div style="font-size:10px;color:#6b7280;line-height:1.3;margin-top:2px;display:-webkit-box;-webkit-line-clamp:1;-webkit-box-orient:vertical;overflow:hidden">${escHtml(it.title)}</div>
19853
19847
  <div style="font-size:9px;color:${cond.color};margin-top:3px;font-weight:600">${t(cond.label)}</div>
19854
19848
  </div>
@@ -19874,8 +19868,8 @@ const SKM_KINDS = [
19874
19868
  function skmKindMeta(k) { return SKM_KINDS.find(x => x.id === k) || { icon: '⚙️', label: k } }
19875
19869
  function skmBillingLabel(mode, price) {
19876
19870
  if (mode === 'free') return `🆓 ${t('免费')}`
19877
- if (mode === 'per_use') return `🔁 ${Number(price).toFixed(0)} WAZ/${t('次')}`
19878
- return `💰 ${Number(price).toFixed(0)} WAZ`
19871
+ if (mode === 'per_use') return `🔁 ${window.fmtPrice(price)}/${t('次')}`
19872
+ return `💰 ${window.fmtPrice(price)}`
19879
19873
  }
19880
19874
  const SKM_STATUS = {
19881
19875
  submitted: { label: '审核中', color: '#f59e0b' },
@@ -19964,9 +19958,9 @@ async function renderSkillDetail(app, id) {
19964
19958
  } else if (l.billing_mode === 'one_time') {
19965
19959
  actionBtn = owned
19966
19960
  ? `<button class="btn btn-primary" style="width:100%;font-size:14px;padding:10px" onclick="skmReadContent('${l.id}','one_time')">${t('查看正文')}</button>`
19967
- : `<button class="btn btn-primary" style="width:100%;font-size:14px;padding:10px" onclick="skmPurchase('${l.id}')">${t('购买')} · ${Number(l.price).toFixed(0)} WAZ</button>`
19961
+ : `<button class="btn btn-primary" style="width:100%;font-size:14px;padding:10px" onclick="skmPurchase('${l.id}')">${t('购买')} · ${window.fmtPrice(l.price)}</button>`
19968
19962
  } else {
19969
- actionBtn = `<button class="btn btn-primary" style="width:100%;font-size:14px;padding:10px" onclick="skmReadContent('${l.id}','per_use')">${t('使用')} · ${Number(l.price).toFixed(0)} WAZ/${t('次')}</button>
19963
+ actionBtn = `<button class="btn btn-primary" style="width:100%;font-size:14px;padding:10px" onclick="skmReadContent('${l.id}','per_use')">${t('使用')} · ${window.fmtPrice(l.price)}/${t('次')}</button>
19970
19964
  <div style="font-size:10px;color:#9ca3af;text-align:center;margin-top:6px">${t('按次付费:每次查看正文都会扣费')}</div>`
19971
19965
  }
19972
19966
  app.innerHTML = shell(`
@@ -20209,7 +20203,7 @@ async function renderSecondhandMarket(app) {
20209
20203
  <button class="btn btn-outline btn-sm" style="font-size:12px;padding:0 10px;white-space:nowrap" onclick="document.getElementById('sh-adv').style.display=document.getElementById('sh-adv').style.display==='none'?'block':'none'">⚙ ${t('筛选')}</button>
20210
20204
  </div>
20211
20205
  <div id="sh-adv" style="display:${(_shFilters.minP || _shFilters.maxP || _shFilters.conds.size>0) ? 'block':'none'};background:#f9fafb;border:1px solid #e5e7eb;border-radius:6px;padding:10px;margin-bottom:10px">
20212
- <div style="font-size:11px;color:#6b7280;margin-bottom:6px">${t('价格区间 (WAZ)')}</div>
20206
+ <div style="font-size:11px;color:#6b7280;margin-bottom:6px">${t('价格区间 (USDC)')}</div>
20213
20207
  <div style="display:flex;gap:6px;align-items:center;margin-bottom:10px">
20214
20208
  <input id="sh-minP" class="form-control" type="number" placeholder="${t('最低')}" style="font-size:12px;flex:1" value="${escAttr(_shFilters.minP)}" oninput="_shFilters.minP=this.value">
20215
20209
  <span style="color:#9ca3af">—</span>
@@ -20283,7 +20277,7 @@ function shItemCard(it) {
20283
20277
  ${Number(it.negotiable) === 1 ? `<span style="position:absolute;top:6px;left:6px;background:rgba(0,0,0,0.7);color:#fff;font-size:9px;padding:2px 6px;border-radius:4px">${t('可议价')}</span>` : ''}
20284
20278
  </div>
20285
20279
  <div style="padding:8px">
20286
- <div style="font-size:14px;font-weight:700;color:#dc2626">${Number(it.price).toFixed(0)} WAZ</div>
20280
+ <div style="font-size:14px;font-weight:700;color:#dc2626">${window.fmtPrice(it.price)}</div>
20287
20281
  <div style="font-size:11px;color:#374151;line-height:1.3;margin:2px 0;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden">${escHtml(it.title)}</div>
20288
20282
  <div style="display:flex;align-items:center;gap:4px;font-size:9px;margin-top:4px">
20289
20283
  <span style="background:${cond.color}1a;color:${cond.color};padding:1px 5px;border-radius:3px;font-weight:600">${t(cond.label)}</span>
@@ -20303,10 +20297,12 @@ async function renderSecondhandPublish(app) {
20303
20297
  <button onclick="history.back()" style="background:none;border:none;font-size:18px;cursor:pointer;color:#6b7280">←</button>
20304
20298
  <h1 style="font-size:18px;font-weight:600;margin:0">${t('发布闲置')}</h1>
20305
20299
  </div>
20300
+ ${window.createKindChooserHtml ? window.createKindChooserHtml('secondhand') : ''}
20306
20301
 
20307
20302
  <div class="card" style="margin-bottom:12px">
20308
20303
  <div class="card-body">
20309
20304
  <div style="font-size:12px;color:#374151;font-weight:600;margin-bottom:8px">${t('图片(首张为封面,最多 9 张)')}</div>
20305
+ <div style="font-size:11px;color:#b45309;background:#fffbeb;border:1px solid #fde68a;border-radius:6px;padding:6px 8px;margin-bottom:8px;line-height:1.5">📸 ${t('请上传真实实物照,勿用网图/广告图;如经举报核实为虚假图片,商品可能被下架')}</div>
20310
20306
  <input type="file" accept="image/*" multiple id="sh-imgs-input" style="display:none" onchange="shHandleImagesChange(this)">
20311
20307
  <div id="sh-imgs-grid" style="display:grid;grid-template-columns:repeat(3,1fr);gap:6px"></div>
20312
20308
  <button class="btn btn-outline btn-sm" style="margin-top:8px;font-size:12px" onclick="document.getElementById('sh-imgs-input').click()">+ ${t('选择图片')}</button>
@@ -20496,7 +20492,7 @@ function shMineRenderItems(allItems) {
20496
20492
  <span style="position:absolute;top:6px;right:6px;background:${statusColor[it.status] || '#9ca3af'};color:#fff;font-size:9px;padding:2px 6px;border-radius:4px;font-weight:600">${t(statusLabel[it.status] || it.status)}</span>
20497
20493
  </div>
20498
20494
  <div style="padding:8px">
20499
- <div style="font-size:14px;font-weight:700;color:#dc2626">${Number(it.price).toFixed(0)} WAZ</div>
20495
+ <div style="font-size:14px;font-weight:700;color:#dc2626">${window.fmtPrice(it.price)}</div>
20500
20496
  <div style="font-size:11px;color:#374151;line-height:1.3;margin:2px 0;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden">${escHtml(it.title)}</div>
20501
20497
  <div style="display:flex;justify-content:space-between;font-size:9px;color:#9ca3af;margin-top:4px">
20502
20498
  <span>👁 ${it.view_count || 0}</span>
@@ -20539,12 +20535,11 @@ async function renderSecondhandDetail(app, id) {
20539
20535
  </div>
20540
20536
  <div style="padding:14px">
20541
20537
  <div style="display:flex;align-items:flex-start;justify-content:space-between;gap:10px;margin-bottom:8px">
20542
- <div style="font-size:24px;font-weight:700;color:#dc2626">${Number(it.price).toFixed(2)} WAZ</div>
20538
+ <div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap"><div style="font-size:24px;font-weight:700;color:#dc2626">${window.fmtPrice(it.price)}</div><span style="background:${cond.color}1a;color:${cond.color};font-size:11px;padding:3px 9px;border-radius:99px;font-weight:600;white-space:nowrap">${t(cond.label)}</span></div>
20543
20539
  ${Number(it.negotiable) === 1 ? `<span style="background:#fef3c7;color:#92400e;font-size:10px;padding:3px 8px;border-radius:99px;font-weight:600;white-space:nowrap">${t('可议价')}</span>` : ''}
20544
20540
  </div>
20545
20541
  <h2 style="font-size:16px;color:#111827;line-height:1.4;margin:0 0 10px">${escHtml(it.title)}</h2>
20546
20542
  <div style="display:flex;flex-wrap:wrap;gap:6px;font-size:10px;margin-bottom:12px">
20547
- <span style="background:${cond.color}1a;color:${cond.color};padding:3px 8px;border-radius:4px;font-weight:600">${t(cond.label)}</span>
20548
20543
  <span style="background:#f3f4f6;color:#6b7280;padding:3px 8px;border-radius:4px">${shCatIcon(it.category)} ${t(shCatLabel(it.category))}</span>
20549
20544
  <span style="background:#f3f4f6;color:#6b7280;padding:3px 8px;border-radius:4px">📦 ${t(ffLabel)}</span>
20550
20545
  ${it.region ? `<span style="background:#f3f4f6;color:#6b7280;padding:3px 8px;border-radius:4px">📍 ${escHtml(it.region)}</span>` : ''}
@@ -20558,8 +20553,9 @@ async function renderSecondhandDetail(app, id) {
20558
20553
  <div style="color:#9ca3af;font-size:10px">@${escHtml(it.seller_handle || '')} · ${t('已浏览')} ${it.view_count}</div>
20559
20554
  </div>
20560
20555
  </div>
20561
- <div style="background:#eef2ff;border-radius:8px;padding:10px;margin-bottom:14px;font-size:11px;color:#3730a3;line-height:1.5">
20562
- 🔒 ${t('WebAZ escrow 保障:付款后资金锁仓,确认收货才放给卖家;面交也可托管。协议费 1%。')}
20556
+ <div style="background:#eef2ff;border-radius:8px;padding:10px 12px;margin-bottom:14px;font-size:10.5px;color:#4338ca;line-height:1.7">
20557
+ <div style="font-weight:600;color:#3730a3;margin-bottom:4px">🛡️ ${t('WebAZ 交易保障')}<span style="font-weight:400;color:#6366f1;margin-left:6px">${t('协议费 1%')}</span></div>
20558
+ 🔒 ${t('资金托管·确认收货才放款')} · 🤝 ${t('面交也可托管')} · 🔎 ${t('成色声明可社区验证')}
20563
20559
  </div>
20564
20560
  ${isOwn ? `
20565
20561
  <button class="btn btn-outline" style="width:100%;font-size:13px;color:#6b7280;border-color:#d1d5db" onclick="doShToggleClose('${it.id}', '${it.status}')">${it.status === 'closed' ? t('重新上架') : (it.status === 'available' ? t('下架') : '')}</button>
@@ -20584,7 +20580,7 @@ async function renderSecondhandDetail(app, id) {
20584
20580
  ${o.cover ? `<img src="${escAttr(o.cover)}" style="width:100%;height:100%;object-fit:cover">` : `<div style="font-size:30px">${shCatIcon(o.category)}</div>`}
20585
20581
  </div>
20586
20582
  <div style="padding:6px">
20587
- <div style="font-size:13px;font-weight:700;color:#dc2626">${Number(o.price).toFixed(0)} WAZ</div>
20583
+ <div style="font-size:13px;font-weight:700;color:#dc2626">${window.fmtPrice(o.price)}</div>
20588
20584
  <div style="font-size:10px;color:#6b7280;line-height:1.3;margin-top:2px;display:-webkit-box;-webkit-line-clamp:1;-webkit-box-orient:vertical;overflow:hidden">${escHtml(o.title)}</div>
20589
20585
  <div style="font-size:9px;color:${ocond.color};margin-top:3px;font-weight:600">${t(ocond.label)}</div>
20590
20586
  </div>
@@ -20622,7 +20618,7 @@ window.openShBuyModal = (id, price, ff) => {
20622
20618
  const canMeet = ff === 'in_person' || ff === 'both'
20623
20619
  _openModal(`
20624
20620
  <h2 style="font-size:16px;font-weight:600;margin-bottom:6px">${t('购买二手物品')}</h2>
20625
- <div style="font-size:18px;color:#dc2626;font-weight:700;margin-bottom:14px">${price.toFixed(2)} WAZ</div>
20621
+ <div style="font-size:18px;color:#dc2626;font-weight:700;margin-bottom:14px">${window.fmtPrice(price)}</div>
20626
20622
  <div style="font-size:12px;color:#374151;font-weight:600;margin-bottom:6px">${t('履约方式')}</div>
20627
20623
  <div style="display:flex;gap:8px;margin-bottom:12px">
20628
20624
  ${canShip ? `<label style="flex:1;border:1px solid #e5e7eb;border-radius:6px;padding:8px;cursor:pointer;font-size:12px;display:flex;align-items:center;gap:6px"><input type="radio" name="sh-ff" value="shipping" ${canShip && !canMeet ? 'checked' : ''}>🚚 ${t('快递')}</label>` : ''}
@@ -21153,7 +21149,7 @@ async function renderRfqFeed(app) {
21153
21149
  const bidsLabel = it.bid_count > 0 ? `💎 ${it.bid_count} ${t('个报价')}` : t('等待商家报价')
21154
21150
  return `<div class="card" style="margin-bottom:8px;padding:12px;cursor:pointer" onclick="navigate('#rfq/${it.id}')">
21155
21151
  <div style="font-size:13px;line-height:1.5">
21156
- ✍️ ${feedActor(it.buyer_id, it.buyer_name, it.buyer_handle)} ${t('求购')} <strong>${escHtml(it.title || it.category)}</strong>
21152
+ ✍️ ${feedActor(it.buyer_id, it.buyer_name, it.buyer_handle)} ${t('想买')} <strong>${escHtml(it.title || it.category)}</strong>
21157
21153
  ${it.qty ? ` × ${it.qty}` : ''}
21158
21154
  ${it.budget ? ` · ${t('预算')} ${it.budget} WAZ` : ''}
21159
21155
  </div>
@@ -21492,7 +21488,7 @@ async function renderMessages(app, deepSub) {
21492
21488
  body = conversations.length === 0
21493
21489
  ? `<div style="text-align:center;color:#9ca3af;padding:40px 0;font-size:13px">${t('暂无会话 — 在订单 / RFQ / 跟卖详情页发起聊天')}</div>`
21494
21490
  : conversations.map(it => {
21495
- const kindLabel = it.kind === 'order' ? '📦 ' + t('订单') : it.kind === 'rfq' ? '📩 ' + t('求购') : '🏷 ' + t('问商品')
21491
+ const kindLabel = (it.kind === 'order' ? '📦 ' + t('订单') : it.kind === 'rfq' ? '📩 ' + t('求购') : '🏷 ' + t('问商品')) + (it.context_title ? ' · ' + escHtml(it.context_title) : '') + ((it.kind === 'order' || it.kind === 'rfq') && it.context_id ? ' #' + String(it.context_id).slice(0, 10) : '') // 订单/问商品显商品名 + 订单/求购显短号 → 同一对方多会话可区分
21496
21492
  return `
21497
21493
  <div class="card" style="padding:12px;margin-bottom:8px;cursor:pointer;${it.my_unread > 0 ? 'border-left:3px solid #4f46e5' : ''}" onclick="location.hash='#chat/${it.id}'">
21498
21494
  <div style="display:flex;justify-content:space-between;align-items:flex-start;gap:10px">
@@ -21694,7 +21690,7 @@ async function renderChatDetail(app, id) {
21694
21690
 
21695
21691
  // 自动滚到底
21696
21692
  const msgsEl = document.getElementById('chat-msgs')
21697
- if (msgsEl) msgsEl.scrollTop = msgsEl.scrollHeight
21693
+ if (msgsEl) { msgsEl.scrollTop = msgsEl.scrollHeight; if (window.startChatPoll) window.startChatPoll(id, messages) } // #30 进入会话后开实时轮询(传初始消息→按末条 id 判新,自清理离开即停)
21698
21694
  }
21699
21695
 
21700
21696
  function renderChatBubble(m, myId) {
@@ -22105,7 +22101,7 @@ async function renderAuctionsFeed(app) {
22105
22101
  const tag = bidCount > 0 ? `🔥 ${bidCount} ${t('次出价')}` : t('暂无出价 · 先到先得')
22106
22102
  return `<div class="card" style="margin-bottom:8px;padding:12px;cursor:pointer" onclick="navigate('#auction/${d.id}')">
22107
22103
  <div style="font-size:13px;line-height:1.5">
22108
- 💎 ${feedActor(d.seller_id, d.seller_name, d.seller_handle)} ${t('发起拍卖')} <strong>${escHtml(d.title)}</strong>
22104
+ 💎 ${feedActor(d.seller_id, d.seller_name, d.seller_handle)} ${t('发起了拍卖')} <strong>${escHtml(d.title)}</strong>
22109
22105
  </div>
22110
22106
  <div style="font-size:11px;color:#6b7280;margin-top:4px">${t('当前')} <span style="color:#dc2626;font-weight:600">${d.current_price} WAZ</span> · ${tag} · ${ts}</div>
22111
22107
  </div>`
@@ -22114,7 +22110,7 @@ async function renderAuctionsFeed(app) {
22114
22110
  <div style="font-size:13px;line-height:1.5">
22115
22111
  ♻️ ${feedActor(d.seller_id, d.seller_name, d.seller_handle)} ${t('发布二手')} <strong>${escHtml(d.title)}</strong>
22116
22112
  </div>
22117
- <div style="font-size:11px;color:#6b7280;margin-top:4px">${d.price} WAZ · ${d.condition_grade || ''} · ${ts}</div>
22113
+ <div style="font-size:11px;color:#6b7280;margin-top:4px">${window.fmtPrice(d.price)} · ${d.condition_grade || ''} · ${ts}</div>
22118
22114
  </div>`
22119
22115
  }
22120
22116
  }).join('')
@@ -22433,7 +22429,7 @@ async function renderLeaderboard(app) {
22433
22429
  const hasRating = u.rating_count > 0
22434
22430
  const ratingColor = hasRating && u.avg_rating >= 4.5 ? '#16a34a' : hasRating && u.avg_rating >= 4 ? '#d97706' : '#9ca3af'
22435
22431
  return `
22436
- <div class="card" style="padding:12px;margin-bottom:8px;display:flex;gap:10px;align-items:center;cursor:pointer" onclick="location.hash='#shop/${u.id}'">
22432
+ <div class="card" style="padding:12px;margin-bottom:8px;display:flex;gap:10px;align-items:center;${u.handle ? 'cursor:pointer' : ''}" ${u.handle ? `onclick="location.hash='#shop/${u.handle}'"` : ''}>
22437
22433
  <div style="font-size:24px;font-weight:800;color:${i<3?'#dc2626':'#9ca3af'};min-width:32px;text-align:center">${i+1}</div>
22438
22434
  <div style="flex:1;min-width:0">
22439
22435
  <div style="font-weight:600;font-size:14px">@${escHtml(u.handle || u.name?.slice(0,8) || '?')}</div>
@@ -22458,7 +22454,7 @@ async function renderLeaderboard(app) {
22458
22454
  🤖 @${escHtml(u.handle || u.name?.slice(0,8) || '?')}
22459
22455
  <span style="font-size:10px;background:${lvlColor}22;color:${lvlColor};padding:1px 7px;border-radius:99px;font-weight:600">${lvlLabel}</span>
22460
22456
  </div>
22461
- <div style="font-size:11px;color:#6b7280;margin-top:2px">${u.calls_30d || 0} ${t('次调用')} (30d) · ${u.keys_count} ${t('个 key')}</div>
22457
+ <div style="font-size:11px;color:#6b7280;margin-top:2px">${t('活跃度')}: ${t({ active: '活跃', quiet: '较少', dormant: '休眠' }[u.activity] || '休眠')}</div>
22462
22458
  </div>
22463
22459
  <div style="text-align:right">
22464
22460
  <div style="font-size:13px;font-weight:700;color:${lvlColor}">${ts}</div>
@@ -22535,7 +22531,7 @@ async function renderLeaderboard(app) {
22535
22531
  </div>
22536
22532
  </div>
22537
22533
  <div style="text-align:right">
22538
- <div style="font-size:14px;font-weight:800;color:#dc2626">${p.price} <span style="font-size:10px;color:#9ca3af">WAZ</span></div>
22534
+ <div style="font-size:14px;font-weight:800;color:#dc2626">${window.fmtPrice(p.price)}</div>
22539
22535
  <div style="font-size:10px;color:#854d0e;margin-top:1px">${pctLabel}</div>
22540
22536
  </div>
22541
22537
  </div>`
@@ -22546,14 +22542,14 @@ async function renderLeaderboard(app) {
22546
22542
  <div style="flex:1;min-width:0">
22547
22543
  <div style="font-weight:600;font-size:14px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${escHtml(p.title)}</div>
22548
22544
  <div style="font-size:11px;color:#6b7280;margin-top:2px">
22549
- @${escHtml(p.seller_handle || p.seller_name?.slice(0,8) || '?')} · ${p.price} WAZ ·
22545
+ @${escHtml(p.seller_handle || p.seller_name?.slice(0,8) || '?')} · ${window.fmtPrice(p.price)} ·
22550
22546
  🛒 ${p.completion_count || 0} · 🤝 ${p.unique_sharer_count || 0} · ❤️ ${p.total_likes || 0}
22551
22547
  </div>
22552
22548
  </div>
22553
22549
  <div style="text-align:right;font-size:12px;font-weight:700;color:#4f46e5">${Number(p.rank_score).toFixed(1)}</div>
22554
22550
  </div>
22555
22551
  `).join('') : items.map((c, i) => `
22556
- <div class="card" style="padding:12px;margin-bottom:8px;display:flex;gap:10px;align-items:center;cursor:pointer" onclick="location.hash='#u/${c.id}'">
22552
+ <div class="card" style="padding:12px;margin-bottom:8px;display:flex;gap:10px;align-items:center;${c.handle ? 'cursor:pointer' : ''}" ${c.handle ? `onclick="location.hash='#u/${c.handle}'"` : ''}>
22557
22553
  <div style="font-size:24px;font-weight:800;color:${i<3?'#dc2626':'#9ca3af'};min-width:32px;text-align:center">${i+1}</div>
22558
22554
  <div style="flex:1;min-width:0">
22559
22555
  <div style="font-weight:600;font-size:14px">@${escHtml(c.handle || c.name?.slice(0,8) || '?')}${c.region ? ' · ' + regionLabel(c.region) : ''}</div>
@@ -22608,16 +22604,16 @@ async function renderP2pBoard(app) {
22608
22604
  <div style="font-size:11px;color:#6b7280;margin-bottom:14px;line-height:1.5">${t('详情存卖家节点 · WebAZ 只锚定哈希 · 客户端验证防作弊')}</div>
22609
22605
  ${items.length === 0 ? `<div style="text-align:center;color:#9ca3af;padding:40px 0">${t('暂无 P2P 商品')}</div>` : items.map(it => {
22610
22606
  let thumb = ''
22611
- try { const imgs = JSON.parse(it.thumbnail_json || '[]'); thumb = imgs[0] || '' } catch {}
22607
+ thumb = window.productThumbSrc(it.thumbnail_json) || thumb
22612
22608
  return `
22613
22609
  <div class="card" style="padding:12px;margin-bottom:10px;cursor:pointer;display:flex;gap:10px" onclick="location.hash='#p2p-shop/${it.id}'">
22614
- ${thumb ? `<img src="${escHtml(thumb)}" style="width:80px;height:80px;object-fit:cover;border-radius:6px;flex-shrink:0">` : `<div style="width:80px;height:80px;background:#f3f4f6;border-radius:6px;display:flex;align-items:center;justify-content:center;font-size:32px;flex-shrink:0">🌐</div>`}
22610
+ ${thumb ? `<img src="${escHtml(thumb)}" onerror="this.outerHTML='🌐'" style="width:80px;height:80px;object-fit:cover;border-radius:6px;flex-shrink:0">` : `<div style="width:80px;height:80px;background:#f3f4f6;border-radius:6px;display:flex;align-items:center;justify-content:center;font-size:32px;flex-shrink:0">🌐</div>`}
22615
22611
  <div style="flex:1;min-width:0">
22616
22612
  <div style="font-weight:600;font-size:14px;margin-bottom:4px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${escHtml(it.title)}</div>
22617
22613
  <div style="font-size:11px;color:#6b7280;margin-bottom:3px">@${escHtml(it.seller_handle || it.seller_id.slice(0,8))} · ${it.region}</div>
22618
22614
  <div style="display:flex;justify-content:space-between;align-items:center">
22619
22615
  <span style="font-size:10px;font-family:monospace;color:#9ca3af">🔒 ${it.content_hash.slice(0,12)}…</span>
22620
- <span style="color:#dc2626;font-weight:700;font-size:14px">${it.price} <span style="font-size:10px;color:#9ca3af;font-weight:500">WAZ</span></span>
22616
+ <span style="color:#dc2626;font-weight:700;font-size:14px">${window.fmtPrice(it.price)}</span>
22621
22617
  </div>
22622
22618
  </div>
22623
22619
  </div>`
@@ -22712,7 +22708,7 @@ async function renderP2pDetail(app, id) {
22712
22708
  if (r?.error) { document.getElementById('p2p-detail').innerHTML = alert$('error', r.error); return }
22713
22709
  const p = r.product
22714
22710
  let thumb = ''
22715
- try { const imgs = JSON.parse(p.thumbnail_json || '[]'); thumb = imgs[0] || '' } catch {}
22711
+ thumb = window.productThumbSrc(p.thumbnail_json) || thumb
22716
22712
 
22717
22713
  // 客户端尝试从 peer_endpoint 拉详情并验签
22718
22714
  let verifyStatus = `<span style="color:#9ca3af">${t('未尝试拉取')}</span>`
@@ -22746,11 +22742,11 @@ async function renderP2pDetail(app, id) {
22746
22742
  document.getElementById('p2p-detail').innerHTML = `
22747
22743
  <button class="btn btn-sm" onclick="history.back()" style="background:#f3f4f6;margin-bottom:14px">←</button>
22748
22744
  <div style="display:flex;gap:14px;margin-bottom:14px">
22749
- ${thumb ? `<img src="${escHtml(thumb)}" style="width:120px;height:120px;object-fit:cover;border-radius:8px;flex-shrink:0">` : ''}
22745
+ ${thumb ? `<img src="${escHtml(thumb)}" onerror="this.outerHTML='🌐'" style="width:120px;height:120px;object-fit:cover;border-radius:8px;flex-shrink:0">` : ''}
22750
22746
  <div style="flex:1">
22751
22747
  <h2 style="font-size:18px;font-weight:700;margin:0 0 4px">${escHtml(p.title)}</h2>
22752
22748
  <div style="font-size:12px;color:#6b7280;margin-bottom:6px">@${escHtml(p.seller_handle || p.seller_id.slice(0,8))} · ${p.region}</div>
22753
- <div style="color:#dc2626;font-weight:700;font-size:22px;margin-bottom:6px">${p.price} <span style="font-size:11px;color:#9ca3af;font-weight:500">WAZ</span></div>
22749
+ <div style="color:#dc2626;font-weight:700;font-size:22px;margin-bottom:6px">${window.fmtPrice(p.price)}</div>
22754
22750
  <div style="font-size:11px;color:#6b7280">${t('库存')} ${p.stock}</div>
22755
22751
  </div>
22756
22752
  </div>
@@ -22769,7 +22765,7 @@ async function renderP2pDetail(app, id) {
22769
22765
  </div>` : `
22770
22766
  <div class="card" style="padding:14px;margin-bottom:12px;background:#fafafa;color:#9ca3af;text-align:center">${t('完整详情未能从节点获取 — 仅展示 WebAZ 上的最小信息')}</div>`}
22771
22767
 
22772
- ${state.user?.role === 'buyer' && p.stock > 0 ? `<button class="btn btn-primary" style="width:100%" onclick="location.hash='#order-product/${p.id}'">${t('购买')} ${p.price} WAZ</button>` : ''}
22768
+ ${state.user?.role === 'buyer' && p.stock > 0 ? `<button class="btn btn-primary" style="width:100%" onclick="location.hash='#order-product/${p.id}'">${t('购买')} ${window.fmtPrice(p.price)}</button>` : ''}
22773
22769
  `
22774
22770
  }
22775
22771
 
@@ -22830,7 +22826,7 @@ function renderPriceHistoryHtml(r) {
22830
22826
  <div style="flex:1;background:#fafafa;padding:8px;border-radius:6px;text-align:center">
22831
22827
  <div style="font-size:10px;color:#6b7280;margin-bottom:2px">${label}</div>
22832
22828
  <div style="font-size:13px;font-weight:600">${fmtPrice(data.avg)} <span style="font-size:9px;color:#9ca3af">${t('均价')}</span></div>
22833
- <div style="font-size:10px;color:#6b7280;margin-top:2px">${data.sales}${t('单')} · ${fmtPrice(data.volume)} WAZ</div>
22829
+ <div style="font-size:10px;color:#6b7280;margin-top:2px">${data.sales}${t('单')} · ${fmtPrice(data.volume)} USDC</div>
22834
22830
  </div>`
22835
22831
 
22836
22832
  return `
@@ -22840,7 +22836,7 @@ function renderPriceHistoryHtml(r) {
22840
22836
  ${windowCard(t('近 90 天'), w.d90)}
22841
22837
  ${windowCard(t('全部'), w.lifetime)}
22842
22838
  </div>
22843
- ${r.category_avg_30d != null ? `<div style="font-size:11px;color:#6b7280;margin-bottom:10px">${t('同类目 30 天均价')}:<strong style="color:#374151">${fmtPrice(r.category_avg_30d)} WAZ</strong>${r.current_price && r.category_avg_30d ? ` · ${t('当前')}${r.current_price > r.category_avg_30d ? '↑' : '↓'}${(Math.abs((r.current_price - r.category_avg_30d) / r.category_avg_30d) * 100).toFixed(0)}%` : ''}</div>` :
22839
+ ${r.category_avg_30d != null ? `<div style="font-size:11px;color:#6b7280;margin-bottom:10px">${t('同类目 30 天均价')}:<strong style="color:#374151">${fmtPrice(r.category_avg_30d)} USDC</strong>${r.current_price && r.category_avg_30d ? ` · ${t('当前')}${r.current_price > r.category_avg_30d ? '↑' : '↓'}${(Math.abs((r.current_price - r.category_avg_30d) / r.category_avg_30d) * 100).toFixed(0)}%` : ''}</div>` :
22844
22840
  `<div style="font-size:11px;color:#9ca3af;margin-bottom:10px;font-style:italic">${t('未设置具体类目 — 无同类对比(卖家可在商品编辑页选择类目)')}</div>`}
22845
22841
  <div style="font-size:11px;color:#374151;margin-bottom:6px;font-weight:600">${t('价位分布')}(${t('90 天,按成交单数')})</div>
22846
22842
  ${bucketsHtml}
@@ -22921,7 +22917,7 @@ async function renderAdminProtocol(app) {
22921
22917
  ${adminLinkCard('🛠️', t('任务建议收件箱'), t('陌生人 / agent 提交的共建任务建议;审阅 → 转正式任务'), '#admin/task-proposals')}
22922
22918
  ${((state.user && state.user.admin_type || 'root') === 'root') ? adminLinkCard('🎟️', t('建任务额度审核'), t('非根管理员的建任务扩容申请;批准 = 限时计数授权(仅 root)'), '#admin/quota-requests') : ''}
22923
22919
  ${adminLinkCard('🔗', t('关联个人贡献账号'), t('把本管理席位的协调贡献归属到你的真实个人账号(需对方确认 + root 审批)'), '#me/operator-claims')}
22924
- ${((state.user && state.user.admin_type || 'root') === 'root') ? adminLinkCard('🪪', t('操作席位关联审批'), t('管理席位→个人贡献账号的关联申请;确认 + 审批 / 撤销(仅 root)'), '#admin/operator-claims') : ''}
22920
+ ${((state.user && state.user.admin_type || 'root') === 'root') ? adminLinkCard('🪪', t('操作席位关联审批'), t('管理席位→个人贡献账号的关联申请;确认 + 审批 / 撤销(仅 root)'), '#admin/operator-claims') : ''}${((state.user && state.user.admin_type || 'root') === 'root') ? adminLinkCard('💳', t('Direct Pay 商户运营'), t('直付:合规录入 / 缓交 / 商品·店铺验证 / 平台服务费预充值 / 上线控制(仅 root)'), '#admin/dp-ops') : ''}
22925
22921
  </div>
22926
22922
  `, 'admin-protocol')
22927
22923
  }
@@ -22971,7 +22967,7 @@ async function renderAdminWishReports(app) {
22971
22967
  }
22972
22968
  window.setWrStatus = (s) => { state._wrStatus = s; renderAdminWishReports(document.getElementById('app')) }
22973
22969
  window.adminWrAction = async (id, action) => {
22974
- if (!confirm(t('确认') + (action === 'dismiss' ? t('驳回') : t('下架')) + t('?'))) return
22970
+ if (!confirm(action === 'dismiss' ? t('确认驳回?') : t('确认下架?'))) return
22975
22971
  const r = await PATCH('/admin/wish-reports/' + id, { action })
22976
22972
  if (r?.error) return alert(r.error)
22977
22973
  renderAdminWishReports(document.getElementById('app'))
@@ -24493,7 +24489,7 @@ async function renderNoteLanding(app, noteId) {
24493
24489
  <div style="flex:1;min-width:0">
24494
24490
  <div style="font-size:10px;color:#9a3412;font-weight:600;margin-bottom:2px">📦 ${t('笔记关联商品')}</div>
24495
24491
  <div style="font-weight:700;font-size:13px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${escHtml(product.title || '')}</div>
24496
- <div style="font-size:15px;font-weight:800;color:#dc2626;margin-top:2px">${Number(product.price || 0).toFixed(2)} WAZ</div>
24492
+ <div style="font-size:15px;font-weight:800;color:#dc2626;margin-top:2px">${window.fmtPrice(product.price || 0)}</div>
24497
24493
  </div>
24498
24494
  <button class="btn btn-primary btn-sm" style="white-space:nowrap" onclick="event.stopPropagation();navigate('#order-product/${product.id}')">${t('去购买')}</button>
24499
24495
  </div>` : ''
@@ -25182,7 +25178,7 @@ async function renderSharesFeed(app, sort) {
25182
25178
  <span>❤️ ${n.stats.likes || 0} · 🔗 ${n.stats.clicks || 0}</span>
25183
25179
  </div>
25184
25180
  ${noteAuthBadges(n.badges, 'sm') ? `<div style="margin-top:4px">${noteAuthBadges(n.badges, 'sm')}</div>` : ''}
25185
- ${n.product ? `<div style="font-size:11px;color:#92400e;margin-top:4px;background:#fef3c7;padding:3px 8px;border-radius:4px;display:inline-block">📦 ${escHtml((n.product.title || '').slice(0, 20))} · ${Number(n.product.price).toFixed(0)} WAZ</div>` : ''}
25181
+ ${n.product ? `<div style="font-size:11px;color:#92400e;margin-top:4px;background:#fef3c7;padding:3px 8px;border-radius:4px;display:inline-block">📦 ${escHtml((n.product.title || '').slice(0, 20))} · ${window.fmtPrice(n.product.price)}</div>` : ''}
25186
25182
  </div>
25187
25183
  </div>
25188
25184
  </div>
@@ -25683,7 +25679,7 @@ async function renderBuyerTrials(app) {
25683
25679
  <div style="display:flex;justify-content:space-between;align-items:flex-start;gap:10px;margin-bottom:8px">
25684
25680
  <div style="flex:1;min-width:0">
25685
25681
  <div style="font-weight:600;font-size:14px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${escHtml(c.product_title || '商品')}</div>
25686
- <div style="font-size:11px;color:#6b7280;margin-top:2px">${c.product_price} WAZ · ${fmtTime(c.claimed_at)}</div>
25682
+ <div style="font-size:11px;color:#6b7280;margin-top:2px">${window.fmtPrice(c.product_price)} · ${fmtTime(c.claimed_at)}</div>
25687
25683
  </div>
25688
25684
  <span style="flex-shrink:0;font-size:10px;background:${st.bg};color:${st.color};padding:2px 8px;border-radius:99px;font-weight:600">${st.label}</span>
25689
25685
  </div>
@@ -25724,7 +25720,7 @@ async function renderSellerTrials(app) {
25724
25720
  <div style="display:flex;justify-content:space-between;align-items:flex-start;gap:10px;margin-bottom:8px">
25725
25721
  <div style="flex:1;min-width:0">
25726
25722
  <div style="font-weight:600;font-size:14px">${escHtml(c.product_title || '商品')}</div>
25727
- <div style="font-size:11px;color:#6b7280;margin-top:2px">${c.product_price} WAZ · ${t('阈值')} ${c.reach_threshold} · ${t('启动')} ${fmtTime(c.created_at)}</div>
25723
+ <div style="font-size:11px;color:#6b7280;margin-top:2px">${window.fmtPrice(c.product_price)} · ${t('阈值')} ${c.reach_threshold} · ${t('启动')} ${fmtTime(c.created_at)}</div>
25728
25724
  </div>
25729
25725
  <span style="flex-shrink:0;font-size:10px;background:${c.status==='active'?'#dcfce7':'#f3f4f6'};color:${c.status==='active'?'#166534':'#6b7280'};padding:2px 8px;border-radius:99px;font-weight:600">${c.status==='active' ? t('进行中') : t('已关闭')}</span>
25730
25726
  </div>