- 新增 hzhub-portal-employee 员工门户前端项目(基于 Vue3 + Element Plus) - 后端登录接口增加返回 nickName 字段 - 移除 KnowledgeInfoController 的 @SaCheckPermission 注解 - 删除 hzhub-portal-company 旧门户项目 - 更新项目文档和架构说明 - 添加后台运行管理脚本(start-all.sh / status-all.sh / stop-all.sh) - 更新 docker-compose 配置 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
358 lines
8.5 KiB
Markdown
358 lines
8.5 KiB
Markdown
# 标签页功能说明
|
||
|
||
## 功能概述
|
||
|
||
已将 `hzhub-admin` 中的标签页导航功能移植到 `hzhub-portal-employee`,使用 Element Plus 组件重新实现。
|
||
|
||
## 主要功能
|
||
|
||
### 1. 标签页管理
|
||
|
||
- 自动添加标签页:路由切换时自动创建对应标签页
|
||
- 关闭标签页:点击关闭按钮关闭当前标签页
|
||
- 固定标签页:固定后的标签页不可关闭,以不同颜色标识
|
||
- 标签页持久化:标签页状态自动保存到本地存储
|
||
|
||
### 2. 工具栏功能
|
||
|
||
- 更多操作菜单:
|
||
- 关闭其他标签页
|
||
- 关闭右侧标签页
|
||
- 关闭所有标签页
|
||
- 全屏切换:切换浏览器全屏模式
|
||
|
||
### 3. 视觉效果
|
||
|
||
- 当前激活标签页高亮显示(主题色)
|
||
- 固定标签页以警告色标识
|
||
- 悬停显示关闭按钮
|
||
- 平滑过渡动画
|
||
- 标签页图标支持(可选)
|
||
|
||
## 技术实现
|
||
|
||
### UI 框架适配
|
||
|
||
从 `hzhub-admin` (Ant Design Vue) 到 `hzhub-portal-employee` (Element Plus) 的适配:
|
||
|
||
| 功能 | hzhub-admin | hzhub-portal-employee |
|
||
|------|-------------|----------------------|
|
||
| UI 框架 | Ant Design Vue + Vben | Element Plus |
|
||
| 图标组件 | `<VbenIcon>` | `<el-icon>` |
|
||
| 下拉菜单 | `<VbenDropdownMenu>` | `<el-dropdown>` |
|
||
| CSS 系统 | TailwindCSS | SCSS + Element Plus 变量 |
|
||
| 图标库 | Vben Icons | Element Plus Icons |
|
||
|
||
### 文件结构
|
||
|
||
```
|
||
src/
|
||
├── stores/
|
||
│ ├── modules/
|
||
│ │ └── tabbar.ts # 标签页状态管理
|
||
│ └── index.ts # 导出 tabbar store
|
||
├── hooks/
|
||
│ └── useTabbar.ts # 标签页操作 Hook
|
||
├── layouts/
|
||
│ ├── components/
|
||
│ │ └── TabsView/
|
||
│ │ └── index.vue # 标签页视图组件
|
||
│ └── LayoutVertical/
|
||
│ └── index.vue # 集成标签页的布局
|
||
└── routers/
|
||
└ modules/
|
||
└── staticRouter.ts # 路由配置
|
||
```
|
||
|
||
### 核心代码
|
||
|
||
#### Tabbar Store
|
||
|
||
```typescript
|
||
export interface TabItem {
|
||
path: string; // 路由路径
|
||
name: string; // 路由名称
|
||
title: string; // 标签页标题
|
||
icon?: string; // 图标
|
||
affix?: boolean; // 是否固定
|
||
query?: any; // 路由参数
|
||
}
|
||
|
||
export const useTabbarStore = defineStore('tabbar', {
|
||
state: (): TabbarState => ({
|
||
tabs: [], // 标签页列表
|
||
activeTab: '/', // 当前激活的标签页
|
||
cachedTabs: new Set(), // 缓存的组件名称
|
||
}),
|
||
|
||
actions: {
|
||
addTab(route), // 添加标签页
|
||
closeTab(path), // 关闭标签页
|
||
closeOtherTabs(path), // 关闭其他标签页
|
||
closeAllTabs(), // 关闭所有标签页
|
||
closeRightTabs(path), // 关闭右侧标签页
|
||
toggleAffixTab(path), // 固定/取消固定
|
||
initAffixTabs(routes), // 初始化固定标签页
|
||
},
|
||
|
||
persist: {
|
||
key: 'hzhub-employee-tabs',
|
||
paths: ['tabs', 'activeTab'],
|
||
},
|
||
});
|
||
```
|
||
|
||
#### useTabbar Hook
|
||
|
||
```typescript
|
||
export function useTabbar() {
|
||
const router = useRouter();
|
||
const route = useRoute();
|
||
const tabbarStore = useTabbarStore();
|
||
|
||
// 监听路由变化,自动添加标签页
|
||
watch(() => route.path, () => {
|
||
if (route.name) {
|
||
tabbarStore.addTab(route);
|
||
}
|
||
}, { immediate: true });
|
||
|
||
// 初始化固定标签页
|
||
watch(() => router.getRoutes(), () => {
|
||
tabbarStore.initAffixTabs(router.getRoutes());
|
||
}, { immediate: true });
|
||
|
||
return {
|
||
currentActive, // 当前激活的标签页
|
||
currentTabs, // 标签页列表
|
||
handleClick, // 点击标签页
|
||
handleClose, // 关闭标签页
|
||
handleUnpin, // 固定/取消固定
|
||
closeOtherTabs, // 关闭其他标签页
|
||
closeAllTabs, // 关闭所有标签页
|
||
closeRightTabs, // 关闭右侧标签页
|
||
};
|
||
}
|
||
```
|
||
|
||
#### TabsView 组件
|
||
|
||
```vue
|
||
<script setup lang="ts">
|
||
import { computed, ref } from 'vue';
|
||
import { useRoute, useRouter } from 'vue-router';
|
||
import { useTabbarStore } from '@/stores';
|
||
|
||
const tabbarStore = useTabbarStore();
|
||
const activeTabPath = computed(() => tabbarStore.activeTab);
|
||
const tabs = computed(() => tabbarStore.tabs);
|
||
const isFullscreen = ref(false);
|
||
|
||
// 点击标签页
|
||
const handleTabClick = (tab: TabItem) => {
|
||
tabbarStore.setActiveTab(tab.path);
|
||
router.push(tab.path);
|
||
};
|
||
|
||
// 关闭标签页
|
||
const handleTabClose = (path: string) => {
|
||
tabbarStore.closeTab(path);
|
||
};
|
||
|
||
// 切换全屏
|
||
const toggleFullscreen = () => {
|
||
isFullscreen.value = !isFullscreen.value;
|
||
if (isFullscreen.value) {
|
||
document.documentElement.requestFullscreen();
|
||
} else {
|
||
document.exitFullscreen();
|
||
}
|
||
};
|
||
</script>
|
||
```
|
||
|
||
#### 布局集成
|
||
|
||
```vue
|
||
<script setup lang="ts">
|
||
import TabsView from '@/layouts/components/TabsView/index.vue';
|
||
import { useTabbar } from '@/hooks/useTabbar';
|
||
|
||
const { handleClose, handleUnpin } = useTabbar();
|
||
</script>
|
||
|
||
<template>
|
||
<div class="layout">
|
||
<Aside />
|
||
<div class="main-area">
|
||
<Header />
|
||
<TabsView
|
||
:show-icon="true"
|
||
@close="handleClose"
|
||
@unpin="handleUnpin"
|
||
/>
|
||
<main class="page-content">
|
||
<router-view />
|
||
</main>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
```
|
||
|
||
## 使用说明
|
||
|
||
### 固定标签页
|
||
|
||
在路由配置中添加 `meta.affix` 属性:
|
||
|
||
```typescript
|
||
{
|
||
path: '/dashboard',
|
||
name: 'dashboard',
|
||
meta: {
|
||
title: '工作台',
|
||
icon: 'Odometer',
|
||
affix: true, // 固定标签页
|
||
},
|
||
}
|
||
```
|
||
|
||
### 隐藏标签页
|
||
|
||
在路由配置中添加 `meta.isHide` 属性:
|
||
|
||
```typescript
|
||
{
|
||
path: '/profile',
|
||
name: 'profile',
|
||
meta: {
|
||
title: '个人中心',
|
||
isHide: '1', // 不显示在标签页中
|
||
},
|
||
}
|
||
```
|
||
|
||
### 禁用缓存
|
||
|
||
在路由配置中添加 `meta.isKeepAlive` 属性:
|
||
|
||
```typescript
|
||
{
|
||
path: '/example',
|
||
name: 'example',
|
||
meta: {
|
||
title: '示例页面',
|
||
isKeepAlive: '0', // 不缓存组件
|
||
},
|
||
}
|
||
```
|
||
|
||
## 样式定制
|
||
|
||
标签页样式使用 Element Plus CSS 变量系统:
|
||
|
||
- **激活状态**: `--el-color-primary` (主题色)
|
||
- **固定状态**: `--el-color-warning` (警告色)
|
||
- **悬停状态**: `--el-color-primary-light-9`
|
||
- **背景色**: `--el-bg-color-page`
|
||
- **边框色**: `--el-border-color-light`
|
||
|
||
可在 `src/styles/var.scss` 中自定义这些变量值。
|
||
|
||
## 注意事项
|
||
|
||
1. **标签页限制**:
|
||
- 固定的标签页不能关闭
|
||
- 至少保留一个标签页
|
||
- 标签页标题过长会自动截断
|
||
|
||
2. **路由要求**:
|
||
- 路由必须设置 `name` 属性
|
||
- 路由必须设置 `meta.title` 属性
|
||
- 隐藏路由(`isHide='1'`)不会添加到标签页
|
||
|
||
3. **持久化配置**:
|
||
- 使用 `pinia-plugin-persistedstate` 自动保存
|
||
- 保存路径:`['tabs', 'activeTab']`
|
||
- 不保存 `cachedTabs` (组件缓存)
|
||
|
||
4. **性能优化**:
|
||
- 使用 `keep-alive` 缓存组件
|
||
- 标签页列表使用 `Set` 优化查找
|
||
- 懒加载图标组件
|
||
|
||
## 与 hzhub-admin 的差异
|
||
|
||
### 保留的功能
|
||
|
||
- ✅ 标签页自动添加/关闭
|
||
- ✅ 标签页固定/取消固定
|
||
- ✅ 批量关闭操作
|
||
- ✅ 标签页持久化
|
||
- ✅ 全屏切换
|
||
- ✅ 标签页图标显示
|
||
|
||
### 简化的功能
|
||
|
||
- ❌ 拖拽排序(暂未实现)
|
||
- ❌ 滚轮滚动(暂未实现)
|
||
- ❌ 中键关闭(暂未实现)
|
||
- ❌ 右键菜单(暂未实现)
|
||
- ❌ 多种标签页样式(plain/brisk/card)
|
||
- ❌ 国际化标题切换
|
||
|
||
### 新增的功能
|
||
|
||
- ✅ 简化的工具栏设计
|
||
- ✅ Element Plus 原生样式
|
||
- ✅ 更简洁的代码结构
|
||
|
||
## 后续优化建议
|
||
|
||
1. **交互增强**:
|
||
- 添加右键菜单功能
|
||
- 支持拖拽排序标签页
|
||
- 支持鼠标滚轮横向滚动
|
||
- 支持中键点击关闭
|
||
|
||
2. **样式定制**:
|
||
- 提供多种标签页样式(chrome/card/plain)
|
||
- 支持自定义标签页主题色
|
||
- 标签页宽度自适应
|
||
|
||
3. **功能扩展**:
|
||
- 标签页分组功能
|
||
- 标签页搜索功能
|
||
- 最近访问的标签页历史
|
||
- 标签页快捷键操作
|
||
|
||
4. **性能优化**:
|
||
- 虚拟滚动优化大量标签页
|
||
- 标签页懒加载优化
|
||
- 组件缓存策略优化
|
||
|
||
## 测试建议
|
||
|
||
### 功能测试
|
||
|
||
1. **标签页管理**:
|
||
- 切换路由自动添加标签页
|
||
- 关闭标签页后自动切换到相邻标签页
|
||
- 固定标签页不可关闭
|
||
- 刷新后标签页状态保持
|
||
|
||
2. **批量操作**:
|
||
- 关闭其他标签页
|
||
- 关闭右侧标签页
|
||
- 关闭所有标签页
|
||
|
||
3. **全屏功能**:
|
||
- 点击全屏按钮进入全屏模式
|
||
- 再次点击退出全屏模式
|
||
|
||
### 兼容性测试
|
||
|
||
- Chrome、Firefox、Safari 浏览器测试
|
||
- 响应式布局测试(桌面端、平板)
|
||
- 标签页滚动测试(超过 10 个标签页)
|
||
- 固定标签页与普通标签页混排测试 |