diff --git a/MEMORY.md b/MEMORY.md
index e2749db..c1cd919 100644
--- a/MEMORY.md
+++ b/MEMORY.md
@@ -464,11 +464,11 @@
## 前端路由展示口径(2026-04-23)
-- 用户可见后台地址默认不再带 `/admin` 前缀(例如 `/users`、`/requirements`、`/dashboard`)。
+- 用户可见后台地址默认不再带 `/admin` 前缀(例如 `/users`、`/requirements`)。
- 兼容策略:
- 无前缀地址通过前端中间层 rewrite 到现有 `app/admin/**` 页面实现;
- 历史 `/admin/**` 地址继续可访问,并自动重定向到无前缀地址。
-- 后台首页映射口径:`/admin` 对外统一为 `/dashboard`。
+- 后台首页映射口径:`/admin` 对外统一为 `/users`。
- 菜单路径展示口径:前端渲染菜单时将后端返回的 `/admin/**` 路径规范化为无前缀地址,避免导航条继续暴露 `/admin`。
## 后台功能下线口径(2026-04-23)
@@ -546,10 +546,10 @@
## 首页与登录口径(2026-04-23)
- `/` 默认作为登录入口页,不再承载“已登录后停留的首页面板”。
-- 登录态(含刷新会话恢复)进入 `/` 时,前端立即跳转 `/dashboard`,实现“登录后直达后台”。
+- 登录态(含刷新会话恢复)进入 `/` 时,前端立即跳转 `/users`,实现“登录后直达后台”。
- 后台壳层文案对齐:
- 未登录访问后台时提示“前往登录”(`/`);
- - 账号菜单提供“后台首页”(`/dashboard`),不再出现“返回首页”歧义入口。
+ - 账号菜单提供“用户管理”(`/users`)作为默认后台入口。
- 退出登录口径:统一跳转到登录页 `/`(不保留在当前后台路由)。
## 站点标题口径(2026-04-24)
@@ -563,14 +563,14 @@
- 左侧为品牌与机器人主题视觉区;
- 右侧为白色登录卡片(品牌头、表单、主操作按钮、辅助链接)。
- 交互口径保持:
- - 登录态进入 `/` 仍自动跳转 `/dashboard`;
+ - 登录态进入 `/` 仍自动跳转 `/users`;
- 登录/注册逻辑不变,视觉改造不改变鉴权接口契约。
## 后台账号入口口径(2026-04-23)
- 后台右上角账号入口采用 AntD `Avatar` 作为下拉触发器,不再使用“账号”文字按钮。
- Avatar 文案默认使用用户名首字母(大写),空值兜底 `U`。
-- 下拉内容口径保持不变:账号信息 + 后台首页 + 退出登录。
+- 下拉内容口径保持不变:账号信息 + 默认后台入口 + 退出登录。
## 后台左侧导航口径(2026-04-24)
@@ -639,7 +639,19 @@
- 当 `NEXT_PUBLIC_API_BASE_URL` 指向 loopback(`localhost/127.0.0.1/::1`)时,前端运行时应保证 API host 与当前页面 host 对齐。
- 只要“配置 host 是 loopback 且与 `window.location.hostname` 不一致”,就自动重写为当前页面 host,并保留配置端口(默认 `8000`)。
-- 目的:避免 `localhost` 与 `127.0.0.1` 混用导致 refresh cookie 跨站语义,从而在 `/dashboard` 硬刷新时误判未登录。
+- 目的:避免 `localhost` 与 `127.0.0.1` 混用导致 refresh cookie 跨站语义,从而在后台路由硬刷新时误判未登录。
+
+## 仪表盘下线口径(2026-05-02)
+
+- 后台“仪表盘”页面已下线,不再作为可访问菜单和默认首页。
+- 前端路由口径:
+ - `/admin`、`/dashboard` 统一重定向到 `/users`;
+ - 登录态进入 `/` 默认跳转到 `/users`;
+ - `web/src/app/admin/page.tsx` 改为重定向页,不再渲染卡片工作台。
+- 后端菜单口径:
+ - `seed_service.DEFAULT_MENUS` 删除 `dashboard`;
+ - `ROLE_MENU_BINDINGS` 删除 admin/user 对 `dashboard` 的绑定;
+ - `legacy_authz_service`、`legacy_admin_rbac_service`、`admin_service` 对 `dashboard` 统一加入下线过滤集合,屏蔽历史库残留菜单记录。
## 后台顶部滚动口径(2026-04-25)
diff --git a/api/app/services/admin_service.py b/api/app/services/admin_service.py
index 110ccf8..57aded9 100644
--- a/api/app/services/admin_service.py
+++ b/api/app/services/admin_service.py
@@ -32,6 +32,7 @@ AUDIT_LOG_LOAD_OPTIONS = (
)
REMOVED_MENU_CODES = {
+ "dashboard",
"admin.wxapp",
"admin.system_message",
"admin.inbox",
@@ -403,7 +404,7 @@ def update_menu(db: Session, menu_id: int, payload: MenuUpdateRequest) -> MenuPu
def delete_menu(db: Session, menu_id: int) -> bool:
menu = get_menu_by_id(db, menu_id)
- if not menu or menu.code in {"dashboard", "admin.users", "admin.roles", "admin.menus", "admin.system_params", "admin.power_lines", "admin.lightning_currents", "admin.lightning_distribution", "admin.workers", "admin.task_monitor", "admin.atp_models", "admin.files", "admin.elevation", "admin.syslog", "admin.wine_runner"}:
+ if not menu or menu.code in {"admin.users", "admin.roles", "admin.menus", "admin.system_params", "admin.power_lines", "admin.lightning_currents", "admin.lightning_distribution", "admin.workers", "admin.task_monitor", "admin.atp_models", "admin.files", "admin.elevation", "admin.syslog", "admin.wine_runner"}:
return False
child_exists = db.scalar(select(Menu.id).where(Menu.parent_id == menu_id))
if child_exists is not None:
diff --git a/api/app/services/legacy_admin_rbac_service.py b/api/app/services/legacy_admin_rbac_service.py
index 341082b..450e637 100644
--- a/api/app/services/legacy_admin_rbac_service.py
+++ b/api/app/services/legacy_admin_rbac_service.py
@@ -30,6 +30,7 @@ from .user_service import queue_users_auth_refresh
PROTECTED_ROLE_IDS = {"admin", "user", "sys_mgr"}
REMOVED_MENU_CODES = {
+ "dashboard",
"admin.wxapp",
"admin.system_message",
"admin.inbox",
@@ -62,7 +63,6 @@ REMOVED_MENU_CODES = {
}
PROTECTED_MENU_CODES = {
- "dashboard",
"admin.users",
"admin.roles",
"admin.menus",
diff --git a/api/app/services/legacy_authz_service.py b/api/app/services/legacy_authz_service.py
index 09db2f5..e9a94d7 100644
--- a/api/app/services/legacy_authz_service.py
+++ b/api/app/services/legacy_authz_service.py
@@ -47,6 +47,7 @@ ADMIN_ROLE_IDS = {
}
DISABLED_MENU_CODES: set[str] = {
+ "dashboard",
"admin.wxapp",
"admin.system_message",
"admin.inbox",
@@ -97,7 +98,6 @@ MENU_CODE_PERMISSION_MAP: dict[str, set[str]] = {
"admin.lightning_currents": {"lightning.read", "lightning.manage"},
"admin.lightning_distribution": {"lightning.read", "lightning.manage"},
"admin.wine_runner": {"wine.read", "wine.manage"},
- "dashboard": {"menu.read"},
}
SYNTHETIC_LEGACY_MENU_ROWS: list[dict[str, Any]] = [
diff --git a/api/app/services/seed_service.py b/api/app/services/seed_service.py
index 7eeebfe..feb1a91 100644
--- a/api/app/services/seed_service.py
+++ b/api/app/services/seed_service.py
@@ -82,19 +82,6 @@ DEFAULT_ROLES: dict[str, dict[str, object]] = {
}
DEFAULT_MENUS: list[dict[str, object]] = [
- {
- "code": "dashboard",
- "name": "仪表盘",
- "path": "/admin",
- "icon": "LayoutDashboard",
- "parent_code": None,
- "type": "menu",
- "sort_order": 10,
- "status": "enabled",
- "visible": True,
- "cacheable": False,
- "permission_code": None,
- },
{
"code": "admin.users",
"name": "用户管理",
@@ -102,7 +89,7 @@ DEFAULT_MENUS: list[dict[str, object]] = [
"icon": "Users",
"parent_code": None,
"type": "menu",
- "sort_order": 20,
+ "sort_order": 10,
"status": "enabled",
"visible": True,
"cacheable": False,
@@ -280,8 +267,8 @@ DEFAULT_MENUS: list[dict[str, object]] = [
]
ROLE_MENU_BINDINGS: dict[str, list[str]] = {
- "admin": ["dashboard", "admin.users", "admin.roles", "admin.menus", "admin.system_params", "admin.power_lines", "admin.lightning_currents", "admin.lightning_distribution", "admin.workers", "admin.task_monitor", "admin.atp_models", "admin.files", "admin.elevation", "admin.syslog", "admin.wine_runner"],
- "user": ["dashboard"],
+ "admin": ["admin.users", "admin.roles", "admin.menus", "admin.system_params", "admin.power_lines", "admin.lightning_currents", "admin.lightning_distribution", "admin.workers", "admin.task_monitor", "admin.atp_models", "admin.files", "admin.elevation", "admin.syslog", "admin.wine_runner"],
+ "user": [],
}
diff --git a/api/app/services/topic_registry.py b/api/app/services/topic_registry.py
index f26d773..e63a6fd 100644
--- a/api/app/services/topic_registry.py
+++ b/api/app/services/topic_registry.py
@@ -17,7 +17,6 @@ TOPIC_RULES: dict[str, TopicRule] = {
"system": TopicRule(allow_any_authenticated_user=True, auto_subscribe=True),
"auth": TopicRule(allow_any_authenticated_user=True, auto_subscribe=True),
"notifications": TopicRule(allow_any_authenticated_user=True, auto_subscribe=True),
- "admin.dashboard": TopicRule(allow_any_authenticated_user=True),
"admin.users": TopicRule(any_permission_codes={"user.manage"}),
"admin.roles": TopicRule(any_permission_codes={"role.read", "role.manage"}),
"admin.menus": TopicRule(any_permission_codes={"menu.read", "menu.manage"}),
diff --git a/memory/2026-05-01.md b/memory/2026-05-01.md
index a7745da..4bc2684 100644
--- a/memory/2026-05-01.md
+++ b/memory/2026-05-01.md
@@ -764,3 +764,51 @@
- 验证:
- 代码走读确认 `seed_service._default_file_storage_backends/_default_file_storage_mounts` 基于 `settings.minio_enabled` 分支,默认行为已切换到 MinIO。
+
+## Work Log - 去掉仪表盘页面(2026-05-02)
+
+- 背景:
+ - Issue `FL-166` 要求去掉后台“仪表盘页面”。
+
+- 本次改动(最小闭环):
+ - 前端路由与默认跳转收口:
+ - `web/src/middleware.ts`
+ - `/admin`、`/dashboard` 统一重定向到 `/users`;
+ - 历史 `/admin/**` 仍保持去前缀重定向;
+ - 无前缀路径继续 rewrite 到 `app/admin/**`。
+ - `web/src/app/page.tsx`
+ - 登录态从跳转 `/dashboard` 改为跳转 `/users`。
+ - `web/src/app/admin/page.tsx`
+ - 仪表盘页面改为纯重定向页(`router.replace("/users")`),不再渲染工作台卡片。
+ - `web/src/app/admin/layout.tsx`
+ - `/admin` 归一化路径改为 `/users`;
+ - 账号下拉“后台首页”改为“用户管理”(`/users`)。
+ - `web/src/app/admin/roles/page.tsx`
+ - 无权限兜底按钮改为返回 `/users`。
+ - `web/src/app/admin/menus/page.tsx`
+ - 受保护菜单集合移除 `dashboard`。
+ - 后端菜单与权限链路清理:
+ - `api/app/services/seed_service.py`
+ - 删除 `dashboard` 种子菜单;
+ - `admin`/`user` 默认角色菜单绑定删除 `dashboard`;
+ - `admin.users` 排序调整为首位(`sort_order=10`)。
+ - `api/app/services/admin_service.py`
+ - `REMOVED_MENU_CODES` 增加 `dashboard`;
+ - 菜单删除保护集合移除 `dashboard`。
+ - `api/app/services/legacy_authz_service.py`
+ - `DISABLED_MENU_CODES` 增加 `dashboard`;
+ - `MENU_CODE_PERMISSION_MAP` 删除 `dashboard` 映射。
+ - `api/app/services/legacy_admin_rbac_service.py`
+ - `REMOVED_MENU_CODES` 增加 `dashboard`;
+ - `PROTECTED_MENU_CODES` 移除 `dashboard`。
+ - `api/app/services/topic_registry.py`
+ - 删除 `admin.dashboard` 主题规则。
+ - 记忆更新:
+ - `MEMORY.md` 新增“仪表盘下线口径(2026-05-02)”,并同步修正历史 `/dashboard` 默认入口描述为 `/users`。
+
+- 验证:
+ - `rg -n "dashboard|/dashboard"` 检查运行代码,确认仅保留兼容重定向/过滤语义,不再保留仪表盘页面入口逻辑。
+
+- 风险与影响:
+ - 历史收藏的 `/dashboard` 链接会跳转到 `/users`(兼容行为)。
+ - 默认 `user` 角色在无其他菜单绑定时,后台菜单可能为空;该改动符合“移除仪表盘菜单”的目标口径。
diff --git a/web/src/app/admin/layout.tsx b/web/src/app/admin/layout.tsx
index adff23a..17a5774 100644
--- a/web/src/app/admin/layout.tsx
+++ b/web/src/app/admin/layout.tsx
@@ -63,7 +63,7 @@ function normalizeAdminPath(path: string | null): string | null {
return path;
}
if (path === "/admin" || path === "/admin/") {
- return "/dashboard";
+ return "/users";
}
if (path.startsWith("/admin/")) {
return path.slice("/admin".length);
@@ -272,7 +272,7 @@ export default function AdminLayout({ children }: { children: ReactNode }) {
{
key: "home",
icon: ,
- label: 后台首页,
+ label: 用户管理,
},
{
key: "logout",
diff --git a/web/src/app/admin/menus/page.tsx b/web/src/app/admin/menus/page.tsx
index 5f8901a..134f420 100644
--- a/web/src/app/admin/menus/page.tsx
+++ b/web/src/app/admin/menus/page.tsx
@@ -57,7 +57,6 @@ const SORT_OPTIONS: Array<{ value: SortKey; label: string }> = [
];
const PROTECTED_MENU_CODES = new Set([
- "dashboard",
"admin.users",
"admin.roles",
"admin.menus",
diff --git a/web/src/app/admin/page.tsx b/web/src/app/admin/page.tsx
index f6eeb0c..8033e3f 100644
--- a/web/src/app/admin/page.tsx
+++ b/web/src/app/admin/page.tsx
@@ -1,220 +1,14 @@
"use client";
-import Link from "next/link";
-import {
- AppstoreOutlined,
- AuditOutlined,
- CodeOutlined,
- DatabaseOutlined,
- DeploymentUnitOutlined,
- FileSearchOutlined,
- FolderOpenOutlined,
- GlobalOutlined,
- GoldOutlined,
- LineChartOutlined,
- SafetyCertificateOutlined,
- SettingOutlined,
- TeamOutlined,
-} from "@ant-design/icons";
-import { Avatar, Card, Col, Empty, Row, Space, Statistic, Tag, Typography, type CardProps } from "antd";
-import type { ComponentType, ReactNode } from "react";
+import { useEffect } from "react";
+import { useRouter } from "next/navigation";
-import { useAuth } from "@/components/auth-provider";
+export default function AdminEntryRedirectPage() {
+ const router = useRouter();
-const AntCard = Card as unknown as ComponentType;
+ useEffect(() => {
+ router.replace("/users");
+ }, [router]);
-type DashboardCard = {
- href: string;
- title: string;
- description: string;
- category: string;
- icon: ReactNode;
- visible: (hasPermission: (code: string) => boolean) => boolean;
-};
-
-const CARDS: DashboardCard[] = [
- {
- href: "/users",
- title: "用户管理",
- description: "查看用户、分配角色、维护账号状态。",
- category: "权限",
- icon: ,
- visible: (hasPermission) => hasPermission("user.manage"),
- },
- {
- href: "/roles",
- title: "角色管理",
- description: "配置角色并分配菜单可见范围。",
- category: "权限",
- icon: ,
- visible: (hasPermission) => hasPermission("role.read") || hasPermission("role.manage"),
- },
- {
- href: "/menus",
- title: "菜单管理",
- description: "维护后台导航结构、菜单层级与展示状态。",
- category: "权限",
- icon: ,
- visible: (hasPermission) => hasPermission("menu.read") || hasPermission("menu.manage"),
- },
- {
- href: "/system-params",
- title: "系统参数",
- description: "维护系统级参数键值、启停状态与变更说明。",
- category: "系统",
- icon: ,
- visible: (hasPermission) => hasPermission("system_param.read") || hasPermission("system_param.manage"),
- },
- {
- href: "/files",
- title: "文件管理",
- description: "统一管理本地/SFTP/S3 文件目录,支持上传、重命名、移动和下载。",
- category: "内容",
- icon: ,
- visible: (hasPermission) => hasPermission("file.read") || hasPermission("file.manage"),
- },
- {
- href: "/workers",
- title: "Worker监控",
- description: "查看 Worker 在线状态、并发、活跃队列和每个 Worker 的任务快照。",
- category: "协作",
- icon: ,
- visible: (hasPermission) => hasPermission("celery.read") || hasPermission("celery.manage"),
- },
- {
- href: "/task-monitor",
- title: "任务监控",
- description: "监控 Celery Worker、队列积压与任务执行状态,快速定位失败与阻塞。",
- category: "协作",
- icon: ,
- visible: (hasPermission) => hasPermission("celery.read") || hasPermission("celery.manage"),
- },
- {
- href: "/power-lines",
- title: "线路管理",
- description: "维护输电线路与杆塔参数,支持导入导出和风险字段管理。",
- category: "电力",
- icon: ,
- visible: (hasPermission) =>
- hasPermission("line.read") || hasPermission("line.manage") || hasPermission("tower.read") || hasPermission("tower.manage"),
- },
- {
- href: "/power-lines/atp-viewer",
- title: "ATP查看器",
- description: "将 ATP 文本转换为 JSON,并用 maxGraph 进行电路图查看。",
- category: "电力",
- icon: ,
- visible: (hasPermission) =>
- hasPermission("line.read") || hasPermission("line.manage") || hasPermission("tower.read") || hasPermission("tower.manage"),
- },
- {
- href: "/lightning-currents",
- title: "雷电幅值统计",
- description: "导入雷电流原始序列并自动提取防雷计算参数。",
- category: "电力",
- icon: ,
- visible: (hasPermission) => hasPermission("lightning.read") || hasPermission("lightning.manage"),
- },
- {
- href: "/lightning-distribution",
- title: "雷电分布统计",
- description: "计算 Ng、空间网格、热力散点和杆塔缓冲区风险。",
- category: "电力",
- icon: ,
- visible: (hasPermission) => hasPermission("lightning.read") || hasPermission("lightning.manage"),
- },
- {
- href: "/elevation",
- title: "高程数据管理",
- description: "维护高程数据集并执行线路杆塔高程回填任务。",
- category: "电力",
- icon: ,
- visible: (hasPermission) => hasPermission("elevation.read") || hasPermission("elevation.manage"),
- },
- {
- href: "/syslog",
- title: "系统日志",
- description: "查看鉴权与会话类审计日志,支持动作与用户筛选。",
- category: "系统",
- icon: ,
- visible: (hasPermission) => hasPermission("menu.read") || hasPermission("menu.manage"),
- },
- {
- href: "/wine-runner",
- title: "Wine执行器",
- description: "通过 Wine 执行 Windows EXE,并实时查看测试日志。",
- category: "研发",
- icon: ,
- visible: (hasPermission) => hasPermission("wine.read") || hasPermission("wine.manage"),
- },
-];
-
-export default function AdminHomePage() {
- const { hasPermission, user } = useAuth();
- const visibleCards = CARDS.filter((item) => item.visible(hasPermission));
- const categoryCount = new Set(visibleCards.map((item) => item.category)).size;
-
- if (visibleCards.length === 0) {
- return (
-
-
-
- );
- }
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 模块导航
-
- 按权限展示,入口遵循 Ant Design 卡片列表模式。
-
-
-
- {visibleCards.map((item) => (
-
-
-
-
-
-
-
- {item.title}
- {item.category}
-
- {item.description}
-
-
-
-
-
- ))}
-
-
-
- );
+ return null;
}
diff --git a/web/src/app/admin/roles/page.tsx b/web/src/app/admin/roles/page.tsx
index a3c7860..8ec5a08 100644
--- a/web/src/app/admin/roles/page.tsx
+++ b/web/src/app/admin/roles/page.tsx
@@ -340,7 +340,7 @@ export default function AdminRolesPage() {
subTitle="你没有访问该页面的权限(需要 role.read)。"
extra={(
)}
/>
diff --git a/web/src/app/page.tsx b/web/src/app/page.tsx
index 717d4d0..ed4dcd1 100644
--- a/web/src/app/page.tsx
+++ b/web/src/app/page.tsx
@@ -24,7 +24,7 @@ export default function Home() {
useEffect(() => {
if (!initializing && user) {
- router.replace("/dashboard");
+ router.replace("/users");
}
}, [initializing, router, user]);
diff --git a/web/src/middleware.ts b/web/src/middleware.ts
index aea1895..ef52a48 100644
--- a/web/src/middleware.ts
+++ b/web/src/middleware.ts
@@ -32,22 +32,26 @@ export function middleware(request: NextRequest) {
// Keep backward compatibility for existing /admin links.
if (pathname === "/admin" || pathname === "/admin/") {
const url = request.nextUrl.clone();
- url.pathname = "/dashboard";
+ url.pathname = "/users";
+ return NextResponse.redirect(url);
+ }
+ if (pathname === "/dashboard" || pathname === "/dashboard/") {
+ const url = request.nextUrl.clone();
+ url.pathname = "/users";
return NextResponse.redirect(url);
}
if (pathname.startsWith("/admin/")) {
const url = request.nextUrl.clone();
- url.pathname = pathname.slice("/admin".length) || "/dashboard";
+ url.pathname = pathname.slice("/admin".length) || "/users";
return NextResponse.redirect(url);
}
// New public URLs without /admin are internally rewritten to existing routes.
const url = request.nextUrl.clone();
- url.pathname = pathname === "/dashboard" ? "/admin" : `/admin${pathname}`;
+ url.pathname = `/admin${pathname}`;
return NextResponse.rewrite(url);
}
export const config = {
matcher: "/:path*",
};
-