From 6e1e424eb2b2671ed34cbbb88b7503d050069086 Mon Sep 17 00:00:00 2001
From: shanshanzhong
Date: Thu, 16 Jul 2026 21:03:18 -0700
Subject: [PATCH] =?UTF-8?q?fix(=E6=8A=BD=E5=A5=96):=20=E6=A6=82=E7=8E=87?=
=?UTF-8?q?=E6=8C=89=20weight/1000=20=E6=98=BE=E7=A4=BA=E4=B8=8E=E5=BD=95?=
=?UTF-8?q?=E5=85=A5=EF=BC=88=E6=94=AF=E6=8C=81=E5=B0=8F=E6=95=B0%?=
=?UTF-8?q?=EF=BC=8C=E6=80=BB=E6=9D=83=E9=87=8D100000=3D100%=EF=BC=89?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- prize-grid: 概率预览/合计/卡片改用 weightToPercent(weight/1000),
合计校验对齐 FULL_TOTAL_WEIGHT=100000;修正原来把原始 weight 当 % 显示(如 49901%)
- prize-form: 概率输入按百分比(0-100,step 0.001),提交 ×1000 存 weight,回填 ÷1000
Co-Authored-By: Claude Opus 4.8 (1M context)
---
.../admin/src/sections/lottery/prize-form.tsx | 11 +++++---
.../admin/src/sections/lottery/prize-grid.tsx | 28 ++++++++++++-------
2 files changed, 25 insertions(+), 14 deletions(-)
diff --git a/apps/admin/src/sections/lottery/prize-form.tsx b/apps/admin/src/sections/lottery/prize-form.tsx
index 1a1a1e9..0eb487d 100644
--- a/apps/admin/src/sections/lottery/prize-form.tsx
+++ b/apps/admin/src/sections/lottery/prize-form.tsx
@@ -43,6 +43,8 @@ import { toast } from "sonner";
import { z } from "zod";
const CENTS_PER_YUAN = 100;
+// 概率单位约定:weight 1000 = 1%(支持 3 位小数概率,总权重 100000 = 100%)。
+const WEIGHT_PER_PERCENT = 1000;
type TranslateFn = (key: string, defaultValue: string) => string;
@@ -102,7 +104,6 @@ const getPrizeSchema = (t: TranslateFn) =>
.optional(),
weight: z
.number()
- .int(t("prize.weightInteger", "Probability must be an integer"))
.min(0, t("prize.weightMin", "Probability must be at least 0"))
.max(100, t("prize.weightMax", "Probability cannot exceed 100")),
unlimited_stock: z.boolean(),
@@ -221,7 +222,7 @@ function toFormValues(prize?: API.LotteryPrize): PrizeFormValues {
type: (prize?.type as PrizeFormValues["type"]) ?? "vpn_duration",
name: prize?.name ?? "",
icon_url: prize?.icon_url ?? "",
- weight: prize?.weight ?? 1,
+ weight: (prize?.weight ?? 0) / WEIGHT_PER_PERCENT,
unlimited_stock: prize ? prize.total_stock === null : true,
total_stock: prize?.total_stock ?? undefined,
is_fallback: prize?.is_fallback ?? false,
@@ -325,7 +326,9 @@ export default function PrizeForm({
name: values.name.trim(),
icon_url: values.icon_url?.trim() ?? "",
config: toConfig(values),
- weight: values.is_fallback ? 0 : values.weight,
+ weight: values.is_fallback
+ ? 0
+ : Math.round(values.weight * WEIGHT_PER_PERCENT),
total_stock: values.unlimited_stock
? null
: (values.total_stock ?? null),
@@ -643,7 +646,7 @@ export default function PrizeForm({
type: "number",
min: 0,
max: 100,
- step: 1,
+ step: 0.001,
suffix: "%",
disabled: isFallback,
},
diff --git a/apps/admin/src/sections/lottery/prize-grid.tsx b/apps/admin/src/sections/lottery/prize-grid.tsx
index a5565a7..4bc9d01 100644
--- a/apps/admin/src/sections/lottery/prize-grid.tsx
+++ b/apps/admin/src/sections/lottery/prize-grid.tsx
@@ -14,9 +14,16 @@ import PrizeForm from "./prize-form";
const DEFAULT_GRID_SIZE = 9;
const CENTER_SLOT = 4;
-const PERCENT_DECIMALS = 1;
-const REQUIRED_TOTAL_PERCENT = 100;
const CENTS_PER_YUAN = 100;
+// 概率单位约定:weight 1000 = 1%,总权重 100000 = 100.000%(支持 3 位小数概率)。
+const WEIGHT_PER_PERCENT = 1000;
+const FULL_TOTAL_WEIGHT = 100 * WEIGHT_PER_PERCENT;
+
+// weightToPercent 把原始 weight 转成百分比字符串(最多 3 位小数,去尾零)。
+function weightToPercent(weight: number): string {
+ const pct = weight / WEIGHT_PER_PERCENT;
+ return `${Number.parseFloat(pct.toFixed(3))}`;
+}
interface PrizeGridProps {
activity: API.LotteryActivity;
@@ -110,7 +117,7 @@ export default function PrizeGrid({ activity }: PrizeGridProps) {
[randomPrizes]
);
const hasFallback = prizes.some((prize) => prize.is_fallback);
- const totalIsValid = totalWeight === REQUIRED_TOTAL_PERCENT;
+ const totalIsValid = totalWeight === FULL_TOTAL_WEIGHT;
const gridSize = activity.grid_size || DEFAULT_GRID_SIZE;
const slots = Array.from({ length: gridSize }, (_, index) => index);
@@ -137,7 +144,8 @@ export default function PrizeGrid({ activity }: PrizeGridProps) {
{randomPrizes.length > 0 && (
- {t("prize.probabilityTotal", "Total")}: {totalWeight}%
+ {t("prize.probabilityTotal", "Total")}:{" "}
+ {weightToPercent(totalWeight)}%
)}
@@ -145,9 +153,7 @@ export default function PrizeGrid({ activity }: PrizeGridProps) {
{randomPrizes.map((prize) => (
- {prize.name}:{" "}
- {((prize.weight / totalWeight) * 100).toFixed(PERCENT_DECIMALS)}
- %
+ {prize.name}: {weightToPercent(prize.weight)}%
))}
@@ -170,7 +176,7 @@ export default function PrizeGrid({ activity }: PrizeGridProps) {
{t(
"prize.totalNot100Hint",
"The drawable prizes currently total {{total}}%. Adjust each prize's win probability so they sum to exactly 100%.",
- { total: totalWeight }
+ { total: weightToPercent(totalWeight) }
)}
@@ -244,8 +250,10 @@ export default function PrizeGrid({ activity }: PrizeGridProps) {
})()}
{t("prize.weightShort", "Prob")}:{" "}
- {prize.is_fallback ? "—" : `${prize.weight}%`} ·{" "}
- {t("prize.stockShort", "Stock")}:{" "}
+ {prize.is_fallback
+ ? "—"
+ : `${weightToPercent(prize.weight)}%`}{" "}
+ · {t("prize.stockShort", "Stock")}:{" "}
{prize.total_stock === null ? "∞" : prize.total_stock}