import Add from "@mui/icons-material/Add";
import EditOutlined from "@mui/icons-material/EditOutlined";
import Inventory2Outlined from "@mui/icons-material/Inventory2Outlined";
import RefreshOutlined from "@mui/icons-material/RefreshOutlined";
import { AdminSwitch } from "@/shared/ui/AdminSwitch.jsx";
import MenuItem from "@mui/material/MenuItem";
import Tab from "@mui/material/Tab";
import Tabs from "@mui/material/Tabs";
import TextField from "@mui/material/TextField";
import {
AdminFormDialog,
AdminFormFieldGrid,
AdminFormSection,
AdminFormSwitchField,
} from "@/shared/ui/AdminFormDialog.jsx";
import { Button } from "@/shared/ui/Button.jsx";
import { DataState } from "@/shared/ui/DataState.jsx";
import { DataTable } from "@/shared/ui/DataTable.jsx";
import { AdminActionIconButton, AdminListBody, AdminListPage, AdminRowActions } from "@/shared/ui/AdminListLayout.jsx";
import { ResourceGroupSelectField } from "@/shared/ui/ResourceGroupSelectDrawer.jsx";
import { formatMillis } from "@/shared/utils/time.js";
import { levelStatusLabels, levelTrackLabels, levelTrackOptions } from "@/features/level-config/constants.js";
import { useLevelConfigPage } from "@/features/level-config/hooks/useLevelConfigPage.js";
import { ResourceSelectField } from "@/features/resources/components/ResourceSelectDrawer.jsx";
import { LevelConfigBatchDialog } from "@/features/level-config/components/LevelConfigBatchDialog.jsx";
import {
levelAvatarFrameResourcesForTrack,
levelBadgeResourcesForTrack,
levelShortBadgeResourcesForTrack,
withSelectedResource,
} from "@/features/level-config/resourceFilters.js";
import styles from "@/features/level-config/level-config.module.css";
export function LevelConfigPage() {
const page = useLevelConfigPage();
const trackTabs = buildTrackTabs(page.config.tracks);
return (
page.changeTrack(nextTrack)}
>
{trackTabs.map((item) => (
{item.label}
{item.ruleCount} 条规则 · {item.tierCount} 个等级层
}
value={item.value}
/>
))}
}
type="button"
variant="primary"
onClick={() => page.openRuleCreate(page.track)}
>
添加规则
}
type="button"
variant="primary"
onClick={() => page.openTierCreate(page.track)}
>
添加等级层
{page.tracks.length ? (
{page.tracks.map((track) => (
))}
) : (
当前无数据
)}
);
}
function LevelTrackSection({ page, track }) {
const rules = track.rules || [];
const tiers = track.tiers || [];
const totalRules = track.rules?.length || 0;
const totalTiers = track.tiers?.length || 0;
const ruleColumns = buildRuleColumns(page);
const tierColumns = buildTierColumns(page);
return (
{track.name || levelTrackLabels[track.track] || track.track}
{track.track}
{totalRules} 条规则
{totalTiers} 个等级层
排序 {track.sortOrder || 0}
page.openTrackEdit(track)}
>
page.openRuleCreate(track.track)}
>
page.openTierCreate(track.track)}
>
}
onClick={() => page.openRuleBatchEdit(track)}
>
批量编辑
}
countLabel={`${rules.length} 条`}
title="每级升级数值"
/>
`${rule.track}-${rule.level}`}
/>
}
onClick={() => page.openTierBatchEdit(track)}
>
批量编辑
}
countLabel={`${tiers.length} 个`}
title="等级层展示与礼物"
/>
tier.tierId || `${tier.track}-${tier.minLevel}-${tier.maxLevel}`}
/>
);
}
function TableBlockHeader({ actions, countLabel, title }) {
return (
{title}
{countLabel}
{actions ?
{actions}
: null}
);
}
function buildTrackTabs(tracks = []) {
return levelTrackOptions.map(([value, label]) => {
const track = tracks.find((item) => item.track === value);
return {
label: track?.name || label,
ruleCount: track?.rules?.length || 0,
tierCount: track?.tiers?.length || 0,
value,
};
});
}
function buildRuleColumns(page) {
return [
{
key: "level",
label: "等级",
width: "minmax(180px, 0.8fr)",
render: (rule) => ,
},
{
key: "requiredValue",
label: "升级数值",
width: "minmax(140px, 0.65fr)",
render: (rule) => formatNumber(rule.requiredValue),
},
{
key: "longBadge",
label: "长徽章素材",
width: "minmax(200px, 1fr)",
render: (rule) => ,
},
{
key: "avatarFrame",
label: "等级头像框",
width: "minmax(200px, 1fr)",
render: (rule) => ,
},
{
key: "shortBadge",
label: "短徽章素材",
width: "minmax(200px, 1fr)",
render: (rule) => ,
},
{
key: "reward",
label: "升级礼物",
width: "minmax(200px, 1fr)",
render: (rule) => ,
},
{
key: "status",
label: "状态",
width: "minmax(116px, 0.55fr)",
render: (rule) => ,
},
{
key: "sort",
label: "排序",
width: "minmax(90px, 0.45fr)",
render: (rule) => rule.sortOrder || 0,
},
{
key: "updatedAt",
label: "更新时间",
width: "minmax(170px, 0.75fr)",
render: (rule) => formatMillis(rule.updatedAtMs || rule.createdAtMs),
},
{
key: "actions",
label: "操作",
width: "minmax(76px, 0.35fr)",
render: (rule) => ,
},
];
}
function buildTierColumns(page) {
return [
{
key: "range",
label: "等级层",
width: "minmax(220px, 1fr)",
render: (tier) => ,
},
{
key: "frame",
label: "头像框",
width: "minmax(200px, 0.9fr)",
render: (tier) => (
),
},
{
key: "badge",
label: "徽章",
width: "minmax(200px, 0.9fr)",
render: (tier) => ,
},
{
key: "reward",
label: "升级礼物",
width: "minmax(200px, 1fr)",
render: (tier) => ,
},
{
key: "status",
label: "状态",
width: "minmax(116px, 0.55fr)",
render: (tier) => ,
},
{
key: "updatedAt",
label: "更新时间",
width: "minmax(170px, 0.75fr)",
render: (tier) => formatMillis(tier.updatedAtMs || tier.createdAtMs),
},
{
key: "actions",
label: "操作",
width: "minmax(76px, 0.35fr)",
render: (tier) => ,
},
];
}
function RuleIdentity({ rule }) {
return (
Lv.{rule.level}
{rule.name || `${levelTrackLabels[rule.track]} ${rule.level}级`}
{levelTrackLabels[rule.track] || rule.track}
);
}
function TierIdentity({ tier }) {
return (
{tier.minLevel}-{tier.maxLevel}
{tier.name || `${tier.minLevel}-${tier.maxLevel}级`}
{levelTrackLabels[tier.track] || tier.track}
);
}
function RuleStatusSwitch({ page, rule }) {
const checked = rule.status === "active";
return (
page.toggleRule(rule, event.target.checked)}
/>
{levelStatusLabel(rule.status)}
);
}
function TierStatusSwitch({ page, tier }) {
const checked = tier.status === "active";
return (
page.toggleTier(tier, event.target.checked)}
/>
{levelStatusLabel(tier.status)}
);
}
function RuleRowActions({ page, rule }) {
return (
page.openRuleEdit(rule)}
>
);
}
function TierRowActions({ page, tier }) {
return (
page.openTierEdit(tier)}
>
);
}
function RuleFormDialog({ page }) {
const open = page.activeAction === "rule-create" || page.activeAction === "rule-edit";
const editing = page.activeAction === "rule-edit";
const form = page.ruleForm;
return (
page.setRuleForm((current) => ({
...current,
avatarFrameResourceId: "",
longBadgeResourceId: "",
shortBadgeResourceId: "",
track: event.target.value,
}))
}
>
{levelTrackOptions.map(([value, label]) => (
))}
page.setRuleForm((current) => ({ ...current, longBadgeResourceId: value }))
}
/>
page.setRuleForm((current) => ({ ...current, avatarFrameResourceId: value }))
}
/>
page.setRuleForm((current) => ({ ...current, shortBadgeResourceId: value }))
}
/>
page.setRuleForm((current) => ({ ...current, rewardResourceGroupId: value }))
}
/>
page.setRuleForm((current) => ({ ...current, status: checked ? "active" : "disabled" }))
}
/>
);
}
function TierFormDialog({ page }) {
const open = page.activeAction === "tier-create" || page.activeAction === "tier-edit";
const editing = page.activeAction === "tier-edit";
const form = page.tierForm;
return (
page.setTierForm((current) => ({
...current,
displayAvatarFrameResourceId: "",
displayBadgeResourceId: "",
track: event.target.value,
}))
}
>
{levelTrackOptions.map(([value, label]) => (
))}
page.setTierForm((current) => ({ ...current, displayAvatarFrameResourceId: value }))
}
/>
page.setTierForm((current) => ({ ...current, displayBadgeResourceId: value }))
}
/>
page.setTierForm((current) => ({ ...current, rewardResourceGroupId: value }))
}
/>
page.setTierForm((current) => ({ ...current, status: checked ? "active" : "disabled" }))
}
/>
);
}
function TrackFormDialog({ page }) {
const form = page.trackForm;
return (
page.setTrackForm((current) => ({ ...current, status: checked ? "active" : "disabled" }))
}
/>
);
}
function ResourceLabel({ id, resources }) {
if (!id) {
return 未配置;
}
const resource = resources.find((item) => Number(item.resourceId) === Number(id));
const preview = resourceImageURL(resource);
return (
{preview ?
: }
{resource?.name || resource?.resourceCode || `资源 #${id}`}
{resource ? resourceMetaLabel(resource, id) : `ID ${id}`}
);
}
function ResourceGroupLabel({ groups, id }) {
if (!id) {
return 未配置;
}
const group = groups.find((item) => Number(item.groupId) === Number(id));
return (
{group?.name || group?.groupCode || `资源组 #${id}`}
ID {id}
);
}
function changeField(setter, key) {
return (event) => {
const value = event.target.value;
setter((current) => ({ ...current, [key]: value }));
};
}
function resourceMetaLabel(resource, id) {
if (resource.resourceType === "avatar_frame") {
const avatarFrameKind = resource.avatarFrameKind || avatarFrameKindFromMetadata(resource.metadataJson);
const levelTrack = resource.levelTrack || levelTrackFromMetadata(resource.metadataJson);
if (avatarFrameKind === "level" && levelTrack) {
return `ID ${id} · 等级头像框 · ${levelTrackLabels[levelTrack] || levelTrack}`;
}
}
const badgeForm = resource.badgeForm || badgeFormFromMetadata(resource.metadataJson);
const badgeKind = resource.badgeKind || badgeKindFromMetadata(resource.metadataJson);
const levelTrack = resource.levelTrack || levelTrackFromMetadata(resource.metadataJson);
if (resource.resourceType === "badge" && badgeKind === "level" && levelTrack) {
const badgeFormLabel = badgeForm === "tile" ? "短徽章" : "长徽章";
return `ID ${id} · ${badgeFormLabel} · ${levelTrackLabels[levelTrack] || levelTrack}`;
}
return `ID ${id}`;
}
function resourceImageURL(resource) {
return imageURL(resource?.previewUrl) || imageURL(resource?.assetUrl) || imageURL(resource?.animationUrl);
}
function imageURL(value) {
const url = String(value || "").trim();
if (!url) {
return "";
}
return /\.(avif|gif|jpe?g|png|webp)(\?|#|$)/i.test(url) ? url : "";
}
function badgeFormFromMetadata(metadataJson) {
if (!metadataJson) {
return "";
}
try {
const metadata = JSON.parse(metadataJson);
const badgeForm = String(metadata?.badge_form || "")
.trim()
.toLowerCase();
return badgeForm === "strip" || badgeForm === "tile" ? badgeForm : "";
} catch {
return "";
}
}
function badgeKindFromMetadata(metadataJson) {
if (!metadataJson) {
return "";
}
try {
const metadata = JSON.parse(metadataJson);
return String(metadata?.badge_kind || "")
.trim()
.toLowerCase() === "level"
? "level"
: "normal";
} catch {
return "";
}
}
function avatarFrameKindFromMetadata(metadataJson) {
if (!metadataJson) {
return "";
}
try {
const metadata = JSON.parse(metadataJson);
const avatarFrameKind = String(metadata?.avatar_frame_kind || "")
.trim()
.toLowerCase();
return avatarFrameKind === "level" || avatarFrameKind === "normal" ? avatarFrameKind : "";
} catch {
return "";
}
}
function levelTrackFromMetadata(metadataJson) {
if (!metadataJson) {
return "";
}
try {
const metadata = JSON.parse(metadataJson);
const levelTrack = String(metadata?.level_track || "")
.trim()
.toLowerCase();
return levelTrack === "wealth" || levelTrack === "game" || levelTrack === "charm" ? levelTrack : "";
} catch {
return "";
}
}
function levelStatusLabel(status) {
return levelStatusLabels[status] || status || "-";
}
function formatNumber(value) {
return Number(value || 0).toLocaleString("zh-CN");
}