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