fix(admin): set user image quota by total instead of delta
Operators expect direct total capacity like space quota; the delta-based grant UI caused wrong results when entering a target amount on production. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -25,7 +25,8 @@ export function UserDetailPage() {
|
|||||||
const [spaceQuotaMb, setSpaceQuotaMb] = useState('5');
|
const [spaceQuotaMb, setSpaceQuotaMb] = useState('5');
|
||||||
const [imageQuota, setImageQuota] = useState<ImageQuotaView | null>(null);
|
const [imageQuota, setImageQuota] = useState<ImageQuotaView | null>(null);
|
||||||
const [imageQuotaLoading, setImageQuotaLoading] = useState(false);
|
const [imageQuotaLoading, setImageQuotaLoading] = useState(false);
|
||||||
const [imageGrant, setImageGrant] = useState({ delta: '', note: '' });
|
const [imageTotal, setImageTotal] = useState('');
|
||||||
|
const [imageGrantNote, setImageGrantNote] = useState('');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!user?.spaceQuotaBytes) return;
|
if (!user?.spaceQuotaBytes) return;
|
||||||
@@ -82,6 +83,14 @@ export function UserDetailPage() {
|
|||||||
};
|
};
|
||||||
}, [user?.id, user?.role]);
|
}, [user?.id, user?.role]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!imageQuota || imageQuota.unlimited || imageQuota.total == null) {
|
||||||
|
setImageTotal('');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setImageTotal(String(imageQuota.total));
|
||||||
|
}, [imageQuota?.total, imageQuota?.unlimited]);
|
||||||
|
|
||||||
const handleRecharge = async (e: React.FormEvent) => {
|
const handleRecharge = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!user) return;
|
if (!user) return;
|
||||||
@@ -100,24 +109,42 @@ export function UserDetailPage() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleImageGrant = async (e: React.FormEvent) => {
|
const handleImageQuotaSave = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!user) return;
|
if (!user) return;
|
||||||
setMessage(null);
|
setMessage(null);
|
||||||
setLocalError(null);
|
setLocalError(null);
|
||||||
setError(null);
|
setError(null);
|
||||||
const delta = Math.floor(Number(imageGrant.delta));
|
if (!imageQuota) {
|
||||||
if (!Number.isFinite(delta) || delta === 0) {
|
setLocalError('暂无额度信息,用户可能尚无有效订阅');
|
||||||
setLocalError('请输入非零整数额度');
|
return;
|
||||||
|
}
|
||||||
|
if (imageQuota.unlimited) {
|
||||||
|
setLocalError('当前为无限额度套餐,无法在此调整');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const targetTotal = Math.floor(Number(imageTotal));
|
||||||
|
if (!Number.isFinite(targetTotal) || targetTotal < 0) {
|
||||||
|
setLocalError('请输入非负整数总额度');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (targetTotal < imageQuota.used) {
|
||||||
|
setLocalError(`总额度不能低于已用量(${imageQuota.used} 张)`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const targetBonus = targetTotal - imageQuota.limit;
|
||||||
|
const delta = targetBonus - imageQuota.bonus;
|
||||||
|
if (delta === 0) {
|
||||||
|
setMessage('额度未变化');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const result = await grantUserImageQuota(user.id, delta, imageGrant.note.trim());
|
const result = await grantUserImageQuota(user.id, delta, imageGrantNote.trim());
|
||||||
setImageQuota(result.quota);
|
setImageQuota(result.quota);
|
||||||
setMessage('图片额度已更新');
|
setMessage('图片额度已更新');
|
||||||
setImageGrant({ delta: '', note: '' });
|
setImageGrantNote('');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setLocalError(err instanceof Error ? err.message : '图片额度调整失败');
|
setLocalError(err instanceof Error ? err.message : '图片额度设置失败');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -245,21 +272,31 @@ export function UserDetailPage() {
|
|||||||
) : (
|
) : (
|
||||||
<p className="muted">{imageQuotaSummary || '暂无额度信息(用户可能尚无订阅)'}</p>
|
<p className="muted">{imageQuotaSummary || '暂无额度信息(用户可能尚无订阅)'}</p>
|
||||||
)}
|
)}
|
||||||
<form className="admin-form" onSubmit={handleImageGrant}>
|
<p className="muted">
|
||||||
|
直接设置本周期图片总额度(套餐额度 + 额外充值)。已用量不变,修改后会反映在「充值」部分。
|
||||||
|
</p>
|
||||||
|
<form className="admin-form" onSubmit={handleImageQuotaSave}>
|
||||||
<input
|
<input
|
||||||
placeholder="调整额度(张,正数充值、负数扣减)"
|
placeholder="图片总额度(张)"
|
||||||
type="number"
|
type="number"
|
||||||
|
min="0"
|
||||||
step="1"
|
step="1"
|
||||||
value={imageGrant.delta}
|
value={imageTotal}
|
||||||
onChange={(e) => setImageGrant((s) => ({ ...s, delta: e.target.value }))}
|
onChange={(e) => setImageTotal(e.target.value)}
|
||||||
|
disabled={!imageQuota || imageQuota.unlimited}
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
placeholder="备注"
|
placeholder="备注"
|
||||||
value={imageGrant.note}
|
value={imageGrantNote}
|
||||||
onChange={(e) => setImageGrant((s) => ({ ...s, note: e.target.value }))}
|
onChange={(e) => setImageGrantNote(e.target.value)}
|
||||||
|
disabled={!imageQuota || imageQuota.unlimited}
|
||||||
/>
|
/>
|
||||||
<button type="submit" className="send-btn">
|
<button
|
||||||
调整额度
|
type="submit"
|
||||||
|
className="send-btn"
|
||||||
|
disabled={!imageQuota || imageQuota.unlimited}
|
||||||
|
>
|
||||||
|
保存额度
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
Reference in New Issue
Block a user