@seasonkoh/webaz 0.1.28 → 0.1.30
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.
- package/README.md +3 -2
- package/README.zh-CN.md +7 -6
- package/dist/bond-refund-blockers.js +50 -0
- package/dist/bond-slash.js +100 -0
- package/dist/bond-terms.js +31 -0
- package/dist/currency.js +16 -0
- package/dist/deposit-rails.js +52 -0
- package/dist/direct-pay-aml-monitor.js +67 -0
- package/dist/direct-pay-aml-review.js +40 -0
- package/dist/direct-pay-base-bond-entry.js +5 -0
- package/dist/direct-pay-bond-rail-clearance.js +99 -0
- package/dist/direct-pay-cancel-refund.js +160 -0
- package/dist/direct-pay-compliance-ingress.js +145 -0
- package/dist/direct-pay-controls.js +136 -0
- package/dist/direct-pay-create.js +231 -0
- package/dist/direct-pay-deferral-quota.js +43 -0
- package/dist/direct-pay-disclosures.js +50 -0
- package/dist/direct-pay-eligibility.js +46 -0
- package/dist/direct-pay-fee-ar.js +241 -0
- package/dist/direct-pay-fee-prepay-request.js +80 -0
- package/dist/direct-pay-launch-readiness.js +108 -0
- package/dist/direct-pay-launch-summary.js +68 -0
- package/dist/direct-pay-ledger.js +94 -0
- package/dist/direct-pay-returns.js +104 -0
- package/dist/direct-pay-stock.js +9 -0
- package/dist/direct-receive-account-qr.js +77 -0
- package/dist/direct-receive-accounts.js +82 -0
- package/dist/direct-receive-deferral.js +171 -0
- package/dist/direct-receive-deposits.js +365 -0
- package/dist/direct-receive-payment-instruction.js +26 -0
- package/dist/free-shipping.js +37 -0
- package/dist/fx-rates.js +78 -0
- package/dist/layer0-foundation/L0-1-database/schema.js +777 -1
- package/dist/layer0-foundation/L0-2-state-machine/engine.js +1 -0
- package/dist/layer0-foundation/L0-2-state-machine/genuine-sale.js +15 -5
- package/dist/layer0-foundation/L0-2-state-machine/transitions.js +114 -0
- package/dist/layer0-foundation/L0-5-manifest/manifest.js +1 -1
- package/dist/layer1-agent/L1-1-mcp-server/cli.js +64 -0
- package/dist/layer1-agent/L1-1-mcp-server/network-mode.js +11 -0
- package/dist/layer1-agent/L1-1-mcp-server/server.js +99 -32
- package/dist/layer2-business/L2-6-notifications/notification-engine.js +110 -41
- package/dist/layer3-trust/L3-1-dispute-engine/dispute-engine.js +133 -31
- package/dist/layer3-trust/L3-1-dispute-engine/evidence-storage.js +16 -4
- package/dist/layer3-trust/L3-1-dispute-engine/mutual-cancel.js +156 -0
- package/dist/layer4-economics/L4-4-skill-market/skill-engine.js +4 -4
- package/dist/ledger.js +12 -3
- package/dist/mcp.js +23 -4
- package/dist/merchant-bond-domain.js +64 -0
- package/dist/merchant-bond-exposure.js +78 -0
- package/dist/merchant-bond-watcher.js +26 -0
- package/dist/payment-rails.js +29 -0
- package/dist/platform-receive-accounts.js +94 -0
- package/dist/product-verification.js +93 -0
- package/dist/pwa/acp-feed.js +3 -2
- package/dist/pwa/arbitration-read-admin.js +37 -0
- package/dist/pwa/arbitrator-lifecycle.js +100 -0
- package/dist/pwa/contract-fingerprint.js +18 -0
- package/dist/pwa/direct-pay-guards.js +20 -0
- package/dist/pwa/direct-pay-order-redaction.js +42 -0
- package/dist/pwa/endpoint-actions.js +3 -0
- package/dist/pwa/human-presence.js +2 -6
- package/dist/pwa/public/app-account.js +9 -9
- package/dist/pwa/public/app-admin-disputes.js +55 -0
- package/dist/pwa/public/app-agent-appeal.js +90 -0
- package/dist/pwa/public/app-agent-approvals.js +93 -0
- package/dist/pwa/public/app-agent-pair.js +127 -0
- package/dist/pwa/public/app-ai.js +10 -10
- package/dist/pwa/public/app-arbitrator-admin.js +87 -0
- package/dist/pwa/public/app-arbitrator-entry.js +9 -0
- package/dist/pwa/public/app-bond-deferral-ui.js +9 -0
- package/dist/pwa/public/app-bond-refund-ui.js +66 -0
- package/dist/pwa/public/app-bond-slash-ui.js +74 -0
- package/dist/pwa/public/app-bond-terms-ui.js +23 -0
- package/dist/pwa/public/app-bond-ui.js +108 -0
- package/dist/pwa/public/app-chat-poll.js +29 -0
- package/dist/pwa/public/app-contribution-hub.js +23 -0
- package/dist/pwa/public/app-create-kinds.js +17 -0
- package/dist/pwa/public/app-direct-pay-accounts.js +141 -0
- package/dist/pwa/public/app-direct-pay-buyer.js +72 -0
- package/dist/pwa/public/app-direct-pay-cancel-refund.js +72 -0
- package/dist/pwa/public/app-direct-pay-compliance.js +67 -0
- package/dist/pwa/public/app-direct-pay-copy.js +12 -0
- package/dist/pwa/public/app-direct-pay-deferral-admin.js +72 -0
- package/dist/pwa/public/app-direct-pay-deferral.js +61 -0
- package/dist/pwa/public/app-direct-pay-fee-center.js +33 -0
- package/dist/pwa/public/app-direct-pay-fee-history.js +39 -0
- package/dist/pwa/public/app-direct-pay-fee-ops.js +112 -0
- package/dist/pwa/public/app-direct-pay-fee-request.js +81 -0
- package/dist/pwa/public/app-direct-pay-fee-requests-admin.js +70 -0
- package/dist/pwa/public/app-direct-pay-memo.js +14 -0
- package/dist/pwa/public/app-direct-pay-negotiation.js +35 -0
- package/dist/pwa/public/app-direct-pay-pay.js +15 -0
- package/dist/pwa/public/app-direct-pay-paymodal.js +32 -0
- package/dist/pwa/public/app-direct-pay-product-verify.js +103 -0
- package/dist/pwa/public/app-direct-pay-readiness.js +38 -0
- package/dist/pwa/public/app-direct-pay-reconcile.js +19 -0
- package/dist/pwa/public/app-direct-pay-returns.js +56 -0
- package/dist/pwa/public/app-direct-pay-reveal.js +82 -0
- package/dist/pwa/public/app-direct-pay-sales-report.js +70 -0
- package/dist/pwa/public/app-direct-pay-store-verify.js +100 -0
- package/dist/pwa/public/app-direct-pay.js +226 -0
- package/dist/pwa/public/app-discover.js +20 -20
- package/dist/pwa/public/app-dispute-close-ui.js +38 -0
- package/dist/pwa/public/app-external-links.js +32 -0
- package/dist/pwa/public/app-free-shipping-ui.js +29 -0
- package/dist/pwa/public/app-gmv-rail-split.js +12 -0
- package/dist/pwa/public/app-listing-commerce-ui.js +72 -0
- package/dist/pwa/public/app-listings.js +4 -4
- package/dist/pwa/public/app-mutual-cancel.js +54 -0
- package/dist/pwa/public/app-notif-templates-orders.js +43 -0
- package/dist/pwa/public/app-notif-templates.js +22 -0
- package/dist/pwa/public/app-order-accept-ui.js +158 -0
- package/dist/pwa/public/app-order-errors.js +50 -0
- package/dist/pwa/public/app-order-labels.js +19 -0
- package/dist/pwa/public/app-order-rail-filter.js +26 -0
- package/dist/pwa/public/app-platform-receive-accounts.js +140 -0
- package/dist/pwa/public/app-poll-governor.js +22 -0
- package/dist/pwa/public/app-prelaunch-waz.js +39 -0
- package/dist/pwa/public/app-price.js +55 -0
- package/dist/pwa/public/app-product-media.js +15 -0
- package/dist/pwa/public/app-profile.js +10 -10
- package/dist/pwa/public/app-purchase-terms-ui.js +68 -0
- package/dist/pwa/public/app-sale-regions-ui.js +38 -0
- package/dist/pwa/public/app-seller.js +5 -5
- package/dist/pwa/public/app-shop.js +19 -19
- package/dist/pwa/public/app-trade-tax-ui.js +33 -0
- package/dist/pwa/public/app.js +264 -295
- package/dist/pwa/public/i18n.js +1068 -197
- package/dist/pwa/public/index.html +58 -0
- package/dist/pwa/public/openapi.json +1845 -653
- package/dist/pwa/public/style.css +3 -0
- package/dist/pwa/routes/admin-analytics.js +4 -2
- package/dist/pwa/routes/admin-direct-receive-deposits.js +533 -0
- package/dist/pwa/routes/admin-protocol-params.js +16 -0
- package/dist/pwa/routes/admin-reports.js +31 -5
- package/dist/pwa/routes/agent-governance.js +1 -1
- package/dist/pwa/routes/agent-grants.js +253 -32
- package/dist/pwa/routes/analytics.js +2 -0
- package/dist/pwa/routes/arbitrator.js +67 -14
- package/dist/pwa/routes/bond-seller.js +162 -0
- package/dist/pwa/routes/buyer-feeds.js +2 -1
- package/dist/pwa/routes/chat.js +6 -1
- package/dist/pwa/routes/dashboards.js +2 -1
- package/dist/pwa/routes/direct-pay-availability.js +196 -0
- package/dist/pwa/routes/direct-pay-cancel-refund.js +111 -0
- package/dist/pwa/routes/direct-pay-disclosure-acks.js +79 -0
- package/dist/pwa/routes/direct-pay-pending-accept.js +222 -0
- package/dist/pwa/routes/direct-pay-returns.js +74 -0
- package/dist/pwa/routes/direct-pay-timeouts.js +248 -0
- package/dist/pwa/routes/direct-receive-accounts.js +155 -0
- package/dist/pwa/routes/direct-receive-payment-instructions.js +45 -0
- package/dist/pwa/routes/disputes-read.js +20 -6
- package/dist/pwa/routes/disputes-write.js +91 -33
- package/dist/pwa/routes/external-anchors.js +4 -2
- package/dist/pwa/routes/fee-prepay-requests.js +66 -0
- package/dist/pwa/routes/fx.js +12 -0
- package/dist/pwa/routes/governance-onboarding.js +46 -8
- package/dist/pwa/routes/leaderboard.js +47 -9
- package/dist/pwa/routes/listings.js +2 -2
- package/dist/pwa/routes/logistics.js +6 -2
- package/dist/pwa/routes/manifests.js +38 -0
- package/dist/pwa/routes/me-data.js +5 -2
- package/dist/pwa/routes/mutual-cancel.js +62 -0
- package/dist/pwa/routes/orders-action.js +297 -16
- package/dist/pwa/routes/orders-create.js +21 -6
- package/dist/pwa/routes/orders-read.js +106 -8
- package/dist/pwa/routes/p2p-products.js +2 -2
- package/dist/pwa/routes/platform-receive-accounts.js +111 -0
- package/dist/pwa/routes/products-create.js +15 -4
- package/dist/pwa/routes/products-links.js +34 -0
- package/dist/pwa/routes/products-list.js +2 -1
- package/dist/pwa/routes/products-update.js +37 -3
- package/dist/pwa/routes/profile-identity.js +4 -2
- package/dist/pwa/routes/promoter.js +3 -0
- package/dist/pwa/routes/referral.js +4 -0
- package/dist/pwa/routes/returns.js +60 -4
- package/dist/pwa/routes/rewards-apply.js +10 -5
- package/dist/pwa/routes/rewards-clearing-mature.js +96 -0
- package/dist/pwa/routes/rewards-escrow-expire.js +6 -2
- package/dist/pwa/routes/seller-directpay-report.js +110 -0
- package/dist/pwa/routes/seller-quota.js +5 -1
- package/dist/pwa/routes/shipping-templates.js +219 -0
- package/dist/pwa/routes/shops.js +2 -1
- package/dist/pwa/routes/snf.js +4 -1
- package/dist/pwa/routes/url-claim.js +2 -2
- package/dist/pwa/routes/users-public.js +8 -0
- package/dist/pwa/routes/wallet-read.js +3 -0
- package/dist/pwa/routes/webauthn.js +3 -3
- package/dist/pwa/server.js +112 -132
- package/dist/runtime/agent-grant-scopes.js +69 -1
- package/dist/runtime/webaz-schema-helpers.js +161 -3
- package/dist/sale-regions.js +116 -0
- package/dist/shipping-templates.js +119 -0
- package/dist/store-verification.js +77 -0
- package/dist/trade-tax.js +99 -0
- package/dist/trade-terms.js +76 -0
- package/dist/version.js +1 -1
- package/package.json +128 -2
package/dist/pwa/public/app.js
CHANGED
|
@@ -171,7 +171,7 @@ window.doRegisterPasskey = async (deviceLabel) => {
|
|
|
171
171
|
window.requestPasskeyGate = async (purpose, purposeData) => {
|
|
172
172
|
if (!isWebAuthnSupported()) throw new Error(t('当前设备不支持 Passkey'))
|
|
173
173
|
const start = await POST('/webauthn/auth/start', { purpose, purpose_data: purposeData })
|
|
174
|
-
if (start.error)
|
|
174
|
+
if (start.error) { const _e = new Error(start.error); _e.code = start.error_code; throw _e } // code=NO_PASSKEY_REGISTERED → 前端才引导注册;其余(取消/设备)不误导
|
|
175
175
|
const opts = start.options
|
|
176
176
|
const publicKey = {
|
|
177
177
|
...opts,
|
|
@@ -566,7 +566,7 @@ async function render(page, params) {
|
|
|
566
566
|
const { status, body } = await apiWithStatus('GET', '/me')
|
|
567
567
|
if (status === 200 && body && !body.error) {
|
|
568
568
|
state.user = body
|
|
569
|
-
connectSSE(); maybePromptPlacementBind(); refreshCartBadge(); maybeShowInstallBanner(); maybeClaimPendingProductShare(); maybeClaimPendingShopReferral(); setTimeout(refreshCompareBadge, 0); refreshChatsBadge(); refreshFeedbackBadge(); refreshSnfBadge(); setTimeout(ensureSnfPull, 800)
|
|
569
|
+
connectSSE(); maybePromptPlacementBind(); refreshCartBadge(); maybeShowInstallBanner(); maybeClaimPendingProductShare(); maybeClaimPendingShopReferral(); setTimeout(refreshCompareBadge, 0); refreshChatsBadge(); refreshFeedbackBadge(); refreshSnfBadge(); setTimeout(ensureSnfPull, 800); if (window.arbEntryHydrate) arbEntryHydrate()
|
|
570
570
|
try { p2pStart() } catch (e) { console.warn('[P2P] start err', e) }
|
|
571
571
|
} else if (status === 401 || status === 403) {
|
|
572
572
|
// 真·鉴权失败:清掉两层存储
|
|
@@ -644,11 +644,11 @@ async function render(page, params) {
|
|
|
644
644
|
return renderContributeTasks(app)
|
|
645
645
|
case 'seller':
|
|
646
646
|
if (state.user?.role === 'logistics') return renderLogistics(app)
|
|
647
|
-
if (state.
|
|
647
|
+
if (state.canArbitrate) return renderDisputeList(app) // PR-E:仲裁入口跟随 can_arbitrate,非 role==='arbitrator'
|
|
648
648
|
return renderSeller(app)
|
|
649
649
|
case 'edit-product': return renderEditProduct(app, params[0])
|
|
650
650
|
case 'wallet': return renderWallet(app)
|
|
651
|
-
case 'agents': return renderConnectedAgents(app)
|
|
651
|
+
case 'agents': return renderConnectedAgents(app); case 'pair': return window.renderAgentPair(app); case 'agent-approvals': return window.renderAgentApprovals(app) // RFC-020 #pair 配对授权页 / #agent-approvals 扩权审批页
|
|
652
652
|
case 'note': return renderNoteLanding(app, params[0])
|
|
653
653
|
case 'tag': return renderTagPage(app, decodeURIComponent(params[0] || ''))
|
|
654
654
|
case 'governance': return renderGovernance(app)
|
|
@@ -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); if (params[0] === 'platform-receive') return renderAdminPlatformReceiveAccounts(app); if (params[0] === 'fee-prepay-requests') return renderAdminFeePrepayRequests(app); if (params[0] === 'arbitrators') return renderAdminArbitrators(app); if (params[0] === 'contribution-ops') return renderAdminContributionHub(app); if (params[0] === 'bond-deposits') return renderAdminBondDeposits(app); if (params[0] === 'agent-strikes') return renderAdminAgentStrikes(app) // PR-②c 缓交 / PR-④b 逐品 / PR-⑤b 店铺 / PR-⑧ 合规录入 / PR-B 商户运营 hub + 平台服务费 + 平台收款方式 + 预充值申请审核 / PR-F 仲裁员管理
|
|
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)
|
|
@@ -845,14 +845,14 @@ async function render(page, params) {
|
|
|
845
845
|
// M7.4:order 含 has_pending_claim=1 时附加一枚"验证中"chip
|
|
846
846
|
function orderStatusBadges(order) {
|
|
847
847
|
if (!order) return ''
|
|
848
|
-
const main = statusBadge(order.status)
|
|
848
|
+
const main = statusBadge(order.status, order.payment_rail)
|
|
849
849
|
if (Number(order.has_pending_claim) === 1) {
|
|
850
850
|
return `${main} <span style="display:inline-block;font-size:10px;background:#eef2ff;color:#3730a3;padding:2px 8px;border-radius:99px;font-weight:600;margin-left:4px">🔎 ${t('验证中')}</span>`
|
|
851
851
|
}
|
|
852
852
|
return main
|
|
853
853
|
}
|
|
854
854
|
|
|
855
|
-
function statusBadge(status) {
|
|
855
|
+
function statusBadge(status, rail) {
|
|
856
856
|
const map = {
|
|
857
857
|
created: ['gray', t('待付款')],
|
|
858
858
|
paid: ['blue', t('待接单')],
|
|
@@ -875,7 +875,8 @@ function statusBadge(status) {
|
|
|
875
875
|
dispute_dismissed: ['gray', t('争议驳回')],
|
|
876
876
|
expired: ['gray', t('已过期')],
|
|
877
877
|
}
|
|
878
|
-
|
|
878
|
+
// 直付(非托管)争议终态:不发生退款/资金释放 → 用信誉裁决语义(dpTerminalBadge,见 app-order-labels.js)
|
|
879
|
+
const [color, label] = (rail === 'direct_p2p' && ((window.dpTerminalBadge && window.dpTerminalBadge(status)) || (window.dpNegotiationBadge && window.dpNegotiationBadge(status)) || (window.dpAcceptBadge && window.dpAcceptBadge(status)))) || map[status] || ['gray', status]
|
|
879
880
|
return `<span class="badge badge-${color}">${label}</span>`
|
|
880
881
|
}
|
|
881
882
|
|
|
@@ -1017,7 +1018,7 @@ function roleHome(role) {
|
|
|
1017
1018
|
function preLaunchBannerHTML() {
|
|
1018
1019
|
if (window._protocolPhase && window._protocolPhase !== 'pre_launch') return ''
|
|
1019
1020
|
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('
|
|
1021
|
+
⚠️ <strong>${t('直付(direct pay)已上线,是你与卖家的真实场外付款;WebAZ 非托管——不代持、不担保、不退款 · 平台托管(escrow)尚未上线(模拟测试币)· 价格按 USDC 计价仅供展示 · 邀请制预发布,勿据此投资或向第三方承诺')}</strong>
|
|
1021
1022
|
<a href="#welcome" style="margin-left:8px;color:#7c2d12;text-decoration:underline;font-weight:600">${t('详情')}</a>
|
|
1022
1023
|
</div>`
|
|
1023
1024
|
}
|
|
@@ -1027,10 +1028,10 @@ function preLaunchBannerHTML() {
|
|
|
1027
1028
|
// 没有 Passkey 登录 / 找回路径,丢 key 换设备后 Passkey 救不回账号(与弹窗"增强不替代恢复邮箱"一致)。
|
|
1028
1029
|
function recoveryBannerHTML() {
|
|
1029
1030
|
const u = state.user
|
|
1030
|
-
if (!u || u.role === 'admin') return ''
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1031
|
+
if (!u || u.role === 'admin' || u.has_password || u.email_verified) return ''
|
|
1032
|
+
if (!location.hash.startsWith('#me') && Date.now() < +(localStorage.getItem('webaz_recovery_dismiss_until') || 0)) return '' // 走查批次2·横幅降噪:可关闭 7 天冷却;「我的」域恒显(设置入口所在,安全提醒不静音到死角)
|
|
1033
|
+
return `<div data-rcv style="background:#fef2f2;border:1px solid #fca5a5;border-radius:8px;padding:10px 14px;font-size:12px;color:#991b1b;line-height:1.6;margin:8px 12px 12px;position:relative">
|
|
1034
|
+
<button title="${t('暂时隐藏(7 天)')}" onclick="localStorage.setItem('webaz_recovery_dismiss_until', String(Date.now() + 7 * 86400e3)); this.closest('[data-rcv]').remove()" style="position:absolute;top:6px;right:8px;background:none;border:none;cursor:pointer;color:#b91c1c;font-size:14px;padding:2px 6px">×</button>
|
|
1034
1035
|
🚨 <strong>${t('账户还没有恢复方式')}</strong> — ${t('闪退或换设备清缓存后可能永久无法登录。')}
|
|
1035
1036
|
<div style="margin-top:8px;display:flex;gap:8px;flex-wrap:wrap">
|
|
1036
1037
|
<button class="btn btn-sm" style="font-size:11px;padding:4px 10px;background:#dc2626;color:#fff;border:none;border-radius:6px;cursor:pointer" onclick="navigate('#me/settings')">${t('立即设置密码 / 绑定邮箱')} →</button>
|
|
@@ -2045,7 +2046,7 @@ window.adminUserBatch = async (action) => {
|
|
|
2045
2046
|
reason = prompt(t('暂停原因(必填,会写入审计)')) || ''
|
|
2046
2047
|
if (!reason.trim()) return alert(t('需填写原因'))
|
|
2047
2048
|
}
|
|
2048
|
-
if (!confirm(t('
|
|
2049
|
+
if (!confirm((action === 'suspend' ? t('确认暂停') : t('确认恢复')) + ' ' + ids.length + ' ' + t('个用户?'))) return
|
|
2049
2050
|
let ok = 0, fail = 0
|
|
2050
2051
|
for (const uid of ids) {
|
|
2051
2052
|
const path = action === 'suspend' ? `/admin/users/${uid}/suspend` : `/admin/users/${uid}/resume`
|
|
@@ -2645,7 +2646,7 @@ async function renderAdminOrders(app, opts = {}) {
|
|
|
2645
2646
|
<div style="display:flex;justify-content:space-between;align-items:flex-start;gap:8px">
|
|
2646
2647
|
<div style="flex:1;min-width:0">
|
|
2647
2648
|
<div style="font-weight:600">${escHtml(o.product_title)}</div>
|
|
2648
|
-
<div style="font-size:12px;color:#6b7280;margin-top:2px">${o.total_amount} WAZ · ${fmtTime(o.created_at)}</div>
|
|
2649
|
+
<div style="font-size:12px;color:#6b7280;margin-top:2px">${o.payment_rail === 'direct_p2p' ? `<span style="background:#fef3c7;color:#92400e;border-radius:99px;padding:1px 8px;font-size:10px;font-weight:600;margin-right:4px">${t('直付')}</span>${o.total_amount} USDC` : `${o.total_amount} WAZ`} · ${fmtTime(o.created_at)}</div>
|
|
2649
2650
|
<div style="font-size:11px;color:#6b7280;margin-top:4px">${t('买家')}: ${escHtml(o.buyer_name)} · ${t('卖家')}: ${escHtml(o.seller_name)}</div>
|
|
2650
2651
|
<div style="font-size:11px;color:#9ca3af;margin-top:2px">${o.id}</div>
|
|
2651
2652
|
</div>
|
|
@@ -2671,34 +2672,7 @@ window.doAdminOrderFilter = () => {
|
|
|
2671
2672
|
renderAdminOrders(document.getElementById('app'), { status })
|
|
2672
2673
|
}
|
|
2673
2674
|
|
|
2674
|
-
|
|
2675
|
-
if (!state.user) { renderLogin(); return }
|
|
2676
|
-
if (!isAdmin()) { app.innerHTML = shell(`<div class="alert alert-info">${t('仅限管理员')}</div>`, 'admin'); return }
|
|
2677
|
-
app.innerHTML = shell(loading$(), 'admin')
|
|
2678
|
-
const data = await GET('/admin/disputes')
|
|
2679
|
-
if (data.error) { app.innerHTML = shell(alert$('error', data.error), 'admin'); return }
|
|
2680
|
-
const list = data.disputes.map(d => `
|
|
2681
|
-
<div class="card" style="margin-bottom:10px;font-size:13px">
|
|
2682
|
-
<div style="display:flex;justify-content:space-between;align-items:flex-start;gap:8px">
|
|
2683
|
-
<div style="flex:1;min-width:0">
|
|
2684
|
-
<div style="font-weight:600">${escHtml(d.product_title || '—')}</div>
|
|
2685
|
-
<div style="font-size:12px;color:#6b7280;margin-top:2px">${escHtml((d.reason || '').slice(0, 80))}</div>
|
|
2686
|
-
<div style="font-size:11px;color:#6b7280;margin-top:6px">${t('原告')}: ${escHtml(d.initiator_name || '—')} → ${t('被告')}: ${escHtml(d.defendant_name || '—')}</div>
|
|
2687
|
-
<div style="font-size:11px;color:#9ca3af;margin-top:4px">${d.id} · ${fmtTime(d.created_at)}</div>
|
|
2688
|
-
</div>
|
|
2689
|
-
<div style="display:flex;flex-direction:column;gap:6px;align-items:flex-end">
|
|
2690
|
-
<span style="background:#fef3c7;color:#92400e;padding:2px 8px;border-radius:8px;font-size:11px">${d.status}</span>
|
|
2691
|
-
${d.total_amount ? `<div style="font-size:12px;color:#374151">${d.total_amount} WAZ</div>` : ''}
|
|
2692
|
-
</div>
|
|
2693
|
-
</div>
|
|
2694
|
-
</div>`).join('') || `<div class="empty"><div class="empty-icon">⚖️</div><div class="empty-text">${t('暂无争议')}</div></div>`
|
|
2695
|
-
app.innerHTML = shell(`
|
|
2696
|
-
<h1 class="page-title">⚖️ ${t('争议查看')}</h1>
|
|
2697
|
-
<div style="margin-bottom:16px"><button class="btn btn-outline btn-sm" style="width:auto" onclick="navigate('#admin')">${t('返回概览')}</button></div>
|
|
2698
|
-
<div style="font-size:12px;color:#6b7280;margin-bottom:10px">${t('共')} ${data.disputes.length} ${t('条结果')}(${t('含已结案')})</div>
|
|
2699
|
-
${list}
|
|
2700
|
-
`, 'admin')
|
|
2701
|
-
}
|
|
2675
|
+
// renderAdminDisputes 已抽出到 app-admin-disputes.js(window.renderAdminDisputes;可区分状态/直付-托管/进度/指派/结案 + 可管理:过滤+钻取)。
|
|
2702
2676
|
|
|
2703
2677
|
async function renderAdminTasks(app, opts = {}) {
|
|
2704
2678
|
if (!state.user) { renderLogin(); return }
|
|
@@ -3378,7 +3352,7 @@ window.openResignModal = (role) => {
|
|
|
3378
3352
|
<li>${t('已 assigned 但未完成的 case 必须先完成 / 转交,否则无法卸任')}</li>
|
|
3379
3353
|
</ul>
|
|
3380
3354
|
</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('
|
|
3355
|
+
<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
3356
|
<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
3357
|
<div id="resign-msg" style="margin-top:12px"></div>
|
|
3384
3358
|
<div style="margin-top:16px;display:flex;gap:8px;justify-content:flex-end">
|
|
@@ -3505,7 +3479,7 @@ async function renderAdminGovernance(app) {
|
|
|
3505
3479
|
<div style="display:flex;justify-content:space-between;align-items:flex-start;gap:10px">
|
|
3506
3480
|
<div style="flex:1">
|
|
3507
3481
|
<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('
|
|
3482
|
+
<div style="font-size:12px;color:#6b7280;margin-top:4px">${t('角色')}: ${roleLabel} · ${t('Region')}: ${escHtml(a.region || '—')} · ${t('Email')}: ${a.email ? '✓' : '✗'}</div>
|
|
3509
3483
|
<div style="font-size:12px;margin-top:6px;display:flex;gap:12px">
|
|
3510
3484
|
<span style="color:${quizOk ? '#16a34a' : '#dc2626'}">${quizOk ? '✓' : '✗'} ${t('题目')} ${a.quiz_score != null ? a.quiz_score + '%' : '—'}</span>
|
|
3511
3485
|
<span style="color:${caseOk ? '#16a34a' : '#dc2626'}">${caseOk ? '✓' : '✗'} ${t('案例 review')}</span>
|
|
@@ -3970,7 +3944,7 @@ async function renderAdminQuotaApps(app) {
|
|
|
3970
3944
|
<div style="font-weight:600">${escHtml(a.user_name)} <span style="font-size:11px;color:#6b7280">${a.email || ''}</span></div>
|
|
3971
3945
|
<div style="font-size:11px;color:#9ca3af;margin-top:2px">${a.user_id} · ${fmtTime(a.applied_at)}</div>
|
|
3972
3946
|
<div style="font-size:13px;margin-top:8px">
|
|
3973
|
-
${t('当前配额')}: <strong>${a.current_quota}</strong> → ${t('
|
|
3947
|
+
${t('当前配额')}: <strong>${a.current_quota}</strong> → ${t('申请配额')}: <strong>${a.requested_quota}</strong>
|
|
3974
3948
|
</div>
|
|
3975
3949
|
${a.reason ? `<div style="font-size:12px;color:#6b7280;margin-top:6px;padding:6px;background:#f9fafb;border-radius:4px">${escHtml(a.reason)}</div>` : ''}
|
|
3976
3950
|
<div style="display:flex;gap:6px;margin-top:10px">
|
|
@@ -4392,7 +4366,7 @@ function renderWelcome(app) {
|
|
|
4392
4366
|
<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
4367
|
<!-- Top nav — frosted glass,左返回,右语言切换(当前语言高亮) -->
|
|
4394
4368
|
<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('#
|
|
4369
|
+
<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
4370
|
<span class="w-lang" style="font-size:13px;font-weight:500">
|
|
4397
4371
|
<span class="${en ? 'w-lang-inactive' : 'w-lang-active'}" ${en ? `onclick="toggleLang();renderWelcome(document.getElementById('app'))"` : ''}>中文</span>
|
|
4398
4372
|
<span class="w-lang-sep">/</span>
|
|
@@ -5473,7 +5447,7 @@ async function renderPromoter(app) {
|
|
|
5473
5447
|
<div class="card" style="text-align:center;padding:10px 6px">
|
|
5474
5448
|
<div style="font-size:11px;color:#6b7280">💰 ${t('累计收益')}</div>
|
|
5475
5449
|
<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>
|
|
5450
|
+
<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
5451
|
</div>
|
|
5478
5452
|
<div class="card" style="text-align:center;padding:10px 6px">
|
|
5479
5453
|
<div style="font-size:11px;color:#6b7280">📊 ${t('近 30 日')}</div>
|
|
@@ -5558,9 +5532,7 @@ async function renderPromoter(app) {
|
|
|
5558
5532
|
const ratioPct = cs.clicks.raw > 0 ? Math.round((cs.clicks.unique / cs.clicks.raw) * 100) : null
|
|
5559
5533
|
// 我买过的(合并进"我的产品分享"作为商品来源子列表)
|
|
5560
5534
|
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
|
-
}
|
|
5535
|
+
const buildProductImg = (images) => window.productThumbSrc ? window.productThumbSrc(images) : ''
|
|
5564
5536
|
const renderBoughtRow = (p) => {
|
|
5565
5537
|
const img = buildProductImg(p.images)
|
|
5566
5538
|
const rate = Number(p.commission_rate || 0)
|
|
@@ -5586,7 +5558,7 @@ async function renderPromoter(app) {
|
|
|
5586
5558
|
|
|
5587
5559
|
return `
|
|
5588
5560
|
<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>`}
|
|
5561
|
+
${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
5562
|
<div style="flex:1;min-width:0">
|
|
5591
5563
|
<div style="font-size:12px;font-weight:600;color:#1f2937;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${escHtml(p.title)}</div>
|
|
5592
5564
|
<div style="font-size:10px;color:#9ca3af;margin-top:1px">
|
|
@@ -6325,8 +6297,6 @@ async function renderReviewsFeed(app) {
|
|
|
6325
6297
|
const rows = items.length === 0
|
|
6326
6298
|
? `<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
6299
|
: 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
6300
|
return `
|
|
6331
6301
|
<div class="card" style="padding:10px 12px;margin-bottom:8px;cursor:pointer" onclick="openExternalReview('${s.id}', '${escHtml(s.external_url || '').replace(/'/g, "\\'")}')">
|
|
6332
6302
|
<div style="display:flex;gap:10px;align-items:center">
|
|
@@ -6334,7 +6304,7 @@ async function renderReviewsFeed(app) {
|
|
|
6334
6304
|
<div style="flex:1;min-width:0">
|
|
6335
6305
|
<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
6306
|
<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}
|
|
6307
|
+
${s.product_title ? `<div style="font-size:11px;color:#6366f1;margin-top:2px">📦 ${escHtml(s.product_title)} · ${window.fmtPrice(s.product_price)}</div>` : ''}
|
|
6338
6308
|
</div>
|
|
6339
6309
|
</div>
|
|
6340
6310
|
</div>`
|
|
@@ -6448,20 +6418,16 @@ window.lookupAnchorAction = async () => {
|
|
|
6448
6418
|
// 商品区块
|
|
6449
6419
|
const productBlock = (r.product) ? (() => {
|
|
6450
6420
|
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 {}
|
|
6421
|
+
let img = window.productThumbSrc(p.images)
|
|
6456
6422
|
const idx = calcIndex(p)
|
|
6457
6423
|
const idxColor = idx == null ? '#9ca3af' : idx >= 8 ? '#dc2626' : idx >= 6 ? '#f59e0b' : '#6b7280'
|
|
6458
6424
|
return `
|
|
6459
6425
|
<div style="border:1px solid #e5e7eb;border-radius:8px;padding:12px;margin-bottom:10px">
|
|
6460
6426
|
<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>`}
|
|
6427
|
+
${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
6428
|
<div style="flex:1;min-width:0">
|
|
6463
6429
|
<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">${
|
|
6430
|
+
<div style="font-size:17px;font-weight:800;color:#dc2626;margin-top:4px">${window.fmtPrice(p.price)}</div>
|
|
6465
6431
|
<div style="font-size:11px;color:#6b7280;margin-top:2px">@${escHtml(p.seller_handle || p.seller_name?.slice(0,8) || '?')}</div>
|
|
6466
6432
|
</div>
|
|
6467
6433
|
</div>
|
|
@@ -6514,17 +6480,17 @@ async function renderGroupBuysLive(app) {
|
|
|
6514
6480
|
? `<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
6481
|
: items.map(it => {
|
|
6516
6482
|
let imageUrl = ''
|
|
6517
|
-
|
|
6483
|
+
imageUrl = window.productThumbSrc(it.images) || imageUrl
|
|
6518
6484
|
const pct = (Number(it.discount_pct) * 100).toFixed(0)
|
|
6519
6485
|
const final = (Number(it.original_price) * (1 - Number(it.discount_pct))).toFixed(2)
|
|
6520
6486
|
const progress = Math.min(100, (Number(it.joined_count) / Number(it.target_count)) * 100)
|
|
6521
6487
|
return `
|
|
6522
6488
|
<div class="card" style="padding:12px;margin-bottom:8px;cursor:pointer;border-left:3px solid #16a34a" onclick="location.hash='#group-buy/${it.id}'">
|
|
6523
6489
|
<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>
|
|
6490
|
+
<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
6491
|
<div style="flex:1;min-width:0">
|
|
6526
6492
|
<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>
|
|
6493
|
+
<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
6494
|
<div style="font-size:10px;color:#9ca3af;margin-top:2px">@${escHtml(it.seller_handle || '')} · ${t('截止')} ${fmtTime(it.ends_at)}</div>
|
|
6529
6495
|
</div>
|
|
6530
6496
|
</div>
|
|
@@ -6562,7 +6528,7 @@ async function renderGroupBuyDetail(app, id) {
|
|
|
6562
6528
|
<h1 class="page-title">👥 ${escHtml(r.product_title)}</h1>
|
|
6563
6529
|
<div class="card" style="background:linear-gradient(135deg,#ecfdf5,#fff);padding:14px;margin-bottom:10px">
|
|
6564
6530
|
<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}
|
|
6531
|
+
<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
6532
|
<div style="font-size:18px;font-weight:700;color:#16a34a;background:#dcfce7;padding:6px 12px;border-radius:8px">-${pct}%</div>
|
|
6567
6533
|
</div>
|
|
6568
6534
|
<div style="font-size:12px;color:#374151;margin-bottom:4px">${t('已成团')} <strong>${joined}/${r.target_count}</strong> · ${t('剩余时间')} ${remainingStr}</div>
|
|
@@ -6572,7 +6538,7 @@ async function renderGroupBuyDetail(app, id) {
|
|
|
6572
6538
|
: expired ? `<div style="color:#9ca3af">${t('已结束,等待结算')}</div>`
|
|
6573
6539
|
: isOwn ? `<div style="color:#9ca3af">${t('自己开的团,等待成团')}</div>`
|
|
6574
6540
|
: 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}
|
|
6541
|
+
: 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
6542
|
: `<div style="color:#9ca3af">${t('仅买家可加入')}</div>`}
|
|
6577
6543
|
</div>
|
|
6578
6544
|
<div style="font-size:13px;font-weight:600;margin:14px 0 8px">${t('已加入')} (${joined})</div>
|
|
@@ -6647,7 +6613,7 @@ async function renderSellerFlashSales(app) {
|
|
|
6647
6613
|
<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
6614
|
<span style="font-size:10px;font-weight:600;color:#fff;background:${phaseColor};padding:2px 8px;border-radius:99px">${phaseLabel}</span>
|
|
6649
6615
|
</div>
|
|
6650
|
-
<div style="font-size:12px;color:#374151">${it.sale_price}
|
|
6616
|
+
<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
6617
|
<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
6618
|
${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
6619
|
</div>`
|
|
@@ -6810,7 +6776,7 @@ window.aiSuggestPrice = async () => {
|
|
|
6810
6776
|
if (res.error) { if (hint) hint.innerHTML = '<span style="color:#dc2626">' + escHtml(res.error) + '</span>'; return }
|
|
6811
6777
|
const priceInp = document.getElementById('ep-price')
|
|
6812
6778
|
if (hint) {
|
|
6813
|
-
hint.innerHTML = `💡 ${t('
|
|
6779
|
+
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
6780
|
}
|
|
6815
6781
|
}
|
|
6816
6782
|
|
|
@@ -6904,10 +6870,10 @@ async function renderReferral(app) {
|
|
|
6904
6870
|
|
|
6905
6871
|
<div class="card" style="background:linear-gradient(135deg,#ecfdf5,#fff);padding:14px;margin-bottom:10px">
|
|
6906
6872
|
<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">
|
|
6873
|
+
<div style="display:grid;grid-template-columns:repeat(${Number(e.clearing_waz||0)>0?4:3},1fr);gap:8px;text-align:center">
|
|
6908
6874
|
<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
6875
|
<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>
|
|
6876
|
+
<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
6877
|
</div>
|
|
6912
6878
|
<div style="font-size:10px;color:#9ca3af;margin-top:6px;text-align:center">${e.total_records} ${t('条记录')} · WAZ</div>
|
|
6913
6879
|
</div>
|
|
@@ -8065,7 +8031,7 @@ async function renderFollowFeed(app) {
|
|
|
8065
8031
|
: items.map(it => {
|
|
8066
8032
|
const meta = TYPE_META[it.type] || { icon: '·', label: it.type, color: '#6b7280' }
|
|
8067
8033
|
let imageUrl = ''
|
|
8068
|
-
|
|
8034
|
+
imageUrl = window.productThumbSrc(it.images) || imageUrl
|
|
8069
8035
|
return `
|
|
8070
8036
|
<div class="card" style="padding:10px 12px;margin-bottom:8px;cursor:pointer" onclick="location.hash='#order-product/${it.product_id}'">
|
|
8071
8037
|
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:6px">
|
|
@@ -8076,10 +8042,10 @@ async function renderFollowFeed(app) {
|
|
|
8076
8042
|
<span style="font-size:10px;color:#9ca3af">${fmtTime(it.ts)}</span>
|
|
8077
8043
|
</div>
|
|
8078
8044
|
<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>
|
|
8045
|
+
<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
8046
|
<div style="flex:1;min-width:0">
|
|
8081
8047
|
<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}
|
|
8048
|
+
<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
8049
|
</div>
|
|
8084
8050
|
</div>
|
|
8085
8051
|
</div>`
|
|
@@ -8123,8 +8089,8 @@ async function renderReturnsCenter(app) {
|
|
|
8123
8089
|
])
|
|
8124
8090
|
const items = list?.items || []
|
|
8125
8091
|
const reasons = RETURN_REASON_LABEL()
|
|
8126
|
-
const STATUS_COLORS = { pending:'#d97706', refunded:'#16a34a', rejected:'#dc2626', cancelled:'#6b7280', accepted_pickup_pending:'#0891b2', picked_up:'#4f46e5' }
|
|
8127
|
-
const STATUS_LABEL = () => ({ pending: t('待处理'), refunded: t('已退款'), rejected: t('已拒绝'), cancelled: t('已取消'), accepted_pickup_pending: '📦 ' + t('待物流上门取件'), picked_up: '🚛 ' + t('已揽收,待卖家确认收到') })
|
|
8092
|
+
const STATUS_COLORS = { pending:'#d97706', refunded:'#16a34a', rejected:'#dc2626', cancelled:'#6b7280', accepted_pickup_pending:'#0891b2', picked_up:'#4f46e5', ...(window.dpReturnStatusColors ? window.dpReturnStatusColors() : {}) }
|
|
8093
|
+
const STATUS_LABEL = () => ({ pending: t('待处理'), refunded: t('已退款'), rejected: t('已拒绝'), cancelled: t('已取消'), accepted_pickup_pending: '📦 ' + t('待物流上门取件'), picked_up: '🚛 ' + t('已揽收,待卖家确认收到'), ...(window.dpReturnStatusLabels ? window.dpReturnStatusLabels() : {}) })
|
|
8128
8094
|
|
|
8129
8095
|
const statsHtml = stats ? `
|
|
8130
8096
|
<div class="card" style="background:linear-gradient(135deg,#fff7ed,#fef3c7);margin-bottom:14px">
|
|
@@ -8990,7 +8956,7 @@ window.doLogin = async () => {
|
|
|
8990
8956
|
if (!key) return showMsg('error', t('请粘贴 api_key'))
|
|
8991
8957
|
state.apiKey = key
|
|
8992
8958
|
const user = await GET('/me')
|
|
8993
|
-
if (user.error) { state.apiKey = null; return showMsg('error', t('无效的 api_key,请重新输入')) }
|
|
8959
|
+
if (user.error) { if (user.error_code === 'AGENT_BLOCKED' && window.renderAgentBlockedAppeal) { try { document.querySelectorAll('.js-modal, .js-sheet').forEach(m => m.remove()) } catch {}; return void window.renderAgentBlockedAppeal(user.error) } state.apiKey = null; return showMsg('error', t('无效的 api_key,请重新输入')) }
|
|
8994
8960
|
state.user = user
|
|
8995
8961
|
await persistApiKey(key)
|
|
8996
8962
|
connectSSE()
|
|
@@ -9176,7 +9142,7 @@ function renderShareBanner(variant) {
|
|
|
9176
9142
|
const tgtKind = ctx.target_kind
|
|
9177
9143
|
const tgtLine = tgt
|
|
9178
9144
|
? (tgtKind === 'product'
|
|
9179
|
-
? `🎁 <strong>${escHtml(tgt.title)}</strong> · ${tgt.price}
|
|
9145
|
+
? `🎁 <strong>${escHtml(tgt.title)}</strong> · ${window.fmtPrice(tgt.price)} <span style="color:#9ca3af">@ ${escHtml(tgt.seller_name)}</span>`
|
|
9180
9146
|
: tgtKind === 'user'
|
|
9181
9147
|
? `👤 <strong>${escHtml(tgt.name)}</strong>${tgt.bio ? ` · ${escHtml(tgt.bio).slice(0, 40)}` : ''}`
|
|
9182
9148
|
: '')
|
|
@@ -10137,7 +10103,7 @@ async function renderBuyPage(app, productId) {
|
|
|
10137
10103
|
const bottomBar = canBuy ? `
|
|
10138
10104
|
<div style="display:flex;gap:8px;align-items:center">
|
|
10139
10105
|
<div style="flex:0 0 auto">
|
|
10140
|
-
<div style="font-size:18px;font-weight:800;color:#dc2626;line-height:1">${livePrice}
|
|
10106
|
+
<div style="font-size:18px;font-weight:800;color:#dc2626;line-height:1">${window.fmtPrice(livePrice)}</div>
|
|
10141
10107
|
${state._flashSale ? `<div style="font-size:10px;color:#9ca3af;text-decoration:line-through">${p.price}</div>` : ''}
|
|
10142
10108
|
</div>
|
|
10143
10109
|
<button class="btn btn-outline" onclick="addToCart('${p.id}')" style="width:auto;padding:11px 14px;font-size:13px" title="${t('加入购物车')}">🛒+</button>
|
|
@@ -10151,18 +10117,18 @@ async function renderBuyPage(app, productId) {
|
|
|
10151
10117
|
${originBadge}
|
|
10152
10118
|
<div class="card">
|
|
10153
10119
|
${productImageGallery(p)}
|
|
10154
|
-
<h2 style="font-size:18px;font-weight:700;margin-bottom:6px">${escHtml(p.title)}</h2
|
|
10120
|
+
<h2 style="font-size:18px;font-weight:700;margin-bottom:6px">${escHtml(p.title)}</h2>${window.extLinksBarHtml ? window.extLinksBarHtml(productId) : ''}
|
|
10155
10121
|
${state._flashSale ? `
|
|
10156
10122
|
<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
10123
|
<div>
|
|
10158
10124
|
<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}
|
|
10125
|
+
<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
10126
|
</div>
|
|
10161
10127
|
<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
10128
|
</div>` : `
|
|
10163
10129
|
<div style="display:flex;align-items:baseline;gap:8px;margin-bottom:8px">
|
|
10164
10130
|
<span style="font-size:22px;font-weight:800;color:#dc2626">${p.price}</span>
|
|
10165
|
-
<span style="font-size:12px;color:#6b7280">
|
|
10131
|
+
<span style="font-size:12px;color:#6b7280" data-usdc-local="${p.price}">USDC${window._fxLocal(p.price) ? ' ≈ ' + window._fxLocal(p.price) : ''}</span>
|
|
10166
10132
|
</div>`}
|
|
10167
10133
|
${Number(p.trial_quota_remaining) > 0 ? `
|
|
10168
10134
|
<div style="background:linear-gradient(135deg,#faf5ff,#f3e8ff);border:1px solid #e9d5ff;border-radius:8px;padding:10px 12px;margin-bottom:10px">
|
|
@@ -10322,7 +10288,7 @@ async function renderBuyPage(app, productId) {
|
|
|
10322
10288
|
offers: {
|
|
10323
10289
|
'@type': 'Offer',
|
|
10324
10290
|
price: p.price,
|
|
10325
|
-
priceCurrency:
|
|
10291
|
+
priceCurrency: 'USDC', // display/pricing unit (USDC≈USD); matches the human USDC display (PR-1c)
|
|
10326
10292
|
availability: (Number(p.stock) || 0) > 0 ? 'https://schema.org/InStock' : 'https://schema.org/OutOfStock',
|
|
10327
10293
|
url: location.origin + '/#order-product/' + p.id,
|
|
10328
10294
|
seller: { '@type': 'Organization', '@id': location.origin + '/#u/' + p.seller_id, name: p.seller_name || p.seller_handle || p.seller_id },
|
|
@@ -10366,14 +10332,14 @@ window.openBuySheet = function(productId) {
|
|
|
10366
10332
|
const summary = hasDefault ? buildAddrString(defaultAddr) : ''
|
|
10367
10333
|
const max = buyMaxFor(prod)
|
|
10368
10334
|
const variants = state._variants || []
|
|
10369
|
-
const firstImg =
|
|
10335
|
+
const firstImg = window.productThumbSrc(prod.images)
|
|
10370
10336
|
|
|
10371
10337
|
const html = `
|
|
10372
10338
|
<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>`}
|
|
10339
|
+
${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
10340
|
<div style="flex:1;min-width:0">
|
|
10375
10341
|
<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}
|
|
10342
|
+
<div style="font-size:18px;font-weight:800;color:#dc2626;margin-top:2px">${window.fmtPrice(livePrice)}</div>
|
|
10377
10343
|
</div>
|
|
10378
10344
|
${variants.length === 0 && max > 0 ? `
|
|
10379
10345
|
<div style="display:flex;align-items:center;gap:6px;flex-shrink:0">
|
|
@@ -10471,11 +10437,12 @@ window.openBuySheet = function(productId) {
|
|
|
10471
10437
|
</div>
|
|
10472
10438
|
</details>
|
|
10473
10439
|
|
|
10440
|
+
${window.dpRailSelectorHtml ? window.dpRailSelectorHtml(prod.id, prod.price) : ''}${window.shipRegionBlockHtml ? window.shipRegionBlockHtml(prod.id) : ''}${window.purchaseTermsBlockHtml ? window.purchaseTermsBlockHtml(prod.id) : ''}
|
|
10474
10441
|
<div id="buy-msg" style="margin-top:10px"></div>
|
|
10475
10442
|
<div id="tax-preview-slot" style="margin-top:8px"></div>
|
|
10476
10443
|
|
|
10477
10444
|
<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})
|
|
10445
|
+
<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
10446
|
</div>
|
|
10480
10447
|
`
|
|
10481
10448
|
openSheet(html, { title: t('下单') })
|
|
@@ -11246,6 +11213,7 @@ window.qtyStep = (delta, max) => {
|
|
|
11246
11213
|
const cur = Math.floor(Number(inp.value) || 1)
|
|
11247
11214
|
const next = Math.max(1, Math.min(max, cur + delta))
|
|
11248
11215
|
inp.value = next
|
|
11216
|
+
if (window.dpRenderAccountFx) window.dpRenderAccountFx() // D3:数量变→刷新分账号 FX(按订单总额,非单价)
|
|
11249
11217
|
}
|
|
11250
11218
|
|
|
11251
11219
|
// B5 捐赠 chip 选择 — 高亮当前选中 + 更新提示文案
|
|
@@ -11275,6 +11243,7 @@ window.qtyClamp = (max) => {
|
|
|
11275
11243
|
if (!Number.isFinite(v) || v < 1) v = 1
|
|
11276
11244
|
if (v > max) v = max
|
|
11277
11245
|
inp.value = v
|
|
11246
|
+
if (window.dpRenderAccountFx) window.dpRenderAccountFx() // D3:数量变→刷新分账号 FX(按订单总额,非单价)
|
|
11278
11247
|
}
|
|
11279
11248
|
|
|
11280
11249
|
window.doBuy = async (productId, price) => {
|
|
@@ -11315,9 +11284,11 @@ window.doBuy = async (productId, price) => {
|
|
|
11315
11284
|
// 数量(默认 1)— 服务端会再校验 stock + MAX_PER_ORDER
|
|
11316
11285
|
const qtyInp = document.getElementById('inp-qty')
|
|
11317
11286
|
const quantity = qtyInp ? Math.max(1, Math.min(Number(qtyInp.max) || 1, Math.floor(Number(qtyInp.value) || 1))) : 1
|
|
11318
|
-
const
|
|
11287
|
+
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)
|
|
11288
|
+
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, ship_to_region: (window.shipSelectedRegion ? window.shipSelectedRegion() : undefined), direct_receive_account_id: (payment_rail === 'direct_p2p' && window.dpSelectedAccountId) ? (window.dpSelectedAccountId() || undefined) : undefined, ...giftPayload })
|
|
11289
|
+
if (payment_rail === 'direct_p2p') return void (window.dpAfterCreate && window.dpAfterCreate(res))
|
|
11319
11290
|
if (res.error_code === 'PRICE_CHANGED') {
|
|
11320
|
-
document.getElementById('buy-msg').innerHTML = alert$('error', `${t('价格已变动')}:${res.old_price} → ${res.new_price}
|
|
11291
|
+
document.getElementById('buy-msg').innerHTML = alert$('error', `${t('价格已变动')}:${window.fmtPrice(res.old_price)} → ${window.fmtPrice(res.new_price)} · ${t('请刷新页面')}`)
|
|
11321
11292
|
return
|
|
11322
11293
|
}
|
|
11323
11294
|
if (res.error) { document.getElementById('buy-msg').innerHTML = alert$('error', res.error); return }
|
|
@@ -11339,11 +11310,11 @@ window.checkCoupon = async (productId, basePrice) => {
|
|
|
11339
11310
|
const r = await GET(`/coupons/preview?code=${encodeURIComponent(code)}&product_id=${productId}`)
|
|
11340
11311
|
if (!r.ok) {
|
|
11341
11312
|
resultEl.innerHTML = `<span style="color:#dc2626">✗ ${escHtml(r.error || t('优惠码无效'))}</span>`
|
|
11342
|
-
if (btnEl) btnEl.innerHTML = `${t('立即下单')} · ${basePrice}
|
|
11313
|
+
if (btnEl) btnEl.innerHTML = `${t('立即下单')} · ${window.fmtPrice(basePrice)}`
|
|
11343
11314
|
return
|
|
11344
11315
|
}
|
|
11345
|
-
resultEl.innerHTML = `<span style="color:#16a34a">✓ ${t('已优惠')} ${r.discount}
|
|
11346
|
-
if (btnEl) btnEl.innerHTML = `${t('立即下单')} · ${r.final_price}
|
|
11316
|
+
resultEl.innerHTML = `<span style="color:#16a34a">✓ ${t('已优惠')} ${window.fmtPrice(r.discount)} · ${t('最终')} ${window.fmtPrice(r.final_price)}</span>`
|
|
11317
|
+
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
11318
|
}
|
|
11348
11319
|
|
|
11349
11320
|
// ─── P13: 购物车页 ────────────────────────────────────────────
|
|
@@ -11712,6 +11683,7 @@ async function renderOrders(app) {
|
|
|
11712
11683
|
const ordersScope = state.ordersScope || (state.user.role === 'seller' ? 'sold' : 'all')
|
|
11713
11684
|
if (ordersScope === 'sold') orders = orders.filter(o => o.seller_id === state.user.id)
|
|
11714
11685
|
else if (ordersScope === 'bought') orders = orders.filter(o => o.buyer_id === state.user.id)
|
|
11686
|
+
const railChipsHtml = window.orderRailChipsHtml(orders); orders = window.orderRailApply(orders) // 类型(支付轨)筛选 chip + 过滤;逻辑在 app-order-rail-filter.js
|
|
11715
11687
|
const groups = {
|
|
11716
11688
|
all: { label: t('全部'), icon: '📋', match: () => true },
|
|
11717
11689
|
pay: { label: t('待付款'), icon: '💳', match: o => o.status === 'created' },
|
|
@@ -11781,18 +11753,18 @@ async function renderOrders(app) {
|
|
|
11781
11753
|
<div class="order-icon">${pending ? '⏰' : getCategoryIcon(o.category)}</div>
|
|
11782
11754
|
<div class="order-info">
|
|
11783
11755
|
<div class="order-title">${escHtml(o.product_title)}</div>
|
|
11784
|
-
<div class="order-meta">${fmtTime(o.created_at)} · ${o.buyer_id === state.user.id ? t('我买的') : t('我卖的')}</div>
|
|
11756
|
+
<div class="order-meta">${fmtTime(o.created_at)} · ${o.buyer_id === state.user.id ? t('我买的') : t('我卖的')}${window.orderRailBadge(o)}</div>
|
|
11785
11757
|
<div style="margin-top:6px">${orderStatusBadges(o)}</div>
|
|
11786
11758
|
</div>
|
|
11787
|
-
<div class="order-amount">${o
|
|
11759
|
+
<div class="order-amount">${window.orderAmountHtml(o)}</div>
|
|
11788
11760
|
</div>
|
|
11789
11761
|
${canRebuy ? `<div style="border-top:1px solid #f3f4f6;padding:8px 14px;display:flex;justify-content:flex-end"><button onclick="event.stopPropagation();navigate('#order-product/${o.product_id}')" style="background:#eef2ff;color:#3730a3;border:1px solid #c7d2fe;padding:4px 12px;border-radius:99px;font-size:11px;font-weight:600;cursor:pointer">🔁 ${t('再次购买')}</button></div>` : ''}
|
|
11790
11762
|
</div>`
|
|
11791
11763
|
}).join('')
|
|
11792
11764
|
|
|
11793
11765
|
const exportRole = state.user?.role === 'seller' ? 'seller' : 'buyer'
|
|
11794
|
-
const exportBtn = `<button class="btn btn-outline btn-sm" style="float:right;font-size:11px;padding:4px 10px" onclick="exportOrders('${exportRole}')">📥 ${t('导出 CSV')}</button>`
|
|
11795
|
-
app.innerHTML = shell(`<h1 class="page-title">${t('我的订单')} ${exportBtn}</h1>${mainTabsHtml}${scopeChipsHtml}${notePromptPlaceholder('orders')}${tabsHtml}${pendingBanner}${list}`, 'orders')
|
|
11766
|
+
const exportBtn = `<button class="btn btn-outline btn-sm" style="float:right;font-size:11px;padding:4px 10px" onclick="exportOrders('${exportRole}','${state.ordersRail || 'all'}')">📥 ${t('导出 CSV')}</button>`
|
|
11767
|
+
app.innerHTML = shell(`<h1 class="page-title">${t('我的订单')} ${exportBtn}</h1>${mainTabsHtml}${scopeChipsHtml}${railChipsHtml}${notePromptPlaceholder('orders')}${tabsHtml}${pendingBanner}${list}`, 'orders')
|
|
11796
11768
|
hydrateNotePrompt('orders')
|
|
11797
11769
|
}
|
|
11798
11770
|
|
|
@@ -11824,9 +11796,9 @@ window.printOrderReceipt = async (orderId) => {
|
|
|
11824
11796
|
<div class="row"><span>${t('商品')}</span><strong>${escHtml(product?.title || '—')}</strong></div>
|
|
11825
11797
|
${variantStr ? `<div class="row"><span>${t('规格')}</span><span>${escHtml(variantStr)}</span></div>` : ''}
|
|
11826
11798
|
<div class="row"><span>${t('数量')}</span><span>${order.quantity}</span></div>
|
|
11827
|
-
<div class="row"><span>${t('单价')}</span><span>${order.unit_price}
|
|
11828
|
-
${Number(order.coupon_discount) > 0 ? `<div class="row"><span>${t('优惠')}</span><span style="color:#dc2626">-${order.coupon_discount}
|
|
11829
|
-
<div class="total">${t('总计')}: ${order.total_amount}
|
|
11799
|
+
<div class="row"><span>${t('单价')}</span><span>${order.unit_price} USDC</span></div>
|
|
11800
|
+
${Number(order.coupon_discount) > 0 ? `<div class="row"><span>${t('优惠')}</span><span style="color:#dc2626">-${order.coupon_discount} USDC</span></div>` : ''}
|
|
11801
|
+
<div class="total">${t('总计')}: ${order.total_amount} USDC</div>
|
|
11830
11802
|
<hr style="margin:14px 0;border:none;border-top:1px solid #ddd">
|
|
11831
11803
|
<div class="muted">${t('收货地址')}: ${escHtml(order.shipping_address || '—')}</div>
|
|
11832
11804
|
<div class="muted">${t('订单状态')}: ${order.status}</div>
|
|
@@ -11835,12 +11807,10 @@ window.printOrderReceipt = async (orderId) => {
|
|
|
11835
11807
|
w.document.close()
|
|
11836
11808
|
}
|
|
11837
11809
|
|
|
11838
|
-
window.exportOrders = (role) => {
|
|
11839
|
-
|
|
11840
|
-
|
|
11841
|
-
|
|
11842
|
-
// 由于 auth() 当前只读 Authorization 头,导出端点需支持 query token;改用 fetch + blob 下载更稳
|
|
11843
|
-
fetch(`/api/orders/export?role=${role}`, { headers: { Authorization: 'Bearer ' + state.apiKey } })
|
|
11810
|
+
window.exportOrders = (role, rail) => {
|
|
11811
|
+
const railQ = (rail === 'escrow' || rail === 'direct_p2p') ? `&rail=${rail}` : '' // 导出与当前筛选一致(对账分轨)
|
|
11812
|
+
// auth() 只读 Authorization 头 → 用 fetch + blob 下载(Content-Disposition: attachment)
|
|
11813
|
+
fetch(`/api/orders/export?role=${role}${railQ}`, { headers: { Authorization: 'Bearer ' + state.apiKey } })
|
|
11844
11814
|
.then(async (r) => {
|
|
11845
11815
|
const truncated = r.headers.get('X-Truncated') === '1'
|
|
11846
11816
|
const limit = r.headers.get('X-Truncated-Limit')
|
|
@@ -11940,7 +11910,7 @@ async function renderOrdersShare(app, mainTabsHtml) {
|
|
|
11940
11910
|
<div style="font-size:28px;flex-shrink:0">${getCategoryIcon(o.category)}</div>
|
|
11941
11911
|
<div style="flex:1;min-width:0">
|
|
11942
11912
|
<div style="font-size:13px;font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${escHtml(o.product_title)}</div>
|
|
11943
|
-
<div style="font-size:11px;color:#6b7280;margin-top:2px"><a href="#order/${o.id}" style="color:#6b7280;text-decoration:none">${t('订单')} ${orderShort}</a> · ${fmtTime(o.updated_at || o.created_at)}${typeof o.total_amount !== 'undefined' ? ` · ${o
|
|
11913
|
+
<div style="font-size:11px;color:#6b7280;margin-top:2px"><a href="#order/${o.id}" style="color:#6b7280;text-decoration:none">${t('订单')} ${orderShort}</a> · ${fmtTime(o.updated_at || o.created_at)}${typeof o.total_amount !== 'undefined' ? ` · ${window.orderAmountHtml(o)}` : ''}</div>
|
|
11944
11914
|
</div>
|
|
11945
11915
|
<div style="display:flex;gap:4px;flex-shrink:0">
|
|
11946
11916
|
<button class="btn btn-outline btn-sm" style="width:auto;padding:5px 8px;font-size:10px;white-space:nowrap" onclick="shareCompletedOrder('${o.product_id}', '${escHtml(o.product_title || '').replace(/'/g, "\\'")}')">🔗 ${t('添加口令')}</button>
|
|
@@ -12184,10 +12154,10 @@ function orderStageTimeline(order, history) {
|
|
|
12184
12154
|
{ key: 'completed', label: t('完成'), icon: '✓' },
|
|
12185
12155
|
]
|
|
12186
12156
|
// 异常状态:单独 banner,不画时间线(防误导)
|
|
12187
|
-
const ANOMALY = ['disputed', 'cancelled', 'fault_seller', 'fault_buyer', 'fault_logistics', 'refunded_partial', 'refunded_full', 'dispute_dismissed', 'expired']
|
|
12157
|
+
const ANOMALY = ['disputed', 'payment_query', 'cancelled', 'fault_seller', 'fault_buyer', 'fault_logistics', 'refunded_partial', 'refunded_full', 'dispute_dismissed', 'expired']
|
|
12188
12158
|
if (ANOMALY.includes(order.status)) {
|
|
12189
12159
|
const colorMap = {
|
|
12190
|
-
disputed: '#f59e0b', cancelled: '#6b7280',
|
|
12160
|
+
disputed: '#f59e0b', payment_query: '#f59e0b', cancelled: '#6b7280',
|
|
12191
12161
|
fault_seller: '#dc2626', fault_buyer: '#dc2626', fault_logistics: '#dc2626',
|
|
12192
12162
|
refunded_full: '#16a34a', refunded_partial: '#3b82f6', dispute_dismissed: '#6b7280', expired: '#9ca3af',
|
|
12193
12163
|
}
|
|
@@ -12201,7 +12171,7 @@ function orderStageTimeline(order, history) {
|
|
|
12201
12171
|
return `<div style="background:#fff;border:0.5px solid #e5e7eb;border-radius:12px;padding:14px 16px;margin-bottom:10px;display:flex;align-items:center;gap:10px">
|
|
12202
12172
|
<div style="width:8px;height:8px;border-radius:50%;background:${c};flex-shrink:0"></div>
|
|
12203
12173
|
<div style="flex:1">
|
|
12204
|
-
<div style="font-size:14px;font-weight:600;color:#1f2937">${labelMap[order.status] || order.status}</div>
|
|
12174
|
+
<div style="font-size:14px;font-weight:600;color:#1f2937">${(order.payment_rail === 'direct_p2p' && ((window.dpTerminalLabel && window.dpTerminalLabel(order.status)) || (window.dpNegotiationLabel && window.dpNegotiationLabel(order.status)) || (window.dpAcceptLabel && window.dpAcceptLabel(order.status)))) || labelMap[order.status] || order.status}</div>
|
|
12205
12175
|
<div style="font-size:11px;color:#8e8e93;margin-top:2px">${t('查看下方时间线了解流转详情')}</div>
|
|
12206
12176
|
</div>
|
|
12207
12177
|
</div>`
|
|
@@ -12221,7 +12191,7 @@ function orderStageTimeline(order, history) {
|
|
|
12221
12191
|
// "当前" = 正在等待的下一阶段(不是已完成的最后阶段)
|
|
12222
12192
|
// created → 等付款;paid → 等接单;accepted → 等发货;shipped/in_transit → 等送达;
|
|
12223
12193
|
// delivered → 等买家确认;confirmed/completed → 全部完成
|
|
12224
|
-
const statusToIdx = { created: 1, paid: 2, accepted: 3, shipped: 4, picked_up: 4, in_transit: 4, delivered: 5, confirmed: 6, completed: 6 }
|
|
12194
|
+
const statusToIdx = { created: 1, direct_pay_window: 1, direct_expired_unconfirmed: 1, paid: 2, accepted: 3, shipped: 4, picked_up: 4, in_transit: 4, delivered: 5, confirmed: 6, completed: 6 }
|
|
12225
12195
|
let currentIdx = statusToIdx[order.status] ?? 0
|
|
12226
12196
|
|
|
12227
12197
|
const dot = (s, i) => {
|
|
@@ -12255,7 +12225,7 @@ function orderStageTimeline(order, history) {
|
|
|
12255
12225
|
// 与顶部紧凑 stepper 互补:stepper 给总览,timeline 给细节
|
|
12256
12226
|
function orderTrackingTimeline(order, history, trackingInfo, STATUS_ZH) {
|
|
12257
12227
|
// 异常订单不显示物流时间线(争议/取消等)— 由顶部 stepper 异常 banner 兜底
|
|
12258
|
-
const ANOMALY = ['disputed', 'cancelled', 'fault_seller', 'fault_buyer', 'fault_logistics', 'refunded_partial', 'refunded_full', 'dispute_dismissed', 'expired']
|
|
12228
|
+
const ANOMALY = ['disputed', 'payment_query', 'cancelled', 'fault_seller', 'fault_buyer', 'fault_logistics', 'refunded_partial', 'refunded_full', 'dispute_dismissed', 'expired']
|
|
12259
12229
|
if (ANOMALY.includes(order.status)) return ''
|
|
12260
12230
|
|
|
12261
12231
|
// 已完成节点 lookup
|
|
@@ -12385,14 +12355,18 @@ function nextActionCard(order, isBuyer, isSeller, activeDeadline, isOverdue) {
|
|
|
12385
12355
|
// 我自己是违约方时只看到警告,不显示按钮(不能自己触发对自己的判责)
|
|
12386
12356
|
const meAtFault = hint.overdueFault && hint.overdueFault === ('fault_' + (isBuyer ? 'buyer' : isSeller ? 'seller' : ''))
|
|
12387
12357
|
const canForceTrigger = isOverdue && hint.overdueFault && !meAtFault && (isBuyer || isSeller)
|
|
12358
|
+
// 直付(非托管):WebAZ 不退款/不放款 → 超时后果只写信誉责任,绝不写"退款/资金释放"
|
|
12359
|
+
const overdueConsequence = order.payment_rail === 'direct_p2p'
|
|
12360
|
+
? ({ paid: '卖家信誉扣分;直付非托管,WebAZ 不退款/不放款', accepted: '卖家信誉扣分;直付非托管,WebAZ 不退款/不放款', in_transit: '物流/履约信誉扣分;直付非托管,WebAZ 不退款', delivered: '系统自动确认直付订单完成;不涉及平台放款' }[order.status] || hint.overdueConsequence)
|
|
12361
|
+
: hint.overdueConsequence
|
|
12388
12362
|
return `<div style="background:${bg};border:0.5px solid ${isOverdue ? '#fecaca' : '#e5e7eb'};border-radius:12px;padding:14px 16px;margin-bottom:10px">
|
|
12389
12363
|
<div style="display:flex;align-items:center;gap:10px">
|
|
12390
12364
|
<div style="font-size:22px;line-height:1">${icon}</div>
|
|
12391
12365
|
<div style="flex:1">
|
|
12392
12366
|
<div style="font-size:14px;font-weight:600;color:${tone}">${t(myHint)}</div>
|
|
12393
12367
|
${activeDeadline?.deadline ? `<div style="font-size:11px;color:#8e8e93;margin-top:2px">${isOverdue ? t('已超时,协议将自动判责') : fmtCountdown(activeDeadline.deadline)}</div>` : ''}
|
|
12394
|
-
${isOverdue &&
|
|
12395
|
-
⚖️ ${t('协议处置')}:${t(
|
|
12368
|
+
${isOverdue && overdueConsequence ? `<div style="margin-top:8px;padding:8px 10px;background:#fff;border-radius:6px;font-size:12px;color:#7c2d12;border:1px dashed #fecaca">
|
|
12369
|
+
⚖️ ${t('协议处置')}:${t(overdueConsequence)}
|
|
12396
12370
|
</div>` : ''}
|
|
12397
12371
|
${canForceTrigger ? `<button onclick="forceTimeoutCheck('${order.id}')" style="margin-top:10px;background:#dc2626;color:#fff;border:none;border-radius:8px;padding:8px 14px;font-size:13px;font-weight:600;cursor:pointer">⚡ ${t('立即触发判责')}</button>
|
|
12398
12372
|
<div style="font-size:10px;color:#9ca3af;margin-top:6px">${t('协议巡检每 5 分钟一次;你也可立即触发,无需等待。')}</div>` : ''}
|
|
@@ -12441,9 +12415,10 @@ async function renderOrderDetail(app, orderId) {
|
|
|
12441
12415
|
|
|
12442
12416
|
// 操作按钮
|
|
12443
12417
|
const actions = getActions(order, isBuyer, isSeller, isLogistic)
|
|
12418
|
+
window._dpOrderRail = order.payment_rail // direct_p2p:路由 mark_paid/confirm 到 Passkey 门(动作在渲染后触发,早设即可);hydrate 移到最终 app.innerHTML 之后(见下)——在此调度会撞 claim-task 的 await 间隙、#dp-order-instr 尚未渲染而 if(!box) 静默 bail → 框永久"加载中"
|
|
12444
12419
|
|
|
12445
12420
|
const STATUS_ZH = {
|
|
12446
|
-
created:'待付款', paid:'待接单', accepted:'待发货', shipped:'已发货',
|
|
12421
|
+
created:'待付款', direct_pay_window:'直付待付款', direct_expired_unconfirmed:'直付超时未确认', paid:'待接单', accepted:'待发货', shipped:'已发货',
|
|
12447
12422
|
picked_up:'已揽收', in_transit:'运输中', delivered:'待确认',
|
|
12448
12423
|
confirmed:'已确认', completed:'已完成', disputed:'争议中',
|
|
12449
12424
|
cancelled:'已取消', fault_seller:'卖家违约', fault_buyer:'买家违约', fault_logistics:'物流违约',
|
|
@@ -12451,11 +12426,11 @@ async function renderOrderDetail(app, orderId) {
|
|
|
12451
12426
|
|
|
12452
12427
|
const historyHtml = (history || []).map(h => `
|
|
12453
12428
|
<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>
|
|
12455
|
-
<div class="timeline-actor">${h.actor_name}(${h.actor_role_name || h.actor_role})</div>
|
|
12429
|
+
<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>
|
|
12430
|
+
<div class="timeline-actor">${escHtml(h.actor_name)}(${escHtml(h.actor_role_name || h.actor_role)})</div>
|
|
12456
12431
|
<div class="timeline-time">${fmtTime(h.created_at)}</div>
|
|
12457
|
-
${h.notes ? `<div class="timeline-evidence" style="color:#6b7280">💬 ${h.notes}</div>` : ''}
|
|
12458
|
-
${(h.evidence_items || []).map(e => `<div class="timeline-evidence">📎 ${e.description}</div>`).join('')}
|
|
12432
|
+
${h.notes ? `<div class="timeline-evidence" style="color:#6b7280">💬 ${escHtml(h.notes)}</div>` : ''}
|
|
12433
|
+
${(h.evidence_items || []).map(e => `<div class="timeline-evidence">📎 ${escHtml(e.description)}</div>`).join('')}
|
|
12459
12434
|
</div>`).join('')
|
|
12460
12435
|
|
|
12461
12436
|
// 物流追踪完整时间轴 — 含订单全流转 + 未到达节点的截止提示
|
|
@@ -12501,21 +12476,21 @@ async function renderOrderDetail(app, orderId) {
|
|
|
12501
12476
|
</div>
|
|
12502
12477
|
</div>
|
|
12503
12478
|
|
|
12504
|
-
${orderStageTimeline(order, history)}
|
|
12479
|
+
${window.wazEscrowOrderBanner ? window.wazEscrowOrderBanner(order, isBuyer) : ''}${orderStageTimeline(order, history)}
|
|
12505
12480
|
${nextActionCard(order, isBuyer, isSeller, activeDeadline, isOverdue)}
|
|
12506
12481
|
|
|
12507
12482
|
<div class="card">
|
|
12508
|
-
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px">
|
|
12509
|
-
<div style="font-size:
|
|
12510
|
-
<div style="display:flex;gap:6px;align-items:center">
|
|
12511
|
-
${(isBuyer || isSeller) ? `<button class="btn btn-sm" style="background:#eef2ff;color:#4338ca;font-size:11px;padding:4px 10px" onclick="openChatForContext('order','${order.id}')">💬 ${t('联系对方')}</button>` : ''}
|
|
12483
|
+
<div style="display:flex;flex-wrap:wrap;justify-content:space-between;align-items:center;gap:8px;margin-bottom:12px">
|
|
12484
|
+
<div style="font-size:12px;color:#9ca3af;font-family:monospace;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1 1 120px;min-width:0" title="${escHtml(order.id)}">${order.id}</div>
|
|
12485
|
+
<div style="display:flex;gap:6px;align-items:center;flex-wrap:wrap;flex-shrink:0;white-space:nowrap">
|
|
12486
|
+
${(isBuyer || isSeller) ? `<button class="btn btn-sm" style="background:#eef2ff;color:#4338ca;font-size:11px;padding:4px 10px;white-space:nowrap" onclick="openChatForContext('order','${order.id}')">💬 ${t('联系对方')}</button>` : ''}
|
|
12512
12487
|
${orderStatusBadges(order)}
|
|
12513
12488
|
</div>
|
|
12514
12489
|
</div>
|
|
12515
|
-
<div class="detail-row"><span class="detail-label">${t('商品')}</span><span class="detail-value">${product?.title || ''}</span></div>
|
|
12516
|
-
<div class="detail-row"><span class="detail-label">${t('金额')}</span><span class="detail-value" style="color:#4f46e5">${
|
|
12490
|
+
<div class="detail-row" style="align-items:flex-start"><span class="detail-label" style="flex-shrink:0;margin-right:12px">${t('商品')}</span><span class="detail-value" style="text-align:left;max-width:78%;word-break:break-word;line-height:1.5">${escHtml(product?.title || '')}</span></div>
|
|
12491
|
+
<div class="detail-row"><span class="detail-label">${t('金额')}</span><span class="detail-value" style="color:#4f46e5">${window.orderAmountHtml(order)}${Number(order.insurance_premium) > 0 ? ` <span style="font-size:10px;color:#6366f1;background:#eef2ff;padding:1px 6px;border-radius:99px;margin-left:4px">🛡 ${t('已购保险')} +${order.insurance_premium}</span>` : ''}</span></div>
|
|
12517
12492
|
<div class="detail-row"><span class="detail-label">${t('下单时间')}</span><span class="detail-value">${fmtTime(order.created_at)}</span></div>
|
|
12518
|
-
${order.shipping_address ? `<div class="detail-row"><span class="detail-label">${t('收货地址')}</span><span class="detail-value">${order.shipping_address}</span></div>` : ''}
|
|
12493
|
+
${order.shipping_address ? `<div class="detail-row"><span class="detail-label">${t('收货地址')}</span><span class="detail-value">${escHtml(order.shipping_address)}</span></div>` : ''}
|
|
12519
12494
|
${order.anonymous_recipient && order.recipient_code ? `<div class="detail-row"><span class="detail-label">🔒 ${t('取件代号')}</span><span class="detail-value" style="font-family:monospace;font-size:14px;font-weight:700;color:#166534;background:#dcfce7;padding:2px 10px;border-radius:6px;letter-spacing:1px">${escHtml(order.recipient_code)}</span></div><div style="font-size:11px;color:#15803d;padding:0 14px 8px">${t('凭此代号到自提点取件 · 卖家和物流不知道你的真实姓名')}</div>` : ''}
|
|
12520
12495
|
${order.anonymous_recipient && !order.recipient_code ? `<div class="detail-row"><span class="detail-label">🔒 ${t('匿名订单')}</span><span class="detail-value" style="font-size:11px;color:#6b7280">${t('买家选择了匿名收货')}</span></div>` : ''}
|
|
12521
12496
|
${Number(order.donation_amount) > 0 ? `<div class="detail-row"><span class="detail-label">❤️ ${t('随单捐赠')}</span><span class="detail-value" style="color:#dc2626;font-weight:600">${order.donation_amount} WAZ</span></div>` : ''}
|
|
@@ -12525,6 +12500,7 @@ async function renderOrderDetail(app, orderId) {
|
|
|
12525
12500
|
${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
12501
|
${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
12502
|
</div>
|
|
12503
|
+
${order.payment_rail === 'direct_p2p' && isBuyer && window.dpOrderDisclosureHtml ? window.dpOrderDisclosureHtml(order) : ''}${window.dpReconcileCard ? window.dpReconcileCard(order, isSeller) : ''}${order.payment_rail === 'direct_p2p' && window.dpNegotiationCard ? window.dpNegotiationCard(order) : ''}${window.dpCancelRefundCard ? window.dpCancelRefundCard(order, isBuyer, isSeller) : ''}${window.dpPendingAcceptCard ? window.dpPendingAcceptCard(order, isBuyer, isSeller) : ''}${window.mutualCancelCard ? window.mutualCancelCard(order, isBuyer, isSeller) : ''}
|
|
12528
12504
|
|
|
12529
12505
|
${trackingHtml}
|
|
12530
12506
|
${disputeHtml}
|
|
@@ -12591,7 +12567,7 @@ async function renderOrderDetail(app, orderId) {
|
|
|
12591
12567
|
<div class="action-title">${t('完整状态历史')}</div>
|
|
12592
12568
|
<div class="timeline">${historyHtml || `<div style="color:#6b7280;font-size:13px">${t('暂无记录')}</div>`}</div>
|
|
12593
12569
|
</div>
|
|
12594
|
-
`, 'orders')
|
|
12570
|
+
`, 'orders'); if (order.payment_rail === 'direct_p2p' && isBuyer && window.dpHydrateOrderDisclosure) window.dpHydrateOrderDisclosure(order.id) // direct_p2p 买家:最终 DOM 已就位,#dp-order-instr 必命中(both-acked→收款说明快照;否则 D1/D2 门)。卡片与 hydrate 均 isBuyer 门(端点本就 NOT_ORDER_BUYER,非买家渲染只会得红色报错噪音)。退货/评价 widget 注入各自子容器,不动此框
|
|
12595
12571
|
|
|
12596
12572
|
// Wave B-3: 退货 widget — 异步加载(仅 completed 订单可退)
|
|
12597
12573
|
// 买家:有退货窗口可申请/查看;卖家:有退货申请时内联查看+处理(accept/reject/received),无申请则隐藏卡
|
|
@@ -13062,14 +13038,14 @@ async function renderReturnWidgetForOrder(order, product) {
|
|
|
13062
13038
|
const events = detail?.timeline || []
|
|
13063
13039
|
const item = detail?.item || mine
|
|
13064
13040
|
const isBuyer = state.user && state.user.id === item.buyer_id
|
|
13065
|
-
const STATUS_COLORS = { pending:'#d97706', accepted:'#16a34a', refunded:'#16a34a', rejected:'#dc2626', cancelled:'#6b7280', escalated:'#6b21a8' }
|
|
13066
|
-
const STATUS_LABEL = () => ({ pending: t('待卖家处理'), accepted: t('已同意'), refunded: t('已退款'), rejected: t('已拒绝'), cancelled: t('已取消'), escalated: t('已升级仲裁') })
|
|
13041
|
+
const STATUS_COLORS = { pending:'#d97706', accepted:'#16a34a', refunded:'#16a34a', rejected:'#dc2626', cancelled:'#6b7280', escalated:'#6b21a8', ...(window.dpReturnStatusColors ? window.dpReturnStatusColors() : {}) }
|
|
13042
|
+
const STATUS_LABEL = () => ({ pending: t('待卖家处理'), accepted: t('已同意'), refunded: t('已退款'), rejected: t('已拒绝'), cancelled: t('已取消'), escalated: t('已升级仲裁'), ...(window.dpReturnStatusLabels ? window.dpReturnStatusLabels() : {}) })
|
|
13067
13043
|
|
|
13068
13044
|
const isActive = !['refunded', 'cancelled', 'escalated'].includes(item.status)
|
|
13069
13045
|
const canEscalate = isBuyer && (
|
|
13070
13046
|
item.status === 'rejected' ||
|
|
13071
|
-
(item.status === 'pending' && (Date.now() - new Date(item.created_at).getTime()) >= 7 * 86400 * 1000)
|
|
13072
|
-
|
|
13047
|
+
(item.status === 'pending' && (Date.now() - new Date(item.created_at).getTime()) >= 7 * 86400 * 1000) ||
|
|
13048
|
+
(window.dpReturnCanEscalate ? window.dpReturnCanEscalate(item, order) : false))
|
|
13073
13049
|
|
|
13074
13050
|
area.innerHTML = `
|
|
13075
13051
|
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px">
|
|
@@ -13079,11 +13055,11 @@ async function renderReturnWidgetForOrder(order, product) {
|
|
|
13079
13055
|
|
|
13080
13056
|
${isSellerView && item.status === 'pending' ? `
|
|
13081
13057
|
<div style="display:flex;gap:8px;margin-bottom:8px">
|
|
13082
|
-
<button class="btn btn-success btn-sm" style="flex:1;font-size:12px" onclick="decideReturn('${item.id}','accept','${order.id}')">${t('接受退款')}</button>
|
|
13058
|
+
<button class="btn btn-success btn-sm" style="flex:1;font-size:12px" onclick="decideReturn('${item.id}','accept','${order.id}')">${order.payment_rail === 'direct_p2p' ? t('同意退货(场外退款)') : t('接受退款')}</button>
|
|
13083
13059
|
<button class="btn btn-outline btn-sm" style="flex:1;font-size:12px;color:#dc2626;border-color:#fecaca" onclick="decideReturn('${item.id}','reject','${order.id}')">${t('拒绝退货')}</button>
|
|
13084
13060
|
</div>` : ''}
|
|
13085
13061
|
${isSellerView && item.status === 'picked_up' ? `
|
|
13086
|
-
<button class="btn btn-success btn-sm" style="width:100%;font-size:12px;margin-bottom:8px" onclick="confirmReturnReceived('${item.id}','${order.id}')">${t('✓ 已收到退货 · 触发退款')}</button>` : ''}
|
|
13062
|
+
<button class="btn btn-success btn-sm" style="width:100%;font-size:12px;margin-bottom:8px" onclick="confirmReturnReceived('${item.id}','${order.id}')">${order.payment_rail === 'direct_p2p' ? '✓ ' + t('已收到退货') : t('✓ 已收到退货 · 触发退款')}</button>` : ''}${window.dpReturnHandshake ? window.dpReturnHandshake(item, isBuyer, isSellerView, order) : ''}
|
|
13087
13063
|
|
|
13088
13064
|
<div style="padding:10px;background:#fafafa;border-radius:8px;margin-bottom:8px">
|
|
13089
13065
|
<div style="font-size:11px;font-weight:600;color:#6b7280;margin-bottom:8px">🧾 ${t('协商时间线')} · ${events.length} ${t('条')}</div>
|
|
@@ -13100,8 +13076,8 @@ async function renderReturnWidgetForOrder(order, product) {
|
|
|
13100
13076
|
|
|
13101
13077
|
${canEscalate ? `
|
|
13102
13078
|
<div style="margin-top:8px;padding:8px 10px;background:#fef2f2;border:1px solid #fecaca;border-radius:6px">
|
|
13103
|
-
<div style="font-size:11px;color:#991b1b;margin-bottom:6px">${item.status === 'rejected' ? t('卖家已拒绝 — 如有异议可升级至平台仲裁') : t('卖家 7 天内未回应 — 可升级至平台仲裁')}</div>
|
|
13104
|
-
<button class="btn btn-sm" style="background:#6b21a8;color:#fff;padding:5px 14px;font-size:11px" onclick="escalateReturn('${item.id}','${order.id}')">⚖️ ${t('升级至仲裁')}</button>
|
|
13079
|
+
<div style="font-size:11px;color:#991b1b;margin-bottom:6px">${(window.dpReturnCanEscalate && window.dpReturnCanEscalate(item, order)) ? window.dpReturnEscalateHint(item) : item.status === 'rejected' ? t('卖家已拒绝 — 如有异议可升级至平台仲裁') : t('卖家 7 天内未回应 — 可升级至平台仲裁')}</div>
|
|
13080
|
+
<button class="btn btn-sm" style="background:#6b21a8;color:#fff;padding:5px 14px;font-size:11px" onclick="escalateReturn('${item.id}','${order.id}','${order.payment_rail || ''}')">⚖️ ${t('升级至仲裁')}</button>
|
|
13105
13081
|
</div>
|
|
13106
13082
|
` : ''}
|
|
13107
13083
|
`
|
|
@@ -13115,7 +13091,7 @@ async function renderReturnWidgetForOrder(order, product) {
|
|
|
13115
13091
|
|
|
13116
13092
|
area.innerHTML = `
|
|
13117
13093
|
<div style="margin-bottom:8px">${t('退货窗口')}:${returnDays} ${t('天')} · ${remainText}</div>
|
|
13118
|
-
<button class="btn btn-outline btn-sm" onclick="openReturnRequestModal('${order.id}', ${Number(order.total_amount)})">${t('申请退货')}</button>
|
|
13094
|
+
<button class="btn btn-outline btn-sm" onclick="openReturnRequestModal('${order.id}', ${Number(order.total_amount)}, '${order.payment_rail === 'direct_p2p' ? 'USDC' : 'WAZ'}')">${t('申请退货')}</button>
|
|
13119
13095
|
`
|
|
13120
13096
|
}
|
|
13121
13097
|
|
|
@@ -13135,8 +13111,8 @@ window.sendReturnMessage = async (returnId, orderId) => {
|
|
|
13135
13111
|
}
|
|
13136
13112
|
}
|
|
13137
13113
|
|
|
13138
|
-
window.escalateReturn = async (returnId, orderId) => {
|
|
13139
|
-
if (!confirm(t('确定升级到仲裁?协议会冻结卖家资产并指派仲裁员审核。'))) return
|
|
13114
|
+
window.escalateReturn = async (returnId, orderId, rail) => {
|
|
13115
|
+
if (!confirm(rail === 'direct_p2p' ? t('确定升级到仲裁?直付订单为信誉裁决:仲裁不经手资金,按证据对卖家作信誉处罚。') : t('确定升级到仲裁?协议会冻结卖家资产并指派仲裁员审核。'))) return
|
|
13140
13116
|
const res = await POST(`/return-requests/${returnId}/escalate`, {})
|
|
13141
13117
|
if (res.error) return toast$(res.error, 'error')
|
|
13142
13118
|
toast$(t('已升级仲裁'), 'success')
|
|
@@ -13214,10 +13190,10 @@ window.selectVariantOption = (dim, val, basePrice) => {
|
|
|
13214
13190
|
const stockTag = Number(match.stock) > 0
|
|
13215
13191
|
? `<span style="color:#16a34a">● ${t('有货')}</span>`
|
|
13216
13192
|
: `<span style="color:#dc2626">${t('缺货')}</span>`
|
|
13217
|
-
summary.innerHTML = `✓ ${price}
|
|
13193
|
+
summary.innerHTML = `✓ ${window.fmtPrice(price)} · ${stockTag}`
|
|
13218
13194
|
if (buyBtn) {
|
|
13219
13195
|
buyBtn.disabled = Number(match.stock) <= 0
|
|
13220
|
-
buyBtn.innerHTML = `${t('立即下单')} · ${price}
|
|
13196
|
+
buyBtn.innerHTML = `${t('立即下单')} · ${window.fmtPrice(price)}`
|
|
13221
13197
|
}
|
|
13222
13198
|
}
|
|
13223
13199
|
|
|
@@ -13287,18 +13263,21 @@ window.toggleOrderAddrEdit = (expand) => {
|
|
|
13287
13263
|
}
|
|
13288
13264
|
}
|
|
13289
13265
|
|
|
13290
|
-
//
|
|
13291
|
-
|
|
13292
|
-
|
|
13293
|
-
|
|
13294
|
-
|
|
13295
|
-
|
|
13296
|
-
|
|
13297
|
-
|
|
13298
|
-
|
|
13299
|
-
|
|
13300
|
-
|
|
13301
|
-
|
|
13266
|
+
// 订单金额显示(RFC-014 USDC 计价):escrow → USDC + 买家本地币参考(fmtPrice,纯展示,无真实托管);
|
|
13267
|
+
// direct_p2p → USDC + 【买家下单时所选卖家收款账户币种】的应付额(dpFxInCurrency,不是本地币——买家只能按卖家
|
|
13268
|
+
// 支持的币种付款)。币种取自账号快照(currency 属非敏感元数据,pre-ack 也可见;仅 instruction/qr 受披露门)。
|
|
13269
|
+
// 该币种无汇率 → dpFxInCurrency 回落币种码,绝不臆造换算。
|
|
13270
|
+
window.orderAmountHtml = (o) => {
|
|
13271
|
+
const usdc = Number(o && o.total_amount)
|
|
13272
|
+
if (o && o.payment_rail === 'direct_p2p') {
|
|
13273
|
+
let cur = ''
|
|
13274
|
+
try { cur = String(JSON.parse(o.direct_pay_account_snapshot || '{}').currency || '').toUpperCase() } catch {}
|
|
13275
|
+
const base = `${Number.isFinite(usdc) ? (Number.isInteger(usdc) ? usdc : usdc.toFixed(2)) : '—'} USDC`
|
|
13276
|
+
if (!cur || cur === 'USDC' || cur === 'USD') return window.fmtPrice(usdc) // USDC 账户:USDC + 买家本地法币参考(fmtPrice)
|
|
13277
|
+
const pay = window.dpFxInCurrency ? window.dpFxInCurrency(usdc, cur) : cur
|
|
13278
|
+
return `${base} <span style="font-size:10px;color:#9ca3af;margin-left:4px;font-weight:400">${t('应付')} ≈ ${pay}</span>`
|
|
13279
|
+
}
|
|
13280
|
+
return window.fmtPrice(usdc)
|
|
13302
13281
|
}
|
|
13303
13282
|
|
|
13304
13283
|
// Wave C-1: 规格格式化(订单详情 / 列表用)
|
|
@@ -13351,7 +13330,7 @@ function RETURN_REASON_LABEL() {
|
|
|
13351
13330
|
}
|
|
13352
13331
|
}
|
|
13353
13332
|
|
|
13354
|
-
window.openReturnRequestModal = (orderId, total) => {
|
|
13333
|
+
window.openReturnRequestModal = (orderId, total, cur = 'WAZ') => {
|
|
13355
13334
|
const labels = RETURN_REASON_LABEL()
|
|
13356
13335
|
const html = `
|
|
13357
13336
|
<div class="js-modal" style="background:rgba(0,0,0,0.6);position:fixed;inset:0;z-index:1000;display:flex;align-items:flex-end;justify-content:center" onclick="this.remove()">
|
|
@@ -13368,9 +13347,9 @@ window.openReturnRequestModal = (orderId, total) => {
|
|
|
13368
13347
|
<textarea class="form-control" id="ret-text" rows="3" maxlength="500" placeholder="${t('描述问题(可选,便于卖家判断)')}"></textarea>
|
|
13369
13348
|
</div>
|
|
13370
13349
|
<div class="form-group">
|
|
13371
|
-
<label class="form-label">${t('退款金额')} (
|
|
13350
|
+
<label class="form-label">${t('退款金额')} (${cur}) *</label>
|
|
13372
13351
|
<input class="form-control" id="ret-amount" type="number" min="0.01" max="${total}" step="0.01" value="${total}">
|
|
13373
|
-
<div style="font-size:11px;color:#9ca3af;margin-top:2px">${t('订单总额')}: ${total}
|
|
13352
|
+
<div style="font-size:11px;color:#9ca3af;margin-top:2px">${t('订单总额')}: ${total} ${cur} · ${t('可申请部分退款')}${cur === 'USDC' ? ' · ' + t('直付退款在协议外完成,金额为参考') : ''}</div>
|
|
13374
13353
|
</div>
|
|
13375
13354
|
<div class="form-group" style="background:#f0fdf4;border:1px solid #86efac;border-radius:8px;padding:10px 12px">
|
|
13376
13355
|
<label style="display:flex;align-items:flex-start;gap:8px;font-size:12px;color:#166534;cursor:pointer">
|
|
@@ -13422,37 +13401,27 @@ function getActions(order, isBuyer, isSeller, isLogistic) {
|
|
|
13422
13401
|
const s = order.status
|
|
13423
13402
|
const isInPerson = order.fulfillment_mode === 'in_person'
|
|
13424
13403
|
const isSelfFulfillSeller = isSeller && !order.logistics_id
|
|
13404
|
+
// 直付(direct_p2p):买家在 direct_pay_window 标记"我已付款"(Passkey 门) 或取消(不门控);其余阶段沿用通用规则
|
|
13405
|
+
if (order.payment_rail === 'direct_p2p' && isBuyer && s === 'direct_pay_window')
|
|
13406
|
+
return [{ action: 'mark_paid', label: '✅ 我已付款', style: 'success' }, { action: 'cancel', label: '取消订单', style: 'secondary' }]
|
|
13407
|
+
if (order.payment_rail === 'direct_p2p' && window.dpNegotiationActions) { const _na = window.dpNegotiationActions(order, isBuyer, isSeller); if (_na) return _na } // 货款协商(payment_query/disputed)动作
|
|
13425
13408
|
// M8 面交订单:买家在 paid / accepted 都可"面交完成"直接结算
|
|
13426
|
-
if (isInPerson && isBuyer && (s === 'paid' || s === 'accepted'))
|
|
13409
|
+
if (isInPerson && isBuyer && (s === 'paid' || s === 'accepted'))
|
|
13427
13410
|
return [{ action: 'confirm_in_person', label: '🤝 面交完成 / 确认收货', style: 'success' }]
|
|
13428
|
-
}
|
|
13429
13411
|
if (isSeller && s === 'paid')
|
|
13430
|
-
return [
|
|
13431
|
-
{ action: 'accept', label: '接单', style: 'success' },
|
|
13432
|
-
{ action: 'decline', label: '拒绝接单', style: 'danger', custom: 'decline' },
|
|
13433
|
-
]
|
|
13412
|
+
return [{ action: 'accept', label: '接单', style: 'success' }, { action: 'decline', label: '拒绝接单', style: 'danger', custom: 'decline' }]
|
|
13434
13413
|
if (isSeller && s === 'accepted' && !isInPerson)
|
|
13435
|
-
return [{ action: 'ship', label: '确认发货', style: 'success', logisticsSelector: true,
|
|
13436
|
-
trackingInput: true,
|
|
13437
|
-
evidencePlaceholder: '包装状态描述 / 货物说明(可选)' }]
|
|
13414
|
+
return [{ action: 'ship', label: '确认发货', style: 'success', logisticsSelector: true, trackingInput: true, evidencePlaceholder: '包装状态描述 / 货物说明(可选)' }, ...(order.payment_rail === 'direct_p2p' ? [{ action: 'report_nonpayment', label: '未收到货款(告知买家核实)', style: 'danger' }] : [])]
|
|
13438
13415
|
if (isSeller && s === 'accepted' && isInPerson)
|
|
13439
13416
|
return [{ action: 'noop_in_person', label: '🤝 面交中(等待买家确认)', style: 'secondary', disabled: true }]
|
|
13440
13417
|
if ((isLogistic || isSelfFulfillSeller) && s === 'shipped')
|
|
13441
|
-
return [{ action: 'pickup', label: '✅ 确认揽收', style: 'success', needsEvidence: true,
|
|
13442
|
-
noteLabel: '快递单号 *', evidencePlaceholder: '如:SF1234567890',
|
|
13443
|
-
helperText: isSelfFulfillSeller ? '自履约订单:你负责回传揽收/单号,超时仍按卖家责任处理。' : '' }]
|
|
13418
|
+
return [{ action: 'pickup', label: '✅ 确认揽收', style: 'success', needsEvidence: true, noteLabel: '快递单号 *', evidencePlaceholder: '如:SF1234567890', helperText: isSelfFulfillSeller ? '自履约订单:你负责回传揽收/单号,超时仍按卖家责任处理。' : '' }]
|
|
13444
13419
|
if ((isLogistic || isSelfFulfillSeller) && s === 'picked_up')
|
|
13445
13420
|
return [{ action: 'transit', label: '🚛 开始运输', style: 'primary' }]
|
|
13446
13421
|
if ((isLogistic || isSelfFulfillSeller) && s === 'in_transit')
|
|
13447
|
-
return [{ action: 'deliver', label: '📬 确认投递', style: 'success', needsEvidence: true,
|
|
13448
|
-
noteLabel: '投递凭证', evidencePlaceholder: '门牌照片描述 / 收件人姓名 / 签收时间',
|
|
13449
|
-
helperText: isSelfFulfillSeller ? '自履约投递需留存签收/门牌/交付说明,买家确认后才结算。' : '' }]
|
|
13422
|
+
return [{ action: 'deliver', label: '📬 确认投递', style: 'success', needsEvidence: true, noteLabel: '投递凭证', evidencePlaceholder: '门牌照片描述 / 收件人姓名 / 签收时间', helperText: isSelfFulfillSeller ? '自履约投递需留存签收/门牌/交付说明,买家确认后才结算。' : '' }]
|
|
13450
13423
|
if (isBuyer && s === 'delivered')
|
|
13451
|
-
return [
|
|
13452
|
-
{ action: 'confirm', label: '确认收货', style: 'success' },
|
|
13453
|
-
{ action: 'dispute', label: '发起争议', style: 'danger', needsEvidence: true,
|
|
13454
|
-
noteLabel: '争议理由', evidencePlaceholder: '描述问题(货不对版/货损/未收到等)' },
|
|
13455
|
-
]
|
|
13424
|
+
return [{ action: 'confirm', label: '确认收货', style: 'success' }, { action: 'dispute', label: '未收到货 / 有问题?', style: 'danger', needsEvidence: true, noteLabel: '争议理由', evidencePlaceholder: '描述问题(未收到 / 货不对版 / 货损等)', helperText: '未收到货?可先联系卖家核实(可能晚到或放了代收点)。确认异常再提交 —— 发起争议后自动确认将暂停,不会因超时被视为已收货。' }]
|
|
13456
13425
|
return null
|
|
13457
13426
|
}
|
|
13458
13427
|
|
|
@@ -13507,13 +13476,15 @@ function renderActions(orderId, actions, order, logisticsCompanies = []) {
|
|
|
13507
13476
|
|
|
13508
13477
|
window.handleAction = async (orderId, action, idx, needsEvidence, hasLogisticsSelector) => {
|
|
13509
13478
|
const msgEl = document.getElementById('action-msg')
|
|
13479
|
+
// 直付:买家 mark_paid/confirm/confirm_in_person 是 RISK 动作 → 走两次披露 + Passkey 门(后端硬强制)
|
|
13480
|
+
if (window._dpOrderRail === 'direct_p2p' && ['mark_paid', 'confirm', 'confirm_in_person'].includes(action)) return void (window.dpHandleAction && window.dpHandleAction(orderId, action))
|
|
13510
13481
|
|
|
13511
13482
|
// M8 面交确认:走专用端点,跳过物流状态机
|
|
13512
13483
|
if (action === 'confirm_in_person') {
|
|
13513
13484
|
if (!confirm(t('确认已收到物品?escrow 将立即释放给卖家,无法撤销。'))) return
|
|
13514
13485
|
msgEl.innerHTML = `<div class="alert alert-info"><span class="spinner"></span>${t('确认中...')}</div>`
|
|
13515
13486
|
const res = await POST(`/orders/${orderId}/confirm-in-person`, {})
|
|
13516
|
-
if (res.error) { msgEl.innerHTML = alert$('error', res.error); return }
|
|
13487
|
+
if (res.error) { msgEl.innerHTML = alert$('error', window.orderErrorText ? window.orderErrorText(res.error_code, res.error) : res.error); return }
|
|
13517
13488
|
msgEl.innerHTML = alert$('success', t('面交完成 — escrow 已释放'))
|
|
13518
13489
|
maybeThankSponsorAndClear()
|
|
13519
13490
|
setTimeout(() => renderOrderDetail(document.getElementById('app'), orderId), 1000)
|
|
@@ -13521,7 +13492,7 @@ window.handleAction = async (orderId, action, idx, needsEvidence, hasLogisticsSe
|
|
|
13521
13492
|
}
|
|
13522
13493
|
if (action === 'noop_in_person') return
|
|
13523
13494
|
const confirmText = {
|
|
13524
|
-
ship: t('确认已经发货?发货后买家将看到物流信息,超时/虚假发货可能进入争议或判责。'),
|
|
13495
|
+
ship: (window._dpOrderRail === 'direct_p2p' && window.dpPayRef) ? `${t('直付订单:发货前请核实货款已到账 —— 银行流水附言应为')} ${window.dpPayRef(orderId)}${t('(同买家同金额多单务必逐单核对参考号)。发货即视为你确认已收到货款;发货后不可再报告未收款,只能走争议。')}` : t('确认已经发货?发货后买家将看到物流信息,超时/虚假发货可能进入争议或判责。'),
|
|
13525
13496
|
pickup: t('确认已揽收并回传凭证?请确保单号或揽收说明真实可追踪。'),
|
|
13526
13497
|
deliver: t('确认已投递?请确保投递凭证真实,买家仍需确认收货后才结算。'),
|
|
13527
13498
|
confirm: t('确认收货?escrow 将进入结算流程,无法撤销。'),
|
|
@@ -13570,7 +13541,7 @@ window.handleAction = async (orderId, action, idx, needsEvidence, hasLogisticsSe
|
|
|
13570
13541
|
|
|
13571
13542
|
const res = await POST(`/orders/${orderId}/action`, body)
|
|
13572
13543
|
if (res.error) {
|
|
13573
|
-
msgEl.innerHTML = alert$('error', res.error)
|
|
13544
|
+
msgEl.innerHTML = alert$('error', window.orderErrorText ? window.orderErrorText(res.error_code, res.error) : res.error)
|
|
13574
13545
|
} else {
|
|
13575
13546
|
msgEl.innerHTML = alert$('success', t('操作成功!'))
|
|
13576
13547
|
// S8: confirm action 触发 completed → 检查是否首单,若是则致谢 + 清 ctx
|
|
@@ -13757,13 +13728,13 @@ function evidenceRequestCard(req, currentUserId) {
|
|
|
13757
13728
|
// evidence_types 存为 JSON 数组字符串
|
|
13758
13729
|
let types = []
|
|
13759
13730
|
try { types = typeof req.evidence_types === 'string' ? JSON.parse(req.evidence_types) : (req.evidence_types || []) } catch(e) {}
|
|
13760
|
-
const typeLabels = types.map(
|
|
13731
|
+
const typeLabels = types.map(et => `${EVIDENCE_TYPE_ICONS[et] || ''}${t(EVIDENCE_TYPE_LABELS[et] || et)}`).join(' ')
|
|
13761
13732
|
|
|
13762
13733
|
const submittedHtml = (req.submitted_items || []).length > 0 ? `
|
|
13763
13734
|
<div style="margin-top:8px">
|
|
13764
13735
|
${req.submitted_items.map(it => `
|
|
13765
13736
|
<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]
|
|
13737
|
+
<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
13738
|
<div style="margin-top:2px">${escHtml(it.description)}</div>
|
|
13768
13739
|
${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
13740
|
</div>`).join('')}
|
|
@@ -13771,26 +13742,26 @@ function evidenceRequestCard(req, currentUserId) {
|
|
|
13771
13742
|
|
|
13772
13743
|
const submitForm = isMe && req.status === 'pending' ? `
|
|
13773
13744
|
<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"
|
|
13745
|
+
<div style="font-size:12px;font-weight:600;color:#92400e;margin-bottom:6px">${t('提交所需证据')}</div>
|
|
13775
13746
|
<div id="er-msg-${req.id}"></div>
|
|
13776
13747
|
<select class="form-control" id="er-type-${req.id}" style="margin-bottom:6px;font-size:13px">
|
|
13777
|
-
<option value=""
|
|
13778
|
-
${types.map(
|
|
13748
|
+
<option value="">${t('— 选择证据类型 —')}</option>
|
|
13749
|
+
${types.map(et => `<option value="${et}">${EVIDENCE_TYPE_ICONS[et]} ${t(EVIDENCE_TYPE_LABELS[et] || et)}</option>`).join('')}
|
|
13779
13750
|
</select>
|
|
13780
13751
|
<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}')"
|
|
13752
|
+
<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">
|
|
13753
|
+
<button class="btn btn-primary btn-sm" style="width:auto" onclick="handleSubmitEvidence('${req.id}','${req.dispute_id}')">${t('提交证据')}</button>
|
|
13783
13754
|
</div>` : ''
|
|
13784
13755
|
|
|
13785
13756
|
return `
|
|
13786
13757
|
<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
13758
|
<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 ? '👤 需要你提供' :
|
|
13759
|
+
<div style="font-size:13px;font-weight:600">${isMe ? t('👤 需要你提供') : `${t('请求')} → ${req.requested_from_name || t('对方')}(${req.requested_from_role || ''})`}</div>
|
|
13789
13760
|
${erStatusBadge(req.status)}
|
|
13790
13761
|
</div>
|
|
13791
|
-
<div style="font-size:12px;color:#6b7280;margin-bottom:4px"
|
|
13762
|
+
<div style="font-size:12px;color:#6b7280;margin-bottom:4px">${t('类型:')}${typeLabels}</div>
|
|
13792
13763
|
${req.description ? `<div style="font-size:13px;margin-bottom:4px">${req.description}</div>` : ''}
|
|
13793
|
-
<div style="font-size:11px;color:#9ca3af"
|
|
13764
|
+
<div style="font-size:11px;color:#9ca3af">${t('截止:')}${fmtTime(req.deadline)}</div>
|
|
13794
13765
|
${submittedHtml}
|
|
13795
13766
|
${submitForm}
|
|
13796
13767
|
</div>`
|
|
@@ -13811,7 +13782,7 @@ const TL_ROLE_META = {
|
|
|
13811
13782
|
}
|
|
13812
13783
|
const TL_TYPE_META = {
|
|
13813
13784
|
open: { icon: '🚩', title: '发起争议', border: '#dc2626' },
|
|
13814
|
-
evidence: { icon: '📎', title: '
|
|
13785
|
+
evidence: { icon: '📎', title: '证据提交', border: '#3b82f6' },
|
|
13815
13786
|
response: { icon: '📝', title: '被告反驳', border: '#f59e0b' },
|
|
13816
13787
|
evidence_request: { icon: '📋', title: '仲裁员索证', border: '#a855f7' },
|
|
13817
13788
|
ruling: { icon: '🏛️', title: '仲裁裁决', border: '#6b21a8' },
|
|
@@ -13838,8 +13809,8 @@ function buildTimelineEvent(ev, dispute, user, actors) {
|
|
|
13838
13809
|
const isMe = user && actor && actor.id === user.id
|
|
13839
13810
|
// 当无 actor(如 system resolved 事件),不重复显示与 role chip 相同的名字
|
|
13840
13811
|
const actorLabel = actor
|
|
13841
|
-
? (actor.handle ? '@' + escHtml(actor.handle) :
|
|
13842
|
-
: ''
|
|
13812
|
+
? (actor.name ? escHtml(actor.name) : (actor.handle ? '@' + escHtml(actor.handle) : ''))
|
|
13813
|
+
: '' // 走查批次3:昵称优先(自动生成的 @handle 可读性差;上方 party chips 本就用昵称,对齐)
|
|
13843
13814
|
|
|
13844
13815
|
// 内容主体 — 按 type 分支
|
|
13845
13816
|
let bodyHtml = ''
|
|
@@ -13849,7 +13820,7 @@ function buildTimelineEvent(ev, dispute, user, actors) {
|
|
|
13849
13820
|
} else if (ev.type === 'evidence') {
|
|
13850
13821
|
const meta = ev.meta || {}
|
|
13851
13822
|
const typeIcon = EVIDENCE_TYPE_ICONS[meta.evidence_type] || '📎'
|
|
13852
|
-
const typeLabel = EVIDENCE_TYPE_LABELS[meta.evidence_type] || meta.evidence_type
|
|
13823
|
+
const typeLabel = t(EVIDENCE_TYPE_LABELS[meta.evidence_type] || meta.evidence_type)
|
|
13853
13824
|
const sizeKb = meta.size ? `${(Number(meta.size)/1024).toFixed(1)} KB` : ''
|
|
13854
13825
|
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
13826
|
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,14 +13857,14 @@ function buildTimelineEvent(ev, dispute, user, actors) {
|
|
|
13886
13857
|
} else if (ev.type === 'ruling' || ev.type === 'resolved') {
|
|
13887
13858
|
const meta = ev.meta || {}
|
|
13888
13859
|
const rulingLabel = meta.ruling || meta.ruling_type
|
|
13889
|
-
const rulingText = rulingLabel ? (RULING_LABELS[rulingLabel] || rulingLabel) : ''
|
|
13860
|
+
const rulingText = rulingLabel ? ((dispute.payment_rail === 'direct_p2p' && window.dpRulingLabel && window.dpRulingLabel(rulingLabel)) || t(RULING_LABELS[rulingLabel] || rulingLabel)) : ''
|
|
13890
13861
|
let liability = []
|
|
13891
13862
|
try { liability = typeof meta.liability_parties === 'string' ? JSON.parse(meta.liability_parties) : (meta.liability_parties || []) } catch {}
|
|
13892
13863
|
bodyHtml = `
|
|
13893
13864
|
${rulingText ? `<div style="font-weight:700;font-size:14px;color:${typeMeta.border};margin-bottom:4px">${t(rulingText)}</div>` : ''}
|
|
13894
13865
|
${ev.body ? `<div style="font-size:13px;line-height:1.5;white-space:pre-wrap;color:#374151">${escHtml(ev.body)}</div>` : ''}
|
|
13895
|
-
${meta.refund_amount != null ? `<div style="font-size:12px;margin-top:6px">💸 ${t('退款金额')}:<strong>${meta.refund_amount} WAZ</strong></div>` : ''}
|
|
13896
|
-
${liability.length > 0 ? `<div style="margin-top:6px">
|
|
13866
|
+
${dispute.payment_rail !== 'direct_p2p' && meta.refund_amount != null ? `<div style="font-size:12px;margin-top:6px">💸 ${t('退款金额')}:<strong>${meta.refund_amount} WAZ</strong></div>` : ''}
|
|
13867
|
+
${dispute.payment_rail !== 'direct_p2p' && liability.length > 0 ? `<div style="margin-top:6px">
|
|
13897
13868
|
<div style="font-size:11px;color:#6b7280;margin-bottom:2px">${t('责任分配')}:</div>
|
|
13898
13869
|
${liability.map(lp => `<div style="font-size:12px;margin-top:2px">${(TL_ROLE_META[lp.role]||TL_ROLE_META.unknown).icon} ${t(lp.role)} ${t('承担')} ${lp.amount} WAZ${lp.insurance_cap!=null?` (${t('保险上限')} ${lp.insurance_cap})`:''}</div>`).join('')}
|
|
13899
13870
|
</div>` : ''}`
|
|
@@ -13938,7 +13909,7 @@ function buildDisputeTimelineSection(dispute, user) {
|
|
|
13938
13909
|
|
|
13939
13910
|
function buildDisputeHtml(dispute, user) {
|
|
13940
13911
|
const isDefendant = user && user.id === dispute.defendant_id
|
|
13941
|
-
const isArbitrator =
|
|
13912
|
+
const isArbitrator = !!state.canArbitrate // PR-E:跟随后端 can_arbitrate(active whitelist),非 user.role;后端 COI/assign 才是边界
|
|
13942
13913
|
// is_party is set by the server; fallback to local check
|
|
13943
13914
|
const isParty = dispute.is_party || (user && dispute.parties && dispute.parties.some(p => p.id === user.id))
|
|
13944
13915
|
const parties = dispute.parties || []
|
|
@@ -14082,20 +14053,13 @@ function buildDisputeHtml(dispute, user) {
|
|
|
14082
14053
|
const arbitrateSection = isArbitrator && (dispute.status === 'open' || dispute.status === 'in_review') ? `
|
|
14083
14054
|
<div style="margin-top:12px;border-top:1px solid #fecaca;padding-top:12px">
|
|
14084
14055
|
<div style="font-weight:600;font-size:13px;margin-bottom:8px">⚖️ 仲裁员裁定(截止 ${fmtTime(dispute.arbitrate_deadline)})</div>
|
|
14085
|
-
|
|
14086
|
-
|
|
14087
|
-
</div>
|
|
14088
|
-
<div id="arbitrate-msg"></div>
|
|
14056
|
+
${window.dpArbFeeNote ? window.dpArbFeeNote(dispute.payment_rail) : ''}
|
|
14057
|
+
<input type="hidden" id="arb-rail" value="${dispute.payment_rail || ''}"><div id="arbitrate-msg"></div>
|
|
14089
14058
|
|
|
14090
14059
|
<div class="form-group" style="margin-bottom:10px">
|
|
14091
14060
|
<label style="font-size:12px;color:#6b7280;display:block;margin-bottom:4px">裁定方式</label>
|
|
14092
14061
|
<div style="display:flex;flex-direction:column;gap:6px">
|
|
14093
|
-
${[
|
|
14094
|
-
['refund_buyer', '🔵 全额退款买家(买家胜诉,卖家承担)'],
|
|
14095
|
-
['release_seller', '🟢 资金释放给卖家(卖家胜诉)'],
|
|
14096
|
-
['partial_refund', '🟡 部分退款(折中,需填金额)'],
|
|
14097
|
-
['liability_split', '⚖️ 责任分配(指定各方赔付额)'],
|
|
14098
|
-
].map(([val, label]) => `
|
|
14062
|
+
${(window.dpArbRulingOptions ? window.dpArbRulingOptions(dispute.payment_rail, dispute.can_dismiss_to_negotiation) : []).map(([val, label]) => `
|
|
14099
14063
|
<label style="display:flex;align-items:center;gap:8px;font-size:13px;cursor:pointer;padding:8px;background:#f9fafb;border-radius:6px">
|
|
14100
14064
|
<input type="radio" name="arb-ruling-radio" value="${val}" onclick="onArbRulingChange('${val}')"> ${label}
|
|
14101
14065
|
</label>`).join('')}
|
|
@@ -14344,14 +14308,15 @@ window.handleArbitrate = async (disputeId) => {
|
|
|
14344
14308
|
if (!reason) { msgEl.innerHTML = alert$('error', t('请填写裁定理由')); return }
|
|
14345
14309
|
|
|
14346
14310
|
let body = { ruling, reason }
|
|
14311
|
+
const nc = document.getElementById('arb-rail')?.value === 'direct_p2p' // 直付:仅信誉裁决 → 不读/不发任何退款/赔付金额
|
|
14347
14312
|
|
|
14348
|
-
if (ruling === 'partial_refund') {
|
|
14313
|
+
if (!nc && ruling === 'partial_refund') {
|
|
14349
14314
|
const amount = document.getElementById('arb-amount')?.value
|
|
14350
14315
|
if (!amount) { msgEl.innerHTML = alert$('error', t('部分退款需填写退款金额')); return }
|
|
14351
14316
|
body = { ...body, refund_amount: Number(amount) }
|
|
14352
14317
|
}
|
|
14353
14318
|
|
|
14354
|
-
if (ruling === 'liability_split') {
|
|
14319
|
+
if (!nc && ruling === 'liability_split') {
|
|
14355
14320
|
const liabilityParties = []
|
|
14356
14321
|
let totalCalc = 0
|
|
14357
14322
|
for (const chk of document.querySelectorAll('.arb-liable-chk:checked')) {
|
|
@@ -14377,8 +14342,8 @@ window.handleArbitrate = async (disputeId) => {
|
|
|
14377
14342
|
body = { ...body, liability_parties: liabilityParties, refund_amount: totalCalc }
|
|
14378
14343
|
}
|
|
14379
14344
|
|
|
14380
|
-
msgEl.innerHTML = `<div class="alert alert-info"><span class="spinner"></span>${t('裁定中...')}</div>`
|
|
14381
|
-
const res = await POST(`/disputes/${disputeId}/arbitrate`, body)
|
|
14345
|
+
let _arbTk; try { _arbTk = await requestPasskeyGate('arbitrate', { dispute_id: disputeId }) } catch (e) { msgEl.innerHTML = alert$('error', (e && e.message ? e.message + ' — ' : '') + t('仲裁裁定需现场真人 Passkey 验证')); return }; msgEl.innerHTML = `<div class="alert alert-info"><span class="spinner"></span>${t('裁定中...')}</div>` // 所有 ruling(含驳回)先取 arbitrate gate token
|
|
14346
|
+
const res = await POST(`/disputes/${disputeId}/arbitrate`, { ...body, webauthn_token: _arbTk })
|
|
14382
14347
|
if (res.error) { msgEl.innerHTML = alert$('error', res.error); return }
|
|
14383
14348
|
|
|
14384
14349
|
// 显示仲裁费明细
|
|
@@ -14391,10 +14356,11 @@ window.handleArbitrate = async (disputeId) => {
|
|
|
14391
14356
|
|
|
14392
14357
|
// 裁定方式切换 → 控制相关输入框显隐
|
|
14393
14358
|
window.onArbRulingChange = (ruling) => {
|
|
14359
|
+
const nc = document.getElementById('arb-rail')?.value === 'direct_p2p' // 直付:无金额/无赔付 → 两个金额区块永不出现
|
|
14394
14360
|
const partialRow = document.getElementById('arb-partial-row')
|
|
14395
14361
|
const liabilityBlock = document.getElementById('arb-liability-block')
|
|
14396
|
-
if (partialRow) partialRow.style.display = ruling === 'partial_refund' ? '' : 'none'
|
|
14397
|
-
if (liabilityBlock) liabilityBlock.style.display = ruling === 'liability_split' ? '' : 'none'
|
|
14362
|
+
if (partialRow) partialRow.style.display = (!nc && ruling === 'partial_refund') ? '' : 'none'
|
|
14363
|
+
if (liabilityBlock) liabilityBlock.style.display = (!nc && ruling === 'liability_split') ? '' : 'none'
|
|
14398
14364
|
}
|
|
14399
14365
|
|
|
14400
14366
|
// 责任分配:勾选责任方时启用金额输入框,并实时计算合计
|
|
@@ -14577,7 +14543,7 @@ async function hydrateSimilarCases(disputeId) {
|
|
|
14577
14543
|
|
|
14578
14544
|
async function renderDisputeList(app) {
|
|
14579
14545
|
if (!state.user) { renderLogin(); return }
|
|
14580
|
-
if (state.
|
|
14546
|
+
if (!state.canArbitrate) { // PR-E:跟随后端 can_arbitrate(active whitelist),whitelist-only(role=buyer)真人仲裁员也能进
|
|
14581
14547
|
app.innerHTML = shell(`
|
|
14582
14548
|
<h1 class="page-title">${t('争议仲裁台')}</h1>
|
|
14583
14549
|
<div class="alert alert-info">${t('此功能仅限仲裁员使用。')}<br>${t('你的角色')}:${state.user.role}</div>
|
|
@@ -14767,7 +14733,7 @@ async function renderLogistics(app) {
|
|
|
14767
14733
|
<div style="flex:1;min-width:0">
|
|
14768
14734
|
<div style="font-size:13px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">${escHtml(o.product_title)}${windowChip}</div>
|
|
14769
14735
|
${o.shipping_address ? `<div style="font-size:11px;color:#6b7280;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-top:2px">📍 ${escHtml(o.shipping_address)}</div>` : ''}
|
|
14770
|
-
<div style="font-size:10px;color:${urgent ? '#dc2626' : '#9ca3af'};margin-top:2px${urgent ? ';font-weight:600' : ''}">${o
|
|
14736
|
+
<div style="font-size:10px;color:${urgent ? '#dc2626' : '#9ca3af'};margin-top:2px${urgent ? ';font-weight:600' : ''}">${window.orderAmountHtml(o)} · ${o.ship_deadline ? `${urgent ? '⚠ ' : ''}${t('截止')} ${fmtTime(o.ship_deadline)}` : fmtTime(o.created_at)}</div>
|
|
14771
14737
|
</div>
|
|
14772
14738
|
${act ? `<div style="font-size:11px;color:#065f46;font-weight:600;flex-shrink:0;white-space:nowrap">${act.short} →</div>` : ''}
|
|
14773
14739
|
</div>
|
|
@@ -14987,7 +14953,7 @@ function renderInsightsBlock(d) {
|
|
|
14987
14953
|
<div class="card" style="padding:12px">
|
|
14988
14954
|
<div style="font-size:10px;color:#9ca3af;text-transform:uppercase">${t('30 天 GMV')}</div>
|
|
14989
14955
|
<div style="font-size:18px;font-weight:800;color:#15803d;margin-top:2px">${Number(s.gmv||0).toFixed(0)} <span style="font-size:11px;font-weight:600;color:#6b7280">WAZ</span></div>
|
|
14990
|
-
<div style="font-size:10px;color:#6b7280;margin-top:2px">${deltaChip(vs.gmv_pct||0, t('环比'))}</div
|
|
14956
|
+
<div style="font-size:10px;color:#6b7280;margin-top:2px">${deltaChip(vs.gmv_pct||0, t('环比'))}</div>${window.gmvRailSplitHtml ? window.gmvRailSplitHtml(s.gmv_escrow, s.gmv_direct_pay) : ''}
|
|
14991
14957
|
</div>
|
|
14992
14958
|
<div class="card" style="padding:12px">
|
|
14993
14959
|
<div style="font-size:10px;color:#9ca3af;text-transform:uppercase">${t('30 天订单')}</div>
|
|
@@ -15310,7 +15276,7 @@ async function renderSeller(app) {
|
|
|
15310
15276
|
<div class="order-meta">${fmtTime(o.created_at)}</div>
|
|
15311
15277
|
<div style="margin-top:6px">${orderStatusBadges(o)}</div>
|
|
15312
15278
|
</div>
|
|
15313
|
-
<div class="order-amount">${o
|
|
15279
|
+
<div class="order-amount">${window.orderAmountHtml(o)}</div>
|
|
15314
15280
|
</div>
|
|
15315
15281
|
</div>`).join('')
|
|
15316
15282
|
const acceptHtml = paidOrders.length === 0
|
|
@@ -15391,7 +15357,7 @@ async function renderSeller(app) {
|
|
|
15391
15357
|
<div style="flex:1;min-width:0;cursor:pointer" onclick="navigate('#edit-product/${p.id}')">
|
|
15392
15358
|
<div style="font-size:13px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">${escHtml(p.title)}</div>
|
|
15393
15359
|
<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
|
|
15360
|
+
<span><strong style="color:#374151">${window.fmtPrice(p.price)}</strong></span>
|
|
15395
15361
|
<span style="color:${outOfStock?'#dc2626':lowStock?'#d97706':'#6b7280'}${outOfStock||lowStock?';font-weight:600':''}">${t('库存')} ${p.stock}${outOfStock?' ⛔':lowStock?' ⚠':''}</span>
|
|
15396
15362
|
${p.completion_count > 0 ? `<span>🛒 ${p.completion_count}</span>` : ''}
|
|
15397
15363
|
</div>
|
|
@@ -15424,7 +15390,7 @@ async function renderSeller(app) {
|
|
|
15424
15390
|
<div style="display:flex;justify-content:space-between;align-items:flex-start;gap:8px">
|
|
15425
15391
|
<div style="flex:1;min-width:0">
|
|
15426
15392
|
<div style="font-weight:600">${escHtml(p.title)}</div>
|
|
15427
|
-
<div style="font-size:13px;color:#6b7280;margin-top:2px">${p.price}
|
|
15393
|
+
<div style="font-size:13px;color:#6b7280;margin-top:2px">${window.fmtPrice(p.price)} · ${t('库存')} ${p.stock}</div>
|
|
15428
15394
|
${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
15395
|
${p.has_pending_task ? `<div style="font-size:11px;color:#d97706;margin-top:2px">⏳ ${t('链接核验中,请等待验证结果')}</div>` : ''}
|
|
15430
15396
|
${p.all_links_revoked ? `<div style="font-size:11px;color:#ef4444;margin-top:2px">❌ ${t('所有链接已失效,请先添加新链接')}</div>` : ''}
|
|
@@ -15445,7 +15411,7 @@ async function renderSeller(app) {
|
|
|
15445
15411
|
<div style="display:flex;justify-content:space-between;align-items:flex-start;gap:8px">
|
|
15446
15412
|
<div style="flex:1;min-width:0">
|
|
15447
15413
|
<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}
|
|
15414
|
+
<div style="font-size:13px;color:#d1d5db;margin-top:2px">${window.fmtPrice(p.price)}</div>
|
|
15449
15415
|
</div>
|
|
15450
15416
|
<div style="display:flex;gap:6px;flex-shrink:0;flex-wrap:wrap;justify-content:flex-end">
|
|
15451
15417
|
<span class="badge badge-red">${t('回收箱')}</span>
|
|
@@ -15516,8 +15482,10 @@ async function renderSeller(app) {
|
|
|
15516
15482
|
${subTabBtn('products', '📦 ' + t('商品'))}
|
|
15517
15483
|
${subTabBtn('marketing', '💎 ' + t('营销'))}
|
|
15518
15484
|
${subTabBtn('skills', '⚡ ' + t('Skill'))}
|
|
15485
|
+
${subTabBtn('settings', '⚙️ ' + t('设置'))}
|
|
15519
15486
|
</div>
|
|
15520
15487
|
`
|
|
15488
|
+
const settingsSection = sellerSubTab === 'settings' ? ((window.dpSellerReadinessSection ? window.dpSellerReadinessSection() : '') + (window.dpSellerFeeSection ? window.dpSellerFeeSection() : '') + (window.dpSalesReportSection ? window.dpSalesReportSection() : '') + (window.dpFeeRequestSection ? window.dpFeeRequestSection() : '') + (window.dpSellerDeferralSection ? window.dpSellerDeferralSection() : '') + (window.dpSellerProductVerifySection ? window.dpSellerProductVerifySection() : '') + (window.dpSellerStoreVerifySection ? window.dpSellerStoreVerifySection() : '') + (window.dpSellerInstructionSection ? window.dpSellerInstructionSection() : '') + (window.draAccountsSection ? window.draAccountsSection() : '') + (window.shipSellerSettingsSection ? window.shipSellerSettingsSection() : '') + (window.bondSellerSection ? window.bondSellerSection() : '')) : ''
|
|
15521
15489
|
|
|
15522
15490
|
// 数据中心区块(30 天聚合 — 销售曲线 / Top 商品 / 客户洞察 / 状态分布)
|
|
15523
15491
|
const insightsBlock = insights ? renderInsightsBlock(insights) : ''
|
|
@@ -15569,6 +15537,7 @@ async function renderSeller(app) {
|
|
|
15569
15537
|
<div style="font-weight:600;font-size:13px;color:#1e40af;margin-top:6px">${t('普通上架')}</div>
|
|
15570
15538
|
<div style="font-size:10px;color:#1d4ed8;margin-top:2px">${t('标准商品 · 即买即发')}</div>
|
|
15571
15539
|
</div>
|
|
15540
|
+
${window.freeShippingMarketingCard ? window.freeShippingMarketingCard() : ''}
|
|
15572
15541
|
<div onclick="location.hash='#seller-trials'" class="card" style="padding:14px;cursor:pointer;background:linear-gradient(135deg,#faf5ff,#fdf2f8);border-color:#ddd6fe">
|
|
15573
15542
|
<div style="font-size:24px">🎁</div>
|
|
15574
15543
|
<div style="font-weight:600;font-size:13px;color:#6b21a8;margin-top:6px">${t('测评免单活动')}</div>
|
|
@@ -15580,8 +15549,7 @@ async function renderSeller(app) {
|
|
|
15580
15549
|
const skillsSection = sellerSubTab === 'skills' ? `
|
|
15581
15550
|
<div style="font-size:12px;color:#6b7280;margin-bottom:10px">${t('Skill 自动化 · 让 Agent 替你接单/报价/发货')}</div>
|
|
15582
15551
|
` : ''
|
|
15583
|
-
// 注:Skill 真实内容由下方 #my-skills-list 同步渲染(mySkills 已同步就绪)
|
|
15584
|
-
// 此前这里有个 #skill-mgmt-content loading$() 占位但全文件无人填充 → spinner 永转,已删除。
|
|
15552
|
+
// 注:Skill 真实内容由下方 #my-skills-list 同步渲染(mySkills 已同步就绪);此前 #skill-mgmt-content loading$() 占位无人填充 → spinner 永转,已删除。
|
|
15585
15553
|
|
|
15586
15554
|
const productsSection = sellerSubTab === 'products' ? `
|
|
15587
15555
|
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px">
|
|
@@ -15600,6 +15568,7 @@ async function renderSeller(app) {
|
|
|
15600
15568
|
${marketingSection}
|
|
15601
15569
|
${skillsSection}
|
|
15602
15570
|
${productsSection}
|
|
15571
|
+
${settingsSection}
|
|
15603
15572
|
|
|
15604
15573
|
<!-- 商品分类标签页(仅 products sub-tab 下显示)-->
|
|
15605
15574
|
${sellerSubTab === 'products' ? `
|
|
@@ -15657,7 +15626,7 @@ async function renderSeller(app) {
|
|
|
15657
15626
|
<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
15627
|
<textarea class="form-control" id="imp-specs" rows="3"></textarea></div>
|
|
15659
15628
|
<div style="display:flex;gap:12px">
|
|
15660
|
-
<div class="form-group" style="flex:1"><label class="form-label">${t('价格(
|
|
15629
|
+
<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
15630
|
<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
15631
|
</div>
|
|
15663
15632
|
<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 +15657,7 @@ async function renderSeller(app) {
|
|
|
15688
15657
|
<div id="add-product-form" style="display:none">
|
|
15689
15658
|
<div class="divider"></div>
|
|
15690
15659
|
<div class="card">
|
|
15691
|
-
<div style="font-weight:700;margin-bottom:16px">${t('上架新商品')}</div>
|
|
15660
|
+
${window.createKindChooserHtml ? window.createKindChooserHtml('new') : ''}<div style="font-weight:700;margin-bottom:16px">${t('上架新商品')}</div>
|
|
15692
15661
|
<div id="add-msg"></div>
|
|
15693
15662
|
<div id="seo-score-bar-prd"></div> <!-- #1052 SEO 友好度评分,setupSeoScoreBar('prd') 启用 -->
|
|
15694
15663
|
|
|
@@ -15711,7 +15680,7 @@ async function renderSeller(app) {
|
|
|
15711
15680
|
<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
15681
|
<textarea class="form-control" id="prd-desc" rows="3" placeholder="${t('材质、尺寸、颜色、适用场景...')}"></textarea></div>
|
|
15713
15682
|
<div style="display:flex;gap:12px">
|
|
15714
|
-
<div class="form-group" style="flex:1"><label class="form-label">${t('价格(
|
|
15683
|
+
<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
15684
|
<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
15685
|
</div>
|
|
15717
15686
|
|
|
@@ -15726,7 +15695,7 @@ async function renderSeller(app) {
|
|
|
15726
15695
|
|
|
15727
15696
|
<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
15697
|
<textarea class="form-control" id="prd-specs" rows="3" placeholder="${t('材质: 陶瓷 容量: 350ml 颜色: 白色')}"></textarea></div>
|
|
15729
|
-
|
|
15698
|
+
<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
15699
|
<div style="display:flex;gap:12px">
|
|
15731
15700
|
<div class="form-group" style="flex:1"><label class="form-label">${t('分类')}</label>
|
|
15732
15701
|
<select class="form-control" id="prd-cat">
|
|
@@ -15739,7 +15708,7 @@ async function renderSeller(app) {
|
|
|
15739
15708
|
<div class="form-group" style="flex:1"><label class="form-label">${t('备货时间(小时)')}</label>
|
|
15740
15709
|
<input class="form-control" id="prd-handling" type="number" value="24" min="1"></div>
|
|
15741
15710
|
</div>
|
|
15742
|
-
|
|
15711
|
+
<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
15712
|
<details style="margin-bottom:16px">
|
|
15744
15713
|
<summary style="font-size:13px;color:#6b7280;cursor:pointer;padding:4px 0">${t('售后与物流(填写越完整,Agent 越优先选择你)')}</summary>
|
|
15745
15714
|
<div style="margin-top:12px;display:flex;gap:12px">
|
|
@@ -15758,8 +15727,8 @@ async function renderSeller(app) {
|
|
|
15758
15727
|
<input type="checkbox" id="prd-fragile" style="width:16px;height:16px">
|
|
15759
15728
|
<label for="prd-fragile" style="font-size:13px">${t('易碎品,需特殊包装')}</label>
|
|
15760
15729
|
</div>
|
|
15761
|
-
</details
|
|
15762
|
-
|
|
15730
|
+
</details>${window.listingCommerceSectionHtml ? window.listingCommerceSectionHtml(null) : ''}
|
|
15731
|
+
<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
15732
|
<!-- 2026-05-24 #981:测评免单 (Trial Review Refund) -->
|
|
15764
15733
|
<details style="margin-bottom:16px" id="prd-trial-section">
|
|
15765
15734
|
<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 +15820,7 @@ async function renderSeller(app) {
|
|
|
15851
15820
|
</div>
|
|
15852
15821
|
</div>
|
|
15853
15822
|
`, 'seller')
|
|
15823
|
+
if (sellerSubTab === 'settings') { window.dpHydrateInstruction && window.dpHydrateInstruction(); window.dpHydrateSellerReadiness && window.dpHydrateSellerReadiness(); window.dpHydrateSellerFee && window.dpHydrateSellerFee(); window.dpHydrateSalesReport && window.dpHydrateSalesReport(); window.dpHydrateFeeRequest && window.dpHydrateFeeRequest(); window.dpHydrateSellerDeferral && window.dpHydrateSellerDeferral(); window.dpHydrateSellerProductVerify && window.dpHydrateSellerProductVerify(); window.dpHydrateSellerStoreVerify && window.dpHydrateSellerStoreVerify(); window.draHydrateAccounts && window.draHydrateAccounts(); window.shipHydrateSellerSettings && window.shipHydrateSellerSettings(); window.bondHydrateSeller && window.bondHydrateSeller() }
|
|
15854
15824
|
|
|
15855
15825
|
// 智能下单"我也要上架"跳过来时:自动切到商品 tab + 展开手工上架表单 + 预填标题
|
|
15856
15826
|
// hashchange 可能多次触发 renderSeller — 用 window cache 保证每次重渲都能应用
|
|
@@ -16124,13 +16094,13 @@ window.doAddProduct = async () => {
|
|
|
16124
16094
|
const desc = document.getElementById('prd-desc').value.trim()
|
|
16125
16095
|
const price = Number(document.getElementById('prd-price').value)
|
|
16126
16096
|
const stock = Number(document.getElementById('prd-stock').value) || 1
|
|
16127
|
-
const category = document.getElementById('prd-cat').value
|
|
16097
|
+
const category = document.getElementById('prd-cat').value; const productType = document.getElementById('prd-type')?.value || 'retail'
|
|
16128
16098
|
const msgEl = document.getElementById('add-msg')
|
|
16129
16099
|
|
|
16130
16100
|
if (!title || !desc || !price) { msgEl.innerHTML = alert$('error', t('请填写商品名、描述、价格')); return }
|
|
16131
16101
|
|
|
16132
|
-
const extTitle = (document.getElementById('prd-ext-title')?.value || '').trim()
|
|
16133
|
-
const commissionPct = Number(document.getElementById('prd-commission')?.value || 10)
|
|
16102
|
+
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()
|
|
16103
|
+
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
16104
|
if (!(commissionPct >= 1 && commissionPct <= 50)) {
|
|
16135
16105
|
msgEl.innerHTML = alert$('error', t('佣金比例需在 1%-50% 之间')); return
|
|
16136
16106
|
}
|
|
@@ -16138,7 +16108,7 @@ window.doAddProduct = async () => {
|
|
|
16138
16108
|
const checkedAliases = Array.from(document.querySelectorAll('#prd-alias-candidates input[type="checkbox"]:checked'))
|
|
16139
16109
|
.map(cb => ({ type: cb.dataset.type, value: cb.dataset.value }))
|
|
16140
16110
|
const payload = {
|
|
16141
|
-
title, description: desc, price, stock, category,
|
|
16111
|
+
title, description: desc, price, stock, category, product_type: productType, brand, model,
|
|
16142
16112
|
specs: parseSpecs(document.getElementById('prd-specs').value),
|
|
16143
16113
|
handling_hours: Number(document.getElementById('prd-handling').value) || 24,
|
|
16144
16114
|
ship_regions: document.getElementById('prd-ship-regions').value.trim() || '全国',
|
|
@@ -16146,14 +16116,14 @@ window.doAddProduct = async () => {
|
|
|
16146
16116
|
return_days: Number(document.getElementById('prd-return').value) ?? 7,
|
|
16147
16117
|
return_condition: document.getElementById('prd-return-cond').value.trim(),
|
|
16148
16118
|
warranty_days: Number(document.getElementById('prd-warranty').value) ?? 0,
|
|
16149
|
-
fragile: document.getElementById('prd-fragile').checked ? 1 : 0,
|
|
16119
|
+
fragile: document.getElementById('prd-fragile').checked ? 1 : 0, low_stock_threshold: lowStock === '' || lowStock == null ? undefined : Number(lowStock), auto_delist_on_zero: autoDelist,
|
|
16150
16120
|
commission_rate: commissionPct / 100,
|
|
16151
16121
|
...(extTitle ? { external_title: extTitle } : {}),
|
|
16152
16122
|
...(checkedAliases.length ? { aliases: checkedAliases } : {}),
|
|
16153
|
-
...(state._addProductImgs?.length ? { image_hashes: state._addProductImgs.map(it => it.hash) } : {}),
|
|
16123
|
+
...(state._addProductImgs?.length ? { image_hashes: state._addProductImgs.map(it => it.hash) } : {}), ...(window.listingCommerceHasOverrides && window.listingCommerceHasOverrides() ? { create_status: 'warehouse' } : {}), // S4:含覆盖的新品先落仓库,全落定后激活
|
|
16154
16124
|
}
|
|
16155
16125
|
const res = await POST('/products', payload)
|
|
16156
|
-
if (res.error) { msgEl.innerHTML = alert$('error', res.error); return }
|
|
16126
|
+
if (res.error) { msgEl.innerHTML = alert$('error', res.error); return } if (res.product_id && window.listingCommerceSave) { const lc = await window.listingCommerceSave(res.product_id, { activate: payload.create_status === 'warehouse' }); if (!lc.ok) { msgEl.innerHTML = alert$('error', (payload.create_status === 'warehouse' ? t('商品已存为仓库(未公开),配送/税费设置失败,请到「我的商品」编辑修复后上架:') : t('单品配送/税费设置失败:')) + lc.error); return } }
|
|
16157
16127
|
|
|
16158
16128
|
// 产品创建成功 → 把每张图的 blob 存到本地 IDB + 注册 manifest(带 thumbnail)
|
|
16159
16129
|
// 服务端只拿到了 hash 数组;blob 字节留在卖家节点
|
|
@@ -16604,7 +16574,7 @@ async function renderEditProduct(app, productId) {
|
|
|
16604
16574
|
<div style="display:flex;gap:12px">
|
|
16605
16575
|
<div class="form-group" style="flex:1">
|
|
16606
16576
|
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:4px">
|
|
16607
|
-
<label class="form-label" style="margin:0">${t('价格(
|
|
16577
|
+
<label class="form-label" style="margin:0">${t('价格(USDC)')}</label>
|
|
16608
16578
|
<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
16579
|
</div>
|
|
16610
16580
|
<input class="form-control" id="ep-price" type="number" value="${p.price || ''}">
|
|
@@ -16703,7 +16673,7 @@ async function renderEditProduct(app, productId) {
|
|
|
16703
16673
|
<label for="ep-fragile" style="font-size:13px">${t('易碎品,需特殊包装')}</label>
|
|
16704
16674
|
</div>
|
|
16705
16675
|
</div>
|
|
16706
|
-
</details
|
|
16676
|
+
</details>${window.listingCommerceSectionHtml ? window.listingCommerceSectionHtml(p) : ''}
|
|
16707
16677
|
<button class="btn btn-primary" onclick="doUpdateProduct('${productId}')">${t('保存修改')}</button>
|
|
16708
16678
|
</div>
|
|
16709
16679
|
|
|
@@ -16888,7 +16858,7 @@ window.doUpdateProduct = async (productId) => {
|
|
|
16888
16858
|
body: JSON.stringify(payload),
|
|
16889
16859
|
}).then(r => r.json())
|
|
16890
16860
|
if (btn) { btn.disabled = false; btn.textContent = t('保存修改') }
|
|
16891
|
-
if (res.error) { msgEl.innerHTML = alert$('error', res.error); return }
|
|
16861
|
+
if (res.error) { msgEl.innerHTML = alert$('error', res.error); return } if (window.listingCommerceSave) { const lc = await window.listingCommerceSave(productId); if (!lc.ok) { msgEl.innerHTML = alert$('error', t('单品配送/税费设置失败:') + lc.error); return } } // S4 单品覆盖
|
|
16892
16862
|
msgEl.innerHTML = alert$('success', t('已保存'))
|
|
16893
16863
|
setTimeout(() => msgEl.innerHTML = '', 2000)
|
|
16894
16864
|
}
|
|
@@ -17116,7 +17086,7 @@ async function p2pConnectTo(peerId) {
|
|
|
17116
17086
|
|
|
17117
17087
|
const offer = await pc.createOffer()
|
|
17118
17088
|
await pc.setLocalDescription(offer)
|
|
17119
|
-
await POST('/signaling/send', { to: peerId, type: 'offer', data: offer })
|
|
17089
|
+
await POST('/signaling/send', { to: peerId, type: 'offer', data: offer }); if (window.pollBoost) pollBoost() // 主动建 P2P 会话 → 握手期间信令保持 3s
|
|
17120
17090
|
return entry
|
|
17121
17091
|
}
|
|
17122
17092
|
|
|
@@ -17300,8 +17270,8 @@ async function p2pStart() {
|
|
|
17300
17270
|
p2pStop()
|
|
17301
17271
|
await p2pHeartbeatTick()
|
|
17302
17272
|
await p2pSignalingTick()
|
|
17303
|
-
_heartbeatTimer = setInterval(p2pHeartbeatTick, 60_000)
|
|
17304
|
-
_signalingTimer = setInterval(p2pSignalingTick, 3_000)
|
|
17273
|
+
_heartbeatTimer = setInterval(() => { if (!document.hidden) p2pHeartbeatTick() }, 60_000) // 后台/锁屏暂停(poll-governor)
|
|
17274
|
+
_signalingTimer = setInterval(() => { if (!window.pollGate || window.pollGate('signaling')) p2pSignalingTick() }, 3_000); window._p2pSigTick = p2pSignalingTick // 按需化:governor 定 3s/60s 节奏+后台暂停(缺 governor fail-open 维持 3s);_p2pSigTick 供回前台补拉
|
|
17305
17275
|
}
|
|
17306
17276
|
|
|
17307
17277
|
function p2pStop() {
|
|
@@ -17342,7 +17312,7 @@ window.p2pUnpinContent = async (hash) => {
|
|
|
17342
17312
|
async function p2pSignalingTick() {
|
|
17343
17313
|
if (!state.user) return
|
|
17344
17314
|
try {
|
|
17345
|
-
const data = await GET('/signaling/poll')
|
|
17315
|
+
const data = await GET('/signaling/poll'); if (window.pollActivity) pollActivity('signaling', (data.signals || []).length) // 有信令进来 → governor 恢复 3s 快节奏
|
|
17346
17316
|
for (const s of (data.signals || [])) {
|
|
17347
17317
|
try { await p2pHandleSignal(s) } catch (e) { console.warn('[P2P] signal handle err', e) }
|
|
17348
17318
|
}
|
|
@@ -17433,7 +17403,7 @@ async function doBatchBuy(urls, addr, auto) {
|
|
|
17433
17403
|
<span style="font-size:12px;font-weight:700;color:${recColor};white-space:nowrap">${recLabel}</span>
|
|
17434
17404
|
</div>
|
|
17435
17405
|
${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}
|
|
17406
|
+
<span>${t('最佳替代')}:${escHtml(res.best_product.title)} · <strong>${window.fmtPrice(res.best_product.price)}</strong></span>
|
|
17437
17407
|
<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
17408
|
</div>` : ''}
|
|
17439
17409
|
${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 +17618,7 @@ window.doAgentBuy = async () => {
|
|
|
17648
17618
|
const bestCard = res.best_product ? `
|
|
17649
17619
|
<div style="background:#f0fdf4;border:1px solid #bbf7d0;border-radius:10px;padding:12px;margin:12px 0">
|
|
17650
17620
|
<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}
|
|
17621
|
+
<div style="font-size:18px;font-weight:700;color:#16a34a;margin-bottom:4px">${window.fmtPrice(res.best_product.price)}</div>
|
|
17652
17622
|
<div style="font-size:12px;color:#6b7280;margin-bottom:8px">${res.best_product.agent_summary || ''}</div>
|
|
17653
17623
|
${!res.auto_bought ? `<button class="btn btn-primary btn-sm" style="width:auto" onclick="navigate('#order-product/${res.best_product.id}')">${t('查看并下单')}</button>` : ''}
|
|
17654
17624
|
</div>` : ''
|
|
@@ -17668,7 +17638,7 @@ window.doAgentBuy = async () => {
|
|
|
17668
17638
|
<div style="font-size:13px;font-weight:500">${p.url_match ? '🎯 ' : ''}${p.title}</div>
|
|
17669
17639
|
<div style="font-size:11px;color:#6b7280">${p.agent_summary || ''}${p.url_match ? ` · <span style="color:#16a34a">${t('同款商品')}</span>` : ''}</div>
|
|
17670
17640
|
</div>
|
|
17671
|
-
<div style="font-weight:700;color:#1d4ed8;white-space:nowrap;margin-left:8px">${p.price}
|
|
17641
|
+
<div style="font-weight:700;color:#1d4ed8;white-space:nowrap;margin-left:8px">${window.fmtPrice(p.price)}</div>
|
|
17672
17642
|
</div>`).join('')}
|
|
17673
17643
|
</div>` : ''
|
|
17674
17644
|
|
|
@@ -17705,7 +17675,7 @@ async function renderVerifyTasks(app) {
|
|
|
17705
17675
|
// ── Verifier 状态 banner(依据 /verifier/status) ─────────────
|
|
17706
17676
|
const vState = vStatus?.state || 'none'
|
|
17707
17677
|
let verifierBanner = ''
|
|
17708
|
-
if (vState === 'none') {
|
|
17678
|
+
if (vState === 'none' && state.user?.role !== 'verifier' && !(state.user?.roles || []).includes('verifier')) { // 已是审核员(role 直授,无申请记录)不再邀请"申请审核员资格"(走查:身份矛盾卡)
|
|
17709
17679
|
verifierBanner = `
|
|
17710
17680
|
<div class="card" style="margin-bottom:16px;background:linear-gradient(135deg,#f5f3ff,#fce7f3);border-color:#c4b5fd">
|
|
17711
17681
|
<div style="font-size:15px;font-weight:600;margin-bottom:6px">🛡 ${t('申请审核员资格')}</div>
|
|
@@ -18349,7 +18319,7 @@ async function renderWallet(app) {
|
|
|
18349
18319
|
<div style="background:#ecfdf5;border-radius:6px;padding:8px">
|
|
18350
18320
|
<div style="font-size:9px;color:#065f46">📡 ${t('推荐分润')}</div>
|
|
18351
18321
|
<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>
|
|
18322
|
+
<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
18323
|
</div>
|
|
18354
18324
|
<div style="background:#fef3c7;border-radius:6px;padding:8px">
|
|
18355
18325
|
<div style="font-size:9px;color:#92400e">⏳ ${t('待结算')}</div>
|
|
@@ -18794,10 +18764,10 @@ function connectSSE() {
|
|
|
18794
18764
|
if (data.type === 'init') {
|
|
18795
18765
|
updateBadge(data.unread)
|
|
18796
18766
|
} else {
|
|
18797
|
-
// 实时推送:更新角标 + 显示 toast
|
|
18767
|
+
// 实时推送:更新角标 + 显示 toast(N1:模板通知按 viewer locale 渲染,旧推送回退原文)
|
|
18798
18768
|
state.unread++
|
|
18799
18769
|
updateBadge(state.unread)
|
|
18800
|
-
showToast(
|
|
18770
|
+
const _tn = window.notifRender ? window.notifRender(data) : data; showToast(_tn.title, _tn.body)
|
|
18801
18771
|
}
|
|
18802
18772
|
}
|
|
18803
18773
|
state.sse.onerror = () => {
|
|
@@ -19848,7 +19818,7 @@ async function shInjectStrip(containerId, opts = {}) {
|
|
|
19848
19818
|
${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
19819
|
</div>
|
|
19850
19820
|
<div style="padding:6px">
|
|
19851
|
-
<div style="font-size:13px;font-weight:700;color:#dc2626">${
|
|
19821
|
+
<div style="font-size:13px;font-weight:700;color:#dc2626">${window.fmtPrice(it.price)}</div>
|
|
19852
19822
|
<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
19823
|
<div style="font-size:9px;color:${cond.color};margin-top:3px;font-weight:600">${t(cond.label)}</div>
|
|
19854
19824
|
</div>
|
|
@@ -19874,8 +19844,8 @@ const SKM_KINDS = [
|
|
|
19874
19844
|
function skmKindMeta(k) { return SKM_KINDS.find(x => x.id === k) || { icon: '⚙️', label: k } }
|
|
19875
19845
|
function skmBillingLabel(mode, price) {
|
|
19876
19846
|
if (mode === 'free') return `🆓 ${t('免费')}`
|
|
19877
|
-
if (mode === 'per_use') return `🔁 ${
|
|
19878
|
-
return `💰 ${
|
|
19847
|
+
if (mode === 'per_use') return `🔁 ${window.fmtPrice(price)}/${t('次')}`
|
|
19848
|
+
return `💰 ${window.fmtPrice(price)}`
|
|
19879
19849
|
}
|
|
19880
19850
|
const SKM_STATUS = {
|
|
19881
19851
|
submitted: { label: '审核中', color: '#f59e0b' },
|
|
@@ -19964,9 +19934,9 @@ async function renderSkillDetail(app, id) {
|
|
|
19964
19934
|
} else if (l.billing_mode === 'one_time') {
|
|
19965
19935
|
actionBtn = owned
|
|
19966
19936
|
? `<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('购买')} · ${
|
|
19937
|
+
: `<button class="btn btn-primary" style="width:100%;font-size:14px;padding:10px" onclick="skmPurchase('${l.id}')">${t('购买')} · ${window.fmtPrice(l.price)}</button>`
|
|
19968
19938
|
} else {
|
|
19969
|
-
actionBtn = `<button class="btn btn-primary" style="width:100%;font-size:14px;padding:10px" onclick="skmReadContent('${l.id}','per_use')">${t('使用')} · ${
|
|
19939
|
+
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
19940
|
<div style="font-size:10px;color:#9ca3af;text-align:center;margin-top:6px">${t('按次付费:每次查看正文都会扣费')}</div>`
|
|
19971
19941
|
}
|
|
19972
19942
|
app.innerHTML = shell(`
|
|
@@ -20209,7 +20179,7 @@ async function renderSecondhandMarket(app) {
|
|
|
20209
20179
|
<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
20180
|
</div>
|
|
20211
20181
|
<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('价格区间 (
|
|
20182
|
+
<div style="font-size:11px;color:#6b7280;margin-bottom:6px">${t('价格区间 (USDC)')}</div>
|
|
20213
20183
|
<div style="display:flex;gap:6px;align-items:center;margin-bottom:10px">
|
|
20214
20184
|
<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
20185
|
<span style="color:#9ca3af">—</span>
|
|
@@ -20283,7 +20253,7 @@ function shItemCard(it) {
|
|
|
20283
20253
|
${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
20254
|
</div>
|
|
20285
20255
|
<div style="padding:8px">
|
|
20286
|
-
<div style="font-size:14px;font-weight:700;color:#dc2626">${
|
|
20256
|
+
<div style="font-size:14px;font-weight:700;color:#dc2626">${window.fmtPrice(it.price)}</div>
|
|
20287
20257
|
<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
20258
|
<div style="display:flex;align-items:center;gap:4px;font-size:9px;margin-top:4px">
|
|
20289
20259
|
<span style="background:${cond.color}1a;color:${cond.color};padding:1px 5px;border-radius:3px;font-weight:600">${t(cond.label)}</span>
|
|
@@ -20303,10 +20273,12 @@ async function renderSecondhandPublish(app) {
|
|
|
20303
20273
|
<button onclick="history.back()" style="background:none;border:none;font-size:18px;cursor:pointer;color:#6b7280">←</button>
|
|
20304
20274
|
<h1 style="font-size:18px;font-weight:600;margin:0">${t('发布闲置')}</h1>
|
|
20305
20275
|
</div>
|
|
20276
|
+
${window.createKindChooserHtml ? window.createKindChooserHtml('secondhand') : ''}
|
|
20306
20277
|
|
|
20307
20278
|
<div class="card" style="margin-bottom:12px">
|
|
20308
20279
|
<div class="card-body">
|
|
20309
20280
|
<div style="font-size:12px;color:#374151;font-weight:600;margin-bottom:8px">${t('图片(首张为封面,最多 9 张)')}</div>
|
|
20281
|
+
<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
20282
|
<input type="file" accept="image/*" multiple id="sh-imgs-input" style="display:none" onchange="shHandleImagesChange(this)">
|
|
20311
20283
|
<div id="sh-imgs-grid" style="display:grid;grid-template-columns:repeat(3,1fr);gap:6px"></div>
|
|
20312
20284
|
<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 +20468,7 @@ function shMineRenderItems(allItems) {
|
|
|
20496
20468
|
<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
20469
|
</div>
|
|
20498
20470
|
<div style="padding:8px">
|
|
20499
|
-
<div style="font-size:14px;font-weight:700;color:#dc2626">${
|
|
20471
|
+
<div style="font-size:14px;font-weight:700;color:#dc2626">${window.fmtPrice(it.price)}</div>
|
|
20500
20472
|
<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
20473
|
<div style="display:flex;justify-content:space-between;font-size:9px;color:#9ca3af;margin-top:4px">
|
|
20502
20474
|
<span>👁 ${it.view_count || 0}</span>
|
|
@@ -20539,12 +20511,11 @@ async function renderSecondhandDetail(app, id) {
|
|
|
20539
20511
|
</div>
|
|
20540
20512
|
<div style="padding:14px">
|
|
20541
20513
|
<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">${
|
|
20514
|
+
<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
20515
|
${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
20516
|
</div>
|
|
20545
20517
|
<h2 style="font-size:16px;color:#111827;line-height:1.4;margin:0 0 10px">${escHtml(it.title)}</h2>
|
|
20546
20518
|
<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
20519
|
<span style="background:#f3f4f6;color:#6b7280;padding:3px 8px;border-radius:4px">${shCatIcon(it.category)} ${t(shCatLabel(it.category))}</span>
|
|
20549
20520
|
<span style="background:#f3f4f6;color:#6b7280;padding:3px 8px;border-radius:4px">📦 ${t(ffLabel)}</span>
|
|
20550
20521
|
${it.region ? `<span style="background:#f3f4f6;color:#6b7280;padding:3px 8px;border-radius:4px">📍 ${escHtml(it.region)}</span>` : ''}
|
|
@@ -20558,8 +20529,9 @@ async function renderSecondhandDetail(app, id) {
|
|
|
20558
20529
|
<div style="color:#9ca3af;font-size:10px">@${escHtml(it.seller_handle || '')} · ${t('已浏览')} ${it.view_count}</div>
|
|
20559
20530
|
</div>
|
|
20560
20531
|
</div>
|
|
20561
|
-
<div style="background:#eef2ff;border-radius:8px;padding:10px;margin-bottom:14px;font-size:
|
|
20562
|
-
|
|
20532
|
+
<div style="background:#eef2ff;border-radius:8px;padding:10px 12px;margin-bottom:14px;font-size:10.5px;color:#4338ca;line-height:1.7">
|
|
20533
|
+
<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>
|
|
20534
|
+
🔒 ${t('资金托管·确认收货才放款')} · 🤝 ${t('面交也可托管')} · 🔎 ${t('成色声明可社区验证')}
|
|
20563
20535
|
</div>
|
|
20564
20536
|
${isOwn ? `
|
|
20565
20537
|
<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 +20556,7 @@ async function renderSecondhandDetail(app, id) {
|
|
|
20584
20556
|
${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
20557
|
</div>
|
|
20586
20558
|
<div style="padding:6px">
|
|
20587
|
-
<div style="font-size:13px;font-weight:700;color:#dc2626">${
|
|
20559
|
+
<div style="font-size:13px;font-weight:700;color:#dc2626">${window.fmtPrice(o.price)}</div>
|
|
20588
20560
|
<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
20561
|
<div style="font-size:9px;color:${ocond.color};margin-top:3px;font-weight:600">${t(ocond.label)}</div>
|
|
20590
20562
|
</div>
|
|
@@ -20622,7 +20594,7 @@ window.openShBuyModal = (id, price, ff) => {
|
|
|
20622
20594
|
const canMeet = ff === 'in_person' || ff === 'both'
|
|
20623
20595
|
_openModal(`
|
|
20624
20596
|
<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">${
|
|
20597
|
+
<div style="font-size:18px;color:#dc2626;font-weight:700;margin-bottom:14px">${window.fmtPrice(price)}</div>
|
|
20626
20598
|
<div style="font-size:12px;color:#374151;font-weight:600;margin-bottom:6px">${t('履约方式')}</div>
|
|
20627
20599
|
<div style="display:flex;gap:8px;margin-bottom:12px">
|
|
20628
20600
|
${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 +21125,7 @@ async function renderRfqFeed(app) {
|
|
|
21153
21125
|
const bidsLabel = it.bid_count > 0 ? `💎 ${it.bid_count} ${t('个报价')}` : t('等待商家报价')
|
|
21154
21126
|
return `<div class="card" style="margin-bottom:8px;padding:12px;cursor:pointer" onclick="navigate('#rfq/${it.id}')">
|
|
21155
21127
|
<div style="font-size:13px;line-height:1.5">
|
|
21156
|
-
✍️ ${feedActor(it.buyer_id, it.buyer_name, it.buyer_handle)} ${t('
|
|
21128
|
+
✍️ ${feedActor(it.buyer_id, it.buyer_name, it.buyer_handle)} ${t('想买')} <strong>${escHtml(it.title || it.category)}</strong>
|
|
21157
21129
|
${it.qty ? ` × ${it.qty}` : ''}
|
|
21158
21130
|
${it.budget ? ` · ${t('预算')} ${it.budget} WAZ` : ''}
|
|
21159
21131
|
</div>
|
|
@@ -21492,7 +21464,7 @@ async function renderMessages(app, deepSub) {
|
|
|
21492
21464
|
body = conversations.length === 0
|
|
21493
21465
|
? `<div style="text-align:center;color:#9ca3af;padding:40px 0;font-size:13px">${t('暂无会话 — 在订单 / RFQ / 跟卖详情页发起聊天')}</div>`
|
|
21494
21466
|
: conversations.map(it => {
|
|
21495
|
-
const kindLabel = it.kind === 'order' ? '📦 ' + t('订单') : it.kind === 'rfq' ? '📩 ' + t('求购') : '🏷 ' + t('问商品')
|
|
21467
|
+
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
21468
|
return `
|
|
21497
21469
|
<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
21470
|
<div style="display:flex;justify-content:space-between;align-items:flex-start;gap:10px">
|
|
@@ -21522,9 +21494,9 @@ async function renderMessages(app, deepSub) {
|
|
|
21522
21494
|
}
|
|
21523
21495
|
body = notifications.length === 0
|
|
21524
21496
|
? `<div style="text-align:center;color:#9ca3af;padding:40px 0;font-size:13px">🔔 ${t('暂无通知')}</div>`
|
|
21525
|
-
: notifications.map(
|
|
21526
|
-
|
|
21527
|
-
try { actions = n.actions ? JSON.parse(n.actions) : [] } catch {}
|
|
21497
|
+
: notifications.map(nRaw => {
|
|
21498
|
+
const n = window.notifRender ? window.notifRender(nRaw) : nRaw // N1:template_key → viewer locale 渲染;旧行回退存量 title/body
|
|
21499
|
+
let actions = []; try { actions = n.actions ? JSON.parse(n.actions) : [] } catch {}
|
|
21528
21500
|
const route = routeFor(n)
|
|
21529
21501
|
const hasActions = Array.isArray(actions) && actions.length > 0
|
|
21530
21502
|
// 卡片 onclick:仅当无 actions 时整卡可点(兼容旧 notif)
|
|
@@ -21694,7 +21666,7 @@ async function renderChatDetail(app, id) {
|
|
|
21694
21666
|
|
|
21695
21667
|
// 自动滚到底
|
|
21696
21668
|
const msgsEl = document.getElementById('chat-msgs')
|
|
21697
|
-
if (msgsEl) msgsEl.scrollTop = msgsEl.scrollHeight
|
|
21669
|
+
if (msgsEl) { msgsEl.scrollTop = msgsEl.scrollHeight; if (window.startChatPoll) window.startChatPoll(id, messages) } // #30 进入会话后开实时轮询(传初始消息→按末条 id 判新,自清理离开即停)
|
|
21698
21670
|
}
|
|
21699
21671
|
|
|
21700
21672
|
function renderChatBubble(m, myId) {
|
|
@@ -22105,7 +22077,7 @@ async function renderAuctionsFeed(app) {
|
|
|
22105
22077
|
const tag = bidCount > 0 ? `🔥 ${bidCount} ${t('次出价')}` : t('暂无出价 · 先到先得')
|
|
22106
22078
|
return `<div class="card" style="margin-bottom:8px;padding:12px;cursor:pointer" onclick="navigate('#auction/${d.id}')">
|
|
22107
22079
|
<div style="font-size:13px;line-height:1.5">
|
|
22108
|
-
💎 ${feedActor(d.seller_id, d.seller_name, d.seller_handle)} ${t('
|
|
22080
|
+
💎 ${feedActor(d.seller_id, d.seller_name, d.seller_handle)} ${t('发起了拍卖')} <strong>${escHtml(d.title)}</strong>
|
|
22109
22081
|
</div>
|
|
22110
22082
|
<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
22083
|
</div>`
|
|
@@ -22114,7 +22086,7 @@ async function renderAuctionsFeed(app) {
|
|
|
22114
22086
|
<div style="font-size:13px;line-height:1.5">
|
|
22115
22087
|
♻️ ${feedActor(d.seller_id, d.seller_name, d.seller_handle)} ${t('发布二手')} <strong>${escHtml(d.title)}</strong>
|
|
22116
22088
|
</div>
|
|
22117
|
-
<div style="font-size:11px;color:#6b7280;margin-top:4px">${d.price}
|
|
22089
|
+
<div style="font-size:11px;color:#6b7280;margin-top:4px">${window.fmtPrice(d.price)} · ${d.condition_grade || ''} · ${ts}</div>
|
|
22118
22090
|
</div>`
|
|
22119
22091
|
}
|
|
22120
22092
|
}).join('')
|
|
@@ -22433,7 +22405,7 @@ async function renderLeaderboard(app) {
|
|
|
22433
22405
|
const hasRating = u.rating_count > 0
|
|
22434
22406
|
const ratingColor = hasRating && u.avg_rating >= 4.5 ? '#16a34a' : hasRating && u.avg_rating >= 4 ? '#d97706' : '#9ca3af'
|
|
22435
22407
|
return `
|
|
22436
|
-
<div class="card" style="padding:12px;margin-bottom:8px;display:flex;gap:10px;align-items:center
|
|
22408
|
+
<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
22409
|
<div style="font-size:24px;font-weight:800;color:${i<3?'#dc2626':'#9ca3af'};min-width:32px;text-align:center">${i+1}</div>
|
|
22438
22410
|
<div style="flex:1;min-width:0">
|
|
22439
22411
|
<div style="font-weight:600;font-size:14px">@${escHtml(u.handle || u.name?.slice(0,8) || '?')}</div>
|
|
@@ -22458,7 +22430,7 @@ async function renderLeaderboard(app) {
|
|
|
22458
22430
|
🤖 @${escHtml(u.handle || u.name?.slice(0,8) || '?')}
|
|
22459
22431
|
<span style="font-size:10px;background:${lvlColor}22;color:${lvlColor};padding:1px 7px;border-radius:99px;font-weight:600">${lvlLabel}</span>
|
|
22460
22432
|
</div>
|
|
22461
|
-
<div style="font-size:11px;color:#6b7280;margin-top:2px">${
|
|
22433
|
+
<div style="font-size:11px;color:#6b7280;margin-top:2px">${t('活跃度')}: ${t({ active: '活跃', quiet: '较少', dormant: '休眠' }[u.activity] || '休眠')}</div>
|
|
22462
22434
|
</div>
|
|
22463
22435
|
<div style="text-align:right">
|
|
22464
22436
|
<div style="font-size:13px;font-weight:700;color:${lvlColor}">${ts}</div>
|
|
@@ -22535,7 +22507,7 @@ async function renderLeaderboard(app) {
|
|
|
22535
22507
|
</div>
|
|
22536
22508
|
</div>
|
|
22537
22509
|
<div style="text-align:right">
|
|
22538
|
-
<div style="font-size:14px;font-weight:800;color:#dc2626">${p.price}
|
|
22510
|
+
<div style="font-size:14px;font-weight:800;color:#dc2626">${window.fmtPrice(p.price)}</div>
|
|
22539
22511
|
<div style="font-size:10px;color:#854d0e;margin-top:1px">${pctLabel}</div>
|
|
22540
22512
|
</div>
|
|
22541
22513
|
</div>`
|
|
@@ -22546,14 +22518,14 @@ async function renderLeaderboard(app) {
|
|
|
22546
22518
|
<div style="flex:1;min-width:0">
|
|
22547
22519
|
<div style="font-weight:600;font-size:14px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${escHtml(p.title)}</div>
|
|
22548
22520
|
<div style="font-size:11px;color:#6b7280;margin-top:2px">
|
|
22549
|
-
@${escHtml(p.seller_handle || p.seller_name?.slice(0,8) || '?')} · ${p.price}
|
|
22521
|
+
@${escHtml(p.seller_handle || p.seller_name?.slice(0,8) || '?')} · ${window.fmtPrice(p.price)} ·
|
|
22550
22522
|
🛒 ${p.completion_count || 0} · 🤝 ${p.unique_sharer_count || 0} · ❤️ ${p.total_likes || 0}
|
|
22551
22523
|
</div>
|
|
22552
22524
|
</div>
|
|
22553
22525
|
<div style="text-align:right;font-size:12px;font-weight:700;color:#4f46e5">${Number(p.rank_score).toFixed(1)}</div>
|
|
22554
22526
|
</div>
|
|
22555
22527
|
`).join('') : items.map((c, i) => `
|
|
22556
|
-
<div class="card" style="padding:12px;margin-bottom:8px;display:flex;gap:10px;align-items:center
|
|
22528
|
+
<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
22529
|
<div style="font-size:24px;font-weight:800;color:${i<3?'#dc2626':'#9ca3af'};min-width:32px;text-align:center">${i+1}</div>
|
|
22558
22530
|
<div style="flex:1;min-width:0">
|
|
22559
22531
|
<div style="font-weight:600;font-size:14px">@${escHtml(c.handle || c.name?.slice(0,8) || '?')}${c.region ? ' · ' + regionLabel(c.region) : ''}</div>
|
|
@@ -22608,16 +22580,16 @@ async function renderP2pBoard(app) {
|
|
|
22608
22580
|
<div style="font-size:11px;color:#6b7280;margin-bottom:14px;line-height:1.5">${t('详情存卖家节点 · WebAZ 只锚定哈希 · 客户端验证防作弊')}</div>
|
|
22609
22581
|
${items.length === 0 ? `<div style="text-align:center;color:#9ca3af;padding:40px 0">${t('暂无 P2P 商品')}</div>` : items.map(it => {
|
|
22610
22582
|
let thumb = ''
|
|
22611
|
-
|
|
22583
|
+
thumb = window.productThumbSrc(it.thumbnail_json) || thumb
|
|
22612
22584
|
return `
|
|
22613
22585
|
<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>`}
|
|
22586
|
+
${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
22587
|
<div style="flex:1;min-width:0">
|
|
22616
22588
|
<div style="font-weight:600;font-size:14px;margin-bottom:4px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${escHtml(it.title)}</div>
|
|
22617
22589
|
<div style="font-size:11px;color:#6b7280;margin-bottom:3px">@${escHtml(it.seller_handle || it.seller_id.slice(0,8))} · ${it.region}</div>
|
|
22618
22590
|
<div style="display:flex;justify-content:space-between;align-items:center">
|
|
22619
22591
|
<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}
|
|
22592
|
+
<span style="color:#dc2626;font-weight:700;font-size:14px">${window.fmtPrice(it.price)}</span>
|
|
22621
22593
|
</div>
|
|
22622
22594
|
</div>
|
|
22623
22595
|
</div>`
|
|
@@ -22712,7 +22684,7 @@ async function renderP2pDetail(app, id) {
|
|
|
22712
22684
|
if (r?.error) { document.getElementById('p2p-detail').innerHTML = alert$('error', r.error); return }
|
|
22713
22685
|
const p = r.product
|
|
22714
22686
|
let thumb = ''
|
|
22715
|
-
|
|
22687
|
+
thumb = window.productThumbSrc(p.thumbnail_json) || thumb
|
|
22716
22688
|
|
|
22717
22689
|
// 客户端尝试从 peer_endpoint 拉详情并验签
|
|
22718
22690
|
let verifyStatus = `<span style="color:#9ca3af">${t('未尝试拉取')}</span>`
|
|
@@ -22746,11 +22718,11 @@ async function renderP2pDetail(app, id) {
|
|
|
22746
22718
|
document.getElementById('p2p-detail').innerHTML = `
|
|
22747
22719
|
<button class="btn btn-sm" onclick="history.back()" style="background:#f3f4f6;margin-bottom:14px">←</button>
|
|
22748
22720
|
<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">` : ''}
|
|
22721
|
+
${thumb ? `<img src="${escHtml(thumb)}" onerror="this.outerHTML='🌐'" style="width:120px;height:120px;object-fit:cover;border-radius:8px;flex-shrink:0">` : ''}
|
|
22750
22722
|
<div style="flex:1">
|
|
22751
22723
|
<h2 style="font-size:18px;font-weight:700;margin:0 0 4px">${escHtml(p.title)}</h2>
|
|
22752
22724
|
<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}
|
|
22725
|
+
<div style="color:#dc2626;font-weight:700;font-size:22px;margin-bottom:6px">${window.fmtPrice(p.price)}</div>
|
|
22754
22726
|
<div style="font-size:11px;color:#6b7280">${t('库存')} ${p.stock}</div>
|
|
22755
22727
|
</div>
|
|
22756
22728
|
</div>
|
|
@@ -22769,7 +22741,7 @@ async function renderP2pDetail(app, id) {
|
|
|
22769
22741
|
</div>` : `
|
|
22770
22742
|
<div class="card" style="padding:14px;margin-bottom:12px;background:#fafafa;color:#9ca3af;text-align:center">${t('完整详情未能从节点获取 — 仅展示 WebAZ 上的最小信息')}</div>`}
|
|
22771
22743
|
|
|
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}
|
|
22744
|
+
${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
22745
|
`
|
|
22774
22746
|
}
|
|
22775
22747
|
|
|
@@ -22830,7 +22802,7 @@ function renderPriceHistoryHtml(r) {
|
|
|
22830
22802
|
<div style="flex:1;background:#fafafa;padding:8px;border-radius:6px;text-align:center">
|
|
22831
22803
|
<div style="font-size:10px;color:#6b7280;margin-bottom:2px">${label}</div>
|
|
22832
22804
|
<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)}
|
|
22805
|
+
<div style="font-size:10px;color:#6b7280;margin-top:2px">${data.sales}${t('单')} · ${fmtPrice(data.volume)} USDC</div>
|
|
22834
22806
|
</div>`
|
|
22835
22807
|
|
|
22836
22808
|
return `
|
|
@@ -22840,7 +22812,7 @@ function renderPriceHistoryHtml(r) {
|
|
|
22840
22812
|
${windowCard(t('近 90 天'), w.d90)}
|
|
22841
22813
|
${windowCard(t('全部'), w.lifetime)}
|
|
22842
22814
|
</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)}
|
|
22815
|
+
${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
22816
|
`<div style="font-size:11px;color:#9ca3af;margin-bottom:10px;font-style:italic">${t('未设置具体类目 — 无同类对比(卖家可在商品编辑页选择类目)')}</div>`}
|
|
22845
22817
|
<div style="font-size:11px;color:#374151;margin-bottom:6px;font-weight:600">${t('价位分布')}(${t('90 天,按成交单数')})</div>
|
|
22846
22818
|
${bucketsHtml}
|
|
@@ -22917,11 +22889,8 @@ async function renderAdminProtocol(app) {
|
|
|
22917
22889
|
${adminLinkCard('🏦', t('慈善基金拨款'), t('从基金池拨款给指定用户'), '#admin/fund-disburse')}
|
|
22918
22890
|
${adminLinkCard('📜', t('审计日志'), t('全部 admin 写操作'), '#admin/audit')}
|
|
22919
22891
|
${adminLinkCard('🛑', t('错误监控'), t('24h 趋势 + burst 告警'), '#admin/errors')}
|
|
22920
|
-
${adminLinkCard('
|
|
22921
|
-
${adminLinkCard('
|
|
22922
|
-
${((state.user && state.user.admin_type || 'root') === 'root') ? adminLinkCard('🎟️', t('建任务额度审核'), t('非根管理员的建任务扩容申请;批准 = 限时计数授权(仅 root)'), '#admin/quota-requests') : ''}
|
|
22923
|
-
${adminLinkCard('🔗', t('关联个人贡献账号'), t('把本管理席位的协调贡献归属到你的真实个人账号(需对方确认 + root 审批)'), '#me/operator-claims')}
|
|
22924
|
-
${((state.user && state.user.admin_type || 'root') === 'root') ? adminLinkCard('🪪', t('操作席位关联审批'), t('管理席位→个人贡献账号的关联申请;确认 + 审批 / 撤销(仅 root)'), '#admin/operator-claims') : ''}
|
|
22892
|
+
${adminLinkCard('🌱', t('共建运营'), t('共建任务建议 / Welcome 提交 / 建任务额度 / 贡献账号关联'), '#admin/contribution-ops')}
|
|
22893
|
+
${((state.user && state.user.admin_type || 'root') === 'root') ? adminLinkCard('💳', t('Direct Pay 商户运营'), t('直付:合规录入 / 缓交 / 商品·店铺验证 / 平台服务费预充值 / 上线控制(仅 root)'), '#admin/dp-ops') : ''}${((state.user && state.user.admin_type || 'root') === 'root') ? adminLinkCard('⚖', t('仲裁员管理'), t('授权/暂停/撤销真人仲裁员(唯一授权源=active 白名单,需现场 Passkey)'), '#admin/arbitrators') + adminLinkCard('🚦', t('Agent 封禁与申诉'), t('待审申诉队列(批准=立即解封)/ 主动签发 strike'), '#admin/agent-strikes') : ''}
|
|
22925
22894
|
</div>
|
|
22926
22895
|
`, 'admin-protocol')
|
|
22927
22896
|
}
|
|
@@ -22971,7 +22940,7 @@ async function renderAdminWishReports(app) {
|
|
|
22971
22940
|
}
|
|
22972
22941
|
window.setWrStatus = (s) => { state._wrStatus = s; renderAdminWishReports(document.getElementById('app')) }
|
|
22973
22942
|
window.adminWrAction = async (id, action) => {
|
|
22974
|
-
if (!confirm(
|
|
22943
|
+
if (!confirm(action === 'dismiss' ? t('确认驳回?') : t('确认下架?'))) return
|
|
22975
22944
|
const r = await PATCH('/admin/wish-reports/' + id, { action })
|
|
22976
22945
|
if (r?.error) return alert(r.error)
|
|
22977
22946
|
renderAdminWishReports(document.getElementById('app'))
|
|
@@ -24493,7 +24462,7 @@ async function renderNoteLanding(app, noteId) {
|
|
|
24493
24462
|
<div style="flex:1;min-width:0">
|
|
24494
24463
|
<div style="font-size:10px;color:#9a3412;font-weight:600;margin-bottom:2px">📦 ${t('笔记关联商品')}</div>
|
|
24495
24464
|
<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">${
|
|
24465
|
+
<div style="font-size:15px;font-weight:800;color:#dc2626;margin-top:2px">${window.fmtPrice(product.price || 0)}</div>
|
|
24497
24466
|
</div>
|
|
24498
24467
|
<button class="btn btn-primary btn-sm" style="white-space:nowrap" onclick="event.stopPropagation();navigate('#order-product/${product.id}')">${t('去购买')}</button>
|
|
24499
24468
|
</div>` : ''
|
|
@@ -25182,7 +25151,7 @@ async function renderSharesFeed(app, sort) {
|
|
|
25182
25151
|
<span>❤️ ${n.stats.likes || 0} · 🔗 ${n.stats.clicks || 0}</span>
|
|
25183
25152
|
</div>
|
|
25184
25153
|
${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))} · ${
|
|
25154
|
+
${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
25155
|
</div>
|
|
25187
25156
|
</div>
|
|
25188
25157
|
</div>
|
|
@@ -25683,7 +25652,7 @@ async function renderBuyerTrials(app) {
|
|
|
25683
25652
|
<div style="display:flex;justify-content:space-between;align-items:flex-start;gap:10px;margin-bottom:8px">
|
|
25684
25653
|
<div style="flex:1;min-width:0">
|
|
25685
25654
|
<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}
|
|
25655
|
+
<div style="font-size:11px;color:#6b7280;margin-top:2px">${window.fmtPrice(c.product_price)} · ${fmtTime(c.claimed_at)}</div>
|
|
25687
25656
|
</div>
|
|
25688
25657
|
<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
25658
|
</div>
|
|
@@ -25724,7 +25693,7 @@ async function renderSellerTrials(app) {
|
|
|
25724
25693
|
<div style="display:flex;justify-content:space-between;align-items:flex-start;gap:10px;margin-bottom:8px">
|
|
25725
25694
|
<div style="flex:1;min-width:0">
|
|
25726
25695
|
<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}
|
|
25696
|
+
<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
25697
|
</div>
|
|
25729
25698
|
<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
25699
|
</div>
|