完善后台样式
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { ArrowLeft, ArrowRight, LogOut } from "lucide-react";
|
||||
import { IconKitSvg } from "../../components/IconKitSvg";
|
||||
import { CornerMarks, Decorations, ToastLike } from "../../components/app-shell";
|
||||
import { ConfirmModal } from "../../components/overlays";
|
||||
import type { Team, User } from "../../types";
|
||||
import type { NavigateFn } from "../route-config";
|
||||
import { AdminLedgersPage, AdminQuotaPage } from "./admin-billing";
|
||||
@@ -15,8 +17,8 @@ import { AdminTeamsPage, AdminUsersPage } from "./admin-teams-users";
|
||||
|
||||
export type AdminNotify = (type: "success" | "error" | "info", text: string) => void;
|
||||
|
||||
// 平台超管后台 · 分阶段上线。section slug 与 URL /admin/<slug> 对应("" = 概览)。
|
||||
// 视觉一律复用 restraint 外壳类(.app/.sidebar/.topbar/.content/.nav-section),只加极少 admin 专属样式。
|
||||
// 平台超管后台 · section slug 与 URL /admin/<slug> 对应("" = 概览)。
|
||||
// 外壳对齐影擎侧栏 / 顶栏,页面样式收敛在 admin-page.css。
|
||||
export type AdminSection = {
|
||||
slug: string;
|
||||
label: string;
|
||||
@@ -41,7 +43,29 @@ export const ADMIN_SECTIONS: AdminSection[] = [
|
||||
{ slug: "governance", label: "治理", icon: "sliders", group: "系统", phase: 9 }
|
||||
];
|
||||
|
||||
const SECTION_GROUPS = ["概览", "团队 · 用户", "内容", "生成", "财务", "系统"];
|
||||
const SECTION_GROUPS = ["团队 · 用户", "内容", "生成", "财务", "系统"];
|
||||
|
||||
const SECTION_BLURB: Record<string, string> = {
|
||||
invites: "发放开团队码,开通新团队",
|
||||
teams: "团队状态、成员与定价系数",
|
||||
users: "停用账号、重置登录密码",
|
||||
prompts: "生图 / 视频提示词模板",
|
||||
quality: "各生成阶段的画质与风格词",
|
||||
reviews: "人像资产送审与结果",
|
||||
tasks: "生成任务、重试与异常",
|
||||
providers: "模型供应商与可用模型",
|
||||
billing: "积分流水与手动调额",
|
||||
quota: "团队额度与消耗策略",
|
||||
governance: "数据体检与项目监控"
|
||||
};
|
||||
|
||||
const GROUP_HINT: Record<string, string> = {
|
||||
"团队 · 用户": "邀请开通、团队状态与账号",
|
||||
"内容": "提示词、质量词与人像审核",
|
||||
"生成": "任务队列与模型供应商",
|
||||
"财务": "积分流水与额度策略",
|
||||
"系统": "数据体检与项目监控"
|
||||
};
|
||||
|
||||
type AdminAppProps = {
|
||||
section: string;
|
||||
@@ -55,6 +79,7 @@ type AdminAppProps = {
|
||||
export function AdminApp({ section, user, team, navigateAdmin, navigate, logout }: AdminAppProps) {
|
||||
const active = ADMIN_SECTIONS.find((s) => s.slug === section) || ADMIN_SECTIONS[0];
|
||||
const [toast, setToast] = useState<{ type: "success" | "error" | "info"; text: string } | null>(null);
|
||||
const [logoutOpen, setLogoutOpen] = useState(false);
|
||||
const notify: AdminNotify = (type, text) => setToast({ type, text });
|
||||
|
||||
// 进后台任一页都滚到顶,行为与主壳 navigate 一致
|
||||
@@ -85,86 +110,86 @@ export function AdminApp({ section, user, team, navigateAdmin, navigate, logout
|
||||
<span className="brand-clip"><img className="brand-logo" src="/assets/yz/logo-horizontal.png" alt="影擎" /></span>
|
||||
</a>
|
||||
</div>
|
||||
<div className="admin-badge mono">[ PLATFORM ADMIN ]</div>
|
||||
{SECTION_GROUPS.map((group) => {
|
||||
const items = ADMIN_SECTIONS.filter((s) => s.group === group);
|
||||
if (items.length === 0) return null;
|
||||
return (
|
||||
<div key={group} className="admin-nav-group">
|
||||
<div className="nav-section">{group}</div>
|
||||
<nav>
|
||||
{items.map((item) => (
|
||||
<a
|
||||
key={item.slug || "overview"}
|
||||
href={item.slug ? `/admin/${item.slug}` : "/admin"}
|
||||
className={active.slug === item.slug ? "active" : ""}
|
||||
title={item.label}
|
||||
aria-label={item.label}
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
navigateAdmin(item.slug);
|
||||
}}
|
||||
>
|
||||
<IconKitSvg name={item.icon} />
|
||||
<span>{item.label}</span>
|
||||
</a>
|
||||
))}
|
||||
</nav>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<div className="aside-foot admin-foot">
|
||||
{team && (
|
||||
<div className="nav-panel">
|
||||
<div className="admin-rail">
|
||||
<p className="admin-rail-mark">平台后台</p>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-ghost btn-sm admin-back"
|
||||
onClick={() => navigate("dashboard")}
|
||||
className={`admin-link${active.slug === "" ? " active" : ""}`}
|
||||
onClick={() => navigateAdmin("")}
|
||||
>
|
||||
<IconKitSvg name="chevronLeft" size={14} /> 返回工作台
|
||||
<IconKitSvg name="dashboard" />
|
||||
<span>概览</span>
|
||||
</button>
|
||||
)}
|
||||
<div className="admin-user">
|
||||
<div className="av">{(user.username || "A").slice(0, 1).toUpperCase()}</div>
|
||||
<div className="admin-user-meta">
|
||||
<span className="nm">{user.username}</span>
|
||||
<span className="rl mono">平台超管</span>
|
||||
{SECTION_GROUPS.map((group) => {
|
||||
const items = ADMIN_SECTIONS.filter((s) => s.group === group);
|
||||
if (items.length === 0) return null;
|
||||
return (
|
||||
<div key={group} className="admin-nav-group">
|
||||
<div className="admin-group">{group}</div>
|
||||
{items.map((item) => (
|
||||
<button
|
||||
key={item.slug}
|
||||
type="button"
|
||||
className={`admin-link${active.slug === item.slug ? " active" : ""}`}
|
||||
onClick={() => navigateAdmin(item.slug)}
|
||||
>
|
||||
<IconKitSvg name={item.icon} />
|
||||
<span>{item.label}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="admin-foot">
|
||||
{team && (
|
||||
<button type="button" className="admin-back" onClick={() => navigate("dashboard")}>
|
||||
<ArrowLeft size={16} strokeWidth={1.8} />
|
||||
返回工作台
|
||||
</button>
|
||||
)}
|
||||
<div className="admin-user">
|
||||
<div className="av">{(user.username || "A").slice(0, 1).toUpperCase()}</div>
|
||||
<div className="admin-user-meta">
|
||||
<span className="nm">{user.username}</span>
|
||||
<span className="rl">平台超管</span>
|
||||
</div>
|
||||
<button type="button" className="admin-logout" title="退出登录" aria-label="退出登录" onClick={() => setLogoutOpen(true)}>
|
||||
<LogOut size={16} strokeWidth={1.8} />
|
||||
</button>
|
||||
</div>
|
||||
<button type="button" className="icon-btn admin-logout" title="退出登录" aria-label="退出登录" onClick={logout}>
|
||||
<IconKitSvg name="logOut" size={16} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
<main>
|
||||
<Decorations />
|
||||
<header className="topbar">
|
||||
<div className="crumbs">
|
||||
<a
|
||||
href="/admin"
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
navigateAdmin("");
|
||||
}}
|
||||
>
|
||||
平台后台
|
||||
</a>
|
||||
{active.slug && (
|
||||
<>
|
||||
<span className="sep">/</span>
|
||||
<span className="here">{active.label}</span>
|
||||
</>
|
||||
)}
|
||||
<div className="admin-crumb">
|
||||
<button type="button" onClick={() => navigateAdmin("")}>平台后台</button>
|
||||
{active.slug ? <span>{active.label}</span> : null}
|
||||
</div>
|
||||
<div className="right">
|
||||
<span className="pill info"><span className="dot" />超管模式</span>
|
||||
<span className="admin-mode">超管模式</span>
|
||||
</div>
|
||||
</header>
|
||||
<div className="content" id="page-content">
|
||||
<CornerMarks />
|
||||
{toast && <ToastLike notice={toast} />}
|
||||
<AdminSectionView section={active} navigateAdmin={navigateAdmin} notify={notify} />
|
||||
<div className="admin-page">
|
||||
<AdminSectionView section={active} navigateAdmin={navigateAdmin} notify={notify} />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<ConfirmModal
|
||||
open={logoutOpen}
|
||||
title="退出登录"
|
||||
icon={<LogOut size={16} />}
|
||||
detail="确定退出当前超管账号?退出后需要重新登录才能进入平台后台。"
|
||||
confirmText="确认退出"
|
||||
onCancel={() => setLogoutOpen(false)}
|
||||
onConfirm={() => { setLogoutOpen(false); logout(); }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -210,34 +235,54 @@ function AdminSectionView({ section, navigateAdmin, notify }: { section: AdminSe
|
||||
return <AdminPlaceholder section={section} />;
|
||||
}
|
||||
|
||||
function AdminOverview({ navigateAdmin }: { navigateAdmin: (s: string) => void }) {
|
||||
const shortcuts = ADMIN_SECTIONS.filter((s) => s.slug);
|
||||
const OVERVIEW_COLS = [
|
||||
["团队 · 用户", "财务"],
|
||||
["内容", "系统"],
|
||||
["生成"]
|
||||
];
|
||||
|
||||
function OverviewGroup({ group, navigateAdmin }: { group: string; navigateAdmin: (s: string) => void }) {
|
||||
const items = ADMIN_SECTIONS.filter((s) => s.group === group);
|
||||
if (items.length === 0) return null;
|
||||
return (
|
||||
<>
|
||||
<div className="page-head">
|
||||
<div>
|
||||
<h1>平台概览</h1>
|
||||
<div className="sub">
|
||||
<span className="mono">跨团队后台</span> · 邀请码、团队用户、内容审核、生成与计费治理
|
||||
</div>
|
||||
</div>
|
||||
<section className="admin-group-block">
|
||||
<div className="admin-group-label">
|
||||
<strong>{group}</strong>
|
||||
<span>{GROUP_HINT[group]}</span>
|
||||
</div>
|
||||
<div className="tip admin-tip">
|
||||
<strong>欢迎进入平台后台</strong>
|
||||
各模块正在分阶段上线 —— 你可从左侧导航进入。已就绪的模块可直接操作,未就绪的会标注上线阶段。
|
||||
</div>
|
||||
<div className="admin-shortcut-grid">
|
||||
{shortcuts.map((s) => (
|
||||
<button key={s.slug} type="button" className="shortcut" onClick={() => navigateAdmin(s.slug)}>
|
||||
<span className="ic"><IconKitSvg name={s.icon} size={16} /></span>
|
||||
<span className="admin-shortcut-text">
|
||||
<span className="t">{s.label}</span>
|
||||
<span className="d">{s.slug}</span>
|
||||
<div className="admin-entry-pair">
|
||||
{items.map((s) => (
|
||||
<button key={s.slug} className="admin-entry" type="button" onClick={() => navigateAdmin(s.slug)}>
|
||||
<IconKitSvg name={s.icon} size={22} strokeWidth={1.8} />
|
||||
<span className="admin-entry-copy">
|
||||
<strong>{s.label}</strong>
|
||||
<small>{SECTION_BLURB[s.slug] || s.slug}</small>
|
||||
</span>
|
||||
<ArrowRight size={18} strokeWidth={1.8} />
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function AdminOverview({ navigateAdmin }: { navigateAdmin: (s: string) => void }) {
|
||||
return (
|
||||
<div className="admin-overview">
|
||||
<section className="admin-welcome">
|
||||
<h1>平台概览</h1>
|
||||
<p>团队、内容、生成与计费,集中在这里处理</p>
|
||||
</section>
|
||||
<div className="admin-groups">
|
||||
{OVERVIEW_COLS.map((col) => (
|
||||
<div className="admin-col" key={col[0]}>
|
||||
{col.map((group) => (
|
||||
<OverviewGroup key={group} group={group} navigateAdmin={navigateAdmin} />
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -247,9 +292,7 @@ function AdminPlaceholder({ section }: { section: AdminSection }) {
|
||||
<div className="page-head">
|
||||
<div>
|
||||
<h1>{section.label}</h1>
|
||||
<div className="sub">
|
||||
<span className="mono">admin · {section.slug}</span>
|
||||
</div>
|
||||
<p>admin · {section.slug}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="empty-state show">
|
||||
|
||||
Reference in New Issue
Block a user