每日免费发放额为 0 时前端隐藏该池

- 后端:module-runtime 的 daily_free_points_per_day 校验由「必须大于 0」放宽为只校验上界,错误变体改名 DailyFreePointsPerDayOverflow
- 前端共享层:mudPoints 新增 shouldShowProfileDailyFreePool,判定 remaining > 0 || reset > 0,判定收口一处
- 钱包入口:PlatformMudPointWalletEntry 在每日免费发放额为 0 且无存量时隐藏该行
- 充值弹层:PlatformProfileRechargeModal 池概览按可见池渲染(三列变两列),扣点顺序提示与泥点确认页文案随可见性切换
- Admin Web:账号配置页每日免费泥点允许填 0(校验、min 与文案同步)
- 测试:删除「每日免费必然存在」断言,新增隐藏 / 有存量仍显示 / 发放额为正三类用例,并修正钱包入口测试在 HEAD 上失效的选择器
- 文档:更新会员与泥点前端改造设计文档 §14 与 shared-memory 决策记录
This commit is contained in:
2026-10-03 16:48:12 +08:00
parent 43f0c51120
commit 1a106eadef
14 changed files with 326 additions and 55 deletions
@@ -94,7 +94,7 @@ test('账号配置页一次保存初始和每日免费泥点', async () => {
);
});
test('账号配置页拒绝非正整数每日免费额度', async () => {
test('账号配置页拒绝负数或非整数每日免费额度', async () => {
render(
<AdminProfileWalletConfigPage
token="admin-token"
@@ -109,5 +109,38 @@ test('账号配置页拒绝非正整数每日免费额度', async () => {
(screen.getByRole('button', { name: '保存' }) as HTMLButtonElement)
.disabled,
).toBe(true);
fireEvent.change(input, { target: { value: '-1' } });
expect(
(screen.getByRole('button', { name: '保存' }) as HTMLButtonElement)
.disabled,
).toBe(true);
expect(upsertProfileWalletConfig).not.toHaveBeenCalled();
});
test('账号配置页允许把每日免费泥点配置为 0', async () => {
const user = userEvent.setup();
vi.mocked(upsertProfileWalletConfig).mockResolvedValue({
...configResponse,
dailyFreePointsPerDay: 0,
});
render(
<AdminProfileWalletConfigPage
token="admin-token"
result={configResponse}
onUnauthorized={vi.fn()}
onResultChange={vi.fn()}
/>,
);
fireEvent.change(await screen.findByLabelText('每日免费泥点数'), {
target: { value: '0' },
});
await user.click(screen.getByRole('button', { name: '保存' }));
expect(screen.getByText('初始 100 泥点,每日免费 0 泥点')).toBeTruthy();
await user.click(screen.getByRole('button', { name: '确认' }));
await waitFor(() =>
expect(upsertProfileWalletConfig).toHaveBeenCalledWith('admin-token', {
initialMudPoints: 100,
dailyFreePointsPerDay: 0,
}),
);
});
@@ -66,11 +66,11 @@ export function AdminProfileWalletConfigPage({
if (isSaving) {
return;
}
const normalizedDailyFreePointsPerDay = parsePositiveInteger(
const normalizedDailyFreePointsPerDay = parseNonNegativeInteger(
dailyFreePointsPerDay,
);
if (!normalizedDailyFreePointsPerDay) {
setErrorMessage('每日免费泥点数必须是大于 0 的整数');
if (normalizedDailyFreePointsPerDay === null) {
setErrorMessage('每日免费泥点数必须是不小于 0 的整数');
return;
}
@@ -139,7 +139,7 @@ export function AdminProfileWalletConfigPage({
<AdminField label="每日免费泥点数">
<input
min={1}
min={0}
step={1}
type="number"
value={dailyFreePointsPerDay}
@@ -154,7 +154,7 @@ export function AdminProfileWalletConfigPage({
disabled={
isSaving ||
!parsePositiveInteger(initialMudPoints) ||
!parsePositiveInteger(dailyFreePointsPerDay)
parseNonNegativeInteger(dailyFreePointsPerDay) === null
}
type="submit"
>
@@ -205,3 +205,11 @@ function parsePositiveInteger(value: string) {
const parsed = Number(value);
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : 0;
}
function parseNonNegativeInteger(value: string): number | null {
if (value.trim() === '') {
return null;
}
const parsed = Number(value);
return Number.isSafeInteger(parsed) && parsed >= 0 ? parsed : null;
}