From c9c711e4f6b8c47fbb1f38031d9af80e23bb359e Mon Sep 17 00:00:00 2001 From: abingyyds Date: Thu, 26 Mar 2026 17:16:53 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=88=86=E7=AB=99=E5=89=8D=E7=AB=AF?= =?UTF-8?q?=E9=BB=98=E8=AE=A4=E4=BD=BF=E7=94=A8=E4=BA=BA=E6=B0=91=E5=B8=81?= =?UTF-8?q?=E6=98=BE=E7=A4=BA=E4=BB=B7=E6=A0=BC=E5=92=8C=E4=BD=99=E9=A2=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 后端 GetDistSiteInfo 返回 currency 配置(符号/汇率/代码) - SiteContext 新增 useCurrency() hook 提供 symbol/rate/fmt - Pricing/Dashboard/Topup/Packages 所有价格从 $ 改为 ¥ 并乘以汇率 - 7 个主题的 Home.jsx 套餐价格同步更新 Co-Authored-By: Claude Opus 4.6 --- src/context/SiteContext.jsx | 21 +++++++++++++++++++++ src/pages/Dashboard.jsx | 8 +++++--- src/pages/Packages.jsx | 12 +++++++----- src/pages/Pricing.jsx | 6 ++++-- src/pages/Topup.jsx | 12 +++++++----- src/themes/claude/Home.jsx | 7 ++++--- src/themes/clean/Home.jsx | 7 ++++--- src/themes/corporate/Home.jsx | 7 ++++--- src/themes/dark/Home.jsx | 7 ++++--- src/themes/default/Home.jsx | 7 ++++--- src/themes/minimal/Home.jsx | 7 ++++--- src/themes/starter/Home.jsx | 7 ++++--- 12 files changed, 72 insertions(+), 36 deletions(-) diff --git a/src/context/SiteContext.jsx b/src/context/SiteContext.jsx index fac7473..f3634b4 100644 --- a/src/context/SiteContext.jsx +++ b/src/context/SiteContext.jsx @@ -41,3 +41,24 @@ export function useSite() { if (!ctx) throw new Error('useSite must be used within SiteProvider'); return ctx; } + +/** + * useCurrency - 获取分站货币配置 + * 返回 { symbol, rate, code, fmt(usdValue) } + * fmt() 将 USD 值转换为显示货币并格式化 + */ +export function useCurrency() { + const { site } = useSite(); + const currency = site?.currency; + const symbol = currency?.symbol || '¥'; + const rate = currency?.exchange_rate || 7; + const code = currency?.code || 'CNY'; + + const fmt = (usdValue, decimals = 4) => { + if (usdValue == null || isNaN(usdValue)) return '-'; + const converted = Number(usdValue) * rate; + return symbol + converted.toFixed(decimals); + }; + + return { symbol, rate, code, fmt }; +} diff --git a/src/pages/Dashboard.jsx b/src/pages/Dashboard.jsx index ccbdf53..6c8f1b9 100644 --- a/src/pages/Dashboard.jsx +++ b/src/pages/Dashboard.jsx @@ -3,12 +3,14 @@ import { Link } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { useAuth } from '../context/AuthContext'; import { getUserUsage, redeemCode, Q, quotaToDollar } from '../api'; +import { useCurrency } from '../context/SiteContext'; import CountUp from '../components/bits/CountUp'; import toast from 'react-hot-toast'; export default function Dashboard() { const { t } = useTranslation(); const { user, refreshUser } = useAuth(); + const { symbol, rate } = useCurrency(); const [usage, setUsage] = useState(null); const [redeemInput, setRedeemInput] = useState(''); const [redeeming, setRedeeming] = useState(false); @@ -40,7 +42,7 @@ export default function Dashboard() { const quota = usage?.quota ?? user?.quota ?? 0; const usedQuota = usage?.used_quota ?? user?.used_quota ?? 0; const requestCount = usage?.request_count ?? user?.request_count ?? 0; - const balanceDollars = quota / Q; + const balanceDollars = quota / Q * rate; return (
@@ -57,7 +59,7 @@ export default function Dashboard() {

{t('dashboard.balance')}

- $ + {symbol}

{t('dashboard.quotaUnits', { count: quota.toLocaleString() })}

@@ -65,7 +67,7 @@ export default function Dashboard() {

{t('dashboard.used')}

- $ + {symbol}

{t('dashboard.quotaUnits', { count: usedQuota.toLocaleString() })}

diff --git a/src/pages/Packages.jsx b/src/pages/Packages.jsx index 3322204..8394835 100644 --- a/src/pages/Packages.jsx +++ b/src/pages/Packages.jsx @@ -3,6 +3,7 @@ import { Link, useNavigate } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { useAuth } from '../context/AuthContext'; import { getSitePackages, getSiteModels, subscribePackage, getActiveSubscriptions, Q } from '../api'; +import { useCurrency } from '../context/SiteContext'; import SpotlightCard from '../components/bits/SpotlightCard'; import { calcOfficialEquivList } from '../utils/officialEquiv'; import RotatingEquiv from '../components/bits/RotatingEquiv'; @@ -24,6 +25,7 @@ export default function Packages() { const { t } = useTranslation(); const { user, refreshUser } = useAuth(); const navigate = useNavigate(); + const { symbol, rate } = useCurrency(); const [packages, setPackages] = useState([]); const [models, setModels] = useState([]); const [loading, setLoading] = useState(true); @@ -144,7 +146,7 @@ export default function Packages() { style={{ width: `${pct}%` }} />
- ${(remain / Q).toFixed(2)} / ${(total / Q).toFixed(2)} + {symbol}{(remain / Q * rate).toFixed(2)} / {symbol}{(total / Q * rate).toFixed(2)} @@ -204,9 +206,9 @@ export default function Packages() { {/* Price */}
- ${Number(pkg.price).toFixed(2)} + {symbol}{(Number(pkg.price) * rate).toFixed(2)} {pkg.original_price > 0 && pkg.original_price > pkg.price && ( - ${Number(pkg.original_price).toFixed(2)} + {symbol}{(Number(pkg.original_price) * rate).toFixed(2)} )}
{pkg.duration > 0 && ( @@ -277,7 +279,7 @@ export default function Packages() { {/* Confirmation Modal */} {confirmPkg && (() => { - const userBalance = (user?.quota || 0) / Q; + const userBalance = (user?.quota || 0) / Q * rate; const pkgPrice = Number(confirmPkg.price); const insufficient = userBalance < pkgPrice; const resetPeriod = confirmPkg.quota_reset_period || 'never'; @@ -301,7 +303,7 @@ export default function Packages() {
)}

- {t('packages.yourBalance')} ${userBalance.toFixed(2)} + {t('packages.yourBalance')} {symbol}{userBalance.toFixed(2)}

{insufficient && (
diff --git a/src/pages/Pricing.jsx b/src/pages/Pricing.jsx index 44a778f..89e8b1e 100644 --- a/src/pages/Pricing.jsx +++ b/src/pages/Pricing.jsx @@ -1,9 +1,11 @@ import React, { useState, useEffect, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { getSiteModels } from '../api'; +import { useCurrency } from '../context/SiteContext'; export default function Pricing() { const { t } = useTranslation(); + const { symbol, rate } = useCurrency(); const [models, setModels] = useState([]); const [vendors, setVendors] = useState([]); const [search, setSearch] = useState(''); @@ -132,10 +134,10 @@ export default function Pricing() { {m.display_name || m.model_name} - {m.input_price != null ? `$${(Number(m.input_price) * 1000).toFixed(4)}` : '-'} + {m.input_price != null ? `${symbol}${(Number(m.input_price) * 1000 * rate).toFixed(4)}` : '-'} - {m.output_price != null ? `$${(Number(m.output_price) * 1000).toFixed(4)}` : '-'} + {m.output_price != null ? `${symbol}${(Number(m.output_price) * 1000 * rate).toFixed(4)}` : '-'} { @@ -324,7 +326,7 @@ export default function Topup() {

{t('dashboard.balance')}

- $ + {symbol}

{t('dashboard.quotaUnits', { count: quota.toLocaleString() })}

@@ -332,7 +334,7 @@ export default function Topup() {

{t('dashboard.used')}

- $ + {symbol}
@@ -371,7 +373,7 @@ export default function Topup() { : 'glass-sm text-page-label hover:text-page hover:bg-page-surface-hover' }`} > - ${val} + {symbol}{val} ))}
@@ -591,7 +593,7 @@ export default function Topup() { {history.map((item, i) => (
-

${item.amount}

+

{symbol}{(Number(item.amount) * rate).toFixed(2)}

{new Date(item.create_time * 1000).toLocaleString()} · {item.payment_method || t('topup.redeemCode')}

diff --git a/src/themes/claude/Home.jsx b/src/themes/claude/Home.jsx index 2186d22..d95a042 100644 --- a/src/themes/claude/Home.jsx +++ b/src/themes/claude/Home.jsx @@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react'; import { Link } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { useAuth } from '../../context/AuthContext'; -import { useSite } from '../../context/SiteContext'; +import { useSite, useCurrency } from '../../context/SiteContext'; import { getSiteModels, getSitePackages, Q } from '../../api'; import { calcOfficialEquivList } from '../../utils/officialEquiv'; import RotatingEquiv from '../../components/bits/RotatingEquiv'; @@ -13,6 +13,7 @@ export default function ClaudeHome() { const { t } = useTranslation(); const { user } = useAuth(); const { site } = useSite(); + const { symbol, rate } = useCurrency(); const [models, setModels] = useState([]); const [packages, setPackages] = useState([]); @@ -159,9 +160,9 @@ export default function ClaudeHome() {

{pkg.name}

{pkg.description &&

{pkg.description}

}
- ${pkg.price} + {symbol}{(Number(pkg.price) * rate).toFixed(2)} {pkg.original_price > pkg.price && ( - ${pkg.original_price} + {symbol}{(Number(pkg.original_price) * rate).toFixed(2)} )} {pkg.duration > 0 &&

{t('home.days', { count: pkg.duration })}

}
diff --git a/src/themes/clean/Home.jsx b/src/themes/clean/Home.jsx index fa52443..f4a1a4f 100644 --- a/src/themes/clean/Home.jsx +++ b/src/themes/clean/Home.jsx @@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react'; import { Link } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { useAuth } from '../../context/AuthContext'; -import { useSite } from '../../context/SiteContext'; +import { useSite, useCurrency } from '../../context/SiteContext'; import { getSiteModels, getSitePackages, Q } from '../../api'; import { calcOfficialEquivList } from '../../utils/officialEquiv'; import RotatingEquiv from '../../components/bits/RotatingEquiv'; @@ -13,6 +13,7 @@ export default function CleanHome() { const { t } = useTranslation(); const { user } = useAuth(); const { site } = useSite(); + const { symbol, rate } = useCurrency(); const [models, setModels] = useState([]); const [packages, setPackages] = useState([]); @@ -172,9 +173,9 @@ export default function CleanHome() {

{pkg.name}

{pkg.description &&

{pkg.description}

}
- ${pkg.price} + {symbol}{(Number(pkg.price) * rate).toFixed(2)} {pkg.original_price > pkg.price && ( - ${pkg.original_price} + {symbol}{(Number(pkg.original_price) * rate).toFixed(2)} )} {pkg.duration > 0 &&

{t('home.days', { count: pkg.duration })}

}
diff --git a/src/themes/corporate/Home.jsx b/src/themes/corporate/Home.jsx index 56324d9..a1cec35 100644 --- a/src/themes/corporate/Home.jsx +++ b/src/themes/corporate/Home.jsx @@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react'; import { Link } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { useAuth } from '../../context/AuthContext'; -import { useSite } from '../../context/SiteContext'; +import { useSite, useCurrency } from '../../context/SiteContext'; import { getSiteModels, getSitePackages, Q } from '../../api'; import { calcOfficialEquivList } from '../../utils/officialEquiv'; import RotatingEquiv from '../../components/bits/RotatingEquiv'; @@ -13,6 +13,7 @@ export default function CorporateHome() { const { t } = useTranslation(); const { user } = useAuth(); const { site } = useSite(); + const { symbol, rate } = useCurrency(); const [models, setModels] = useState([]); const [packages, setPackages] = useState([]); @@ -171,9 +172,9 @@ export default function CorporateHome() {

{pkg.name}

{pkg.description &&

{pkg.description}

}
- ${pkg.price} + {symbol}{(Number(pkg.price) * rate).toFixed(2)} {pkg.original_price > pkg.price && ( - ${pkg.original_price} + {symbol}{(Number(pkg.original_price) * rate).toFixed(2)} )} {pkg.duration > 0 &&

{t('home.days', { count: pkg.duration })}

}
diff --git a/src/themes/dark/Home.jsx b/src/themes/dark/Home.jsx index 4600c71..4932c6d 100644 --- a/src/themes/dark/Home.jsx +++ b/src/themes/dark/Home.jsx @@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react'; import { Link } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { useAuth } from '../../context/AuthContext'; -import { useSite } from '../../context/SiteContext'; +import { useSite, useCurrency } from '../../context/SiteContext'; import { getSiteModels, getSitePackages, Q } from '../../api'; import { calcOfficialEquivList } from '../../utils/officialEquiv'; import RotatingEquiv from '../../components/bits/RotatingEquiv'; @@ -13,6 +13,7 @@ export default function DarkHome() { const { t } = useTranslation(); const { user } = useAuth(); const { site } = useSite(); + const { symbol, rate } = useCurrency(); const [models, setModels] = useState([]); const [packages, setPackages] = useState([]); @@ -162,9 +163,9 @@ export default function DarkHome() {

{pkg.name}

{pkg.description &&

{pkg.description}

}
- ${pkg.price} + {symbol}{(Number(pkg.price) * rate).toFixed(2)} {pkg.original_price > pkg.price && ( - ${pkg.original_price} + {symbol}{(Number(pkg.original_price) * rate).toFixed(2)} )} {pkg.duration > 0 &&

{t('home.days', { count: pkg.duration })}

}
diff --git a/src/themes/default/Home.jsx b/src/themes/default/Home.jsx index 5622d0d..c35b8b6 100644 --- a/src/themes/default/Home.jsx +++ b/src/themes/default/Home.jsx @@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react'; import { Link } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { useAuth } from '../../context/AuthContext'; -import { useSite } from '../../context/SiteContext'; +import { useSite, useCurrency } from '../../context/SiteContext'; import { getSiteModels, getSitePackages, Q } from '../../api'; import { calcOfficialEquivList } from '../../utils/officialEquiv'; import RotatingEquiv from '../../components/bits/RotatingEquiv'; @@ -13,6 +13,7 @@ export default function DefaultHome() { const { t } = useTranslation(); const { user } = useAuth(); const { site } = useSite(); + const { symbol, rate } = useCurrency(); const [models, setModels] = useState([]); const [packages, setPackages] = useState([]); @@ -171,9 +172,9 @@ export default function DefaultHome() {

{pkg.name}

{pkg.description &&

{pkg.description}

}
- ${pkg.price} + {symbol}{(Number(pkg.price) * rate).toFixed(2)} {pkg.original_price > pkg.price && ( - ${pkg.original_price} + {symbol}{(Number(pkg.original_price) * rate).toFixed(2)} )} {pkg.duration > 0 &&

{t('home.days', { count: pkg.duration })}

}
diff --git a/src/themes/minimal/Home.jsx b/src/themes/minimal/Home.jsx index 940eb13..0d1d440 100644 --- a/src/themes/minimal/Home.jsx +++ b/src/themes/minimal/Home.jsx @@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react'; import { Link } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { useAuth } from '../../context/AuthContext'; -import { useSite } from '../../context/SiteContext'; +import { useSite, useCurrency } from '../../context/SiteContext'; import { getSiteModels, getSitePackages, Q } from '../../api'; import { calcOfficialEquivList } from '../../utils/officialEquiv'; import RotatingEquiv from '../../components/bits/RotatingEquiv'; @@ -13,6 +13,7 @@ export default function MinimalHome() { const { t } = useTranslation(); const { user } = useAuth(); const { site } = useSite(); + const { symbol, rate } = useCurrency(); const [models, setModels] = useState([]); const [packages, setPackages] = useState([]); @@ -154,9 +155,9 @@ export default function MinimalHome() {

{pkg.name}

{pkg.description &&

{pkg.description}

}
- ${pkg.price} + {symbol}{(Number(pkg.price) * rate).toFixed(2)} {pkg.original_price > pkg.price && ( - ${pkg.original_price} + {symbol}{(Number(pkg.original_price) * rate).toFixed(2)} )} {pkg.duration > 0 &&

{t('home.days', { count: pkg.duration })}

}
diff --git a/src/themes/starter/Home.jsx b/src/themes/starter/Home.jsx index c2e9da3..893edab 100644 --- a/src/themes/starter/Home.jsx +++ b/src/themes/starter/Home.jsx @@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react'; import { Link } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { useAuth } from '../../context/AuthContext'; -import { useSite } from '../../context/SiteContext'; +import { useSite, useCurrency } from '../../context/SiteContext'; import { getSiteModels, getSitePackages, Q } from '../../api'; import { calcOfficialEquivList } from '../../utils/officialEquiv'; import RotatingEquiv from '../../components/bits/RotatingEquiv'; @@ -13,6 +13,7 @@ export default function StarterHome() { const { t } = useTranslation(); const { user } = useAuth(); const { site } = useSite(); + const { symbol, rate } = useCurrency(); const [models, setModels] = useState([]); const [packages, setPackages] = useState([]); @@ -177,9 +178,9 @@ export default function StarterHome() {

{pkg.name}

{pkg.description &&

{pkg.description}

}
- ${pkg.price} + {symbol}{(Number(pkg.price) * rate).toFixed(2)} {pkg.original_price > pkg.price && ( - ${pkg.original_price} + {symbol}{(Number(pkg.original_price) * rate).toFixed(2)} )} {pkg.duration > 0 &&

{t('home.days', { count: pkg.duration })}

}