fix: 限制自定义充值最低金额
This commit is contained in:
@@ -911,6 +911,7 @@ export function App() {
|
||||
projects={projects}
|
||||
team={currentTeam}
|
||||
onRecharge={(amount, bonusPoints) => action(() => api.recharge({ amount, bonus_points: bonusPoints }), "充值成功")}
|
||||
onNotify={(type, text) => setNotice({ type, text })}
|
||||
/>
|
||||
);
|
||||
case "team":
|
||||
|
||||
@@ -766,6 +766,7 @@ main { position: relative; background: var(--background-base); min-width: 0; }
|
||||
.field-label { font-size: 13px; font-weight: 500; color: var(--accent-black); }
|
||||
.field-label .req { color: var(--accent-crimson); margin-left: 2px; }
|
||||
.field-hint { font-size: 12px; color: var(--black-alpha-48); }
|
||||
.field-hint.is-error { color: var(--accent-crimson); }
|
||||
.input, .textarea, .select {
|
||||
height: 36px;
|
||||
padding: 0 14px;
|
||||
@@ -783,6 +784,10 @@ main { position: relative; background: var(--background-base); min-width: 0; }
|
||||
border-color: var(--heat-40);
|
||||
box-shadow: inset 0 0 0 1px var(--heat-40);
|
||||
}
|
||||
.input.is-invalid, .textarea.is-invalid, .select.is-invalid {
|
||||
border-color: var(--accent-crimson);
|
||||
box-shadow: inset 0 0 0 1px var(--accent-crimson);
|
||||
}
|
||||
.input::placeholder, .textarea::placeholder { color: var(--black-alpha-48); }
|
||||
.input:disabled, .textarea:disabled, .select:disabled {
|
||||
background: var(--black-alpha-5);
|
||||
|
||||
@@ -59,6 +59,7 @@ const RECHARGE: Array<{ amt: number; gift: string; bonus: boolean; bonusAmt: num
|
||||
{ amt: 1000, gift: "+800 积分赠送", bonus: true, bonusAmt: 800 },
|
||||
{ amt: 3000, gift: "+3000 积分赠送", bonus: true, bonusAmt: 3000 }
|
||||
];
|
||||
const MIN_RECHARGE_AMOUNT = 50;
|
||||
|
||||
const STAGES: Array<{ k: string; color: string; bucket: keyof BillingTrend["by_stage"] }> = [
|
||||
{ k: "视频片段(Seedance)", color: "var(--heat)", bucket: "video" },
|
||||
@@ -113,12 +114,13 @@ function TopupModal({ open, channel, amount, bonus, rate, close, onDone }: {
|
||||
);
|
||||
}
|
||||
|
||||
export function AccountPage({ billing, projects, team, onRecharge }: {
|
||||
export function AccountPage({ billing, projects, team, onRecharge, onNotify }: {
|
||||
billing: BillingSummary | null;
|
||||
projects: Project[];
|
||||
// team prop 用于读取团队级月限额(与团队管理页保持同一来源 · #14)
|
||||
team?: Team | null;
|
||||
onRecharge: (amount: number, bonus: number) => void | Promise<unknown>;
|
||||
onNotify: (type: "success" | "error" | "info", text: string) => void;
|
||||
}) {
|
||||
const [tab, setTab] = useState<Tab>("overview");
|
||||
// 积分汇率(积分/¥):预览换算与后端 recharge 同源,汇率改配后不至于「预览 ×10 实到 ×N」
|
||||
@@ -175,11 +177,29 @@ export function AccountPage({ billing, projects, team, onRecharge }: {
|
||||
const [billJump, setBillJump] = useState("");
|
||||
|
||||
const selectedCard = RECHARGE.find((item) => item.amt === recharge);
|
||||
const effectiveAmount = Number(customAmt) > 0 ? Number(customAmt) : recharge;
|
||||
const effectiveBonus = Number(customAmt) > 0 ? 0 : selectedCard?.bonusAmt || 0;
|
||||
const customAmount = Number(customAmt);
|
||||
const hasCustomAmount = customAmt.length > 0;
|
||||
const customAmountIsValid = !hasCustomAmount || (Number.isFinite(customAmount) && customAmount >= MIN_RECHARGE_AMOUNT);
|
||||
const effectiveAmount = hasCustomAmount ? customAmount : recharge;
|
||||
const effectiveBonus = hasCustomAmount ? 0 : selectedCard?.bonusAmt || 0;
|
||||
const setCustomIntegerAmount = (value: string) => setCustomAmt(value.replace(/\D/g, "").replace(/^0+/, ""));
|
||||
|
||||
function validateCustomAmount() {
|
||||
if (customAmountIsValid) return true;
|
||||
onNotify("error", `最小充值金额为 ${MIN_RECHARGE_AMOUNT} 元`);
|
||||
return false;
|
||||
}
|
||||
|
||||
function openTopup(channel: "wechat" | "alipay") {
|
||||
if (!validateCustomAmount()) return;
|
||||
setTopupChannel(channel);
|
||||
}
|
||||
|
||||
async function submitRecharge() {
|
||||
if (!validateCustomAmount()) {
|
||||
setTopupChannel(null);
|
||||
return;
|
||||
}
|
||||
if (effectiveAmount <= 0) return;
|
||||
await onRecharge(effectiveAmount, effectiveBonus); // 父级 action 成功后弹全局「充值成功」toast
|
||||
setTopupChannel(null);
|
||||
@@ -319,13 +339,25 @@ export function AccountPage({ billing, projects, team, onRecharge }: {
|
||||
</div>
|
||||
<div className="pay-row">
|
||||
<div className="pay-title">自定义金额</div>
|
||||
<input className="input" placeholder="最低 ¥50,可输入任意金额" type="text" inputMode="numeric" pattern="[0-9]*" value={customAmt} onChange={(event) => setCustomIntegerAmount(event.target.value)} />
|
||||
<input
|
||||
className={`input${!customAmountIsValid ? " is-invalid" : ""}`}
|
||||
placeholder="最低 ¥50,可输入任意金额"
|
||||
type="number"
|
||||
min={MIN_RECHARGE_AMOUNT}
|
||||
step="1"
|
||||
inputMode="numeric"
|
||||
value={customAmt}
|
||||
aria-invalid={!customAmountIsValid}
|
||||
aria-describedby="custom-recharge-error"
|
||||
onChange={(event) => setCustomIntegerAmount(event.target.value)}
|
||||
/>
|
||||
{!customAmountIsValid && <div className="field-hint is-error" id="custom-recharge-error" role="alert">充值金额不能低于 {MIN_RECHARGE_AMOUNT} 元</div>}
|
||||
<div className="pay-btn-row">
|
||||
<button className="btn pay-method-btn pay-wechat" type="button" aria-label="微信支付" onClick={() => setTopupChannel("wechat")}>
|
||||
<button className="btn pay-method-btn pay-wechat" type="button" aria-label="微信支付" onClick={() => openTopup("wechat")}>
|
||||
<span className="pay-logo" aria-hidden="true"><img src="/assets/pay-wechat.png" alt="" /></span>
|
||||
微信支付
|
||||
</button>
|
||||
<button className="btn pay-method-btn pay-alipay" type="button" aria-label="支付宝" onClick={() => setTopupChannel("alipay")}>
|
||||
<button className="btn pay-method-btn pay-alipay" type="button" aria-label="支付宝" onClick={() => openTopup("alipay")}>
|
||||
<span className="pay-logo" aria-hidden="true"><img src="/assets/pay-alipay.png" alt="" /></span>
|
||||
支付宝
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user