feat: 添加员工门户项目及相关后端改造

- 新增 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>
This commit is contained in:
大壮
2026-04-13 03:47:33 +00:00
parent 4e82f8e1e2
commit 278e507e8a
1310 changed files with 7243 additions and 1248 deletions

View File

@@ -0,0 +1,358 @@
# 标签页功能说明
## 功能概述
已将 `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 个标签页)
- 固定标签页与普通标签页混排测试