diff --git a/apps/admin/dist.zip b/apps/admin/dist.zip index b046de4..4d195bb 100644 Binary files a/apps/admin/dist.zip and b/apps/admin/dist.zip differ diff --git a/apps/admin/src/sections/lottery/grant-dialog.tsx b/apps/admin/src/sections/lottery/grant-dialog.tsx index c52dab2..60f6604 100644 --- a/apps/admin/src/sections/lottery/grant-dialog.tsx +++ b/apps/admin/src/sections/lottery/grant-dialog.tsx @@ -31,6 +31,16 @@ const grantSchema = z.object({ source_ref: z.string().trim().min(1), }); +// 数字输入统一用 type="text" + 该 formatOutput,避开 number 类型 input 的实时钳制问题。 +const numberOutput = (v: string | number): number | undefined => { + const s = String(v ?? "").trim(); + if (s === "") { + return; + } + const n = Number(s); + return Number.isNaN(n) ? undefined : n; +}; + type GrantFormValues = z.infer; const SOURCE_REF_RANDOM_LENGTH = 6; @@ -123,10 +133,11 @@ export default function GrantDialog({ {t("grant.userId", "User ID")} field.onChange(value)} placeholder={t("grant.userIdPlaceholder", "e.g. 87433")} - type="number" + type="text" value={field.value} /> @@ -148,10 +159,11 @@ export default function GrantDialog({ {t("grant.amount", "Amount")} field.onChange(value)} placeholder="1" - type="number" + type="text" value={field.value} /> diff --git a/apps/admin/src/sections/lottery/prize-form.tsx b/apps/admin/src/sections/lottery/prize-form.tsx index b1511d6..3866492 100644 --- a/apps/admin/src/sections/lottery/prize-form.tsx +++ b/apps/admin/src/sections/lottery/prize-form.tsx @@ -46,6 +46,18 @@ const CENTS_PER_YUAN = 100; // 概率单位约定:weight 1000 = 1%(支持 3 位小数概率,总权重 100000 = 100%)。 const WEIGHT_PER_PERCENT = 1000; +// numberOutput 把 text 输入转成数字(空/非法→undefined)。数字输入统一用 type="text" +// + 该 formatOutput,避开 EnhancedInput 在 type="number" 下 min/max 实时钳制导致的 +// “输入 0 / 改中间位跳变”问题;提交仍是数字,后端收 int。 +const numberOutput = (v: string | number): number | undefined => { + const s = String(v ?? "").trim(); + if (s === "") { + return; + } + const n = Number(s); + return Number.isNaN(n) ? undefined : n; +}; + type TranslateFn = (key: string, defaultValue: string) => string; const PRIZE_TYPE_OPTIONS: { @@ -442,9 +454,9 @@ export default function PrizeForm({ "duration_days", t("prize.durationDays", "Duration (days)"), { - type: "number", - min: 1, - step: 1, + type: "text", + inputMode: "numeric", + formatOutput: numberOutput, placeholder: t("prize.durationDaysPlaceholder", "e.g. 1"), suffix: t("prize.daysSuffix", "days"), } @@ -538,9 +550,9 @@ export default function PrizeForm({ "claim_ttl_hours", t("prize.claimTtlHours", "Claim window (hours)"), { - type: "number", - min: 1, - step: 1, + type: "text", + inputMode: "numeric", + formatOutput: numberOutput, placeholder: "168", suffix: t("prize.hoursSuffix", "hours"), }, @@ -561,10 +573,9 @@ export default function PrizeForm({ "weight", t("prize.weight", "Win probability (%)"), { - type: "number", - min: 0, - max: 100, - step: 0.001, + type: "text", + inputMode: "decimal", + formatOutput: numberOutput, suffix: "%", disabled: isFallback, }, @@ -587,9 +598,9 @@ export default function PrizeForm({ "total_stock", t("prize.totalStock", "Total Stock"), { - type: "number", - min: 1, - step: 1, + type: "text", + inputMode: "numeric", + formatOutput: numberOutput, placeholder: t("prize.totalStockPlaceholder", "e.g. 100"), } )}