diff --git a/app/(admin)/settings/admin-settings-client.tsx b/app/(admin)/settings/admin-settings-client.tsx index d8a850a74..3e17927ef 100644 --- a/app/(admin)/settings/admin-settings-client.tsx +++ b/app/(admin)/settings/admin-settings-client.tsx @@ -73,6 +73,14 @@ import { normalizeMembershipConfig, normalizeMembershipUserOverrides, } from "@/lib/membership/config"; +import { + defaultPointsPackages, + defaultPointsSystemConfig, + normalizePointsPackages, + normalizePointsSystemConfig, + type PointsPackage, + type PointsSystemConfig, +} from "@/lib/points/config"; const fetcher = (url: string) => fetch(url).then((res) => res.json()); @@ -85,6 +93,8 @@ type User = { role: "guest" | "regular" | "admin"; isActive: boolean; monthlyImageLimit: number | null; + pointsBalance: number; + pointsGiftedTotal: number; createdAt: string; }; @@ -119,6 +129,7 @@ type ImageGenConfig = { supportsNegativePrompt?: boolean; // 是否支持反向提示词 supportsWatermark?: boolean; // 是否支持水印开关 supportsSeed?: boolean; // 是否支持随机种子 + creditCostPerImage?: number; // 每张图片积分消耗 enableLoadingPlaceholder?: boolean; // 是否启用加载占位图 loadingColor?: string; // 占位图主题色,如 "purple", "blue", "green" }; @@ -178,6 +189,8 @@ type SiteSettings = { canvasAutoModeDefaultModelId: string | null; lovartComposerPrefix: string; lovartComposerPreviewTexts: string[]; + pointsSystemConfig: PointsSystemConfig; + pointsPackages: PointsPackage[]; lovartPromoText: string; lovartPromoActionText: string; lovartHeroTitle: string; @@ -316,6 +329,7 @@ export function AdminSettingsClient() { const [userSearch, setUserSearch] = useState(""); const [userPage, setUserPage] = useState(1); const [userLimit] = useState(10); + const [giftPointsDraftByUser, setGiftPointsDraftByUser] = useState>({}); // 批量设置出图限额状态 const [showBatchLimitDialog, setShowBatchLimitDialog] = useState(false); @@ -415,6 +429,8 @@ export function AdminSettingsClient() { "帮我把这张产品图做成高级电商海报", "基于参考图延展 3 套同风格方案", ], + pointsSystemConfig: defaultPointsSystemConfig, + pointsPackages: defaultPointsPackages, lovartPromoText: "现在升级,立享 Kling 3.0 与 Nano Banana Pro,最高 5 折优惠", lovartPromoActionText: "立即升级", lovartHeroTitle: "由壹点AI 让设计更简单", @@ -430,6 +446,9 @@ export function AdminSettingsClient() { const [membershipOverridesDraft, setMembershipOverridesDraft] = useState( JSON.stringify(defaultMembershipUserOverrides, null, 2) ); + const [pointsPackagesDraft, setPointsPackagesDraft] = useState( + JSON.stringify(defaultPointsPackages, null, 2) + ); useEffect(() => { if (siteSettingsData) { @@ -439,6 +458,12 @@ export function AdminSettingsClient() { const normalizedMembershipOverrides = normalizeMembershipUserOverrides( (siteSettingsData as any).membershipUserOverrides ); + const normalizedPointsSystemConfig = normalizePointsSystemConfig( + (siteSettingsData as any).pointsSystemConfig + ); + const normalizedPointsPackages = normalizePointsPackages( + (siteSettingsData as any).pointsPackages + ); setSiteSettings({ siteName: siteSettingsData.siteName || "AI Chatbot", @@ -476,6 +501,8 @@ export function AdminSettingsClient() { "帮我把这张产品图做成高级电商海报", "基于参考图延展 3 套同风格方案", ], + pointsSystemConfig: normalizedPointsSystemConfig, + pointsPackages: normalizedPointsPackages, lovartPromoText: siteSettingsData.lovartPromoText || "现在升级,立享 Kling 3.0 与 Nano Banana Pro,最高 5 折优惠", @@ -491,6 +518,7 @@ export function AdminSettingsClient() { }); setMembershipConfigDraft(JSON.stringify(normalizedMembershipConfig, null, 2)); setMembershipOverridesDraft(JSON.stringify(normalizedMembershipOverrides, null, 2)); + setPointsPackagesDraft(JSON.stringify(normalizedPointsPackages, null, 2)); } }, [siteSettingsData]); @@ -864,6 +892,7 @@ export function AdminSettingsClient() { supportsNegativePrompt: false, supportsWatermark: false, supportsSeed: false, + creditCostPerImage: 0, enableLoadingPlaceholder: true, loadingColor: "purple", } as ImageGenConfig, @@ -942,6 +971,32 @@ export function AdminSettingsClient() { } }; + const handleGiftPoints = async (userId: string) => { + const rawValue = giftPointsDraftByUser[userId] || ""; + const points = Number.parseInt(rawValue, 10); + if (!Number.isFinite(points) || points <= 0) { + toast.error("请输入有效的赠送积分(大于 0)"); + return; + } + + try { + const response = await fetch("/api/admin/users", { + method: "PATCH", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ id: userId, giftPoints: points }), + }); + const data = await response.json(); + if (!response.ok) { + throw new Error(data?.message || data?.error || "赠送失败"); + } + toast.success(`已赠送 ${points} 积分`); + setGiftPointsDraftByUser((prev) => ({ ...prev, [userId]: "" })); + mutateUsers(); + } catch (error) { + toast.error(error instanceof Error ? error.message : "赠送积分失败"); + } + }; + const handleDeleteUser = async (userId: string) => { if (!confirm("确定要删除此用户吗?")) return; try { @@ -988,6 +1043,7 @@ export function AdminSettingsClient() { try { let parsedMembershipConfig: ReturnType; let parsedMembershipOverrides: ReturnType; + let parsedPointsPackages: ReturnType; try { parsedMembershipConfig = normalizeMembershipConfig( @@ -1007,10 +1063,18 @@ export function AdminSettingsClient() { return; } + try { + parsedPointsPackages = normalizePointsPackages(JSON.parse(pointsPackagesDraft)); + } catch { + toast.error("积分套餐 JSON 格式错误,请检查后再保存"); + return; + } + const payload = { ...siteSettings, membershipConfig: parsedMembershipConfig, membershipUserOverrides: parsedMembershipOverrides, + pointsPackages: parsedPointsPackages, }; const response = await fetch("/api/site-settings", { @@ -1022,6 +1086,7 @@ 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) { @@ -1488,6 +1553,7 @@ export function AdminSettingsClient() { supportsNegativePrompt: false, supportsWatermark: false, supportsSeed: false, + creditCostPerImage: 0, enableLoadingPlaceholder: true, loadingColor: "purple", }, @@ -1729,6 +1795,10 @@ export function AdminSettingsClient() {
月度出图限额: {user.monthlyImageLimit ? `${user.monthlyImageLimit} 张/月` : "无限制"}
+
+ 剩余积分: {Number(user.pointsBalance || 0).toLocaleString()} | 累计赠送:{" "} + {Number(user.pointsGiftedTotal || 0).toLocaleString()} +
{/* 配额设置 */} @@ -1778,6 +1848,33 @@ export function AdminSettingsClient() { > {user.isActive ? "禁用" : "激活"} +
+ + setGiftPointsDraftByUser((prev) => ({ + ...prev, + [user.id]: e.target.value, + })) + } + onKeyDown={(e) => { + if (e.key === "Enter") { + void handleGiftPoints(user.id); + } + }} + /> + +
@@ -2860,6 +2957,88 @@ export function AdminSettingsClient() {
+
+
+
+ +
+ 可开启按模型扣积分、配置积分套餐;后续接入支付后可直接用于充值。 +
+
+ +
+
+
+
+ +
+ 开启后按模型单价和图片张数扣除积分 +
+
+ + setSiteSettings({ + ...siteSettings, + pointsSystemConfig: { + ...siteSettings.pointsSystemConfig, + enabled: checked, + }, + }) + } + /> +
+
+
+ +
+ 开启后积分不足直接返回错误,不执行生图任务 +
+
+ + setSiteSettings({ + ...siteSettings, + pointsSystemConfig: { + ...siteSettings.pointsSystemConfig, + blockOnInsufficient: checked, + }, + }) + } + /> +
+
+
+ +