@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
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// [PRELAUNCH-WAZ-SIM] 预发布期 escrow 模拟币提醒。预发布期 escrow 用模拟 WAZ(非真钱),
|
|
2
|
+
// 为防买家把测试币当真实价值,做三件事 —— 全部门控在 window._wazSimulated:
|
|
3
|
+
// 1) 支付方式不再默认预选 escrow(app-direct-pay.js: dpRailSelectorHtml 的 checked + dpSelectedRail 兜底)
|
|
4
|
+
// 2) 买家切到 escrow 时内联提醒(wazEscrowRailNote 注入 #dp-rail-block,app-direct-pay.js 无需扩容)
|
|
5
|
+
// 3) 生成的 escrow 订单在买家端订单详情顶部强提醒(wazEscrowOrderBanner,app.js renderOrderDetail 调用)
|
|
6
|
+
// 真实结算上线时:把下面 _wazSimulated 置 false(一处)→ 全部 no-op、escrow 预选行为恢复。
|
|
7
|
+
// 彻底移除:grep '[PRELAUNCH-WAZ-SIM]' / '_wazSimulated'(本文件 + app.js + app-direct-pay.js + index.html
|
|
8
|
+
// + package.json check:pwa-syntax + .github/workflows/ci.yml + complexity-ratchet-guard.ts + i18n.js)。
|
|
9
|
+
// UI-only:不碰 wallet/escrow/settlement/订单钱路;纯展示 + 一个"必须显式选 rail"的前端门(后端不变)。
|
|
10
|
+
window._wazSimulated = true
|
|
11
|
+
|
|
12
|
+
// 内联提醒文案(单一真相,两处复用)。
|
|
13
|
+
const WAZ_SIM_NOTE = () => '⚠️ ' + t('测试模式:WAZ 为模拟货币,不代表真实价值。此为预发布演示,你"托管"的并非真实资金,确认收货后释放的也是测试币。')
|
|
14
|
+
|
|
15
|
+
// 买家在"支付方式"里选中 Escrow 时的内联提醒。selectedRail = 当前选中的 rail 值('escrow' / 'direct_p2p' / undefined)。
|
|
16
|
+
// 注入到 #dp-rail-block 内,避免扩容 app-direct-pay.js(其已到 LOC 上限)。非模拟期直接 no-op。
|
|
17
|
+
window.wazEscrowRailNote = (selectedRail) => {
|
|
18
|
+
if (!window._wazSimulated) return
|
|
19
|
+
const block = document.getElementById('dp-rail-block'); if (!block) return
|
|
20
|
+
const host = block.querySelector('div') || block
|
|
21
|
+
let el = document.getElementById('dp-rail-escrow-sim')
|
|
22
|
+
if (selectedRail !== 'escrow') { if (el) el.style.display = 'none'; return }
|
|
23
|
+
if (!el) {
|
|
24
|
+
el = document.createElement('div'); el.id = 'dp-rail-escrow-sim'
|
|
25
|
+
el.style.cssText = 'margin-top:8px;font-size:11px;line-height:1.6;color:#92400e;background:#fffbeb;border:1px solid #fde68a;border-radius:8px;padding:8px 10px'
|
|
26
|
+
host.appendChild(el)
|
|
27
|
+
}
|
|
28
|
+
el.textContent = WAZ_SIM_NOTE()
|
|
29
|
+
el.style.display = ''
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// 生成 escrow 订单后,买家端订单详情顶部的强提醒横幅。非 escrow / 非买家 / 非模拟 → 空串(不渲染)。
|
|
33
|
+
window.wazEscrowOrderBanner = (order, isBuyer) => {
|
|
34
|
+
if (!window._wazSimulated || !isBuyer || !order || order.payment_rail === 'direct_p2p') return ''
|
|
35
|
+
return `<div style="border:1px solid #fde68a;background:linear-gradient(135deg,#fffbeb,#fef3c7);border-radius:12px;padding:12px 14px;margin-bottom:10px">
|
|
36
|
+
<div style="font-size:13px;font-weight:700;color:#92400e;margin-bottom:4px">🧪 ${t('测试托管订单')}</div>
|
|
37
|
+
<div style="font-size:12px;line-height:1.6;color:#374151">${t('本单为预发布演示:WAZ 是模拟货币,不代表真实价值,本协议尚未进行真实资金结算。请勿据此向任何人支付真实款项。')}</div>
|
|
38
|
+
</div>`
|
|
39
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// Buyer price display (DISPLAY-ONLY) — render a product price in USDC with a real-time local-currency hint.
|
|
2
|
+
// fmtPrice(usdc) → a <span data-usdc-price> wrapper whose content is "<amount> USDC" + (when live rates + a
|
|
3
|
+
// non-USD buyer currency are available) a muted "≈ <local>". USDC is the display/pricing unit (money.ts:
|
|
4
|
+
// 1 WAZ = 1 USDC = 1e6 base-units, USDC-aligned); this NEVER implies WebAZ holds/settles real USDC —
|
|
5
|
+
// escrow is still simulated WAZ, direct-pay is off-platform.
|
|
6
|
+
// Local currency is derived from the buyer's account region (no manual picker). Rates come from /api/fx/rates
|
|
7
|
+
// (cached + fallback server-side). If rates are unavailable the price simply shows USDC — display never breaks.
|
|
8
|
+
// First-paint race: rates load async; when they arrive, refreshFxPrices() re-renders already-painted price
|
|
9
|
+
// nodes ([data-usdc-price] / [data-usdc-local]) so the "≈ local" hint appears without a full re-render.
|
|
10
|
+
window._fxRates = null
|
|
11
|
+
window.loadFxRates = async () => {
|
|
12
|
+
try {
|
|
13
|
+
const res = await fetch('/api/fx/rates', { signal: AbortSignal.timeout(6000) })
|
|
14
|
+
if (res.ok) { window._fxRates = await res.json(); window.refreshFxPrices() }
|
|
15
|
+
} catch { /* leave null → prices show USDC-only */ }
|
|
16
|
+
}
|
|
17
|
+
window.buyerCurrency = () => {
|
|
18
|
+
const map = { china: 'CNY', us: 'USD', eu: 'EUR', india: 'INR', singapore: 'SGD', global_north: 'USD', global: 'USD' }
|
|
19
|
+
const region = (window.state && window.state.user && window.state.user.region) || ''
|
|
20
|
+
return map[String(region).toLowerCase()] || 'USD'
|
|
21
|
+
}
|
|
22
|
+
// usdc amount → localized "≈ ¥X" string, or '' (USD buyer / no rates / unusable rate → show USDC only).
|
|
23
|
+
window._fxLocal = (usdc) => {
|
|
24
|
+
const r = window._fxRates
|
|
25
|
+
const cur = window.buyerCurrency()
|
|
26
|
+
if (!r || !r.rates || cur === 'USD') return ''
|
|
27
|
+
const rate = Number(r.rates[cur])
|
|
28
|
+
const n = Number(usdc)
|
|
29
|
+
if (!(rate > 0) || !Number.isFinite(n)) return ''
|
|
30
|
+
const local = n * rate
|
|
31
|
+
const sym = { CNY: '¥', EUR: '€', INR: '₹', SGD: 'S$', USD: '$', IDR: 'Rp', MYR: 'RM', PHP: '₱', VND: '₫', THB: '฿' }[cur] || (cur + ' ')
|
|
32
|
+
return sym + (local >= 100 ? String(Math.round(local)) : local.toFixed(2))
|
|
33
|
+
}
|
|
34
|
+
// visible price content (no wrapper): "<amount> USDC" + optional muted "≈ local".
|
|
35
|
+
window._fxPriceInner = (usdc) => {
|
|
36
|
+
const n = Number(usdc)
|
|
37
|
+
const disp = Number.isFinite(n) ? (Number.isInteger(n) ? String(n) : n.toFixed(2)) : ''
|
|
38
|
+
const base = disp + ' USDC'
|
|
39
|
+
const loc = window._fxLocal(usdc)
|
|
40
|
+
return loc ? base + ` <span style="opacity:.6;font-weight:400;font-size:.85em;white-space:nowrap">≈ ${loc}</span>` : base
|
|
41
|
+
}
|
|
42
|
+
// price (USDC decimal) → display HTML. data-usdc-price lets refreshFxPrices() update it once live rates arrive.
|
|
43
|
+
window.fmtPrice = (usdc) => `<span data-usdc-price="${Number(usdc)}">${window._fxPriceInner(usdc)}</span>`
|
|
44
|
+
// re-render already-painted price nodes after rates load (fixes the first-paint race). Safe no-op if no nodes.
|
|
45
|
+
window.refreshFxPrices = () => {
|
|
46
|
+
const doc = window.document
|
|
47
|
+
if (!doc || !doc.querySelectorAll) return
|
|
48
|
+
doc.querySelectorAll('[data-usdc-price]').forEach((el) => { el.innerHTML = window._fxPriceInner(el.getAttribute('data-usdc-price')) })
|
|
49
|
+
doc.querySelectorAll('[data-usdc-local]').forEach((el) => {
|
|
50
|
+
const p = el.getAttribute('data-usdc-local'); const loc = window._fxLocal(p)
|
|
51
|
+
el.innerHTML = 'USDC' + (loc ? ' ≈ ' + loc : '')
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
// fire-and-forget at load; rates are global (no auth), so they can load before the user object is known.
|
|
55
|
+
window.loadFxRates()
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Shared product-image resolver for cards / lists (paired with the public thumb endpoint).
|
|
2
|
+
// productThumbSrc(images): a stored image ref → a loadable <img src>. A 64-hex content hash → the public
|
|
3
|
+
// thumbnail endpoint /api/manifests/<hash>/thumb; an already-usable data:/http(s)/root-relative URL →
|
|
4
|
+
// passthrough; anything else / empty → '' (no image). `images` may be a JSON-array string, a real array,
|
|
5
|
+
// or a legacy CSV string. Card <img>s pair this with onerror="this.outerHTML='📦'" for the 404 (missing
|
|
6
|
+
// thumbnail manifest) fallback, so a bad ref degrades to the icon instead of a broken image.
|
|
7
|
+
window.productThumbSrc = (images) => {
|
|
8
|
+
let arr = images
|
|
9
|
+
if (typeof images === 'string') { try { arr = JSON.parse(images) } catch { arr = images.split(',') } }
|
|
10
|
+
const first = (Array.isArray(arr) ? arr : []).map(s => String(s).trim()).filter(Boolean)[0] || ''
|
|
11
|
+
if (!first) return ''
|
|
12
|
+
if (/^[0-9a-f]{64}$/i.test(first)) return '/api/manifests/' + first + '/thumb'
|
|
13
|
+
if (/^(https?:|data:|\/)/i.test(first)) return first
|
|
14
|
+
return ''
|
|
15
|
+
}
|
|
@@ -261,7 +261,7 @@ function profileContentTabs(isOwner, role, active) {
|
|
|
261
261
|
...(isOwner ? [
|
|
262
262
|
{ k: 'reposted', label: '🔁 ' + t('转发') },
|
|
263
263
|
{ k: 'liked', label: '❤ ' + t('赞') },
|
|
264
|
-
{ k: 'bookmarked', label: '★ ' + t('
|
|
264
|
+
{ k: 'bookmarked', label: '★ ' + t('已收藏') },
|
|
265
265
|
] : []),
|
|
266
266
|
]
|
|
267
267
|
return `<div style="display:flex;gap:18px;border-bottom:1px solid #f3f4f6;margin-bottom:12px;overflow-x:auto;-webkit-overflow-scrolling:touch">
|
|
@@ -315,7 +315,7 @@ async function loadUserContentFeed(userId, tab) {
|
|
|
315
315
|
${img ? `<img src="${escHtml(img)}" style="width:100%;aspect-ratio:1;object-fit:cover;display:block" onerror="this.style.display='none'">` : `<div style="aspect-ratio:1;background:#f9fafb;display:flex;align-items:center;justify-content:center;font-size:32px">♻️</div>`}
|
|
316
316
|
<div style="padding:8px">
|
|
317
317
|
<div style="font-size:12px;font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${escHtml(s.title)}</div>
|
|
318
|
-
<div style="font-size:13px;font-weight:700;color:#dc2626;margin-top:2px">${s.price}
|
|
318
|
+
<div style="font-size:13px;font-weight:700;color:#dc2626;margin-top:2px">${window.fmtPrice(s.price)}</div>
|
|
319
319
|
<div style="font-size:10px;color:#9ca3af;margin-top:2px">${CG[s.condition_grade] || s.condition_grade}${s.status === 'reserved' ? ' · ' + t('已预订') : ''}</div>
|
|
320
320
|
</div>
|
|
321
321
|
</div>`
|
|
@@ -336,7 +336,7 @@ async function loadUserContentFeed(userId, tab) {
|
|
|
336
336
|
${img ? `<img src="${escHtml(img)}" style="width:100%;aspect-ratio:1;object-fit:cover;display:block" onerror="this.style.display='none'">` : `<div style="aspect-ratio:1;background:#f9fafb;display:flex;align-items:center;justify-content:center;font-size:32px">${getCategoryIcon(p.category)}</div>`}
|
|
337
337
|
<div style="padding:8px">
|
|
338
338
|
<div style="font-size:12px;font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${escHtml(p.title)}</div>
|
|
339
|
-
<div style="font-size:13px;font-weight:700;color:#1f2937;margin-top:2px">${p.price}
|
|
339
|
+
<div style="font-size:13px;font-weight:700;color:#1f2937;margin-top:2px">${window.fmtPrice(p.price)}</div>
|
|
340
340
|
<div style="font-size:10px;color:#9ca3af;margin-top:2px">✅ ${p.completion_count || 0} · ❤ ${p.total_likes || 0}</div>
|
|
341
341
|
</div>
|
|
342
342
|
</div>`
|
|
@@ -631,7 +631,7 @@ async function renderNearby(app) {
|
|
|
631
631
|
<div style="width:38px;height:38px;border-radius:8px;background:#f3f4f6;display:flex;align-items:center;justify-content:center;font-size:18px;flex-shrink:0">${getCategoryIcon(p.category)}</div>
|
|
632
632
|
<div style="flex:1;min-width:0">
|
|
633
633
|
<div style="font-size:13px;font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${escHtml(p.title)}</div>
|
|
634
|
-
<div style="font-size:11px;color:#6b7280">${p.price}
|
|
634
|
+
<div style="font-size:11px;color:#6b7280">${window.fmtPrice(p.price)} · 🔥 ${p.buyers} ${t('人买')}</div>
|
|
635
635
|
</div>
|
|
636
636
|
</div>
|
|
637
637
|
<span style="font-size:13px;color:#9ca3af">›</span>
|
|
@@ -821,8 +821,8 @@ async function renderNearbyFeed(app) {
|
|
|
821
821
|
<h3 style="font-size:13px;font-weight:700;margin:14px 0 8px">🔥 ${t('近 24h 同城热销商品')}</h3>
|
|
822
822
|
${topProducts.slice(0,5).map(p => `
|
|
823
823
|
<div class="card" style="padding:10px 12px;margin-bottom:6px;cursor:pointer" onclick="navigate('#order-product/${p.id}')">
|
|
824
|
-
<div style="font-size:13px;font-weight:600">${escHtml(p.title)} <span style="color:#dc2626;font-weight:700">${p.price}
|
|
825
|
-
<div style="font-size:11px;color:#6b7280;margin-top:2px">🛒 ${p.buy_count} ${t('单')} ·
|
|
824
|
+
<div style="font-size:13px;font-weight:600">${escHtml(p.title)} <span style="color:#dc2626;font-weight:700">${window.fmtPrice(p.price)}</span></div>
|
|
825
|
+
<div style="font-size:11px;color:#6b7280;margin-top:2px">🛒 ${p.buy_count} ${t('单')} · ${t('同城共鸣')}</div>
|
|
826
826
|
</div>`).join('')}` : ''}
|
|
827
827
|
`
|
|
828
828
|
}
|
|
@@ -1264,7 +1264,7 @@ async function renderSellerMyHome(app) {
|
|
|
1264
1264
|
` : ''
|
|
1265
1265
|
|
|
1266
1266
|
// 顺序:工作中心 → 销售扩展 → 效率工具 → 资金/沟通 → 信任与协议(条件显示)→ 社交发现 → 公益折叠
|
|
1267
|
-
app.innerHTML = shell(mySubTabsHTML('dashboard') + header + workGrid + marketGrid + commsGrid + trustGrid + socialGrid + charitySection, 'me')
|
|
1267
|
+
app.innerHTML = shell(mySubTabsHTML('dashboard') + header + workGrid + ((state.canArbitrate && role !== 'arbitrator' && window.arbTaishCard) ? window.arbTaishCard() : '') + marketGrid + commsGrid + trustGrid + socialGrid + charitySection, 'me')
|
|
1268
1268
|
}
|
|
1269
1269
|
|
|
1270
1270
|
// ─── 买家 #me 专业版(聚焦购物/订单/AI;慈善折叠次要) ────────
|
|
@@ -1512,7 +1512,7 @@ async function renderBuyerMyHome(app) {
|
|
|
1512
1512
|
`
|
|
1513
1513
|
|
|
1514
1514
|
// 顺序:我的购物 → 我的市场记录 → Agent 进阶 → 通信 → 信任与协议(条件显示)→ 公益折叠 → 账户与配置
|
|
1515
|
-
app.innerHTML = shell(mySubTabsHTML('dashboard') + header + notePromptPlaceholder('me') + shopGrid + marketGrid + aiGrid + commsGrid + trustGrid + socialGrid + charitySection + settingsGrid, 'me')
|
|
1515
|
+
app.innerHTML = shell(mySubTabsHTML('dashboard') + header + notePromptPlaceholder('me') + shopGrid + marketGrid + aiGrid + commsGrid + trustGrid + ((state.canArbitrate && !isExternalArb && window.arbTaishCard) ? window.arbTaishCard() : '') + socialGrid + charitySection + settingsGrid, 'me') // isExternalArb 已在"外部仲裁员·已批准"区渲染仲裁台 → 兜底卡不再重复(走查:同页两张仲裁台卡)
|
|
1516
1516
|
hydrateNotePrompt('me')
|
|
1517
1517
|
}
|
|
1518
1518
|
|
|
@@ -1545,8 +1545,8 @@ async function renderMyHome(app, subTab) {
|
|
|
1545
1545
|
|
|
1546
1546
|
// dashboard: 按角色分支(现有 renderer 内部会注入 mySubTabsHTML)
|
|
1547
1547
|
app.innerHTML = shell(loading$(), 'me')
|
|
1548
|
-
const
|
|
1549
|
-
const TRUSTED_ROLES = ['admin', 'verifier', 'logistics', 'arbitrator']
|
|
1548
|
+
if (!state.canArbitrate) { try { const _as = await GET('/arbitrator/status'); state.canArbitrate = !!(_as && _as.can_arbitrate); state.arbitratorStatus = (_as && _as.arbitrator_status) || 'none' } catch {} } // 现场重查:刚加白名单的仲裁员无需重登即可在 #me 刷出仲裁台入口
|
|
1549
|
+
const role = state.user.role; const TRUSTED_ROLES = ['admin', 'verifier', 'logistics', 'arbitrator']
|
|
1550
1550
|
if (TRUSTED_ROLES.includes(role)) {
|
|
1551
1551
|
return renderTrustedMyHome(app, role)
|
|
1552
1552
|
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// 买家下单前【购买条款聚合 + 收货地区入口】(S5)。UI ONLY,只读聚合 S1-S4 已存字段(GET /products/:id/shipping-options?ship_to_region)。
|
|
2
|
+
// ① 地区入口 _shipRegionInputHtml:运费模板→下拉;无模板但可售区=白名单→按白名单下拉;无模板+排除式/仅平台限制→自由输入(否则 region_required 却无入口=卡单)。
|
|
3
|
+
// ② 条款卡 _ptHydrate:可售裁定 / 运费覆盖·费用·时效 or 需询价 / 满额免邮 / 目的区价内含税 / DDU-DDP / 平台不代收税声明。
|
|
4
|
+
// 不动订单金额、不收税。收货地区变更由地区入口调 _purchaseTermsRefresh 重拉。诚实披露:税费/进口责任均卖家声明。
|
|
5
|
+
;(function () {
|
|
6
|
+
// ① 收货地区输入(app-order-accept-ui 的地区块委托到此,便于随 shipping-options 字段演进;shipSelectedRegion 读 select/自由输入两态)
|
|
7
|
+
window._shipRegionInputHtml = (o) => {
|
|
8
|
+
const tpl = Array.isArray(o.template) ? o.template : []
|
|
9
|
+
const sr = o.sale_regions // {mode, include?, exclude?} | null
|
|
10
|
+
const listRegions = (!tpl.length && sr && sr.mode === 'list' && Array.isArray(sr.include)) ? sr.include : []
|
|
11
|
+
const label = `<label class="form-label" style="font-size:12px">📍 ${t('收货国家/地区')} *</label>`
|
|
12
|
+
const refresh = 'window._purchaseTermsRefresh && window._purchaseTermsRefresh()'
|
|
13
|
+
const opts = tpl.length
|
|
14
|
+
? tpl.map(e => `<option value="${escHtml(e.region)}">${e.region === '*' ? t('其他地区(通用运费)') : escHtml(e.region)} · ${t('运费')} ${e.fee}${e.est_days ? ` · ${escHtml(e.est_days)} ${t('天')}` : ''}</option>`).join('')
|
|
15
|
+
: listRegions.map(r => `<option value="${escHtml(r)}">${escHtml(r)}</option>`).join('')
|
|
16
|
+
if (!opts && !o.quote_outside_template) // 无任何可选项且无询价 → 自由输入(排除式可售区 / 仅平台限制 / 配置异常)
|
|
17
|
+
return `${label}<input class="form-control" id="ship-region-other" maxlength="16" placeholder="${t('地区代码,如 US / JP / DE')}" oninput="${refresh}" style="font-size:13px;text-transform:uppercase">
|
|
18
|
+
<div style="font-size:11px;color:#6b7280;margin-top:4px">${t('该商品设有可售地区限制,请填写收货地区以确认可否下单。')}</div>`
|
|
19
|
+
return `${label}
|
|
20
|
+
<select class="form-control" id="ship-region-select" style="font-size:13px" onchange="document.getElementById('ship-region-other').style.display = this.value === '__other' ? 'block' : 'none'; ${refresh}">
|
|
21
|
+
<option value="">${t('请选择')}</option>${opts}
|
|
22
|
+
${o.quote_outside_template ? `<option value="__other">${t('其他地区(需卖家报价运费,直付)')}</option>` : ''}
|
|
23
|
+
</select>
|
|
24
|
+
<input class="form-control" id="ship-region-other" maxlength="16" placeholder="${t('地区代码,如 US / JP / DE')}" oninput="${refresh}" style="display:none;margin-top:6px;font-size:13px;text-transform:uppercase">
|
|
25
|
+
<div style="font-size:11px;color:#6b7280;margin-top:4px">${o.quote_outside_template ? t('模板内地区运费自动计入总额;其他地区由卖家先报价、你确认后再付款。') : (tpl.length ? t('运费按所选地区自动计入订单总额。') : t('该商品按可售地区下单,运费以卖家约定为准。'))}</div>`
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ② 条款聚合卡
|
|
29
|
+
window.purchaseTermsBlockHtml = (productId) => { window._ptPid = productId; setTimeout(() => window._ptHydrate(productId), 70); return `<div id="purchase-terms-block" style="display:none;margin-top:10px"></div>` }
|
|
30
|
+
window._purchaseTermsRefresh = () => window._ptHydrate(window._ptPid, window.shipSelectedRegion ? window.shipSelectedRegion() : undefined)
|
|
31
|
+
window._ptHydrate = async (productId, region) => {
|
|
32
|
+
const box = document.getElementById('purchase-terms-block'); if (!box) return
|
|
33
|
+
const o = await GET(`/products/${productId}/shipping-options${region ? '?ship_to_region=' + encodeURIComponent(region) : ''}`).catch(() => null); if (!o) return
|
|
34
|
+
box.style.display = 'none'
|
|
35
|
+
const rows = []
|
|
36
|
+
// 可售裁定(最优先;配置异常无论是否选区都醒目红条,不诱导买家提交后才 503;其余不可售需选区后才判)
|
|
37
|
+
const sell = o.sellable || { ok: true, reason: 'ok' }
|
|
38
|
+
if (!sell.ok && (region || sell.reason === 'platform_policy_invalid')) {
|
|
39
|
+
const invalid = sell.reason === 'platform_policy_invalid'
|
|
40
|
+
const msg = invalid ? t('平台合规配置异常,该商品暂时无法下单,请稍后再试。')
|
|
41
|
+
: sell.reason === 'product_restricted' ? t('平台合规限制:该商品暂不支持销售到所选地区。')
|
|
42
|
+
: sell.reason === 'region_not_for_sale' ? t('卖家未将所选地区设为可售范围,该地区暂不可下单。')
|
|
43
|
+
: t('该商品设有可售地区限制,请选择收货地区确认。')
|
|
44
|
+
box.innerHTML = `<div class="card" style="border:1px solid #fca5a5;background:#fef2f2;padding:10px 12px;font-size:12px;color:#991b1b;line-height:1.6">🚫 <strong>${invalid ? t('暂时无法下单') : t('该地区不可售')}</strong> — ${msg}</div>`
|
|
45
|
+
box.style.display = 'block'; return
|
|
46
|
+
}
|
|
47
|
+
// 运费裁定(有模板+选了区)
|
|
48
|
+
const rs = o.resolved_shipping
|
|
49
|
+
if (rs) rows.push(rs.covered
|
|
50
|
+
? `<div style="color:#374151">📦 ${t('运费')}:${rs.fee === 0 ? t('免运费') : rs.fee}${rs.est_days ? ` · ${t('约')} ${escHtml(String(rs.est_days))} ${t('天')}` : ''}</div>`
|
|
51
|
+
: rs.quote_required ? `<div style="color:#92400e">📦 ${t('该地区需卖家先报运费,确认后再付款(直付)。')}</div>`
|
|
52
|
+
: `<div style="color:#991b1b">📦 ${t('卖家暂不配送到该地区。')}</div>`)
|
|
53
|
+
if (o.free_shipping_threshold) rows.push(`<div style="color:#166534">🚚 ${t('满')} ${o.free_shipping_threshold} ${t('免运费')}</div>`)
|
|
54
|
+
// 进口责任 DDU/DDP
|
|
55
|
+
const duty = o.import_duty_terms
|
|
56
|
+
if (duty === 'ddu') rows.push(`<div style="color:#92400e">🛃 <strong>${t('进口关税/税:到境自付(DDU)')}</strong> — ${t('本商品价格不含目的国进口关税/税;跨境到货时可能由承运人向你收取,金额由海关核定,与卖家/平台无关。')}</div>`)
|
|
57
|
+
else if (duty === 'ddp') rows.push(`<div style="color:#166534">🛃 <strong>${t('进口关税/税:卖家已含(DDP)')}</strong> — ${t('卖家声明价格已包含目的国进口关税/税,正常到货应无额外费用。')}</div>`)
|
|
58
|
+
// 价内含税
|
|
59
|
+
const lines = Array.isArray(o.tax_included_lines) ? o.tax_included_lines : []
|
|
60
|
+
for (const l of lines) rows.push(`<div style="color:#374151">🧾 ${t('价内已含')}:${escHtml(l.label)}${l.rate_pct != null ? ' ' + l.rate_pct + '%' : ''}${l.region && l.region !== '*' ? ' (' + escHtml(l.region) + ')' : ''}${l.note ? ' · ' + escHtml(l.note) : ''}</div>`)
|
|
61
|
+
if (rows.length === 0) return // 无任何条款 → 不臆造
|
|
62
|
+
const hasTaxOrDuty = duty || lines.length > 0
|
|
63
|
+
box.innerHTML = `<div class="card" style="border:1px solid #e5e7eb;background:#fafafa;padding:10px 12px;font-size:12px;line-height:1.7">
|
|
64
|
+
<div style="font-weight:600;color:#6b7280;margin-bottom:4px">🌍 ${t('购买条款')}${region ? '(' + t('发往') + ' ' + escHtml(region) + ')' : ''}</div>${rows.join('')}
|
|
65
|
+
${hasTaxOrDuty ? `<div style="color:#9ca3af;font-size:11px;margin-top:4px">${t('税费与进口责任为卖家自行声明,平台不代收代缴税费(直付非托管)。')}</div>` : ''}</div>`
|
|
66
|
+
box.style.display = 'block'
|
|
67
|
+
}
|
|
68
|
+
})()
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// 可售区域 UI(跨境 S1)。UI ONLY —— 判定/校验全在后端(gate 在建单,REGION_NOT_FOR_SALE 硬拒不询价)。
|
|
2
|
+
// 注入:包装 shipHydrateSellerSettings / shipSaveSellerSettings(接单与运费卡尾部追加,capped 文件净零)。
|
|
3
|
+
// 语义:与运费模板分层 —— 这里是"卖不卖",运费模板是"多少钱";平台合规名单商家不可放宽。
|
|
4
|
+
;(function () {
|
|
5
|
+
const _origHydrate = window.shipHydrateSellerSettings
|
|
6
|
+
window.shipHydrateSellerSettings = async () => {
|
|
7
|
+
await _origHydrate()
|
|
8
|
+
const box = document.getElementById('ship-settings-body'); if (!box || document.getElementById('sr-mode')) return
|
|
9
|
+
const s = await GET('/seller/shipping-settings').catch(() => null)
|
|
10
|
+
const rule = (s && s.store_sale_regions) || null
|
|
11
|
+
const codes = rule ? (rule.mode === 'list' ? (rule.include || []) : (rule.exclude || [])) : []
|
|
12
|
+
box.insertAdjacentHTML('beforeend', `
|
|
13
|
+
<div style="border-top:1px dashed #e5e7eb;margin:10px 0;padding-top:10px">
|
|
14
|
+
<label class="form-label" style="font-size:12px">${t('可售区域(店铺默认;与运费无关 —— 这里是"卖不卖",运费模板是"多少钱")')}</label>
|
|
15
|
+
<select class="form-control" id="sr-mode" style="font-size:13px;margin-bottom:6px" onchange="document.getElementById('sr-codes-row').style.display = this.value ? '' : 'none'">
|
|
16
|
+
<option value="" ${!rule ? 'selected' : ''}>${t('不限(全球可下单)')}</option>
|
|
17
|
+
<option value="list" ${rule && rule.mode === 'list' ? 'selected' : ''}>${t('仅允许这些地区')}</option>
|
|
18
|
+
<option value="all" ${rule && rule.mode === 'all' ? 'selected' : ''}>${t('全球可卖,但排除这些地区')}</option>
|
|
19
|
+
</select>
|
|
20
|
+
<div id="sr-codes-row" style="${rule ? '' : 'display:none'}">
|
|
21
|
+
<input class="form-control" id="sr-codes" placeholder="${t('地区代码,空格分隔,如:SG MY CN')}" value="${escHtml(codes.join(' '))}" style="font-size:12px;font-family:monospace;margin-bottom:6px">
|
|
22
|
+
</div>
|
|
23
|
+
<div style="font-size:11px;color:#9ca3af">${t('不可售地区的买家下单会被直接拒绝(不走询价);设置只影响之后的新订单,单品可在接口单独覆盖。')}</div>
|
|
24
|
+
</div>`)
|
|
25
|
+
}
|
|
26
|
+
const _origSave = window.shipSaveSellerSettings
|
|
27
|
+
window.shipSaveSellerSettings = async () => {
|
|
28
|
+
await _origSave()
|
|
29
|
+
const modeEl = document.getElementById('sr-mode'); if (!modeEl) return
|
|
30
|
+
const mode = modeEl.value
|
|
31
|
+
const codes = (document.getElementById('sr-codes')?.value || '').trim().split(/\s+/).filter(Boolean)
|
|
32
|
+
const body = !mode ? { store_sale_regions: null }
|
|
33
|
+
: { store_sale_regions: { mode, ...(mode === 'list' ? { include: codes } : { exclude: codes }) } }
|
|
34
|
+
const r = await POST('/seller/shipping-template', body)
|
|
35
|
+
const msg = document.getElementById('ship-set-msg')
|
|
36
|
+
if (r.error && msg) msg.innerHTML = `<span style="color:#dc2626">${escHtml(r.error)}</span>`
|
|
37
|
+
}
|
|
38
|
+
})()
|
|
@@ -44,7 +44,7 @@ async function renderSellerAnalytics(app) {
|
|
|
44
44
|
: `<div style="display:flex;gap:3px;align-items:flex-end;height:80px">
|
|
45
45
|
${trend.map(d => {
|
|
46
46
|
const h = Math.max(3, (Number(d.gmv) / maxGmv) * 70)
|
|
47
|
-
return `<div title="${d.date}: ${Number(d.gmv).toFixed(0)}
|
|
47
|
+
return `<div title="${d.date}: ${Number(d.gmv).toFixed(0)} USDC · ${d.orders} ${t('单')}" style="flex:1;background:linear-gradient(180deg,#6366f1,#a5b4fc);height:${h}px;border-radius:2px 2px 0 0;cursor:help"></div>`
|
|
48
48
|
}).join('')}
|
|
49
49
|
</div>
|
|
50
50
|
<div style="display:flex;justify-content:space-between;font-size:9px;color:#9ca3af;margin-top:4px">
|
|
@@ -59,11 +59,11 @@ async function renderSellerAnalytics(app) {
|
|
|
59
59
|
<div style="font-size:13px;font-weight:700;color:${i === 0 ? '#f59e0b' : i < 3 ? '#6366f1' : '#9ca3af'};width:22px;text-align:center">${i + 1}</div>
|
|
60
60
|
<div style="flex:1;min-width:0">
|
|
61
61
|
<div style="font-size:12px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${escHtml(p.title)}</div>
|
|
62
|
-
<div style="font-size:10px;color:#9ca3af">${p.price}
|
|
62
|
+
<div style="font-size:10px;color:#9ca3af">${window.fmtPrice(p.price)}</div>
|
|
63
63
|
</div>
|
|
64
64
|
<div style="text-align:right">
|
|
65
65
|
<div style="font-size:13px;font-weight:600;color:#16a34a">${p.sales} ${t('单')}</div>
|
|
66
|
-
<div style="font-size:10px;color:#9ca3af">${fmt2(p.revenue)}
|
|
66
|
+
<div style="font-size:10px;color:#9ca3af">${fmt2(p.revenue)} USDC</div>
|
|
67
67
|
</div>
|
|
68
68
|
</div>`).join('')
|
|
69
69
|
|
|
@@ -80,7 +80,7 @@ async function renderSellerAnalytics(app) {
|
|
|
80
80
|
<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:8px;text-align:center">
|
|
81
81
|
<div>
|
|
82
82
|
<div style="font-size:18px;font-weight:700;color:#4f46e5">${fmt2(r.orders.gmv)}${delta(Number(r.orders.gmv), Number(r.prev_window?.gmv))}</div>
|
|
83
|
-
<div style="font-size:10px;color:#6b7280">GMV (
|
|
83
|
+
<div style="font-size:10px;color:#6b7280">GMV (USDC)</div>${window.gmvRailSplitHtml ? window.gmvRailSplitHtml(r.orders.gmv_escrow, r.orders.gmv_direct_pay, fmt2) : ''}
|
|
84
84
|
</div>
|
|
85
85
|
<div>
|
|
86
86
|
<div style="font-size:18px;font-weight:700;color:#16a34a">${fmt(r.orders.completed_orders)}${delta(Number(r.orders.completed_orders), Number(r.prev_window?.completed_orders))}</div>
|
|
@@ -88,7 +88,7 @@ async function renderSellerAnalytics(app) {
|
|
|
88
88
|
</div>
|
|
89
89
|
<div>
|
|
90
90
|
<div style="font-size:18px;font-weight:700;color:#f59e0b">${fmt2(r.orders.aov)}</div>
|
|
91
|
-
<div style="font-size:10px;color:#6b7280">${t('客单价')} (
|
|
91
|
+
<div style="font-size:10px;color:#6b7280">${t('客单价')} (USDC)</div>
|
|
92
92
|
</div>
|
|
93
93
|
</div>
|
|
94
94
|
</div>
|
|
@@ -101,10 +101,10 @@ async function renderWishlist(app) {
|
|
|
101
101
|
: items.map(it => {
|
|
102
102
|
const cur = Number(it.current_price), delta = Number(it.price_delta || 0), pct = Number(it.price_delta_pct || 0)
|
|
103
103
|
const priceTag = delta < 0
|
|
104
|
-
? `<span style="color:#dc2626;font-weight:700">${cur}
|
|
104
|
+
? `<span style="color:#dc2626;font-weight:700">${window.fmtPrice(cur)} <span style="font-size:11px;background:#fef2f2;color:#991b1b;padding:1px 6px;border-radius:99px;font-weight:600;margin-left:4px">↓${Math.abs(pct).toFixed(0)}%</span></span>`
|
|
105
105
|
: delta > 0
|
|
106
|
-
? `<span style="color:#374151;font-weight:600">${cur}
|
|
107
|
-
: `<span style="color:#374151;font-weight:600">${cur}
|
|
106
|
+
? `<span style="color:#374151;font-weight:600">${window.fmtPrice(cur)} <span style="font-size:11px;color:#9ca3af">(${t('已涨')} ${pct.toFixed(0)}%)</span></span>`
|
|
107
|
+
: `<span style="color:#374151;font-weight:600">${window.fmtPrice(cur)}</span>`
|
|
108
108
|
const stockTag = Number(it.stock) === 0
|
|
109
109
|
? `<span style="background:#f3f4f6;color:#6b7280;font-size:10px;padding:1px 6px;border-radius:4px">${t('缺货')}</span>`
|
|
110
110
|
: ''
|
|
@@ -178,7 +178,7 @@ async function renderWaitlist(app) {
|
|
|
178
178
|
<div style="flex:1;min-width:0">
|
|
179
179
|
<div style="font-size:14px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${escHtml(it.title)}</div>
|
|
180
180
|
<div style="display:flex;gap:6px;align-items:center;margin-top:4px;font-size:12px">
|
|
181
|
-
<span style="color:#374151;font-weight:600">${it.price}
|
|
181
|
+
<span style="color:#374151;font-weight:600">${window.fmtPrice(it.price)}</span>
|
|
182
182
|
${stockTag}
|
|
183
183
|
${notifiedTag}
|
|
184
184
|
</div>
|
|
@@ -607,14 +607,14 @@ async function renderForYou(app) {
|
|
|
607
607
|
const meta = labels[b]
|
|
608
608
|
const cards = grouped[b].map(p => {
|
|
609
609
|
let imageUrl = ''
|
|
610
|
-
|
|
610
|
+
imageUrl = window.productThumbSrc(p.images) || imageUrl
|
|
611
611
|
return `
|
|
612
612
|
<div class="card" style="padding:8px 10px;margin-bottom:6px;cursor:pointer" onclick="location.hash='#order-product/${p.id}'">
|
|
613
613
|
<div style="display:flex;gap:10px;align-items:center">
|
|
614
|
-
<div style="font-size:24px;flex-shrink:0">${imageUrl ? `<img src="${escHtml(imageUrl)}" style="width:42px;height:42px;border-radius:6px;object-fit:cover">` : getCategoryIcon(p.category) || '📦'}</div>
|
|
614
|
+
<div style="font-size:24px;flex-shrink:0">${imageUrl ? `<img src="${escHtml(imageUrl)}" onerror="this.outerHTML='📦'" style="width:42px;height:42px;border-radius:6px;object-fit:cover">` : getCategoryIcon(p.category) || '📦'}</div>
|
|
615
615
|
<div style="flex:1;min-width:0">
|
|
616
616
|
<div style="font-size:13px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${escHtml(p.title)}</div>
|
|
617
|
-
<div style="font-size:12px;color:#4f46e5;font-weight:600">${p.price}
|
|
617
|
+
<div style="font-size:12px;color:#4f46e5;font-weight:600">${window.fmtPrice(p.price)} <span style="font-size:10px;color:#9ca3af;font-weight:400">· @${escHtml(p.seller_handle || '')}${p.sales_count > 0 ? ' · 🛒 ' + p.sales_count : ''}</span></div>
|
|
618
618
|
</div>
|
|
619
619
|
</div>
|
|
620
620
|
</div>`
|
|
@@ -694,8 +694,8 @@ async function renderCompare(app, ids) {
|
|
|
694
694
|
{ label: t('商品'), render: (p) => `<a href="#order-product/${p.id}" style="font-size:13px;font-weight:600;color:#374151;display:block;overflow:hidden;text-overflow:ellipsis">${escHtml(p.title)}</a>` },
|
|
695
695
|
{ label: t('价格'), render: (p, i) => {
|
|
696
696
|
const sale = flashes[i]?.sale
|
|
697
|
-
if (sale) return `<span style="color:#dc2626;font-weight:700">${sale.sale_price}</span
|
|
698
|
-
return `<span style="color:#4f46e5;font-weight:700">${p.price}</span
|
|
697
|
+
if (sale) return `<span style="color:#dc2626;font-weight:700">${window.fmtPrice(sale.sale_price)}</span><br><span style="font-size:10px;color:#9ca3af;text-decoration:line-through">${p.price} USDC</span>`
|
|
698
|
+
return `<span style="color:#4f46e5;font-weight:700">${window.fmtPrice(p.price)}</span>`
|
|
699
699
|
} },
|
|
700
700
|
{ label: t('库存'), render: (p) => `<span style="color:${Number(p.stock) === 0 ? '#dc2626' : Number(p.stock) <= 3 ? '#d97706' : '#374151'}">${p.stock}</span>` },
|
|
701
701
|
{ label: t('卖家'), render: (p) => `<a href="#shop/${p.seller_id}" style="font-size:11px;color:#6366f1">@${escHtml(p.seller_name || '')}</a>` },
|
|
@@ -787,14 +787,14 @@ async function renderShopPage(app, identifier) {
|
|
|
787
787
|
? `<div style="text-align:center;padding:30px;color:#9ca3af;font-size:13px">${t('该卖家暂无商品')}</div>`
|
|
788
788
|
: products.map(p => {
|
|
789
789
|
let imageUrl = ''
|
|
790
|
-
|
|
790
|
+
imageUrl = window.productThumbSrc(p.images) || imageUrl
|
|
791
791
|
return `
|
|
792
792
|
<div class="card" style="padding:10px 12px;cursor:pointer" onclick="location.hash='#order-product/${p.id}'">
|
|
793
793
|
<div style="display:flex;gap:10px;align-items:center">
|
|
794
|
-
<div style="font-size:28px;flex-shrink:0">${imageUrl ? `<img src="${escHtml(imageUrl)}" style="width:42px;height:42px;border-radius:6px;object-fit:cover">` : getCategoryIcon(p.category) || '📦'}</div>
|
|
794
|
+
<div style="font-size:28px;flex-shrink:0">${imageUrl ? `<img src="${escHtml(imageUrl)}" onerror="this.outerHTML='📦'" style="width:42px;height:42px;border-radius:6px;object-fit:cover">` : getCategoryIcon(p.category) || '📦'}</div>
|
|
795
795
|
<div style="flex:1;min-width:0">
|
|
796
796
|
<div style="font-size:13px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${escHtml(p.title)}</div>
|
|
797
|
-
<div style="font-size:12px;color:#4f46e5;font-weight:600">${p.price}
|
|
797
|
+
<div style="font-size:12px;color:#4f46e5;font-weight:600">${window.fmtPrice(p.price)} <span style="font-size:10px;color:#9ca3af;font-weight:400">· ${stockBadgeHtml(p)}${p.sales_count > 0 ? ' · 🛒 ' + p.sales_count : ''}</span></div>
|
|
798
798
|
</div>
|
|
799
799
|
</div>
|
|
800
800
|
</div>`
|
|
@@ -930,15 +930,15 @@ async function renderEditorPicks(app) {
|
|
|
930
930
|
const sellers = r?.sellers || []
|
|
931
931
|
const productCards = products.map(p => {
|
|
932
932
|
let imageUrl = ''
|
|
933
|
-
|
|
933
|
+
imageUrl = window.productThumbSrc(p.images) || imageUrl
|
|
934
934
|
return `
|
|
935
935
|
<div class="card" style="padding:12px;margin-bottom:8px;cursor:pointer;border-left:3px solid #d97706" onclick="location.hash='#order-product/${p.target_id}'">
|
|
936
936
|
${p.title ? `<div style="font-size:11px;color:#d97706;font-weight:600;margin-bottom:4px">📌 ${escHtml(p.title)}</div>` : ''}
|
|
937
937
|
<div style="display:flex;gap:10px;align-items:center">
|
|
938
|
-
<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(p.category) || '📦'}</div>
|
|
938
|
+
<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(p.category) || '📦'}</div>
|
|
939
939
|
<div style="flex:1;min-width:0">
|
|
940
940
|
<div style="font-size:13px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${escHtml(p.product_title)}</div>
|
|
941
|
-
<div style="font-size:12px;color:#4f46e5;font-weight:600">${p.price}
|
|
941
|
+
<div style="font-size:12px;color:#4f46e5;font-weight:600">${window.fmtPrice(p.price)} · @${escHtml(p.seller_handle || '')}</div>
|
|
942
942
|
</div>
|
|
943
943
|
</div>
|
|
944
944
|
${p.note ? `<div style="font-size:11px;color:#374151;margin-top:6px;padding:6px 8px;background:#fef3c7;border-radius:6px">${escHtml(p.note)}</div>` : ''}
|
|
@@ -1118,18 +1118,18 @@ async function renderFlashSalesLive(app) {
|
|
|
1118
1118
|
? `<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>`
|
|
1119
1119
|
: items.map(it => {
|
|
1120
1120
|
let imageUrl = ''
|
|
1121
|
-
|
|
1121
|
+
imageUrl = window.productThumbSrc(it.images) || imageUrl
|
|
1122
1122
|
const save = (Number(it.original_price) - Number(it.sale_price)).toFixed(2)
|
|
1123
1123
|
const pct = ((1 - Number(it.sale_price) / Number(it.original_price)) * 100).toFixed(0)
|
|
1124
1124
|
return `
|
|
1125
1125
|
<div class="card" style="padding:10px 12px;margin-bottom:8px;cursor:pointer;border-left:3px solid #dc2626" onclick="location.hash='#order-product/${it.product_id}'">
|
|
1126
1126
|
<div style="display:flex;gap:10px;align-items:center">
|
|
1127
|
-
<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>
|
|
1127
|
+
<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>
|
|
1128
1128
|
<div style="flex:1;min-width:0">
|
|
1129
1129
|
<div style="font-size:13px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${escHtml(it.title)}</div>
|
|
1130
1130
|
<div style="font-size:12px;margin-top:2px">
|
|
1131
|
-
<span style="color:#dc2626;font-weight:700">${it.sale_price}
|
|
1132
|
-
<span style="font-size:11px;color:#9ca3af;text-decoration:line-through;margin-left:4px">${it.original_price}</span>
|
|
1131
|
+
<span style="color:#dc2626;font-weight:700">${window.fmtPrice(it.sale_price)}</span>
|
|
1132
|
+
<span style="font-size:11px;color:#9ca3af;text-decoration:line-through;margin-left:4px">${it.original_price} USDC</span>
|
|
1133
1133
|
<span style="font-size:10px;color:#dc2626;background:#fee2e2;padding:1px 5px;border-radius:99px;margin-left:4px;font-weight:600">-${pct}%</span>
|
|
1134
1134
|
</div>
|
|
1135
1135
|
<div style="font-size:10px;color:#9ca3af;margin-top:2px">@${escHtml(it.seller_handle || '')} · ${t('截止')} ${fmtTime(it.ends_at)}${it.max_qty > 0 ? ' · ' + t('限') + ' ' + it.max_qty + ' / ' + t('已售') + ' ' + it.sold_count : ''}</div>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// 跨境税费/进口责任【卖家设置】(S3;S5 起买家披露移至 app-purchase-terms-ui.js)。UI ONLY —— seller-declared,平台不算不收代缴。
|
|
2
|
+
// 店铺设置卡追加 DDP/DDU 下拉 + 价内含税行(包装 shipHydrateSellerSettings/Save)。诚实纪律:仅"价内已含"披露,向买家加收税不支持。
|
|
3
|
+
;(function () {
|
|
4
|
+
// ── 卖家店铺设置(包装既有运费设置卡)──
|
|
5
|
+
const _origHydrate = window.shipHydrateSellerSettings
|
|
6
|
+
window.shipHydrateSellerSettings = async () => {
|
|
7
|
+
await _origHydrate()
|
|
8
|
+
const box = document.getElementById('ship-settings-body'); if (!box || document.getElementById('tt-duty')) return
|
|
9
|
+
const s = await GET('/seller/shipping-settings').catch(() => null)
|
|
10
|
+
const duty = s && s.store_import_duty_terms; const lines = (s && s.store_tax_lines) || []
|
|
11
|
+
const tplText = lines.map(l => `${l.region} ${l.label}${l.rate_pct != null ? ' ' + l.rate_pct : ''}`).join('\n')
|
|
12
|
+
box.insertAdjacentHTML('beforeend', `
|
|
13
|
+
<div style="border-top:1px dashed #e5e7eb;margin:10px 0;padding-top:10px">
|
|
14
|
+
<label class="form-label" style="font-size:12px">${t('跨境进口责任(店铺默认;卖家声明,平台不代收税)')}</label>
|
|
15
|
+
<select class="form-control" id="tt-duty" style="font-size:13px;margin-bottom:8px">
|
|
16
|
+
<option value="" ${!duty ? 'selected' : ''}>${t('不声明(默认)')}</option>
|
|
17
|
+
<option value="ddu" ${duty === 'ddu' ? 'selected' : ''}>${t('DDU — 买家到境自付关税/税')}</option>
|
|
18
|
+
<option value="ddp" ${duty === 'ddp' ? 'selected' : ''}>${t('DDP — 卖家已含关税/税')}</option>
|
|
19
|
+
</select>
|
|
20
|
+
<label class="form-label" style="font-size:12px">${t('价内已含税声明(每行:地区码 税名 [税率%];* 为通用;如 SG GST 9)')}</label>
|
|
21
|
+
<textarea class="form-control" id="tt-tax" rows="2" placeholder="SG GST 9 * VAT" style="font-size:12px;font-family:monospace">${escHtml(tplText)}</textarea>
|
|
22
|
+
<div style="font-size:11px;color:#9ca3af;margin-top:4px">${t('仅"价内已含"披露;向买家加收的税暂不支持(平台不代收)。设置只影响之后的新订单。')}</div>
|
|
23
|
+
</div>`)
|
|
24
|
+
}
|
|
25
|
+
const _origSave = window.shipSaveSellerSettings
|
|
26
|
+
window.shipSaveSellerSettings = async () => {
|
|
27
|
+
await _origSave()
|
|
28
|
+
const dutyEl = document.getElementById('tt-duty'); if (!dutyEl) return
|
|
29
|
+
const lines = (document.getElementById('tt-tax')?.value || '').split('\n').map(l => l.trim()).filter(Boolean).map(l => { const m = l.split(/\s+/); return { region: m[0], label: m[1] || '', ...(m[2] ? { rate_pct: Number(m[2]) } : {}), kind: 'included' } })
|
|
30
|
+
const r = await POST('/seller/shipping-template', { store_import_duty_terms: dutyEl.value || null, store_tax_lines: lines.length ? lines : null })
|
|
31
|
+
const msg = document.getElementById('ship-set-msg'); if (r.error && msg) msg.innerHTML = `<span style="color:#dc2626">${escHtml(r.error)}</span>`
|
|
32
|
+
}
|
|
33
|
+
})()
|