feat(抽奖): 奖品类型精简为 免费时长+虚拟货币(人工发放)+谢谢参与;重建 dist.zip
PR Check / Lint, Check, Test and Build (push) Has been cancelled

- prize-form 只保留 vpn_duration / crypto / none(去掉佣金/实物/人工其他)
- crypto 标签改「虚拟货币」
- 重新 vite build 并打包 apps/admin/dist.zip(部署产物,含全部抽奖新功能)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-17 01:37:54 -07:00
parent fd94fb80c1
commit 93d4091254
3 changed files with 6 additions and 88 deletions
Binary file not shown.
@@ -119,7 +119,7 @@
"type": "奖品类型", "type": "奖品类型",
"typeCommission": "佣金", "typeCommission": "佣金",
"typeCoupon": "优惠券", "typeCoupon": "优惠券",
"typeCrypto": "加密货币", "typeCrypto": "虚拟货币",
"typeManualOther": "人工发放(其他)", "typeManualOther": "人工发放(其他)",
"typeNone": "谢谢参与", "typeNone": "谢谢参与",
"typePhysical": "实物奖品", "typePhysical": "实物奖品",
+5 -87
View File
@@ -59,43 +59,21 @@ const PRIZE_TYPE_OPTIONS: {
labelDefault: "VPN Duration", labelDefault: "VPN Duration",
}, },
{ {
value: "commission", value: "crypto",
labelKey: "prize.typeCommission", labelKey: "prize.typeCrypto",
labelDefault: "Commission", labelDefault: "Crypto",
}, },
{ {
value: "none", value: "none",
labelKey: "prize.typeNone", labelKey: "prize.typeNone",
labelDefault: "No Prize (Thanks)", labelDefault: "No Prize (Thanks)",
}, },
{
value: "crypto",
labelKey: "prize.typeCrypto",
labelDefault: "Crypto",
},
{
value: "physical",
labelKey: "prize.typePhysical",
labelDefault: "Physical",
},
{
value: "manual_other",
labelKey: "prize.typeManualOther",
labelDefault: "Manual (Other)",
},
]; ];
const getPrizeSchema = (t: TranslateFn) => const getPrizeSchema = (t: TranslateFn) =>
z z
.object({ .object({
type: z.enum([ type: z.enum(["vpn_duration", "crypto", "none"]),
"vpn_duration",
"commission",
"none",
"crypto",
"physical",
"manual_other",
]),
name: z.string().min(1, t("prize.nameRequired", "Name is required")), name: z.string().min(1, t("prize.nameRequired", "Name is required")),
icon_url: z icon_url: z
.string() .string()
@@ -134,24 +112,6 @@ const getPrizeSchema = (t: TranslateFn) =>
) )
); );
} }
if (data.type === "commission") {
const yuan = data.amount_yuan ?? 0;
const cents = yuan * CENTS_PER_YUAN;
if (yuan <= 0) {
issue(
"amount_yuan",
t("prize.amountRequired", "Amount must be greater than 0")
);
} else if (Math.abs(Math.round(cents) - cents) > 1e-6) {
issue(
"amount_yuan",
t(
"prize.amountPrecision",
"Amount supports at most 2 decimal places"
)
);
}
}
if (data.type === "crypto") { if (data.type === "crypto") {
if (!data.crypto_amount?.trim()) { if (!data.crypto_amount?.trim()) {
issue( issue(
@@ -256,11 +216,6 @@ function toConfig(values: PrizeFormValues): Record<string, any> {
} }
return config; return config;
} }
if (values.type === "commission") {
return {
amount_cents: Math.round((values.amount_yuan ?? 0) * CENTS_PER_YUAN),
};
}
if (values.type === "crypto") { if (values.type === "crypto") {
return withTtl({ return withTtl({
amount: values.crypto_amount?.trim() ?? "", amount: values.crypto_amount?.trim() ?? "",
@@ -271,13 +226,6 @@ function toConfig(values: PrizeFormValues): Record<string, any> {
.filter(Boolean), .filter(Boolean),
}); });
} }
if (values.type === "physical" || values.type === "manual_other") {
const config: Record<string, any> = {};
if (values.sku_name?.trim()) {
config.sku_name = values.sku_name.trim();
}
return withTtl(config);
}
return {}; return {};
} }
@@ -548,23 +496,6 @@ export default function PrizeForm({
)} )}
/> />
)} )}
{type === "commission" &&
renderInput(
"amount_yuan",
t("prize.amountYuan", "Commission Amount (CNY)"),
{
type: "number",
min: 0,
step: 0.01,
placeholder: "0.00",
prefix: "¥",
suffix: t("prize.yuanSuffix", "CNY"),
},
t(
"prize.amountUnitHint",
"Entered in CNY (yuan), stored as cents; up to 2 decimal places"
)
)}
{type === "none" && ( {type === "none" && (
<p className="rounded-md bg-muted/50 p-3 text-muted-foreground text-xs"> <p className="rounded-md bg-muted/50 p-3 text-muted-foreground text-xs">
{t( {t(
@@ -601,20 +532,7 @@ export default function PrizeForm({
)} )}
</> </>
)} )}
{(type === "physical" || type === "manual_other") && {type === "crypto" && (
renderInput(
"sku_name",
t("prize.skuName", "Item name (optional)"),
{
placeholder: t(
"prize.skuNamePlaceholder",
"e.g. Limited T-shirt"
),
}
)}
{(type === "crypto" ||
type === "physical" ||
type === "manual_other") && (
<> <>
{renderInput( {renderInput(
"claim_ttl_hours", "claim_ttl_hours",