feat: 用户徽章与等级体系,积分经济管理重构
- 新增徽章系统:badge 服务与 handler、管理端配置页、BadgeChip/LevelBadge 展示组件 - 新增等级模型:model/level 及前端 levels 元数据 - 管理端经济模块拆分为 points/badges/levels 独立页面,EconomySettings 迁入设置 - 积分、签到、评论等服务适配等级与徽章逻辑
This commit is contained in:
93
frontend/app/admin/points/PointsTabs.tsx
Normal file
93
frontend/app/admin/points/PointsTabs.tsx
Normal file
@@ -0,0 +1,93 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useState } from "react";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import { AdminSegmented } from "@/components/admin";
|
||||
import type { EconomyStats, PublicSettings } from "@/lib/api";
|
||||
import EconomyOverview from "./EconomyOverview";
|
||||
import EconomySettings from "../settings/EconomySettings";
|
||||
import UserPointsTab from "./UserPointsTab";
|
||||
|
||||
type TabKey = "overview" | "rules" | "users";
|
||||
|
||||
const TAB_OPTIONS = [
|
||||
{ key: "overview", label: "经济总览" },
|
||||
{ key: "rules", label: "积分规则" },
|
||||
{ key: "users", label: "用户积分" },
|
||||
];
|
||||
|
||||
/**
|
||||
* 积分管理页标签壳:经济总览(管理员及以上)/ 积分规则 + 用户积分(超管/站长)。
|
||||
* tab 记在 URL query(?tab=)便于刷新/分享保持;三个面板保持挂载、用 display 切换,
|
||||
* 避免切标签丢失规则的未保存修改与用户列表状态。
|
||||
*/
|
||||
export default function PointsTabs({
|
||||
stats,
|
||||
settings,
|
||||
canManage,
|
||||
}: {
|
||||
stats: EconomyStats | null;
|
||||
settings: PublicSettings | null;
|
||||
canManage: boolean;
|
||||
}) {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const queryTab = searchParams.get("tab");
|
||||
const [tab, setTab] = useState<TabKey>(() => {
|
||||
const t = queryTab;
|
||||
if (canManage && (t === "rules" || t === "users")) return t;
|
||||
return "overview";
|
||||
});
|
||||
|
||||
const switchTab = useCallback(
|
||||
(next: string) => {
|
||||
setTab(next as TabKey);
|
||||
const params = new URLSearchParams(searchParams.toString());
|
||||
if (next === "overview") params.delete("tab");
|
||||
else params.set("tab", next);
|
||||
const qs = params.toString();
|
||||
router.replace(qs ? `?${qs}` : "?", { scroll: false });
|
||||
},
|
||||
[router, searchParams]
|
||||
);
|
||||
|
||||
const options = canManage
|
||||
? TAB_OPTIONS
|
||||
: TAB_OPTIONS.filter((o) => o.key === "overview");
|
||||
|
||||
return (
|
||||
<div>
|
||||
{canManage ? (
|
||||
<div className="mb-4">
|
||||
<AdminSegmented
|
||||
options={options}
|
||||
value={tab}
|
||||
onChange={(v) => switchTab(String(v))}
|
||||
ariaLabel="积分管理标签"
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{/* 各面板保持挂载(display 切换):规则未保存修改与用户列表状态不因切标签丢失 */}
|
||||
<div style={tab === "overview" ? undefined : { display: "none" }}>
|
||||
{stats ? (
|
||||
<EconomyOverview stats={stats} />
|
||||
) : (
|
||||
<div className="panel p-8 text-center">
|
||||
<p className="meta">暂时无法加载积分数据</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{canManage ? (
|
||||
<>
|
||||
<div style={tab === "rules" ? undefined : { display: "none" }}>
|
||||
{settings ? <EconomySettings initial={settings} /> : null}
|
||||
</div>
|
||||
<div style={tab === "users" ? undefined : { display: "none" }}>
|
||||
<UserPointsTab />
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user