积分套餐改为可视化表单配置
This commit is contained in:
parent
7555a6d7f6
commit
95630e735d
@ -446,9 +446,6 @@ export function AdminSettingsClient() {
|
||||
const [membershipOverridesDraft, setMembershipOverridesDraft] = useState(
|
||||
JSON.stringify(defaultMembershipUserOverrides, null, 2)
|
||||
);
|
||||
const [pointsPackagesDraft, setPointsPackagesDraft] = useState(
|
||||
JSON.stringify(defaultPointsPackages, null, 2)
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (siteSettingsData) {
|
||||
@ -518,7 +515,6 @@ export function AdminSettingsClient() {
|
||||
});
|
||||
setMembershipConfigDraft(JSON.stringify(normalizedMembershipConfig, null, 2));
|
||||
setMembershipOverridesDraft(JSON.stringify(normalizedMembershipOverrides, null, 2));
|
||||
setPointsPackagesDraft(JSON.stringify(normalizedPointsPackages, null, 2));
|
||||
}
|
||||
}, [siteSettingsData]);
|
||||
|
||||
@ -1043,7 +1039,6 @@ export function AdminSettingsClient() {
|
||||
try {
|
||||
let parsedMembershipConfig: ReturnType<typeof normalizeMembershipConfig>;
|
||||
let parsedMembershipOverrides: ReturnType<typeof normalizeMembershipUserOverrides>;
|
||||
let parsedPointsPackages: ReturnType<typeof normalizePointsPackages>;
|
||||
|
||||
try {
|
||||
parsedMembershipConfig = normalizeMembershipConfig(
|
||||
@ -1063,12 +1058,7 @@ export function AdminSettingsClient() {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
parsedPointsPackages = normalizePointsPackages(JSON.parse(pointsPackagesDraft));
|
||||
} catch {
|
||||
toast.error("积分套餐 JSON 格式错误,请检查后再保存");
|
||||
return;
|
||||
}
|
||||
const parsedPointsPackages = normalizePointsPackages(siteSettings.pointsPackages);
|
||||
|
||||
const payload = {
|
||||
...siteSettings,
|
||||
@ -1086,7 +1076,6 @@ export function AdminSettingsClient() {
|
||||
setSiteSettings(payload);
|
||||
setMembershipConfigDraft(JSON.stringify(parsedMembershipConfig, null, 2));
|
||||
setMembershipOverridesDraft(JSON.stringify(parsedMembershipOverrides, null, 2));
|
||||
setPointsPackagesDraft(JSON.stringify(parsedPointsPackages, null, 2));
|
||||
toast.success("网站设置保存成功");
|
||||
mutateSiteSettings();
|
||||
} catch (error) {
|
||||
@ -1094,6 +1083,43 @@ export function AdminSettingsClient() {
|
||||
}
|
||||
};
|
||||
|
||||
const updatePointsPackage = (index: number, updater: (item: PointsPackage) => PointsPackage) => {
|
||||
setSiteSettings((prev) => {
|
||||
const list = [...(prev.pointsPackages || [])];
|
||||
if (!list[index]) return prev;
|
||||
list[index] = updater(list[index]);
|
||||
return { ...prev, pointsPackages: list };
|
||||
});
|
||||
};
|
||||
|
||||
const addPointsPackage = () => {
|
||||
setSiteSettings((prev) => {
|
||||
const list = [...(prev.pointsPackages || [])];
|
||||
const nextIndex = list.length + 1;
|
||||
list.push({
|
||||
id: `package-${Date.now()}`,
|
||||
name: `套餐${nextIndex}`,
|
||||
points: 1000,
|
||||
price: 9.9,
|
||||
currency: "CNY",
|
||||
isEnabled: true,
|
||||
});
|
||||
return { ...prev, pointsPackages: list };
|
||||
});
|
||||
};
|
||||
|
||||
const removePointsPackage = (index: number) => {
|
||||
setSiteSettings((prev) => {
|
||||
const list = [...(prev.pointsPackages || [])];
|
||||
if (list.length <= 1) {
|
||||
toast.error("至少保留一个积分套餐");
|
||||
return prev;
|
||||
}
|
||||
list.splice(index, 1);
|
||||
return { ...prev, pointsPackages: list };
|
||||
});
|
||||
};
|
||||
|
||||
// --- 抠图API配置逻辑 ---
|
||||
const handleSaveRmbgConfig = async () => {
|
||||
try {
|
||||
@ -2973,8 +2999,8 @@ export function AdminSettingsClient() {
|
||||
setSiteSettings({
|
||||
...siteSettings,
|
||||
pointsSystemConfig: defaultPointsSystemConfig,
|
||||
pointsPackages: defaultPointsPackages,
|
||||
});
|
||||
setPointsPackagesDraft(JSON.stringify(defaultPointsPackages, null, 2));
|
||||
toast.success("已恢复积分配置默认模板");
|
||||
}}
|
||||
>
|
||||
@ -3026,16 +3052,111 @@ export function AdminSettingsClient() {
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="pointsPackagesDraft">积分套餐 JSON</Label>
|
||||
<Textarea
|
||||
id="pointsPackagesDraft"
|
||||
value={pointsPackagesDraft}
|
||||
onChange={(e) => setPointsPackagesDraft(e.target.value)}
|
||||
rows={8}
|
||||
className="font-mono text-xs"
|
||||
/>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
字段示例:`id`、`name`、`points`、`price`、`currency`、`isEnabled`。
|
||||
<div className="flex items-center justify-between">
|
||||
<Label>积分套餐</Label>
|
||||
<Button type="button" variant="outline" size="sm" onClick={addPointsPackage}>
|
||||
<Plus className="mr-1.5 h-3.5 w-3.5" />
|
||||
新增套餐
|
||||
</Button>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
{(siteSettings.pointsPackages || []).map((pkg, index) => (
|
||||
<div key={`${pkg.id}-${index}`} className="rounded-lg border p-3 space-y-3">
|
||||
<div className="grid grid-cols-1 gap-3 md:grid-cols-6">
|
||||
<div className="space-y-1 md:col-span-2">
|
||||
<Label className="text-xs text-muted-foreground">套餐名称</Label>
|
||||
<Input
|
||||
value={pkg.name}
|
||||
onChange={(e) =>
|
||||
updatePointsPackage(index, (item) => ({
|
||||
...item,
|
||||
name: e.target.value,
|
||||
}))
|
||||
}
|
||||
placeholder="如:入门包"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label className="text-xs text-muted-foreground">积分数量</Label>
|
||||
<Input
|
||||
type="number"
|
||||
min={1}
|
||||
value={pkg.points}
|
||||
onChange={(e) =>
|
||||
updatePointsPackage(index, (item) => ({
|
||||
...item,
|
||||
points: Math.max(1, Number.parseInt(e.target.value || "1", 10)),
|
||||
}))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label className="text-xs text-muted-foreground">售价</Label>
|
||||
<Input
|
||||
type="number"
|
||||
min={0}
|
||||
step="0.01"
|
||||
value={pkg.price}
|
||||
onChange={(e) =>
|
||||
updatePointsPackage(index, (item) => ({
|
||||
...item,
|
||||
price: Math.max(0, Number.parseFloat(e.target.value || "0")),
|
||||
}))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label className="text-xs text-muted-foreground">货币</Label>
|
||||
<Input
|
||||
value={pkg.currency}
|
||||
onChange={(e) =>
|
||||
updatePointsPackage(index, (item) => ({
|
||||
...item,
|
||||
currency: String(e.target.value || "CNY").toUpperCase(),
|
||||
}))
|
||||
}
|
||||
placeholder="CNY"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-end justify-end gap-2">
|
||||
<div className="flex items-center gap-2 rounded-md border px-2 py-1.5">
|
||||
<Switch
|
||||
checked={pkg.isEnabled !== false}
|
||||
onCheckedChange={(checked) =>
|
||||
updatePointsPackage(index, (item) => ({
|
||||
...item,
|
||||
isEnabled: checked,
|
||||
}))
|
||||
}
|
||||
/>
|
||||
<span className="text-xs text-muted-foreground">启用</span>
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={() => removePointsPackage(index)}
|
||||
title="删除套餐"
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label className="text-xs text-muted-foreground">套餐ID(系统内部标识)</Label>
|
||||
<Input
|
||||
value={pkg.id}
|
||||
onChange={(e) =>
|
||||
updatePointsPackage(index, (item) => ({
|
||||
...item,
|
||||
id: e.target.value.replace(/\s+/g, "-"),
|
||||
}))
|
||||
}
|
||||
placeholder="starter-1000"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user