Files
jiang13-bbs/frontend/app/admin/analytics/AnalyticsShell.tsx
freefire 7b327bc8cc feat: 站点访问统计与可配置侧边栏组件
- 新增访问统计:visitstats 服务与查询、visit_capture 中间件、爬虫/来源识别、管理端 analytics 页面
- 新增侧边栏:sidebar 服务与 handler、管理端 sidebar 配置页、前端侧边栏组件
- 新增 widget 嵌入代码生成(widgetCode)与 widgetApi
- 遥测逻辑迁移至 handler 层,删除 service/telemetry
- env 示例补充 Docker 部署可信代理 IP 说明
2026-09-24 18:53:45 +08:00

75 lines
2.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import Link from "next/link";
import { usePathname, useRouter } from "next/navigation";
import { useRef, type KeyboardEvent, type ReactNode } from "react";
import { BarChart3 } from "lucide-react";
import { AdminPageHeader } from "@/components/admin";
export const ANALYTICS_TABS: { href: string; label: string }[] = [
{ href: "/admin/analytics", label: "概览" },
{ href: "/admin/analytics/realtime", label: "实时" },
{ href: "/admin/analytics/sources", label: "来源分析" },
{ href: "/admin/analytics/pages", label: "受访页面" },
{ href: "/admin/analytics/visitors", label: "访客画像" },
{ href: "/admin/analytics/security", label: "爬虫与安全" },
{ href: "/admin/analytics/details", label: "访问明细" },
{ href: "/admin/analytics/settings", label: "设置" },
];
export default function AnalyticsShell({ children }: { children: ReactNode }) {
const pathname = usePathname();
const router = useRouter();
const navRefs = useRef<(HTMLAnchorElement | null)[]>([]);
// ←→/Home/End 键盘导航,范式同 SettingsShell(纵向改横向)
const onNavKeyDown = (e: KeyboardEvent<HTMLAnchorElement>, i: number) => {
if (
e.key !== "ArrowRight" &&
e.key !== "ArrowLeft" &&
e.key !== "Home" &&
e.key !== "End"
) {
return;
}
e.preventDefault();
let next = i;
if (e.key === "ArrowRight") next = (i + 1) % ANALYTICS_TABS.length;
else if (e.key === "ArrowLeft") next = (i + ANALYTICS_TABS.length - 1) % ANALYTICS_TABS.length;
else if (e.key === "Home") next = 0;
else next = ANALYTICS_TABS.length - 1;
navRefs.current[next]?.focus();
router.push(ANALYTICS_TABS[next].href);
};
return (
<div className="admin-analytics-page">
<AdminPageHeader
icon={BarChart3}
title="访问统计"
description="站点流量、来源、访客与安全事件的采集与分析"
/>
<nav className="admin-analytics-tabs" aria-label="访问统计分类">
{ANALYTICS_TABS.map((item, i) => {
const active = pathname === item.href;
return (
<Link
key={item.href}
ref={(el) => {
navRefs.current[i] = el;
}}
href={item.href}
className="admin-analytics-tab"
aria-current={active ? "page" : undefined}
onKeyDown={(e) => onNavKeyDown(e, i)}
>
{item.label}
</Link>
);
})}
</nav>
{children}
</div>
);
}