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

@@ -32,10 +32,18 @@ docker-compose down
### Backend Development (Spring Boot)
```bash
# Run AI service locally
cd hzhub-ai/hzhub-admin
# Run AI service locally (foreground)
cd hzhub-ai
mvn spring-boot:run -Dspring-boot.run.profiles=dev
# Run AI service locally (background)
cd hzhub-ai
./start.sh # Start service in background
./status.sh # Check service status
./logs.sh # View logs
./stop.sh # Stop service
./restart.sh # Restart service
# Build all modules
cd hzhub-ai
mvn clean package
@@ -48,20 +56,28 @@ mvn clean package
mvn test
```
**💡 Tip:** For background service management, see [SERVICE_MANAGEMENT.md](../SERVICE_MANAGEMENT.md)
### Frontend Development (Vue 3 + Vben Admin)
```bash
# Admin portal development
cd hzhub-admin
pnpm install # Install dependencies
pnpm dev # Start dev server
pnpm dev # Start dev server (foreground)
./start.sh # Start dev server (background)
./status.sh # Check service status
./logs.sh # View logs
pnpm build # Build all packages
pnpm --filter=@vben/web-antd build:prod # Build admin frontend
# Company portal development
cd hzhub-portal-company
# Employee portal development
cd hzhub-portal-employee
pnpm install
pnpm dev
pnpm dev # Start dev server (foreground)
./start.sh # Start dev server (background)
./status.sh # Check service status
./logs.sh # View logs
# Dealer portal development
cd hzhub-portal-dealer
@@ -69,6 +85,8 @@ pnpm install
pnpm dev
```
**💡 Tip:** For background service management (start/stop/restart/status/logs), see [SERVICE_MANAGEMENT.md](../SERVICE_MANAGEMENT.md)
## Architecture
### Multi-Service Structure
@@ -76,7 +94,7 @@ pnpm dev
```
┌─────────────────────────────────────────┐
│ Frontend Layer │
│ hzhub-admin | hzhub-portal-company
│ hzhub-admin | hzhub-portal-employee
│ | hzhub-portal-dealer │
└────────────┬────────────────────────────┘
@@ -125,7 +143,7 @@ hzhub-admin/
└── package.json # Root monorepo config
```
**Portal applications** (hzhub-portal-company, hzhub-portal-dealer) are Vue 3 apps with:
**Portal applications** (hzhub-portal-employee, hzhub-portal-dealer) are Vue 3 apps with:
- Composition API (`<script setup>`)
- Pinia state management with persistence
- Element Plus UI components
@@ -282,7 +300,7 @@ Frontend Dockerfiles in each portal directory:
| Service | Port | Access |
|---------|------|--------|
| hzhub-admin (frontend) | 5666 | http://localhost:5666 |
| hzhub-portal-company | 5137 | http://localhost:5137 |
| hzhub-portal-employee | 5137 | http://localhost:5137 |
| hzhub-portal-dealer | 5138 | http://localhost:5138 |
| hzhub-ai (backend API) | 6039 | http://localhost:6039 |
| MySQL | 3306 | localhost:3306 |
@@ -314,7 +332,7 @@ For admin portal:
3. Add route in `apps/web-antd/src/router/`
4. Add menu configuration
For portals (company/dealer):
For portals (employee/dealer):
1. Add API module in `src/api/` with `index.ts` and `types.ts`
2. Create page in `src/pages/`
3. Define route in `src/routers/modules/`

View File

@@ -12,9 +12,9 @@ HZHub汇智中台是面向企业级市场的业务中台系统集成 AI
┌─────────────────────────────────────────────────────────────────────────┐
│ 前端接入层 │
├─────────────────┬─────────────────┬─────────────────────────────────────┤
│ 管理后台 │ 公司门户 │ 经销商门户 │
│ 管理后台 │ 员工门户 │ 经销商门户 │
│ (hzhub-admin) │ (hzhub-portal- │ (hzhub-portal-dealer) │
│ · 模型管理 │ company) │ · 企业微信H5 │
│ · 模型管理 │ employee) │ · 企业微信H5 │
│ · 知识库配置 │ · 企业微信H5 │ · 自助开单 │
│ · 智能体编排 │ · 审批流程 │ · 自助对账/发货 │
│ · 系统管理 │ · 经销商管理 │ · 进销存 │
@@ -60,7 +60,7 @@ hzhub/
├── hzhub-erp/ # ERP服务新建
├── hzhub-gateway/ # API网关新建
├── hzhub-admin/ # 管理后台复用hzhub-admin
├── hzhub-portal-company/ # 公司门户复用hzhub-portal
├── hzhub-portal-employee/ # 员工门户复用hzhub-portal
├── hzhub-portal-dealer/ # 经销商门户复用hzhub-portal
├── hzhub-deploy/ # 部署配置
│ └── docker-compose.yml
@@ -101,7 +101,7 @@ hzhub/
- ✅ 前端管理后台 Docker 化 (hzhub-admin)
- ⏳ ERP服务 (开发中)
- ⏳ API网关 (开发中)
-公司门户 (待配置)
-员工门户 (待配置)
- ⏳ 经销商门户 (待配置)
## 快速开始
@@ -123,9 +123,47 @@ docker-compose up -d
启动完成后访问:
- 🌐 管理后台: http://localhost:5666
- 🚀 员工门户: http://localhost:5137
- 🔧 AI服务API: http://localhost:6039
- 🔄 n8n工作流: http://localhost:5678
### 本地开发启动(后台运行)
项目提供了便捷的后台运行管理脚本:
```bash
# 一键启动所有服务
cd /data/hzhub
./start-all.sh
# 查看所有服务状态
./status-all.sh
# 一键停止所有服务
./stop-all.sh
```
或分别启动各服务:
```bash
# 启动后端服务
cd hzhub-ai
./start.sh # 启动
./status.sh # 查看状态
./logs.sh # 查看日志
./stop.sh # 停止
# 启动管理后台
cd hzhub-admin
./start.sh
# 启动员工门户
cd hzhub-portal-employee
./start.sh
```
📖 详细说明请查看 [服务管理文档](./SERVICE_MANAGEMENT.md)
### 服务清单
| 服务名 | 容器名 | 端口 | 说明 |

286
SERVICE_MANAGEMENT.md Executable file
View File

@@ -0,0 +1,286 @@
# 服务管理脚本使用说明
本项目为 `hzhub-admin``hzhub-ai``hzhub-portal-employee` 三个项目提供了后台运行管理脚本,方便开发调试。
## 📋 脚本列表
每个项目根目录下都包含以下脚本:
| 脚本 | 功能 | 说明 |
|------|------|------|
| `start.sh` | 启动服务 | 后台启动开发服务器 |
| `stop.sh` | 停止服务 | 停止后台运行的服务 |
| `restart.sh` | 重启服务 | 先停止后启动 |
| `status.sh` | 查看状态 | 查看服务运行状态和最新日志 |
| `logs.sh` | 查看日志 | 实时查看服务日志 |
## 🚀 快速开始
### 1. 启动后端服务 (hzhub-ai)
```bash
cd /data/hzhub/hzhub-ai
./start.sh
```
**输出示例:**
```
=========================================
启动 hzhub-ai 后端服务
=========================================
🚀 启动 Spring Boot 服务...
⏳ 等待服务启动中...
✅ 服务启动成功
PID: 12345
日志: /data/hzhub/hzhub-ai/logs/backend.log
API: http://localhost:6039
```
**访问地址:** http://localhost:6039
### 2. 启动管理后台 (hzhub-admin)
```bash
cd /data/hzhub/hzhub-admin
./start.sh
```
**访问地址:** http://localhost:5666
### 3. 启动员工门户 (hzhub-portal-employee)
```bash
cd /data/hzhub/hzhub-portal-employee
./start.sh
```
**访问地址:** http://localhost:5137
## 📊 查看服务状态
```bash
# 查看后端服务状态
cd /data/hzhub/hzhub-ai
./status.sh
# 查看管理后台状态
cd /data/hzhub/hzhub-admin
./status.sh
# 查看员工门户状态
cd /data/hzhub/hzhub-portal-employee
./status.sh
```
**输出示例:**
```
=========================================
hzhub-ai 服务状态
=========================================
状态: 🟢 运行中
PID: 12345
PID PPID CMD ELAPSED
12345 1 mvn spring-boot:run -Dspring- 00:15:32
日志文件: /data/hzhub/hzhub-ai/logs/backend.log
API地址: http://localhost:6039
端口状态: ✅ 6039 端口正在监听
```
## 📝 查看实时日志
```bash
# 查看后端日志
cd /data/hzhub/hzhub-ai
./logs.sh
# 或直接使用tail命令
tail -f /data/hzhub/hzhub-ai/logs/backend.log
```
`Ctrl+C` 退出日志查看。
## 🔄 重启服务
```bash
# 重启后端服务
cd /data/hzhub/hzhub-ai
./restart.sh
# 重启管理后台
cd /data/hzhub/hzhub-admin
./restart.sh
# 重启员工门户
cd /data/hzhub/hzhub-portal-employee
./restart.sh
```
## 🛑 停止服务
```bash
# 停止后端服务
cd /data/hzhub/hzhub-ai
./stop.sh
# 停止管理后台
cd /data/hzhub/hzhub-admin
./stop.sh
# 停止员工门户
cd /data/hzhub/hzhub-portal-employee
./stop.sh
```
## 💡 使用技巧
### 启动顺序建议
1. **先启动后端服务** (hzhub-ai)
```bash
cd /data/hzhub/hzhub-ai && ./start.sh
```
2. **等待后端完全启动** (约30-60秒)
```bash
./status.sh # 查看端口6039是否监听
```
3. **启动前端应用**
```bash
cd /data/hzhub/hzhub-admin && ./start.sh
cd /data/hzhub/hzhub-portal-employee && ./start.sh
```
### 一键启动所有服务
创建一个启动脚本 `/data/hzhub/start-all.sh`
```bash
#!/bin/bash
echo "启动所有 HZHub 服务..."
cd /data/hzhub/hzhub-ai
./start.sh
sleep 10
cd /data/hzhub/hzhub-admin
./start.sh
cd /data/hzhub/hzhub-portal-employee
./start.sh
echo "所有服务启动完成!"
```
### 一键停止所有服务
创建一个停止脚本 `/data/hzhub/stop-all.sh`
```bash
#!/bin/bash
echo "停止所有 HZHub 服务..."
cd /data/hzhub/hzhub-portal-employee
./stop.sh
cd /data/hzhub/hzhub-admin
./stop.sh
cd /data/hzhub/hzhub-ai
./stop.sh
echo "所有服务已停止!"
```
## ⚙️ 服务信息
| 项目 | 端口 | 日志文件 | PID文件 |
|------|------|----------|---------|
| hzhub-ai | 6039 | logs/backend.log | .pid |
| hzhub-admin | 5666 | logs/dev.log | .pid |
| hzhub-portal-employee | 5137 | logs/dev.log | .pid |
## 🔍 常见问题
### Q: 服务启动失败怎么办?
**A:** 检查以下几点:
1. 查看日志文件:`./logs.sh` 或 `tail -f logs/*.log`
2. 检查端口是否被占用:`netstat -tuln | grep 端口号`
3. 确认依赖是否安装:前端项目执行 `pnpm install`
4. 后端项目检查 Maven 是否可用:`mvn -v`
### Q: 如何确认服务是否完全启动?
**A:**
- **后端服务**:执行 `./status.sh` 查看6039端口是否监听
- **前端服务**:访问对应端口查看页面是否正常显示
### Q: PID文件损坏怎么办
**A:** 手动删除PID文件
```bash
rm .pid
./start.sh
```
### Q: 进程僵死无法停止?
**A:** 查找并强制终止进程:
```bash
# 查找进程
ps aux | grep "spring-boot\|vite\|pnpm"
# 强制终止
kill -9 <PID>
# 清理PID文件
rm .pid
```
## 📁 日志管理
### 日志位置
所有日志都保存在各项目的 `logs/` 目录下:
```
hzhub-ai/
├── logs/
│ └── backend.log
hzhub-admin/
├── logs/
│ └── dev.log
hzhub-portal-employee/
├── logs/
│ └── dev.log
```
### 日志轮转
建议定期清理日志文件:
```bash
# 清空日志文件
> logs/backend.log
# 或删除旧日志
rm logs/*.log
```
## ⚠️ 注意事项
1. **端口冲突**:确保端口 6039、5666、5137 未被其他服务占用
2. **资源占用**后端服务启动较慢30-60秒请耐心等待
3. **开发模式**:这些脚本仅用于开发调试,生产环境请使用 Docker 部署
4. **数据持久化**:前端开发服务器会自动热重载,后端服务需要手动重启
## 🔗 相关文档
- [项目主文档](../CLAUDE.md)
- [部署文档](../hzhub-deploy/README.md)
- [开发环境配置](../docs/project/plan/phase-1.md)

View File

@@ -60,7 +60,7 @@ foshanhuiya/hzhub/
├── hzhub-erp/ # ERP服务Spring Boot 4.0
├── hzhub-gateway/ # API网关Spring Boot 4.0
├── hzhub-admin/ # 管理后台前端
├── hzhub-portal-company/ # 公司门户前端
├── hzhub-portal-employee/ # 公司门户前端
├── hzhub-portal-dealer/ # 经销商门户前端
└── hzhub-deploy/ # 部署脚本与配置
```

View File

@@ -31,7 +31,7 @@ HZHub汇智中台是基于 HZHub-AI 构建的企业级业务中台系统
| hzhub-ai | AI 核心能力 | Spring Boot 4.0 + Spring AI 2.0 |
| hzhub-erp | ERP 数据适配 | Spring Boot 4.0 + JDBC |
| hzhub-admin | 管理后台前端 | Vue 3 |
| hzhub-portal-company | 公司门户前端 | Vue 3 |
| hzhub-portal-employee | 公司门户前端 | Vue 3 |
| hzhub-portal-dealer | 经销商门户前端 | Vue 3 |
### 2.3 数据存储

View File

@@ -18,7 +18,7 @@ digraph HZHubArchitectureCN {
labeljust=left;
admin [label="hzhub-admin\n管理后台\n• 模型管理\n• 知识库配置\n• 智能体编排", fillcolor="#BBDEFB", color="#1976D2"];
company [label="hzhub-portal-company\n公司门户\n• 企业微信H5\n• 审批流程\n• 销售CRM\n• BI报表", fillcolor="#BBDEFB", color="#1976D2"];
company [label="hzhub-portal-employee\n员工门户\n• 企业微信H5\n• 审批流程\n• 销售CRM\n• BI报表", fillcolor="#BBDEFB", color="#1976D2"];
dealer [label="hzhub-portal-dealer\n经销商门户\n• 企业微信H5\n• 自助开单\n• 进销存\n• AI素材生成", fillcolor="#BBDEFB", color="#1976D2"];
}

View File

@@ -18,7 +18,7 @@ digraph HZHubArchitecture {
labeljust=left;
admin [label="hzhub-admin\nManagement Portal\n• Model Management\n• Knowledge Base Config\n• Agent Orchestration", fillcolor="#BBDEFB", color="#1976D2"];
company [label="hzhub-portal-company\nCompany Portal\n• WeChat Work H5\n• Approval Workflow\n• Sales CRM\n• BI Reports", fillcolor="#BBDEFB", color="#1976D2"];
company [label="hzhub-portal-employee\nEmployee Portal\n• WeChat Work H5\n• Approval Workflow\n• Sales CRM\n• BI Reports", fillcolor="#BBDEFB", color="#1976D2"];
dealer [label="hzhub-portal-dealer\nDealer Portal\n• WeChat Work H5\n• Self-service Order\n• Inventory Mgmt\n• AI Material Gen", fillcolor="#BBDEFB", color="#1976D2"];
}

View File

@@ -1,7 +1,7 @@
graph TB
subgraph 前端层["🖥️ 前端接入层"]
A1["hzhub-admin<br/>管理后台"]
A2["hzhub-portal-company<br/>公司门户"]
A2["hzhub-portal-employee<br/>员工门户"]
A3["hzhub-portal-dealer<br/>经销商门户"]
end

View File

@@ -289,7 +289,7 @@ async function runWorkflow() {
### 6.3 hzhub-portal推荐结构
```
hzhub-portal-company/ # 公司门户
hzhub-portal-employee/ # 公司门户
├── src/
│ ├── components/
│ │ ├── ErpQueryBuilder/ # ERP查询构建器

View File

@@ -152,7 +152,7 @@ HZHub 技术栈决策:
├── pnpm workspace + Turbo
└── 复用hzhub-admin的packages
门户前端 (hzhub-portal-company / hzhub-portal-dealer)
门户前端 (hzhub-portal-employee / hzhub-portal-dealer)
├── Vue 3 + TypeScript
├── Element Plus X复用hzhub-portal组件
├── UnoCSS
@@ -167,7 +167,7 @@ HZHub 技术栈决策:
| P0 | hzhub-ai | 复用ruoyi-chat | 1周 |
| P0 | hzhub-admin | 复用hzhub-admin | 1周 |
| P1 | hzhub-erp | 自研SQL Server适配 | 2-3周 |
| P1 | hzhub-portal-company | 复用+改造 | 2周 |
| P1 | hzhub-portal-employee | 复用+改造 | 2周 |
| P2 | hzhub-portal-dealer | 复用+改造 | 2周 |
| P2 | 流程编排ERP节点 | 扩展aiflow | 1周 |
@@ -271,7 +271,7 @@ HZHub 技术栈决策:
**目标**:门户开发
**任务**
1. 开发hzhub-portal-company
1. 开发hzhub-portal-employee
2. 开发hzhub-portal-dealer
3. 集成AI组件到门户
4. 企业微信对接

View File

@@ -63,7 +63,7 @@
- [x] 启动基础设施MySQL、Redis、Weaviate、n8n、MinIO
- [x] 验证AI服务运行
- [x] 验证管理后台运行
- [x] 配置hzhub-portal-company
- [x] 配置hzhub-portal-employee
- [x] 配置hzhub-portal-dealer
- [x] RuoYi→HZHub重命名
- [ ] 测试前端登录功能
@@ -108,7 +108,7 @@
**目标**:完成公司门户和经销商门户
**任务清单**
- [ ] 初始化hzhub-portal-company
- [ ] 初始化hzhub-portal-employee
- [ ] 初始化hzhub-portal-dealer
- [ ] 开发公司门户页面
- [ ] 开发经销商门户页面

View File

@@ -10,7 +10,7 @@
搭建可运行的基础框架,包括:
- [x] AI服务可用hzhub-ai
- [x] 管理后台可用hzhub-admin
- [x] 公司门户可用hzhub-portal-company
- [x] 公司门户可用hzhub-portal-employee
- [x] 经销商门户可用hzhub-portal-dealer
- [x] 基础设施就绪MySQL、Redis、Weaviate、n8n、MinIO
- [ ] 开发环境配置完成
@@ -58,7 +58,7 @@
| 任务 | 负责人 | 状态 | 备注 |
|------|--------|------|------|
| 配置hzhub-admin | 大壮 | ✅ 已完成 | 端口5666Nginx |
| 配置hzhub-portal-company | 大壮 | ✅ 已完成 | 端口5137Nginx |
| 配置hzhub-portal-employee | 大壮 | ✅ 已完成 | 端口5137Nginx |
| 配置hzhub-portal-dealer | 大壮 | ✅ 已完成 | 端口5138Nginx |
| 验证前端服务 | 大壮 | ✅ 已完成 | 全部可访问 |
@@ -187,7 +187,7 @@ services:
services:
hzhub-ai: # Spring Boot - 端口6039
hzhub-admin: # Nginx + Vue - 端口5666
hzhub-portal-company: # Nginx + Vue - 端口5137
hzhub-portal-employee: # Nginx + Vue - 端口5137
hzhub-portal-dealer: # Nginx + Vue - 端口5138
```

View File

@@ -35,7 +35,7 @@
- hzhub-admin复用hzhub-admin
- hzhub-erp新建
- hzhub-gateway新建
- hzhub-portal-company/dealer复用hzhub-portal
- hzhub-portal-employee/dealer复用hzhub-portal
4. **建立项目管理文档**
- 总体计划8周4个阶段

View File

@@ -16,7 +16,7 @@
| 配置hzhub-ai数据库 | 3.30 | 3.27 | ✅ 已完成 |
| 验证AI服务运行 | 4.1 | 3.27 | ✅ 已完成 |
| 配置hzhub-admin | 4.2 | 3.27 | ✅ 已完成 |
| 配置hzhub-portal-company | - | 3.27 | ✅ 已完成 |
| 配置hzhub-portal-employee | - | 3.27 | ✅ 已完成 |
| 配置hzhub-portal-dealer | - | 3.27 | ✅ 已完成 |
| RuoYi→HZHub重命名 | - | 3.27 | ✅ 已完成 |
| 修复租户管理日期格式问题 | - | 4.02 | ✅ 已完成 |
@@ -44,7 +44,7 @@
4. **前端服务全部Docker化**
- hzhub-admin (管理后台) - 端口5666
- hzhub-portal-company (公司门户) - 端口5137
- hzhub-portal-employee (公司门户) - 端口5137
- hzhub-portal-dealer (经销商门户) - 端口5138
5. **RuoYi到HZHub全面重命名**
@@ -76,7 +76,7 @@
services:
hzhub-ai: 端口6039
hzhub-admin: 端口5666
hzhub-portal-company: 端口5137
hzhub-portal-employee: 端口5137
hzhub-portal-dealer: 端口5138
hzhub-mysql: 端口3306
hzhub-redis: 端口6379

1
hzhub-admin/.pid Normal file
View File

@@ -0,0 +1 @@
3357568

16
hzhub-admin/logs.sh Executable file
View File

@@ -0,0 +1,16 @@
#!/bin/bash
# HZHub Admin 前端项目日志查看脚本
# 功能:查看服务运行日志
PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)"
LOG_FILE="$PROJECT_DIR/logs/dev.log"
if [ ! -f "$LOG_FILE" ]; then
echo "⚠️ 日志文件不存在: $LOG_FILE"
echo "服务可能未运行或未产生日志"
exit 1
fi
echo "查看 hzhub-admin 日志 (Ctrl+C 退出)"
echo "========================================="
tail -f "$LOG_FILE"

18
hzhub-admin/restart.sh Executable file
View File

@@ -0,0 +1,18 @@
#!/bin/bash
# HZHub Admin 前端项目重启脚本
# 功能:重启后台运行的 hzhub-admin 前端开发服务器
PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)"
echo "========================================="
echo "重启 hzhub-admin 开发服务器"
echo "========================================="
# 停止服务
"$PROJECT_DIR/stop.sh"
# 等待一秒
sleep 1
# 启动服务
"$PROJECT_DIR/start.sh"

53
hzhub-admin/start.sh Executable file
View File

@@ -0,0 +1,53 @@
#!/bin/bash
# HZHub Admin 前端项目启动脚本
# 功能:后台启动 hzhub-admin 前端开发服务器
PROJECT_NAME="hzhub-admin"
PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)"
PID_FILE="$PROJECT_DIR/.pid"
LOG_FILE="$PROJECT_DIR/logs/dev.log"
# 创建日志目录
mkdir -p "$PROJECT_DIR/logs"
echo "========================================="
echo "启动 $PROJECT_NAME 开发服务器"
echo "========================================="
# 检查是否已经在运行
if [ -f "$PID_FILE" ]; then
PID=$(cat "$PID_FILE")
if ps -p "$PID" > /dev/null 2>&1; then
echo "⚠️ 服务已在运行中 (PID: $PID)"
echo "如需重启,请先执行 ./stop.sh"
exit 1
else
echo "清理无效的PID文件"
rm -f "$PID_FILE"
fi
fi
# 启动服务
echo "🚀 启动开发服务器..."
cd "$PROJECT_DIR"
# 使用nohup后台运行pnpm dev
nohup pnpm dev > "$LOG_FILE" 2>&1 &
PID=$!
# 等待一秒检查进程是否成功启动
sleep 2
if ps -p "$PID" > /dev/null 2>&1; then
echo "$PID" > "$PID_FILE"
echo "✅ 服务启动成功"
echo " PID: $PID"
echo " 日志: $LOG_FILE"
echo " 访问: http://localhost:5666"
echo ""
echo "查看日志: tail -f $LOG_FILE"
echo "查看状态: ./status.sh"
else
echo "❌ 服务启动失败"
echo "请查看日志: $LOG_FILE"
exit 1
fi

40
hzhub-admin/status.sh Executable file
View File

@@ -0,0 +1,40 @@
#!/bin/bash
# HZHub Admin 前端项目状态检查脚本
# 功能:查看服务运行状态
PROJECT_NAME="hzhub-admin"
PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)"
PID_FILE="$PROJECT_DIR/.pid"
LOG_FILE="$PROJECT_DIR/logs/dev.log"
echo "========================================="
echo " $PROJECT_NAME 服务状态"
echo "========================================="
# 检查PID文件
if [ ! -f "$PID_FILE" ]; then
echo "状态: ⚪ 未运行"
exit 0
fi
PID=$(cat "$PID_FILE")
# 检查进程是否存在
if ps -p "$PID" > /dev/null 2>&1; then
echo "状态: 🟢 运行中"
echo "PID: $PID"
# 显示进程详细信息
ps -p "$PID" -o pid,ppid,cmd,etime
echo ""
echo "日志文件: $LOG_FILE"
echo "访问地址: http://localhost:5666"
echo ""
echo "最近10行日志:"
echo "---"
tail -n 10 "$LOG_FILE" 2>/dev/null || echo "暂无日志"
else
echo "状态: 🔴 已停止 (PID文件存在但进程不存在)"
rm -f "$PID_FILE"
fi

44
hzhub-admin/stop.sh Executable file
View File

@@ -0,0 +1,44 @@
#!/bin/bash
# HZHub Admin 前端项目停止脚本
# 功能:停止后台运行的 hzhub-admin 前端开发服务器
PROJECT_NAME="hzhub-admin"
PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)"
PID_FILE="$PROJECT_DIR/.pid"
echo "========================================="
echo "停止 $PROJECT_NAME 开发服务器"
echo "========================================="
# 检查PID文件是否存在
if [ ! -f "$PID_FILE" ]; then
echo "⚠️ 未找到PID文件服务可能未运行"
exit 0
fi
PID=$(cat "$PID_FILE")
# 检查进程是否存在
if ! ps -p "$PID" > /dev/null 2>&1; then
echo "⚠️ 进程不存在 (PID: $PID)"
rm -f "$PID_FILE"
exit 0
fi
# 停止进程
echo "🛑 正在停止服务 (PID: $PID)..."
kill "$PID"
# 等待进程结束
sleep 2
# 检查进程是否已停止
if ps -p "$PID" > /dev/null 2>&1; then
echo "⚠️ 进程未响应,强制终止..."
kill -9 "$PID"
sleep 1
fi
# 清理PID文件
rm -f "$PID_FILE"
echo "✅ 服务已停止"

1
hzhub-ai/.pid Normal file
View File

@@ -0,0 +1 @@
3999223

View File

@@ -58,7 +58,7 @@ spring:
driverClassName: com.mysql.cj.jdbc.Driver
# jdbc 所有参数配置参考 https://lionli.blog.csdn.net/article/details/122018562
# rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题)
url: jdbc:mysql://127.0.0.1:3306/ruoyi_ai?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
url: jdbc:mysql://127.0.0.1:3306/hzhub?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
username: root
password: hzhub123
# agent:

View File

@@ -105,7 +105,7 @@ public class LoginUser implements Serializable {
/**
* 用户昵称
*/
private String nickname;
private String nickName;
/**
* 角色对象

View File

@@ -40,7 +40,6 @@ public class KnowledgeInfoController extends BaseController {
/**
* 查询知识库列表
*/
@SaCheckPermission("system:info:list")
@GetMapping("/list")
public TableDataInfo<KnowledgeInfoVo> list(KnowledgeInfoBo bo, PageQuery pageQuery) {
return knowledgeInfoService.queryPageList(bo, pageQuery);

View File

@@ -2,6 +2,7 @@ package org.hzhub.system.domain.vo;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import org.hzhub.common.core.domain.model.LoginUser;
/**
* 登录验证信息
@@ -51,4 +52,10 @@ public class LoginVo {
*/
private String openid;
/**
* 用户信息(用于前端展示昵称等)
*/
@JsonProperty("userInfo")
private LoginUser userInfo;
}

View File

@@ -154,7 +154,7 @@ public class SysLoginService {
loginUser.setUserId(userId);
loginUser.setDeptId(user.getDeptId());
loginUser.setUsername(user.getUserName());
loginUser.setNickname(user.getNickName());
loginUser.setNickName(user.getNickName());
loginUser.setUserType(user.getUserType());
loginUser.setMenuPermission(permissionService.getMenuPermission(userId));
loginUser.setRolePermission(permissionService.getRolePermission(userId));

View File

@@ -84,6 +84,7 @@ public class PasswordAuthStrategy implements IAuthStrategy {
loginVo.setAccessToken(StpUtil.getTokenValue());
loginVo.setExpireIn(StpUtil.getTokenTimeout());
loginVo.setClientId(client.getClientId());
loginVo.setUserInfo(loginUser);
return loginVo;
}

16
hzhub-ai/logs.sh Executable file
View File

@@ -0,0 +1,16 @@
#!/bin/bash
# HZHub AI 后端服务日志查看脚本
# 功能:查看服务运行日志
PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)"
LOG_FILE="$PROJECT_DIR/logs/backend.log"
if [ ! -f "$LOG_FILE" ]; then
echo "⚠️ 日志文件不存在: $LOG_FILE"
echo "服务可能未运行或未产生日志"
exit 1
fi
echo "查看 hzhub-ai 日志 (Ctrl+C 退出)"
echo "========================================="
tail -f "$LOG_FILE"

18
hzhub-ai/restart.sh Executable file
View File

@@ -0,0 +1,18 @@
#!/bin/bash
# HZHub AI 后端服务重启脚本
# 功能:重启后台运行的 hzhub-ai Spring Boot 服务
PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)"
echo "========================================="
echo "重启 hzhub-ai 后端服务"
echo "========================================="
# 停止服务
"$PROJECT_DIR/stop.sh"
# 等待两秒确保完全停止
sleep 2
# 启动服务
"$PROJECT_DIR/start.sh"

59
hzhub-ai/start.sh Executable file
View File

@@ -0,0 +1,59 @@
#!/bin/bash
# HZHub AI 后端服务启动脚本
# 功能:后台启动 hzhub-ai Spring Boot 服务
# 注意:实际启动的是 hzhub-admin 子模块
PROJECT_NAME="hzhub-ai"
PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)"
PID_FILE="$PROJECT_DIR/.pid"
LOG_FILE="$PROJECT_DIR/logs/backend.log"
# 创建日志目录
mkdir -p "$PROJECT_DIR/logs"
echo "========================================="
echo "启动 $PROJECT_NAME 后端服务"
echo "========================================="
# 检查是否已经在运行
if [ -f "$PID_FILE" ]; then
PID=$(cat "$PID_FILE")
if ps -p "$PID" > /dev/null 2>&1; then
echo "⚠️ 服务已在运行中 (PID: $PID)"
echo "如需重启,请先执行 ./stop.sh"
exit 1
else
echo "清理无效的PID文件"
rm -f "$PID_FILE"
fi
fi
# 启动服务
echo "🚀 启动 Spring Boot 服务..."
cd "$PROJECT_DIR/hzhub-admin"
# 使用nohup后台运行Maven Spring Boot
nohup mvn spring-boot:run -Dspring-boot.run.profiles=dev > "$LOG_FILE" 2>&1 &
PID=$!
# 等待更长时间检查进程是否成功启动Spring Boot启动较慢
echo "⏳ 等待服务启动中..."
sleep 5
if ps -p "$PID" > /dev/null 2>&1; then
echo "$PID" > "$PID_FILE"
echo "✅ 服务启动成功"
echo " PID: $PID"
echo " 日志: $LOG_FILE"
echo " API: http://localhost:6039"
echo ""
echo "查看日志: tail -f $LOG_FILE"
echo "查看状态: ./status.sh"
echo ""
echo "💡 提示: Spring Boot 完整启动需要30-60秒"
echo " 请执行 ./logs.sh 或 tail -f $LOG_FILE 查看启动进度"
else
echo "❌ 服务启动失败"
echo "请查看日志: $LOG_FILE"
exit 1
fi

49
hzhub-ai/status.sh Executable file
View File

@@ -0,0 +1,49 @@
#!/bin/bash
# HZHub AI 后端服务状态检查脚本
# 功能:查看服务运行状态
PROJECT_NAME="hzhub-ai"
PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)"
PID_FILE="$PROJECT_DIR/.pid"
LOG_FILE="$PROJECT_DIR/logs/backend.log"
echo "========================================="
echo " $PROJECT_NAME 服务状态"
echo "========================================="
# 检查PID文件
if [ ! -f "$PID_FILE" ]; then
echo "状态: ⚪ 未运行"
exit 0
fi
PID=$(cat "$PID_FILE")
# 检查进程是否存在
if ps -p "$PID" > /dev/null 2>&1; then
echo "状态: 🟢 运行中"
echo "PID: $PID"
# 显示进程详细信息
ps -p "$PID" -o pid,ppid,cmd,etime
echo ""
echo "日志文件: $LOG_FILE"
echo "API地址: http://localhost:6039"
echo ""
# 检查端口是否在监听
if netstat -tuln 2>/dev/null | grep -q ":6039 "; then
echo "端口状态: ✅ 6039 端口正在监听"
else
echo "端口状态: ⏳ 6039 端口未监听 (可能还在启动中)"
fi
echo ""
echo "最近20行日志:"
echo "---"
tail -n 20 "$LOG_FILE" 2>/dev/null || echo "暂无日志"
else
echo "状态: 🔴 已停止 (PID文件存在但进程不存在)"
rm -f "$PID_FILE"
fi

44
hzhub-ai/stop.sh Executable file
View File

@@ -0,0 +1,44 @@
#!/bin/bash
# HZHub AI 后端服务停止脚本
# 功能:停止后台运行的 hzhub-ai Spring Boot 服务
PROJECT_NAME="hzhub-ai"
PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)"
PID_FILE="$PROJECT_DIR/.pid"
echo "========================================="
echo "停止 $PROJECT_NAME 后端服务"
echo "========================================="
# 检查PID文件是否存在
if [ ! -f "$PID_FILE" ]; then
echo "⚠️ 未找到PID文件服务可能未运行"
exit 0
fi
PID=$(cat "$PID_FILE")
# 检查进程是否存在
if ! ps -p "$PID" > /dev/null 2>&1; then
echo "⚠️ 进程不存在 (PID: $PID)"
rm -f "$PID_FILE"
exit 0
fi
# 停止进程
echo "🛑 正在停止服务 (PID: $PID)..."
kill "$PID"
# 等待进程结束
sleep 3
# 检查进程是否已停止
if ps -p "$PID" > /dev/null 2>&1; then
echo "⚠️ 进程未响应,强制终止..."
kill -9 "$PID"
sleep 2
fi
# 清理PID文件
rm -f "$PID_FILE"
echo "✅ 服务已停止"

View File

@@ -84,7 +84,7 @@ services:
- "6039:6039"
environment:
SPRING_PROFILES_ACTIVE: dev
SPRING_DATASOURCE_DYNAMIC_DATASOURCE_MASTER_URL: jdbc:mysql://hzhub-mysql:3306/ruoyi_ai?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
SPRING_DATASOURCE_DYNAMIC_DATASOURCE_MASTER_URL: jdbc:mysql://hzhub-mysql:3306/hzhub?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
SPRING_DATASOURCE_DYNAMIC_DATASOURCE_MASTER_USERNAME: root
SPRING_DATASOURCE_DYNAMIC_DATASOURCE_MASTER_PASSWORD: hzhub123
SPRING_DATA_REDIS_HOST: hzhub-redis
@@ -127,12 +127,12 @@ services:
networks:
- hzhub-network
# hzhub-portal-company (公司门户)
hzhub-portal-company:
# hzhub-portal-employee (员工门户)
hzhub-portal-employee:
build:
context: ../hzhub-portal-company
context: ../hzhub-portal-employee
dockerfile: Dockerfile
container_name: hzhub-portal-company
container_name: hzhub-portal-employee
ports:
- "5137:5137"
environment:

View File

@@ -1,150 +0,0 @@
# HZHub-AI 用户端
<div align="center">
<img src="https://github.com/ageerle/hzhub-ai/raw/main/docs/image/logo.png" alt="HZHub AI Logo" width="120" height="120">
### 企业级AI助手平台 - 用户前端
*HZHub-AI 的用户前端,提供 AI 对话、智能体交互、知识库问答等功能*
**[在线体验](https://web.pandarobot.chat)** | **[后端服务](https://github.com/ageerle/hzhub-ai)** | **[管理后台](https://github.com/ageerle/hzhub-admin)**
</div>
## 技术栈
- **框架**: Vue 3 + TypeScript
- **UI组件**: Ant Design Vue
- **状态管理**: Pinia
- **构建工具**: Vite
## Docker 部署
本用户端支持两种 Docker 部署方式:
### 方式一:一键启动所有服务(推荐)
使用 `docker-compose-all.yaml` 可以一键启动所有服务(包括后端、管理端、用户端及依赖服务):
```bash
# 克隆后端仓库
git clone https://github.com/ageerle/hzhub-ai.git
cd hzhub-ai
# 启动所有服务(从镜像仓库拉取预构建镜像)
docker-compose -f docker-compose-all.yaml up -d
# 访问用户端
# 地址: http://localhost:25137
# 账号: admin / admin123
```
### 方式二:分步部署(源码编译)
如果您需要从源码构建,请按照以下步骤操作:
#### 第一步:部署后端服务
```bash
# 进入后端项目目录
cd hzhub-ai
# 启动后端服务(源码编译构建)
docker-compose up -d --build
# 等待后端服务启动完成
docker-compose logs -f backend
```
#### 第二步:部署用户端
```bash
# 进入用户端项目目录
cd hzhub-portal
# 构建并启动用户端
docker-compose up -d --build
# 访问用户端
# 地址: http://localhost:5137
```
#### 第三步:部署管理端(可选)
```bash
# 进入管理端项目目录
cd hzhub-admin
# 构建并启动管理端
docker-compose up -d --build
# 访问管理端
# 地址: http://localhost:5666
```
### 服务端口说明
| 服务 | 端口 | 说明 |
|------|------|------|
| 用户端 | 5137 | 用户前端访问地址 |
| 管理端 | 5666 | 管理后台访问地址 |
| 后端服务 | 6039 | 后端 API 服务 |
| MySQL | 23306 | 数据库服务 |
| Redis | 6379 | 缓存服务 |
| Weaviate | 28080 | 向量数据库 |
| MinIO | 9000/9090 | 对象存储 |
### 镜像仓库
所有镜像托管在阿里云容器镜像服务:
```
crpi-31mraxd99y2gqdgr.cn-beijing.personal.cr.aliyuncs.com/ruoyi_ai
```
可用镜像:
- `mysql:v3` - MySQL 数据库(包含初始化 SQL
- `redis:6.2` - Redis 缓存
- `weaviate:1.30.0` - 向量数据库
- `minio:latest` - 对象存储
- `hzhub-ai-backend:latest` - 后端服务
- `hzhub-ai-admin:latest` - 管理端前端
- `hzhub-ai-web:latest` - 用户端前端
## 本地开发
```bash
# 安装依赖
pnpm install
# 启动开发服务器
pnpm dev
# 构建生产版本
pnpm build
```
## 常见问题
**Q: 用户端无法连接后端服务?**
A: 请确保后端服务已启动,并检查环境变量 `UPSTREAM_URL` 配置是否正确。
**Q: 一键启动和分步部署有什么区别?**
A: 一键启动使用预构建的镜像,部署速度快;分步部署从源码编译,适合需要自定义修改的场景。
## 开源协议
本项目采用 **MIT 开源协议**,详情请查看 [license](license) 文件。
---
<div align="center">
**[⭐ 点个Star支持一下](https://github.com/ageerle/hzhub-portal)** • **[Fork 开始贡献](https://github.com/ageerle/hzhub-portal/fork)**
*用 ❤️ 打造,由 HZHub AI 开源社区维护*
</div>

View File

@@ -1,15 +0,0 @@
// 全局默认配置项
// 首页地址[默认]
export const HOME_URL: string = '/chat';
// 默认主题颜色
export const DEFAULT_THEME_COLOR: string = '#2992FF';
// 折叠阈值
export const COLLAPSE_THRESHOLD: number = 600;
// 左侧菜单宽度
export const SIDE_BAR_WIDTH: number = 280;
// 路由白名单地址[本地存在的路由 staticRouter.ts 中]
export const ROUTER_WHITE_LIST: string[] = ['/chat', '/chat/not_login', '/403', '/404'];

View File

@@ -1,70 +0,0 @@
<!-- 纵向布局作为基础布局 -->
<script setup lang="ts">
import { useSafeArea } from '@/hooks/useSafeArea';
import { useWindowWidthObserver } from '@/hooks/useWindowWidthObserver';
import Aside from '@/layouts/components/Aside/index.vue';
import Header from '@/layouts/components/Header/index.vue';
import Main from '@/layouts/components/Main/index.vue';
import { useDesignStore } from '@/stores';
const designStore = useDesignStore();
const isCollapse = computed(() => designStore.isCollapse);
/* 是否移入了安全区 */
useSafeArea({
direction: 'left',
size: 50,
onChange(isInSafeArea) {
// 设置悬停为 true
designStore.isSafeAreaHover = isInSafeArea;
},
enabled: isCollapse, // 折叠才开启监听
});
/** 监听窗口大小变化,折叠侧边栏 */
useWindowWidthObserver();
</script>
<template>
<el-container class="layout-container">
<el-header class="layout-header">
<Header />
</el-header>
<el-container class="layout-container-main">
<Aside />
<el-main class="layout-main">
<!-- 路由页面 -->
<Main />
</el-main>
</el-container>
</el-container>
</template>
<style lang="scss" scoped>
.layout-container {
position: relative;
width: 100%;
height: 100vh;
overflow: hidden;
.layout-header {
padding: 0;
}
.layout-main {
height: 100%;
padding: 0;
}
.layout-container-main {
margin-left: var(--sidebar-left-container-default-width, 0);
transition: margin-left 0.3s ease;
}
}
/** 去除菜单右侧边框 */
.el-menu {
border-right: none;
}
.layout-scrollbar {
width: 100%;
}
</style>

View File

@@ -1,643 +0,0 @@
<!-- Aside 侧边栏 -->
<script setup lang="ts">
import type { ConversationItem } from 'vue-element-plus-x/types/Conversations';
import type { ChatSessionVo } from '@/api/session/types';
import { useRoute, useRouter } from 'vue-router';
import { get_session } from '@/api';
import logo from '@/assets/images/logo.png';
import Collapse from '@/layouts/components/Header/components/Collapse.vue';
import { useDesignStore } from '@/stores';
import { useSessionStore } from '@/stores/modules/session';
const route = useRoute();
const router = useRouter();
const designStore = useDesignStore();
const sessionStore = useSessionStore();
const sessionId = computed(() => route.params?.id);
const conversationsList = computed(() => sessionStore.sessionList);
const loadMoreLoading = computed(() => sessionStore.isLoadingMore);
const active = ref<string | undefined>();
// 固定应用列表
const appList = ref([
{
id: 'ai-chat',
name: 'AI 对话',
icon: 'ChatLineRound',
route: '/chat',
},
{
id: 'ai-image',
name: 'AI 画图',
icon: 'Picture',
route: '/ai-image',
},
{
id: 'ai-video',
name: 'AI 视频',
icon: 'VideoCamera',
route: '/ai-video',
},
{
id: 'ai-ppt',
name: 'AI PPT',
icon: 'Document',
route: '/ai-ppt',
},
]);
const activeApp = ref('ai-chat');
const searchKeyword = ref('');
const activeFooterBtn = ref<'agent' | 'knowledge' | null>(null);
// 切换应用
function handleAppClick(app: typeof appList.value[0]) {
activeApp.value = app.id;
// 这里可以添加路由跳转逻辑
// router.push(app.route);
}
// 智能体中心
function handleAgentCenter() {
activeFooterBtn.value = activeFooterBtn.value === 'agent' ? null : 'agent';
console.log('打开智能体中心');
// router.push('/agent-center');
}
// 知识库管理
function handleKnowledgeBase() {
activeFooterBtn.value = activeFooterBtn.value === 'knowledge' ? null : 'knowledge';
console.log('打开知识库管理');
// router.push('/knowledge-base');
}
onMounted(async () => {
// 默认选中 AI 对话应用
activeApp.value = 'ai-chat';
// 获取会话列表
console.log('[Aside.onMounted] 开始获取会话列表');
await sessionStore.requestSessionList();
console.log('[Aside.onMounted] 获取会话列表完成conversationsList.length:', conversationsList.value.length);
console.log('[Aside.onMounted] conversationsList:', conversationsList.value);
// 高亮最新会话
if (conversationsList.value.length > 0 && sessionId.value) {
console.log('[Aside.onMounted] 获取当前选中会话sessionId:', sessionId.value);
const currentSessionRes = await get_session(`${sessionId.value}`);
// 通过 ID 查询详情,设置当前会话 (因为有分页)
sessionStore.setCurrentSession(currentSessionRes.data);
}
});
watch(
() => sessionStore.currentSession,
(newValue) => {
active.value = newValue ? `${newValue.id}` : undefined;
},
);
// 创建会话
function handleCreatChat() {
// 创建会话, 跳转到默认聊天
sessionStore.createSessionBtn();
}
// 切换会话
function handleChange(item: ConversationItem<ChatSessionVo>) {
sessionStore.setCurrentSession(item);
router.replace({
name: 'chatWithId',
params: {
id: item.id,
},
});
}
// 处理组件触发的加载更多事件
async function handleLoadMore() {
if (!sessionStore.hasMore)
return; // 无更多数据时不加载
await sessionStore.loadMoreSessions();
}
// 右键菜单
function handleMenuCommand(command: string, item: ConversationItem<ChatSessionVo>) {
switch (command) {
case 'delete':
ElMessageBox.confirm('删除后,聊天记录将不可恢复。', '确定删除对话?', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
confirmButtonClass: 'el-button--danger',
cancelButtonClass: 'el-button--info',
roundButton: true,
autofocus: false,
})
.then(async () => {
// 删除会话
await sessionStore.deleteSessions([item.id!]);
// 检查删除的是否为当前选中会话,若是则返回默认页
nextTick(() => {
if (item.id === active.value) {
// 如果删除当前会话,返回到默认页面
sessionStore.createSessionBtn();
}
});
})
.catch(() => {
// 取消删除
});
break;
case 'rename':
ElMessageBox.prompt('', '编辑对话名称', {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputErrorMessage: '请输入对话名称',
confirmButtonClass: 'el-button--primary',
cancelButtonClass: 'el-button--info',
roundButton: true,
inputValue: item.sessionTitle, // 设置默认值
autofocus: false,
inputValidator: (value) => {
if (!value) {
return false;
}
return true;
},
}).then(({ value }) => {
sessionStore
.updateSession({
id: item.id!,
sessionTitle: value,
sessionContent: item.sessionContent,
})
.then(() => {
ElMessage({
type: 'success',
message: '修改成功',
});
nextTick(() => {
// 如果是当前会话,则更新当前选中会话信息
if (sessionStore.currentSession?.id === item.id) {
sessionStore.setCurrentSession({
...item,
sessionTitle: value,
});
}
});
});
});
break;
default:
break;
}
}
</script>
<template>
<div
class="aside-container"
:class="{
'aside-container-suspended': designStore.isSafeAreaHover,
'aside-container-collapse': designStore.isCollapse,
// 折叠且未激活悬停时添加 no-delay 类
'no-delay': designStore.isCollapse && !designStore.hasActivatedHover,
}"
>
<div class="aside-wrapper">
<div v-if="!designStore.isCollapse" class="aside-header">
<div class="flex items-center gap-8px hover:cursor-pointer" @click="handleCreatChat">
<el-image :src="logo" alt="logo" fit="cover" class="logo-img" />
<span class="logo-text max-w-150px text-overflow">HZHub-AI</span>
</div>
<Collapse class="ml-auto" />
</div>
<div class="aside-body">
<!-- 搜索框 -->
<div class="search-wrapper">
<el-input
v-model="searchKeyword"
placeholder="搜索对话"
clearable
class="search-input"
>
<template #prefix>
<el-icon>
<Search />
</el-icon>
</template>
</el-input>
</div>
<!-- 应用入口区域 -->
<div class="app-list-wrapper">
<div class="app-list-title">应用</div>
<div class="app-list">
<div
v-for="app in appList"
:key="app.id"
class="app-item"
:class="{ 'app-item-active': activeApp === app.id }"
@click="handleAppClick(app)"
>
<el-icon class="app-icon">
<component :is="app.icon" />
</el-icon>
<span class="app-name">{{ app.name }}</span>
</div>
</div>
</div>
<!-- 分割线 -->
<div class="divider" />
<div class="aside-content">
<div v-if="conversationsList.length > 0" class="conversations-wrap overflow-hidden">
<Conversations
v-model:active="active"
:items="conversationsList"
:label-max-width="200"
:show-tooltip="true"
:tooltip-offset="60"
show-built-in-menu
groupable
row-key="id"
label-key="sessionTitle"
tooltip-placement="right"
:load-more="handleLoadMore"
:load-more-loading="loadMoreLoading"
:items-style="{
marginLeft: '8px',
userSelect: 'none',
borderRadius: '10px',
padding: '8px 12px',
}"
:items-active-style="{
backgroundColor: '#fff',
boxShadow: '0 1px 2px rgba(0, 0, 0, 0.05)',
color: 'rgba(0, 0, 0, 0.85)',
}"
:items-hover-style="{
backgroundColor: 'rgba(0, 0, 0, 0.04)',
}"
@menu-command="handleMenuCommand"
@change="handleChange"
/>
</div>
<el-empty v-else class="h-full flex-center" description="暂无对话记录" />
</div>
</div>
<!-- 底部悬浮按钮 -->
<div class="aside-footer">
<div class="footer-btn" :class="{ active: activeFooterBtn === 'agent' }" @click="handleAgentCenter">
<el-icon class="footer-btn-icon">
<Avatar />
</el-icon>
<span class="footer-btn-text">智能体中心</span>
</div>
<div class="footer-divider" />
<div class="footer-btn" :class="{ active: activeFooterBtn === 'knowledge' }" @click="handleKnowledgeBase">
<el-icon class="footer-btn-icon">
<FolderOpened />
</el-icon>
<span class="footer-btn-text">知识库管理</span>
</div>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
// 基础样式
.aside-container {
position: absolute;
top: 0;
left: 0;
z-index: 11;
width: var(--sidebar-default-width);
height: 100%;
pointer-events: auto;
background-color: var(--sidebar-background-color);
border-right: 0.5px solid var(--s-color-border-tertiary, rgb(0 0 0 / 8%));
.aside-wrapper {
display: flex;
flex-direction: column;
height: 100%;
// 侧边栏头部样式
.aside-header {
display: flex;
align-items: center;
height: 36px;
margin: 10px 12px 0;
.logo-img {
box-sizing: border-box;
width: 36px;
height: 36px;
padding: 4px;
overflow: hidden;
background-color: #ffffff;
border-radius: 50%;
img {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
}
}
.logo-text {
font-size: 16px;
font-weight: 700;
color: rgb(0 0 0 / 85%);
transform: skewX(-2deg);
}
}
// 侧边栏内容样式
.aside-body {
// 搜索框样式
.search-wrapper {
padding: 16px 12px 12px;
.search-input {
:deep(.el-input__wrapper) {
padding: 8px 12px;
background-color: rgb(0 0 0 / 4%);
border-radius: 10px;
box-shadow: none;
transition: all 0.2s ease;
&:hover {
background-color: rgb(0 0 0 / 6%);
}
&.is-focus {
background-color: #ffffff;
box-shadow: 0 0 0 1px rgb(0 87 255 / 20%);
}
}
:deep(.el-input__inner) {
font-size: 14px;
color: rgb(0 0 0 / 85%);
&::placeholder {
color: rgb(0 0 0 / 45%);
}
}
:deep(.el-input__prefix) {
color: rgb(0 0 0 / 45%);
}
}
}
// 应用列表区域
.app-list-wrapper {
padding: 16px 12px 0;
.app-list-title {
padding-left: 6px;
margin-bottom: 8px;
font-size: 12px;
font-weight: 500;
color: rgb(0 0 0 / 45%);
}
.app-list {
display: flex;
flex-direction: column;
gap: 4px;
.app-item {
display: flex;
gap: 10px;
align-items: center;
padding: 10px 12px;
cursor: pointer;
user-select: none;
border-radius: 10px;
transition: all 0.2s ease;
&:hover {
background-color: rgb(0 0 0 / 4%);
}
&.app-item-active {
color: rgb(0 0 0 / 85%);
background-color: #ffffff;
box-shadow: 0 1px 2px rgb(0 0 0 / 5%);
.app-icon {
color: #0057ff;
}
}
.app-icon {
width: 20px;
height: 20px;
font-size: 20px;
color: rgb(0 0 0 / 45%);
transition: color 0.2s ease;
}
.app-name {
font-size: 14px;
font-weight: 500;
color: rgb(0 0 0 / 85%);
}
}
}
}
// 分割线
.divider {
height: 1px;
margin: 12px;
background-color: rgb(0 0 0 / 6%);
}
.creat-chat-btn-wrapper {
padding: 0 12px;
.creat-chat-btn {
display: flex;
gap: 6px;
align-items: center;
padding: 8px 6px;
margin-top: 16px;
margin-bottom: 6px;
color: #0057ff;
cursor: pointer;
user-select: none;
background-color: rgb(0 87 255 / 6%);
border: 1px solid rgb(0 102 255 / 15%);
border-radius: 12px;
&:hover {
background-color: rgb(0 87 255 / 12%);
}
.creat-chat-text {
font-size: 14px;
font-weight: 700;
line-height: 22px;
}
.add-icon {
width: 24px;
height: 24px;
font-size: 16px;
}
.svg-icon {
height: 24px;
margin-left: auto;
color: rgb(0 87 255 / 30%);
}
}
}
.aside-content {
display: flex;
flex: 1;
flex-direction: column;
height: 100%;
min-height: 0;
// 会话列表高度-基础样式(调整高度以适应应用区域和底部按钮)
.conversations-wrap {
height: calc(100vh - 400px);
.label {
display: flex;
align-items: center;
height: 100%;
}
}
}
}
// 底部悬浮按钮样式
.aside-footer {
position: absolute;
right: 12px;
bottom: 20px;
left: 12px;
z-index: 20;
display: flex;
gap: 0;
align-items: center;
padding: 8px;
background-color: #f3f4f6;
border-radius: 10px;
.footer-btn {
display: flex;
flex: 1;
flex-direction: column;
gap: 4px;
align-items: center;
justify-content: center;
padding: 8px 4px;
cursor: pointer;
user-select: none;
background-color: transparent;
border-radius: 8px;
transition: all 0.2s ease;
&:hover {
background-color: rgb(0 0 0 / 4%);
}
&.active {
background-color: rgb(0 87 255 / 8%);
.footer-btn-icon {
color: #0057ff;
}
.footer-btn-text {
color: #0057ff;
}
}
.footer-btn-icon {
width: 20px;
height: 20px;
font-size: 20px;
color: rgb(0 0 0 / 65%);
transition: color 0.2s ease;
}
.footer-btn-text {
font-size: 11px;
font-weight: 500;
line-height: 1.2;
color: rgb(0 0 0 / 65%);
text-align: center;
transition: color 0.2s ease;
}
}
.footer-divider {
flex-shrink: 0;
width: 1px;
height: 40px;
background-color: rgb(0 0 0 / 10%);
}
}
}
}
// 折叠样式
.aside-container-collapse {
position: absolute;
top: 54px;
z-index: 22;
height: auto;
max-height: calc(100% - 110px);
padding-bottom: 12px;
overflow: hidden;
/* 禁用悬停事件 */
pointer-events: none;
border: 1px solid rgb(0 0 0 / 8%);
border-radius: 15px;
box-shadow:
0 10px 20px 0 rgb(0 0 0 / 10%),
0 0 1px 0 rgb(0 0 0 / 15%);
opacity: 0;
// 指定样式过渡
// 向左偏移一个宽度
transform: translateX(-100%);
transition: opacity 0.3s ease 0.3s, transform 0.3s ease 0.3s;
/* 新增:未激活悬停时覆盖延迟 */
&.no-delay {
transition-delay: 0s, 0s;
}
}
// 悬停样式
.aside-container-collapse:hover,
.aside-container-collapse.aside-container-suspended {
height: auto;
max-height: calc(100% - 110px);
padding-bottom: 12px;
overflow: hidden;
pointer-events: auto;
border: 1px solid rgb(0 0 0 / 8%);
border-radius: 15px;
box-shadow:
0 10px 20px 0 rgb(0 0 0 / 10%),
0 0 1px 0 rgb(0 0 0 / 15%);
// 直接在这里写悬停时的样式(与 aside-container-suspended 一致)
opacity: 1;
// 过渡动画沿用原有设置
transform: translateX(15px);
transition: opacity 0.3s ease 0s, transform 0.3s ease 0s;
// 会话列表高度-悬停样式
.conversations-wrap {
height: calc(100vh - 155px) !important;
}
}
// 样式穿透
:deep() {
// 会话列表背景色
.conversations-list {
background-color: transparent !important;
}
// 群组标题样式 和 侧边栏菜单背景色一致
.conversation-group-title {
padding-left: 12px !important;
font-weight: 700 !important;
color: rgb(0 0 0 / 85%) !important;
background-color: var(--sidebar-background-color) !important;
}
}
</style>

View File

@@ -1,100 +0,0 @@
<!-- Header 头部 -->
<script setup lang="ts">
import { onKeyStroke } from '@vueuse/core';
import { SIDE_BAR_WIDTH } from '@/config/index';
import { useDesignStore, useUserStore } from '@/stores';
import { useSessionStore } from '@/stores/modules/session';
import Avatar from './components/Avatar.vue';
import Collapse from './components/Collapse.vue';
import CreateChat from './components/CreateChat.vue';
import LoginBtn from './components/LoginBtn.vue';
import TitleEditing from './components/TitleEditing.vue';
const userStore = useUserStore();
const designStore = useDesignStore();
const sessionStore = useSessionStore();
const currentSession = computed(() => sessionStore.currentSession);
onMounted(() => {
// 全局设置侧边栏默认宽度 (这个是不变的,一开始就设置)
document.documentElement.style.setProperty(`--sidebar-default-width`, `${SIDE_BAR_WIDTH}px`);
if (designStore.isCollapse) {
document.documentElement.style.setProperty(`--sidebar-left-container-default-width`, ``);
}
else {
document.documentElement.style.setProperty(
`--sidebar-left-container-default-width`,
`${SIDE_BAR_WIDTH}px`,
);
}
});
// 定义 Ctrl+K 的处理函数
function handleCtrlK(event: KeyboardEvent) {
event.preventDefault(); // 防止默认行为
sessionStore.createSessionBtn();
}
// 设置全局的键盘按键监听
onKeyStroke(event => event.ctrlKey && event.key.toLowerCase() === 'k', handleCtrlK, {
passive: false,
});
</script>
<template>
<div class="header-container">
<div class="header-box relative z-10 top-0 left-0 right-0">
<div class="absolute left-0 right-0 top-0 bottom-0 flex items-center flex-row">
<div
class="overflow-hidden flex h-full items-center flex-row flex-1 w-fit flex-shrink-0 min-w-0"
>
<div class="w-full flex items-center flex-row">
<!-- 左边 -->
<div
v-if="designStore.isCollapse"
class="left-box flex h-full items-center pl-20px gap-12px flex-shrink-0 flex-row"
>
<Collapse />
<CreateChat />
<div v-if="currentSession" class="w-0.5px h-30px bg-[rgba(217,217,217)]" />
</div>
<!-- 中间 -->
<div class="middle-box flex-1 min-w-0 ml-12px">
<TitleEditing />
</div>
</div>
</div>
<!-- 右边 -->
<div class="right-box flex h-full items-center pr-20px flex-shrink-0 mr-auto flex-row">
<Avatar v-show="userStore.token" />
<LoginBtn v-show="!userStore.token" />
</div>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.header-container {
display: flex;
flex-shrink: 0;
flex-direction: column;
width: 100%;
height: fit-content;
.header-box {
width: 100%;
width: calc(
100% - var(--sidebar-left-container-default-width, 0px) - var(
--sidebar-right-container-default-width,
0px
)
);
height: var(--header-container-default-heigth);
margin: 0 var(--sidebar-right-container-default-width, 0) 0
var(--sidebar-left-container-default-width, 0);
}
}
</style>

View File

@@ -1,78 +0,0 @@
import type { RouteRecordRaw } from 'vue-router';
import { HOME_URL } from '@/config';
// LayoutRouter[布局路由]
export const layoutRouter: RouteRecordRaw[] = [
{
path: '/',
redirect: HOME_URL,
component: () => import('@/layouts/index.vue'),
children: [
{
path: HOME_URL,
name: 'chat',
component: () => import('@/pages/chat/index.vue'),
meta: {
// title: '通用聊天页面',
isDefaultChat: true,
icon: 'HomeFilled',
// isHide: '1', // 是否在菜单中隐藏[0是1否] 预留
// isKeepAlive: '0', // 是否缓存路由数据[0是1否] 预留
// isFull: '1', // 是否全屏[0是1否] 预留
// enName: "Master Station", // 英文名称 预留
},
},
{
path: '/chat/:id',
name: 'chatWithId',
component: () => import('@/pages/chat/index.vue'),
meta: {
// title: '带 ID 的聊天页面',
isDefaultChat: false,
},
},
],
},
];
// staticRouter[静态路由] 预留
export const staticRouter: RouteRecordRaw[] = [];
// errorRouter (错误页面路由)
export const errorRouter = [
{
path: '/403',
name: '403',
component: () => import('@/pages/error/403.vue'),
meta: {
title: '403页面',
enName: '403 Page', // 英文名称
icon: 'QuestionFilled', // 菜单图标
isHide: '1', // 代表路由在菜单中是否隐藏,是否隐藏[0隐藏1显示]
isLink: '1', // 是否外链[有值则是外链]
isKeepAlive: '0', // 是否缓存路由数据[0是1否]
isFull: '1', // 是否缓存全屏[0是1否]
isAffix: '1', // 是否缓存固定路由[0是1否]
},
},
{
path: '/404',
name: '404',
component: () => import('@/pages/error/404.vue'),
meta: {
title: '404页面',
enName: '404 Page', // 英文名称
icon: 'CircleCloseFilled', // 菜单图标
isHide: '1', // 代表路由在菜单中是否隐藏,是否隐藏[0隐藏1显示]
isLink: '1', // 是否外链[有值则是外链]
isKeepAlive: '0', // 是否缓存路由数据[0是1否]
isFull: '1', // 是否缓存全屏[0是1否]
isAffix: '1', // 是否缓存固定路由[0是1否]
},
},
// 找不到path将跳转404页面
{
path: '/:pathMatch(.*)*',
component: () => import('@/pages/error/404.vue'),
},
];

View File

@@ -1,9 +0,0 @@
@use './media';
@use './btn-style';
@use 'reset-css';
@use './element-plus';
@use './elx';
@use './markdown';
body{
overflow: hidden;
}

View File

@@ -1,24 +0,0 @@
:root {
/* 头部高度 */
--header-container-default-heigth: 56px;
/* 左侧侧边栏背景色 */
--sidebar-background-color: #f3f4f6;
/* 登录弹框变量 */
--login-dialog-width: 738px;
--login-dialog-height: 416px;
--login-dialog-padding: 0px;
--login-dialog-section-padding: 0px;
--login-dialog-border-radius: 24px;
--login-dialog-mode-toggle-color: #409eff;
--login-dialog-logo-background: #ffffff;
--login-dialog-logo-text-color: #191919;
/* 覆盖 element-plus 样式 */
--el-border-radius-base: 12px !important;
--el-messagebox-border-radius: 16px !important;
}

View File

@@ -1,69 +0,0 @@
import type { HookFetchPlugin } from 'hook-fetch';
import { ElMessage } from 'element-plus';
import hookFetch from 'hook-fetch';
import { sseTextDecoderPlugin } from 'hook-fetch/plugins';
import router from '@/routers';
import { useUserStore } from '@/stores';
interface BaseResponse {
code: number;
data: never;
msg: string;
rows: never;
}
export const request = hookFetch.create<BaseResponse, 'data' | 'rows'>({
baseURL: import.meta.env.VITE_API_URL,
headers: {
'Content-Type': 'application/json',
},
plugins: [sseTextDecoderPlugin({ json: true, prefix: 'data:' })],
});
function jwtPlugin(): HookFetchPlugin<BaseResponse> {
const userStore = useUserStore();
return {
name: 'jwt',
beforeRequest: async (config) => {
config.headers = new Headers(config.headers);
config.headers.set('authorization', `Bearer ${userStore.token}`);
config.headers.set('ClientID', import.meta.env.VITE_CLIENT_ID);
return config;
},
afterResponse: async (response) => {
// console.log(response);
if (response.result?.code === 200) {
return response;
}
// 处理403逻辑
if (response.result?.code === 403) {
// 跳转到403页面确保路由已配置
router.replace({
name: '403',
});
ElMessage.error(response.result?.msg);
return Promise.reject(response);
}
// 处理401逻辑
if (response.result?.code === 401) {
// 如果没有权限,退出,且弹框提示登录
userStore.logout();
userStore.openLoginDialog();
}
ElMessage.error(response.result?.msg);
return Promise.reject(response);
},
};
}
request.use(jwtPlugin());
export const post = request.post;
export const get = request.get;
export const put = request.put;
export const del = request.delete;
export default request;

View File

@@ -0,0 +1,4 @@
# 开发环境配置
VITE_API_URL=
VITE_CLIENT_ID=e5cd7e4891bf95d1d19206ce24a7b32e
VITE_WEB_TITLE=Employee Portal 企业员工门户

View File

@@ -0,0 +1 @@
3422048

View File

@@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Project Overview
This is **hzhub-portal**, a Vue 3 AI chat application built with TypeScript, Vite, and Element Plus. It provides a chat interface for AI interactions with support for sessions, models, and various authentication methods.
This is **hzhub-portal-employee**, a Vue 3 Employee Portal application built with TypeScript, Vite, and Element Plus. It provides enterprise employee portal features including workflow approvals, CRM management, BI reports, and AI application integration.
## Commands

View File

@@ -0,0 +1,149 @@
# HZHub 员工门户
<div align="center">
### 企业员工门户系统
*HZHub 企业员工门户提供审批流程、销售CRM、BI报表、AI应用集成等功能*
**[后端服务](https://github.com/hzhub/hzhub-ai)** | **[管理后台](https://github.com/hzhub/hzhub-admin)** | **[经销商门户](https://github.com/hzhub/hzhub-portal-dealer)**
</div>
## 功能特性
- 🔄 **审批流程**: 支持多级审批、流程管理
- 📊 **BI报表**: 实时数据分析、可视化报表
- 🤝 **CRM管理**: 客户管理、销售跟踪
- 🤖 **AI应用**: AI助手、智能问答
- 📱 **企业微信**: 支持企业微信H5集成
## 技术栈
- **框架**: Vue 3 + TypeScript
- **UI组件**: Element Plus
- **状态管理**: Pinia
- **构建工具**: Vite
- **HTTP请求**: hook-fetch (支持SSE流式传输)
## Docker 部署
### 一键启动(推荐)
```bash
cd hzhub-deploy
docker-compose up -d
# 访问员工门户
# 地址: http://localhost:5137
```
### 单独部署
```bash
# 进入项目目录
cd hzhub-portal-employee
# 构建并启动
docker-compose up -d --build
```
### 服务端口
| 服务 | 端口 | 说明 |
|------|------|------|
| 员工门户 | 5137 | 员工前端访问地址 |
| 经销商门户 | 5138 | 经销商前端访问地址 |
| 管理后台 | 5666 | 管理后台访问地址 |
| 后端服务 | 6039 | 后端 API 服务 |
## 本地开发
```bash
# 安装依赖
pnpm install
# 启动开发服务器
pnpm dev
# 类型检查
pnpm build
# 代码检查
pnpm lint
```
## 环境变量配置
项目根目录 `.env.development` 文件:
```bash
VITE_API_URL= # 后端API地址默认通过Vite代理
VITE_CLIENT_ID=e5cd7e4891bf95d1d19206ce24a7b32e
VITE_WEB_TITLE=Employee Portal 企业员工门户
```
## 登录超时配置
员工门户默认登录超时时间为30分钟可通过修改数据库配置调整为1小时或其他时长。
**详细配置说明**: [TIMEOUT_CONFIG.md](./TIMEOUT_CONFIG.md)
**快速修改**:
```bash
# 执行SQL更新超时时间为1小时
mysql -u root -phzhub123 hzhub < /tmp/update_timeout_1hour.sql
```
**注意**: 修改后需要重新登录才能生效。
## 多租户支持
系统支持多租户架构,不同公司实体(集团总部、汇亚公司、恒福公司、玛缇公司)通过登录时选择不同租户进行数据隔离。
### 登录方式
1. **URL参数方式**: `http://localhost:5137/login?tenant=000002`
2. **手动选择方式**: 在登录界面选择公司
## 项目结构
```
hzhub-portal-employee/
├── src/
│ ├── api/ # API模块
│ ├── components/ # 可复用组件
│ ├── layouts/ # 布局组件
│ ├── pages/ # 页面组件
│ ├── routers/ # 路由配置
│ ├── stores/ # Pinia状态管理
│ ├── styles/ # 样式文件
│ └── utils/ # 工具函数
├── public/ # 静态资源
├── .env.development # 开发环境配置
├── Dockerfile # Docker构建文件
├── nginx.conf # Nginx配置
└── package.json # 项目配置
```
## 常见问题
**Q: 如何切换不同公司登录?**
A: 通过URL参数 `?tenant=租户ID` 或在登录页面手动选择公司。
**Q: 如何连接后端服务?**
A: 开发环境通过Vite代理生产环境通过Nginx代理配置 `UPSTREAM_URL`
## 开源协议
本项目采用 **MIT 开源协议**
---
<div align="center">
*用 ❤️ 打造,由 HZHub 团队维护*
</div>

View File

@@ -0,0 +1,150 @@
# 员工门户登录超时配置说明
## 问题需求
修改员工门户(hzhub-portal-employee)的登录超时时间为1小时超时后自动退出。
## 配置原理
### Token超时机制
HZHub使用Sa-Token进行认证管理token超时时间由 `sys_client` 表的 `active_timeout` 字段控制:
| 字段 | 说明 | 单位 | 默认值 |
|------|------|------|--------|
| `active_timeout` | token活跃超时时间 | 秒 | 1800 (30分钟) |
| `timeout` | token固定超时时间 | 秒 | 604800 (7天) |
**区别说明:**
- **active_timeout**: 用户活跃超时用户在指定时间内无操作则token失效
- **timeout**: token绝对超时从登录开始计时无论是否活跃都将在该时间后失效
### 客户端配置
员工门户使用的客户端ID: `e5cd7e4891bf95d1d19206ce24a7b32e`(来自 .env.development
## 修改方案
### 方法1执行SQL更新推荐
执行以下SQL脚本将员工门户的超时时间修改为1小时
```bash
# 连接数据库
mysql -u root -phzhub123
# 执行更新SQL
source /tmp/update_employee_timeout.sql
```
或手动执行SQL
```sql
-- 查询当前配置
SELECT id, client_id, client_key, active_timeout, timeout, status
FROM sys_client
WHERE client_id = 'e5cd7e4891bf95d1d19206ce24a7b32e';
-- 更新为1小时超时
UPDATE sys_client
SET active_timeout = 3600
WHERE client_id = 'e5cd7e4891bf95d1d19206ce24a7b32e';
-- 验证结果
SELECT id, client_id, client_key, active_timeout, timeout, status
FROM sys_client
WHERE client_id = 'e5cd7e4891bf95d1d19206ce24a7b32e';
```
### 方法2通过管理后台修改
1. 登录管理后台: http://localhost:5666
2. 进入 "系统管理" -> "客户端管理"
3. 找到 `client_id = e5cd7e4891bf95d1d19206ce24a7b32e` 的记录
4. 编辑该记录,将 `活跃超时时间` 修改为 `3600`
5. 保存
## 超时时间建议
根据不同场景,建议的超时时间配置:
| 场景 | active_timeout | timeout | 说明 |
|------|----------------|---------|------|
| 内部员工 | 3600 (1小时) | 604800 (7天) | 平衡安全性和便利性 |
| 移动端APP | 7200 (2小时) | 604800 (7天) | 移动端使用频率高 |
| 公共终端 | 1800 (30分钟) | 86400 (1天) | 安全性优先 |
| 超级管理员 | 1800 (30分钟) | 86400 (1天) | 高安全性要求 |
## 验证配置生效
修改后需要**重新登录**才能生效:
1. 清除浏览器缓存或使用隐身模式
2. 访问员工门户登录页面
3. 登录后等待1小时不操作
4. 再次访问应自动跳转到登录页面
## 技术实现
### 后端实现
登录时通过 `SaLoginParameter.setActiveTimeout()` 设置:
```java
// PasswordAuthStrategy.java
SaLoginParameter model = new SaLoginParameter();
model.setActiveTimeout(client.getActiveTimeout()); // 从sys_client表读取
LoginHelper.login(loginUser, model);
```
### 前端响应
前端通过以下方式检测token过期
1. **API响应401**: 拦截器检测到401状态码自动跳转登录页
2. **路由守卫**: 访问页面时检查token是否有效
3. **本地存储**: Pinia持久化存储token过期后自动清除
## 相关配置文件
- **后端配置**: `/data/hzhub/hzhub-ai/hzhub-common/hzhub-common-satoken/src/main/resources/common-satoken.yml`
- `dynamic-active-timeout: true` - 允许动态设置token有效期
- **前端配置**: `/data/hzhub/hzhub-portal-employee/.env.development`
- `VITE_CLIENT_ID=e5cd7e4891bf95d1d19206ce24a7b32e` - 客户端ID
- **数据库表**: `sys_client`
- `active_timeout` - 活跃超时时间(秒)
- `timeout` - 固定超时时间(秒)
## 常见问题
### Q: 修改后没有生效?
A: 需要**重新登录**。已登录的token不会立即失效只有新登录的token才会使用新的超时时间。
### Q: 如何测试超时是否正确?
A:
1. 登录系统
2. 等待设定的超时时间如1小时
3. 不进行任何操作
4. 刷新页面或访问API应自动跳转到登录页
### Q: 可以设置为永不过期吗?
A: 不建议。为了安全考虑,应该设置合理的超时时间。如需延长,可以设置为更大的值:
```sql
-- 设置为8小时
UPDATE sys_client SET active_timeout = 28800 WHERE client_id = 'e5cd7e4891bf95d1d19206ce24a7b32e';
-- 设置为12小时
UPDATE sys_client SET active_timeout = 43200 WHERE client_id = 'e5cd7e4891bf95d1d19206ce24a7b32e';
```
## 相关文档
- [Sa-Token官方文档](https://sa-token.cc/)
- [HZHub认证流程](../docs/architecture/README.md)
- [服务管理脚本](./SERVICE_MANAGEMENT.md)

View File

@@ -0,0 +1,169 @@
# 动态公司名称配置说明
## 修改内容
已将员工门户的左上角标题从固定的"HZHub 企业员工门户"改为动态显示"登录用户对应公司名称 + 企业员工门户"。
## 修改文件
### 1. Aside侧边栏组件
**文件**: `src/layouts/components/Aside/index.vue`
**修改内容**:
- 引入 `useUserStore` 获取用户信息
- 创建 `tenantNameMap` 映射租户ID到公司名称
- 使用 `computed` 计算属性动态获取公司名称
- 将固定的 "HZHub" 改为动态的 `{{ companyName }}`
**关键代码**:
```typescript
import { useUserStore } from '@/stores';
import { computed } from 'vue';
const userStore = useUserStore();
// 租户ID到公司名称的映射
const tenantNameMap: Record<string, string> = {
'000001': '集团总部',
'000002': '汇亚公司',
'000003': '恒福公司',
'000004': '玛缇公司',
};
// 计算当前用户的公司名称
const companyName = computed(() => {
const tenantId = userStore.userInfo?.tenantId;
return tenantId ? tenantNameMap[tenantId] || 'HZHub' : 'HZHub';
});
```
### 2. 登录页面
**文件**: `src/pages/login/index.vue`
**修改内容**:
- 从URL参数获取 `tenant` 参数
- 使用相同的 `tenantNameMap` 映射
- 根据URL参数动态显示公司名称
**关键代码**:
```typescript
import { useRoute } from 'vue-router';
import { computed } from 'vue';
const route = useRoute();
// 从URL参数获取租户ID并映射到公司名称
const companyName = computed(() => {
const tenantId = route.query.tenant as string;
return tenantId ? tenantNameMap[tenantId] || 'HZHub' : 'HZHub';
});
```
## 公司名称映射关系
| 租户ID | 公司名称 | 登录URL示例 |
|--------|----------|-------------|
| 000001 | 集团总部 | http://localhost:5137/login?tenant=000001 |
| 000002 | 汇亚公司 | http://localhost:5137/login?tenant=000002 |
| 000003 | 恒福公司 | http://localhost:5137/login?tenant=000003 |
| 000004 | 玛缇公司 | http://localhost:5137/login?tenant=000004 |
## 显示效果
### 登录页面
- **集团总部用户**: 左上角显示 "集团总部 企业员工门户"
- **汇亚公司用户**: 左上角显示 "汇亚公司 企业员工门户"
- **恒福公司用户**: 左上角显示 "恒福公司 企业员工门户"
- **玛缇公司用户**: 左上角显示 "玛缇公司 企业员工门户"
### 主界面侧边栏
登录后,侧边栏顶部会显示对应公司的名称:
```
[公司名称]
企业员工门户
```
## 扩展性
### 添加新公司
如果需要添加新的公司,只需在两个组件的 `tenantNameMap` 中添加映射:
```typescript
const tenantNameMap: Record<string, string> = {
'000001': '集团总部',
'000002': '汇亚公司',
'000003': '恒福公司',
'000004': '玛缇公司',
'000005': '新公司名称', // 添加新公司
};
```
### 从后端获取公司名称
如果需要从后端动态获取公司名称,可以:
1. **修改 LoginUser 类型**,添加 `companyName` 字段:
```typescript
export interface LoginUser {
// ... 现有字段
companyName?: string; // 新增公司名称字段
}
```
2. **修改 Aside 组件**,直接使用 `companyName`
```typescript
const companyName = computed(() => {
return userStore.userInfo?.companyName || 'HZHub';
});
```
3. **后端接口**:在登录接口返回用户信息时,包含 `companyName` 字段
## 测试验证
### 测试步骤
1. **登录页面测试**
- 访问 `http://localhost:5137/login?tenant=000002`
- 检查登录页面左上角是否显示 "汇亚公司 企业员工门户"
2. **主界面测试**
- 使用不同公司的账号登录
- 检查侧边栏顶部是否显示对应公司名称
3. **切换租户测试**
- 登录汇亚公司账号
- 退出登录
- 用恒福公司账号登录
- 检查标题是否正确切换
### 预期结果
- ✅ 登录页面根据URL参数显示对应公司名称
- ✅ 主界面侧边栏显示登录用户的公司名称
- ✅ 切换不同公司登录时,标题正确更新
- ✅ 未识别的租户ID显示为默认值 "HZHub"
## 注意事项
1. **租户ID一致性**:确保 `tenantId` 与数据库 `sys_tenant` 表中的 `tenant_id` 字段一致
2. **映射完整性**所有可能的租户ID都应该在 `tenantNameMap` 中有对应映射
3. **默认值**未识别的租户ID会显示默认值 "HZHub"
4. **性能优化**:使用 `computed` 计算属性,避免不必要的重复计算
## 相关文件
- `src/layouts/components/Aside/index.vue` - 侧边栏组件
- `src/pages/login/index.vue` - 登录页面
- `src/stores/modules/user.ts` - 用户状态管理
- `src/api/auth/types.ts` - 用户类型定义
## 后续优化建议
1. **统一管理映射关系**:将 `tenantNameMap` 提取到统一的配置文件
2. **支持多语言**:添加国际化支持,显示不同语言的公司名称
3. **动态获取**从后端API动态获取公司名称避免硬编码
4. **Logo定制**不同公司使用不同的Logo图标

View File

@@ -0,0 +1,198 @@
# 个人中心功能说明
## 功能概述
已将 `hzhub-admin` 中的个人中心功能移植到 `hzhub-portal-employee`,并进行了以下修改:
1. **移除收藏夹菜单项**
2. **将"设置"改为"个人中心"**
3. **移植个人中心页面和功能**
## 主要功能
### 1. 个人信息展示
- 用户头像(支持上传更换)
- 用户昵称、账号
- 手机号码、邮箱
- 所属部门和岗位
- 角色信息
- 上次登录时间
### 2. 基本设置
- 修改昵称
- 修改邮箱
- 修改性别
- 修改手机号
### 3. 安全设置
- 修改密码
- 密码强度验证
- 确认密码校验
## 技术实现
### UI框架适配
由于 `hzhub-admin` 使用 **Ant Design Vue**,而 `hzhub-portal-employee` 使用 **Element Plus**,进行了以下适配:
| Ant Design Vue | Element Plus | 说明 |
|----------------|--------------|------|
| `<a-card>` | `<el-card>` | 卡片容器 |
| `<a-descriptions>` | `<el-descriptions>` | 描述列表 |
| `<a-tabs>` | `<el-tabs>` | 标签页 |
| `<a-form>` | `<el-form>` | 表单 |
| `<a-upload>` | `<el-upload>` | 文件上传 |
### 文件结构
```
src/
├── api/
│ └── profile/
│ ├── index.ts # API接口
│ └── types.ts # 类型定义
├── pages/
│ └── profile/
│ ├── index.vue # 个人中心主页面
│ └── components/
│ ├── BasicSetting.vue # 基本设置组件
│ └── SecuritySetting.vue # 安全设置组件
└── layouts/
└── components/
└── Header/
└── UserDropdown.vue # 用户下拉菜单(已修改)
```
## 使用说明
### 访问个人中心
1. **通过下拉菜单**:点击右上角用户头像 → 选择"个人中心"
2. **直接访问**:访问 `/profile` 路由
### 修改个人信息
1. 进入个人中心页面
2. 在"基本设置"标签页修改信息
3. 点击"保存修改"按钮
### 修改密码
1. 进入个人中心页面
2. 切换到"安全设置"标签页
3. 输入当前密码和新密码
4. 点击"修改密码"按钮
5. 修改成功后需要重新登录
### 更换头像
1. 点击头像区域的"更换头像"
2. 选择图片文件(支持 JPG、PNG 格式,最大 2MB
3. 上传成功后自动更新显示
## API接口
### 获取用户信息
```typescript
GET /system/user/profile
Response: UserProfile
```
### 更新用户信息
```typescript
PUT /system/user/profile
Request: {
userId: number
nickName: string
email: string
phonenumber: string
sex: string
}
```
### 修改密码
```typescript
PUT /system/user/profile/updatePwd
Request: {
oldPassword: string
newPassword: string
}
```
### 上传头像
```typescript
POST /system/user/profile/avatar
Request: FormData (avatarfile: File)
```
## 注意事项
1. **头像上传限制**
- 仅支持图片格式JPG、PNG等
- 文件大小不超过 2MB
2. **密码修改**
- 新密码长度 6-20 个字符
- 需要输入确认密码
- 修改成功后需要重新登录
3. **表单验证**
- 邮箱格式验证
- 手机号格式验证(中国大陆手机号)
- 必填项验证
4. **实时更新**
- 修改个人信息后,右上角用户信息会同步更新
- 头像更新后会立即刷新显示
## 与 hzhub-admin 的差异
### 保留的功能
- ✅ 个人信息展示
- ✅ 基本设置(修改昵称、邮箱、性别、手机号)
- ✅ 安全设置(修改密码)
- ✅ 头像上传
### 简化的功能
- ❌ 移除"账号绑定"功能(暂不需要)
- ❌ 移除"在线设备"功能(暂不需要)
- ⚠️ 简化了表单验证使用Element Plus原生验证
## 后续优化建议
1. **头像裁剪**:添加图片裁剪功能
2. **更多安全选项**
- 两步验证
- 登录日志查看
3. **个性化设置**
- 主题色切换
- 字体大小调整
4. **国际化**:支持多语言显示
## 测试建议
### 功能测试
1. **个人信息修改**
- 修改昵称并保存
- 验证右上角显示是否更新
2. **头像上传**
- 上传符合要求的图片
- 上传超大文件(应失败)
- 上传非图片文件(应失败)
3. **密码修改**
- 输入错误旧密码(应失败)
- 两次新密码不一致(应失败)
- 正确修改密码并验证重新登录
### 兼容性测试
- Chrome、Firefox、Safari 浏览器测试
- 响应式布局测试(桌面端、平板、移动端)

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 个标签页)
- 固定标签页与普通标签页混排测试

View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2026 ruoyi-ai
Copyright (c) 2026 hzhub
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

16
hzhub-portal-employee/logs.sh Executable file
View File

@@ -0,0 +1,16 @@
#!/bin/bash
# HZHub Portal Employee 前端项目日志查看脚本
# 功能:查看服务运行日志
PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)"
LOG_FILE="$PROJECT_DIR/logs/dev.log"
if [ ! -f "$LOG_FILE" ]; then
echo "⚠️ 日志文件不存在: $LOG_FILE"
echo "服务可能未运行或未产生日志"
exit 1
fi
echo "查看 hzhub-portal-employee 日志 (Ctrl+C 退出)"
echo "========================================="
tail -f "$LOG_FILE"

View File

@@ -1,22 +1,22 @@
{
"name": "hzhub-portal",
"name": "hzhub-portal-employee",
"type": "module",
"version": "0.0.0",
"private": true,
"description": "hzhub-portal open-source PC AI template",
"description": "HZHub Employee Portal - 企业员工门户系统",
"author": {
"name": "HeJiaYue520",
"email": "2834007710@qq.com",
"url": "https://github.com/HeJiaYue520/HeJiaYue520"
"name": "HZHub Team",
"email": "hzhub@example.com",
"url": "https://github.com/hzhub"
},
"license": "MIT",
"homepage": "https://github.com/element-plus-x/ruoyi-element-ai",
"homepage": "https://github.com/hzhub/hzhub-portal-employee",
"repository": {
"type": "git",
"url": "git@github.com:element-plus-x/ruoyi-element-ai.git"
"url": "git@github.com:hzhub/hzhub-portal-employee.git"
},
"bugs": {
"url": "https://github.com/element-plus-x/ruoyi-element-ai/issues"
"url": "https://github.com/hzhub/hzhub-portal-employee/issues"
},
"scripts": {
"dev": "vite",

View File

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -0,0 +1,18 @@
#!/bin/bash
# HZHub Portal Employee 前端项目重启脚本
# 功能:重启后台运行的员工门户前端开发服务器
PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)"
echo "========================================="
echo "重启 hzhub-portal-employee 开发服务器"
echo "========================================="
# 停止服务
"$PROJECT_DIR/stop.sh"
# 等待一秒
sleep 1
# 启动服务
"$PROJECT_DIR/start.sh"

View File

@@ -0,0 +1,40 @@
import type { UserProfile, UpdatePasswordParam } from './types';
import { get, put, post } from '@/utils/request';
/**
* 用户个人主页信息
* @returns userInformation
*/
export function userProfile() {
return get<UserProfile>('/system/user/profile').json();
}
/**
* 更新用户个人主页信息
* @param data
* @returns void
*/
export function userProfileUpdate(data: any) {
return put<void>('/system/user/profile', { body: data }).json();
}
/**
* 用户修改密码
* @param data
* @returns void
*/
export function userUpdatePassword(data: UpdatePasswordParam) {
return put<void>('/system/user/profile/updatePwd', { body: data }).json();
}
/**
* 用户更新个人头像
* @param file 文件
* @returns void
*/
export function userUpdateAvatar(file: File) {
const formData = new FormData();
formData.append('avatarfile', file);
return post<void>('/system/user/profile/avatar', { body: formData }).json();
}

View File

@@ -0,0 +1,69 @@
export interface Dept {
deptId: number;
parentId: number;
parentName?: any;
ancestors: string;
deptName: string;
orderNum: number;
leader: string;
phone?: any;
email: string;
status: string;
createTime?: any;
}
export interface Role {
roleId: number;
roleName: string;
roleKey: string;
roleSort: number;
dataScope: string;
menuCheckStrictly?: any;
deptCheckStrictly?: any;
status: string;
remark: string;
createTime?: any;
flag: boolean;
superAdmin: boolean;
}
export interface User {
userId: number;
tenantId: string;
deptId: number;
userName: string;
nickName: string;
userType: string;
email: string;
phonenumber: string;
sex: string;
avatar: string;
status: string;
loginIp: string;
loginDate: string;
remark: string;
createTime: string;
dept: Dept;
roles: Role[];
roleIds?: string[];
postIds?: string[];
roleId: number;
deptName: string;
}
/**
* @description 用户个人主页信息
* @param user 用户信息
* @param roleGroup 角色名称
* @param postGroup 岗位名称
*/
export interface UserProfile {
user: User;
roleGroup: string;
postGroup: string;
}
export interface UpdatePasswordParam {
oldPassword: string;
newPassword: string;
}

View File

Before

Width:  |  Height:  |  Size: 281 B

After

Width:  |  Height:  |  Size: 281 B

View File

Before

Width:  |  Height:  |  Size: 310 B

After

Width:  |  Height:  |  Size: 310 B

View File

Before

Width:  |  Height:  |  Size: 256 B

After

Width:  |  Height:  |  Size: 256 B

View File

Before

Width:  |  Height:  |  Size: 275 B

After

Width:  |  Height:  |  Size: 275 B

View File

Before

Width:  |  Height:  |  Size: 364 B

After

Width:  |  Height:  |  Size: 364 B

View File

Before

Width:  |  Height:  |  Size: 434 B

After

Width:  |  Height:  |  Size: 434 B

View File

Before

Width:  |  Height:  |  Size: 236 B

After

Width:  |  Height:  |  Size: 236 B

View File

Before

Width:  |  Height:  |  Size: 271 B

After

Width:  |  Height:  |  Size: 271 B

Some files were not shown because too many files have changed in this diff Show More