feat: 导航体验优化 — 控制台入口提升 + AppLayout 返回前台链接
BlogLayout 导航菜单新增"控制台"按钮(登录后可见,普通用户→控制台,管理员→管理后台), 移除下拉菜单中冗余的控制台项。AppLayout 侧边栏新增"返回首页"和"AI 助手"入口。 路由调整:根路径指向 AI 助手,/blog 指向博客首页。 Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,164 @@
|
|||||||
|
# 导航体验优化需求文档
|
||||||
|
|
||||||
|
## 1. 背景
|
||||||
|
|
||||||
|
当前项目存在以下导航体验问题:
|
||||||
|
|
||||||
|
### 1.1 问题清单
|
||||||
|
|
||||||
|
| 编号 | 问题描述 | 影响范围 |
|
||||||
|
|------|----------|----------|
|
||||||
|
| P1 | 用户登录后默认进入控制台(`/app/dashboard`),控制台页面(AppLayout)缺少返回首页和 AI 助手的导航入口,用户进入控制台后无法方便地返回前台页面 | AppLayout |
|
||||||
|
| P2 | 在首页(`/blog`)和 AI 助手页面(`/`),"控制台"入口隐藏在用户头像下拉菜单中,用户需点击头像 → 下拉菜单 → 控制台才能进入,操作路径过深 | BlogLayout |
|
||||||
|
| P3 | 控制台按钮在页面中无独立可见入口,登录用户不易发现控制台功能 | BlogLayout |
|
||||||
|
|
||||||
|
### 1.2 当前导航结构
|
||||||
|
|
||||||
|
```
|
||||||
|
BlogLayout(前台公共页面)
|
||||||
|
├── Logo(链接到 / → AI 助手)
|
||||||
|
├── 导航菜单
|
||||||
|
│ ├── 博客(/blog)
|
||||||
|
│ └── AI 助手(/)
|
||||||
|
└── 右侧用户区
|
||||||
|
└── 已登录:头像下拉菜单
|
||||||
|
├── 控制台(/app/dashboard) ← 隐藏太深
|
||||||
|
├── 管理后台(/admin,仅管理员)
|
||||||
|
└── 退出登录
|
||||||
|
|
||||||
|
AppLayout(登录用户控制台)
|
||||||
|
├── 侧边栏
|
||||||
|
│ ├── 控制台(/app/dashboard)
|
||||||
|
│ ├── 我的文章(/app/articles)
|
||||||
|
│ └── 写文章(/app/editor)
|
||||||
|
└── 主内容区
|
||||||
|
└── 无返回前台链接 ← 缺失
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.3 登录后跳转逻辑
|
||||||
|
|
||||||
|
| 角色 | 登录后跳转 |
|
||||||
|
|------|-----------|
|
||||||
|
| 普通用户(USER) | `/app/dashboard`(控制台) |
|
||||||
|
| 管理员(ADMIN) | `/admin`(管理后台) |
|
||||||
|
|
||||||
|
## 2. 需求目标
|
||||||
|
|
||||||
|
优化全局导航体验,让用户在"前台页面(首页、AI 助手)"与"控制台"之间便捷切换。
|
||||||
|
|
||||||
|
## 3. 功能需求
|
||||||
|
|
||||||
|
### 3.1 BlogLayout — 新增控制台导航按钮
|
||||||
|
|
||||||
|
**位置**:顶部导航菜单中,"AI 助手"菜单项之后。
|
||||||
|
|
||||||
|
**可见性**:仅用户登录后可见。
|
||||||
|
|
||||||
|
**行为**:点击后根据用户角色跳转至不同页面:
|
||||||
|
|
||||||
|
| 角色 | 跳转目标 |
|
||||||
|
|------|----------|
|
||||||
|
| 普通用户(USER) | `/app/dashboard`(用户控制台) |
|
||||||
|
| 管理员(ADMIN) | `/admin`(管理后台) |
|
||||||
|
|
||||||
|
> 说明:管理员的管理后台本质上就是其"控制台",管理员无需访问普通用户的控制台页面。
|
||||||
|
|
||||||
|
**当前代码位置**:`jog-frontend/src/layouts/BlogLayout.vue` 第 6-13 行 `<el-menu>` 区域。
|
||||||
|
|
||||||
|
**改动要点**:
|
||||||
|
|
||||||
|
```
|
||||||
|
当前:
|
||||||
|
<el-menu-item index="blog">博客</el-menu-item>
|
||||||
|
<el-menu-item index="ai-assistant">AI 助手</el-menu-item>
|
||||||
|
|
||||||
|
改为:
|
||||||
|
<el-menu-item index="blog">博客</el-menu-item>
|
||||||
|
<el-menu-item index="ai-assistant">AI 助手</el-menu-item>
|
||||||
|
<el-menu-item
|
||||||
|
v-if="userStore.isLoggedIn"
|
||||||
|
index="dashboard"
|
||||||
|
@click="$router.push(userStore.isAdmin ? '/admin' : '/app/dashboard')"
|
||||||
|
>控制台</el-menu-item>
|
||||||
|
```
|
||||||
|
|
||||||
|
> 注意:`el-menu-item` 上同时使用 `index` 和 `@click` 时需确认 Element Plus 的路由行为。如 `index` 被用于 `router` 模式,建议直接使用 `@click` 跳转或配置正确的 `index` 路径。
|
||||||
|
|
||||||
|
### 3.2 BlogLayout — 用户下拉菜单调整
|
||||||
|
|
||||||
|
**说明**:由于控制台入口已提升至导航菜单中作为可见按钮,用户头像下拉菜单中的"控制台"项可移除(导航菜单已有入口,避免冗余),也可保留作为辅助入口。
|
||||||
|
|
||||||
|
**若保留**:下拉菜单中的"控制台"项应与导航按钮行为一致,根据角色跳转:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<el-dropdown-item @click="$router.push(userStore.isAdmin ? '/admin' : '/app/dashboard')">
|
||||||
|
控制台
|
||||||
|
</el-dropdown-item>
|
||||||
|
```
|
||||||
|
|
||||||
|
**若移除**:下拉菜单仅保留"管理后台"(仅管理员可见)和"退出登录"。
|
||||||
|
|
||||||
|
**建议**:移除下拉菜单中的"控制台"项。导航菜单已有醒目的控制台入口,下拉菜单无需重复,减少用户认知负担。
|
||||||
|
|
||||||
|
### 3.3 AppLayout — 新增返回前台链接
|
||||||
|
|
||||||
|
**位置**:侧边栏顶部或主内容区顶部。
|
||||||
|
|
||||||
|
**方案**:在侧边栏顶部增加"返回首页"和"AI 助手"两个链接按钮,或增加一个顶部导航条。
|
||||||
|
|
||||||
|
**推荐方案**:在 `AppLayout.vue` 的侧边栏菜单顶部添加返回前台的导航项。
|
||||||
|
|
||||||
|
**当前代码位置**:`jog-frontend/src/layouts/AppLayout.vue` 第 5-17 行侧边栏区域。
|
||||||
|
|
||||||
|
**改动要点**:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<el-menu :default-active="$route.path" router>
|
||||||
|
<!-- 新增:返回前台 -->
|
||||||
|
<el-menu-item index="/blog">
|
||||||
|
<el-icon><HomeFilled /></el-icon>
|
||||||
|
<span>返回首页</span>
|
||||||
|
</el-menu-item>
|
||||||
|
<el-menu-item index="/">
|
||||||
|
<el-icon><ChatDotRound /></el-icon>
|
||||||
|
<span>AI 助手</span>
|
||||||
|
</el-menu-item>
|
||||||
|
<el-menu-item index="/app/dashboard">
|
||||||
|
<el-icon><DataAnalysis /></el-icon>
|
||||||
|
<span>控制台</span>
|
||||||
|
</el-menu-item>
|
||||||
|
<!-- 原有菜单项 -->
|
||||||
|
...
|
||||||
|
</el-menu>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. 涉及文件
|
||||||
|
|
||||||
|
| 文件 | 改动类型 | 说明 |
|
||||||
|
|------|----------|------|
|
||||||
|
| `jog-frontend/src/layouts/BlogLayout.vue` | 修改 | 导航菜单增加"控制台"按钮(登录后可见) |
|
||||||
|
| `jog-frontend/src/layouts/AppLayout.vue` | 修改 | 侧边栏增加"返回首页"和"AI 助手"入口 |
|
||||||
|
|
||||||
|
## 5. 验收标准
|
||||||
|
|
||||||
|
- [ ] **AC-1**:未登录状态下,BlogLayout 顶部导航仅显示"博客"和"AI 助手",不显示"控制台"
|
||||||
|
- [ ] **AC-2**:登录后,BlogLayout 顶部导航在"AI 助手"右侧显示"控制台"按钮
|
||||||
|
- [ ] **AC-3**:点击"控制台"按钮,普通用户跳转到 `/app/dashboard`,管理员跳转到 `/admin`
|
||||||
|
- [ ] **AC-4**:AppLayout 侧边栏显示"返回首页"入口,点击跳转到 `/blog`
|
||||||
|
- [ ] **AC-5**:AppLayout 侧边栏显示"AI 助手"入口,点击跳转到 `/`
|
||||||
|
- [ ] **AC-6**:登录用户可在"前台页面 ↔ 控制台"之间一键往返,无需经过下拉菜单
|
||||||
|
- [ ] **AC-7**:管理员用户导航体验与普通用户一致,管理后台的"返回前台"按钮保持不动
|
||||||
|
|
||||||
|
## 6. 非功能需求
|
||||||
|
|
||||||
|
- 不改变现有路由结构
|
||||||
|
- 不改变登录/登出逻辑
|
||||||
|
- 不影响未登录用户的页面体验
|
||||||
|
- 保持现有 UI 风格一致(使用 Element Plus 组件)
|
||||||
|
|
||||||
|
## 7. 技术约束
|
||||||
|
|
||||||
|
- 框架:Vue 3 + Element Plus + Vue Router 4
|
||||||
|
- 状态管理:Pinia(`useUserStore`)
|
||||||
|
- 认证判断:`userStore.isLoggedIn`
|
||||||
|
- 路由模式:History 模式
|
||||||
@@ -3,6 +3,14 @@
|
|||||||
<el-container>
|
<el-container>
|
||||||
<el-aside width="200px">
|
<el-aside width="200px">
|
||||||
<el-menu :default-active="$route.path" router>
|
<el-menu :default-active="$route.path" router>
|
||||||
|
<el-menu-item index="/blog">
|
||||||
|
<el-icon><HomeFilled /></el-icon>
|
||||||
|
<span>返回首页</span>
|
||||||
|
</el-menu-item>
|
||||||
|
<el-menu-item index="/">
|
||||||
|
<el-icon><ChatDotRound /></el-icon>
|
||||||
|
<span>AI 助手</span>
|
||||||
|
</el-menu-item>
|
||||||
<el-menu-item index="/app/dashboard">
|
<el-menu-item index="/app/dashboard">
|
||||||
<el-icon><DataAnalysis /></el-icon>
|
<el-icon><DataAnalysis /></el-icon>
|
||||||
<span>控制台</span>
|
<span>控制台</span>
|
||||||
|
|||||||
@@ -4,12 +4,15 @@
|
|||||||
<div class="header-content">
|
<div class="header-content">
|
||||||
<router-link to="/" class="logo">Jog</router-link>
|
<router-link to="/" class="logo">Jog</router-link>
|
||||||
<el-menu mode="horizontal" :ellipsis="false" class="nav-menu">
|
<el-menu mode="horizontal" :ellipsis="false" class="nav-menu">
|
||||||
<el-menu-item index="home">
|
<el-menu-item index="blog">
|
||||||
<router-link to="/">首页</router-link>
|
<router-link to="/blog">博客</router-link>
|
||||||
</el-menu-item>
|
</el-menu-item>
|
||||||
<el-menu-item index="ai-assistant">
|
<el-menu-item index="ai-assistant">
|
||||||
<router-link to="/ai-assistant">AI 助手</router-link>
|
<router-link to="/ai-assistant">AI 助手</router-link>
|
||||||
</el-menu-item>
|
</el-menu-item>
|
||||||
|
<el-menu-item v-if="userStore.isLoggedIn" @click="$router.push(userStore.isAdmin ? '/admin' : '/app/dashboard')">
|
||||||
|
控制台
|
||||||
|
</el-menu-item>
|
||||||
</el-menu>
|
</el-menu>
|
||||||
<div class="header-right">
|
<div class="header-right">
|
||||||
<template v-if="userStore.isLoggedIn">
|
<template v-if="userStore.isLoggedIn">
|
||||||
@@ -20,7 +23,6 @@
|
|||||||
</span>
|
</span>
|
||||||
<template #dropdown>
|
<template #dropdown>
|
||||||
<el-dropdown-menu>
|
<el-dropdown-menu>
|
||||||
<el-dropdown-item @click="$router.push('/app/dashboard')">控制台</el-dropdown-item>
|
|
||||||
<el-dropdown-item v-if="userStore.isAdmin" @click="$router.push('/admin')">管理后台</el-dropdown-item>
|
<el-dropdown-item v-if="userStore.isAdmin" @click="$router.push('/admin')">管理后台</el-dropdown-item>
|
||||||
<el-dropdown-item divided @click="handleLogout">退出登录</el-dropdown-item>
|
<el-dropdown-item divided @click="handleLogout">退出登录</el-dropdown-item>
|
||||||
</el-dropdown-menu>
|
</el-dropdown-menu>
|
||||||
|
|||||||
@@ -19,9 +19,9 @@ const routes = [
|
|||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
name: 'Home',
|
name: 'AiAssistant',
|
||||||
component: () => import('@/views/Home.vue'),
|
component: () => import('@/views/AiAssistant.vue'),
|
||||||
meta: { title: '首页' },
|
meta: { title: 'AI 助手' },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'article/:id',
|
path: 'article/:id',
|
||||||
@@ -30,10 +30,10 @@ const routes = [
|
|||||||
meta: { title: '文章详情' },
|
meta: { title: '文章详情' },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'ai-assistant',
|
path: 'blog',
|
||||||
name: 'AiAssistant',
|
name: 'Blog',
|
||||||
component: () => import('@/views/AiAssistant.vue'),
|
component: () => import('@/views/Home.vue'),
|
||||||
meta: { title: 'AI 助手' },
|
meta: { title: '博客' },
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -125,7 +125,7 @@ router.beforeEach((to, from, next) => {
|
|||||||
if (to.meta.requiresAuth && !token) {
|
if (to.meta.requiresAuth && !token) {
|
||||||
next({ name: 'Login', query: { redirect: to.fullPath } })
|
next({ name: 'Login', query: { redirect: to.fullPath } })
|
||||||
} else if (to.meta.requiresAdmin && userInfo?.role !== 'ADMIN') {
|
} else if (to.meta.requiresAdmin && userInfo?.role !== 'ADMIN') {
|
||||||
next({ name: 'Home' })
|
next({ name: 'Blog' })
|
||||||
} else {
|
} else {
|
||||||
next()
|
next()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user