Add user authentication and role management
This commit is contained in:
@@ -12,6 +12,8 @@ import {
|
||||
formatShanghaiDate as formatDate,
|
||||
formatShanghaiToday,
|
||||
} from "../lib/date-utils";
|
||||
import type { AuthUser } from "../lib/user-auth";
|
||||
import UsersPage from "./users-page";
|
||||
|
||||
type Partner = {
|
||||
id: string;
|
||||
@@ -122,7 +124,8 @@ type NavKey =
|
||||
| "tasks"
|
||||
| "distributions"
|
||||
| "resources"
|
||||
| "recovery";
|
||||
| "recovery"
|
||||
| "users";
|
||||
|
||||
const NAV_ITEMS: Array<{ key: NavKey; label: string; mark: string }> = [
|
||||
{ key: "overview", label: "工作台", mark: "⌂" },
|
||||
@@ -130,6 +133,7 @@ const NAV_ITEMS: Array<{ key: NavKey; label: string; mark: string }> = [
|
||||
{ key: "distributions", label: "内容分发", mark: "↗" },
|
||||
{ key: "resources", label: "KOC资源", mark: "◎" },
|
||||
{ key: "recovery", label: "数据回收", mark: "◫" },
|
||||
{ key: "users", label: "用户管理", mark: "♙" },
|
||||
];
|
||||
|
||||
const EMPTY_DATA: DashboardData = {
|
||||
@@ -301,7 +305,7 @@ async function compressScreenshot(file: File) {
|
||||
});
|
||||
}
|
||||
|
||||
export default function Home() {
|
||||
export default function Home({ currentUser }: { currentUser: AuthUser }) {
|
||||
const [data, setData] = useState<DashboardData>(EMPTY_DATA);
|
||||
const [activeNav, setActiveNav] = useState<NavKey>("overview");
|
||||
const [loading, setLoading] = useState(true);
|
||||
@@ -322,6 +326,12 @@ export default function Home() {
|
||||
const [metricForm, setMetricForm] = useState({ exposure: "", views: "" });
|
||||
const [exportingTaskId, setExportingTaskId] = useState<string | null>(null);
|
||||
const [exportingResources, setExportingResources] = useState(false);
|
||||
const isManager = currentUser.role === "super_admin" || currentUser.role === "admin";
|
||||
const visibleNavItems = NAV_ITEMS.filter((item) => {
|
||||
if (item.key === "resources" && currentUser.role === "user") return false;
|
||||
if (item.key === "users" && !isManager) return false;
|
||||
return true;
|
||||
});
|
||||
|
||||
const loadData = useCallback(async () => {
|
||||
try {
|
||||
@@ -399,6 +409,8 @@ export default function Home() {
|
||||
const recentDistributions = data.distributions.slice(0, 6);
|
||||
|
||||
const navigate = (key: NavKey) => {
|
||||
if (key === "resources" && currentUser.role === "user") return;
|
||||
if (key === "users" && !isManager) return;
|
||||
setActiveNav(key);
|
||||
setMenuOpen(false);
|
||||
};
|
||||
@@ -646,6 +658,15 @@ export default function Home() {
|
||||
title: "数据回收",
|
||||
subtitle: "按任务设置自动采集日,集中查看最新公开数据与创作者截图。",
|
||||
},
|
||||
users: {
|
||||
title: "用户管理",
|
||||
subtitle: "由后台统一创建账号和重置密码,不开放自助注册与个人改密。",
|
||||
},
|
||||
};
|
||||
|
||||
const logout = async () => {
|
||||
await fetch("/api/auth/logout", { method: "POST" }).catch(() => null);
|
||||
window.location.assign("/login");
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -661,7 +682,7 @@ export default function Home() {
|
||||
|
||||
<nav className="main-nav" aria-label="主导航">
|
||||
<p className="nav-caption">运营中心</p>
|
||||
{NAV_ITEMS.map((item) => (
|
||||
{visibleNavItems.map((item) => (
|
||||
<button
|
||||
key={item.key}
|
||||
className={activeNav === item.key ? "active" : ""}
|
||||
@@ -683,14 +704,14 @@ export default function Home() {
|
||||
<span>{stats.activeTasks} 个任务正在分发</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="sidebar-user">
|
||||
<div className="avatar mint">吴</div>
|
||||
<button className="sidebar-user" onClick={() => void logout()} title="退出登录">
|
||||
<div className="avatar mint">{currentUser.username.slice(0, 1).toUpperCase()}</div>
|
||||
<div>
|
||||
<strong>运营负责人</strong>
|
||||
<span>小吴</span>
|
||||
<strong>{currentUser.username}</strong>
|
||||
<span>{currentUser.role === "super_admin" ? "超级管理员" : currentUser.role === "admin" ? "管理员" : "普通用户"}</span>
|
||||
</div>
|
||||
<span className="chevron">⌄</span>
|
||||
</div>
|
||||
<span className="chevron">退出</span>
|
||||
</button>
|
||||
</aside>
|
||||
|
||||
{menuOpen && <button className="sidebar-scrim" onClick={() => setMenuOpen(false)} aria-label="关闭菜单" />}
|
||||
@@ -750,6 +771,7 @@ export default function Home() {
|
||||
distributions={recentDistributions}
|
||||
tasks={data.tasks}
|
||||
onNavigate={navigate}
|
||||
showResources={currentUser.role !== "user"}
|
||||
/>
|
||||
)}
|
||||
{activeNav === "tasks" && (
|
||||
@@ -774,6 +796,9 @@ export default function Home() {
|
||||
onExport={exportResources}
|
||||
/>
|
||||
)}
|
||||
{activeNav === "users" && isManager && (
|
||||
<UsersPage currentUser={currentUser} />
|
||||
)}
|
||||
{activeNav === "recovery" && (
|
||||
<RecoveryPage
|
||||
tasks={data.tasks}
|
||||
@@ -942,6 +967,7 @@ function OverviewPage({
|
||||
distributions,
|
||||
tasks,
|
||||
onNavigate,
|
||||
showResources,
|
||||
}: {
|
||||
stats: {
|
||||
resources: number;
|
||||
@@ -953,6 +979,7 @@ function OverviewPage({
|
||||
distributions: Distribution[];
|
||||
tasks: Task[];
|
||||
onNavigate: (key: NavKey) => void;
|
||||
showResources: boolean;
|
||||
}) {
|
||||
const cards = [
|
||||
{
|
||||
@@ -979,7 +1006,7 @@ function OverviewPage({
|
||||
hint: stats.pendingRecovery > 0 ? "需要跟进" : "全部按时完成",
|
||||
tone: "orange",
|
||||
},
|
||||
];
|
||||
].filter((card) => showResources || card.label !== "已沉淀账号资源");
|
||||
return (
|
||||
<>
|
||||
<div className="stat-grid">
|
||||
|
||||
Reference in New Issue
Block a user