# KPL 考培练系统 功能迭代更新日志

**日期**: 2026-01-29  
**版本**: v1.5.0

---

## 一、奖章条件优化

### 修复内容
- 修复 `badge_service.py` 中统计查询的 SQL 语法问题
- 将非标准 `func.if_` 替换为 SQLAlchemy 标准 `case` 语句
- 优化考试统计逻辑：通过数、满分数、优秀数分开查询
- 添加 `func.coalesce` 处理空值

### 新增功能
- `check_badges_by_category()` - 按类别检查奖章
- `check_exam_badges()` - 考试后触发
- `check_practice_badges()` - 练习后触发
- `check_streak_badges()` - 签到后触发
- `check_level_badges()` - 等级变化后触发

### 文件变更
- `backend/app/services/badge_service.py`

---

## 二、移动端适配

### 适配页面
| 页面 | 文件 | 适配要点 |
|------|------|----------|
| 登录页 | `views/login/index.vue` | 表单全宽、背景动画隐藏、钉钉安全区域 |
| 课程中心 | `views/trainee/course-center.vue` | 单列卡片、分类横向滚动、操作按钮网格化 |
| 课程详情 | `views/trainee/course-detail.vue` | 侧边栏折叠、视频自适应、工具栏响应式 |
| 考试页面 | `views/exam/practice.vue` | 选项垂直排列、按钮放大、弹窗全屏 |
| 成长路径 | `views/trainee/growth-path.vue` | 雷达图缩放、卡片堆叠、统计区折叠 |
| 排行榜 | `views/trainee/leaderboard.vue` | 列表简化、签到按钮全宽 |

### 技术方案
- 使用 `@media (max-width: 768px)` 和 `@media (max-width: 480px)` 断点
- 钉钉环境使用 `env(safe-area-inset-*)` 适配安全区域

---

## 三、证书系统

### 数据库设计
```sql
-- 证书模板表
CREATE TABLE certificate_templates (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(100) NOT NULL,
    type ENUM('course', 'exam', 'achievement') NOT NULL,
    background_url VARCHAR(500),
    template_html TEXT,
    template_style TEXT,
    is_active BOOLEAN DEFAULT TRUE,
    ...
);

-- 用户证书表
CREATE TABLE user_certificates (
    id INT PRIMARY KEY AUTO_INCREMENT,
    user_id INT NOT NULL,
    template_id INT NOT NULL,
    certificate_no VARCHAR(50) UNIQUE NOT NULL,
    title VARCHAR(200) NOT NULL,
    ...
);
```

### 后端实现
- **模型**: `CertificateTemplate`, `UserCertificate`, `CertificateType`
- **服务**: `CertificateService`
  - `issue_course_certificate()` - 颁发课程证书
  - `issue_exam_certificate()` - 颁发考试证书
  - `issue_achievement_certificate()` - 颁发成就证书
  - `generate_certificate_image()` - 生成分享图片
  - `get_certificate_by_no()` - 验证证书

### API 端点
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/certificates/me` | 我的证书列表 |
| GET | `/certificates/{id}` | 证书详情 |
| GET | `/certificates/{id}/image` | 获取分享图片 |
| GET | `/certificates/{id}/download` | 下载证书 |
| GET | `/certificates/verify/{no}` | 验证证书（无需登录） |
| POST | `/certificates/issue/course` | 颁发课程证书 |
| POST | `/certificates/issue/exam` | 颁发考试证书 |

### 前端实现
- **API**: `frontend/src/api/certificate.ts`
- **页面**: `frontend/src/views/trainee/my-certificates.vue`
- **功能**: 证书列表、分类筛选、预览、分享、下载

### 文件变更
- `backend/migrations/add_certificate_system.sql` (新增)
- `backend/app/models/certificate.py` (新增)
- `backend/app/services/certificate_service.py` (新增)
- `backend/app/api/v1/endpoints/certificate.py` (新增)
- `backend/app/models/__init__.py` (修改)
- `backend/app/api/v1/__init__.py` (修改)
- `frontend/src/api/certificate.ts` (新增)
- `frontend/src/views/trainee/my-certificates.vue` (新增)
- `frontend/src/router/index.ts` (修改)

---

## 四、数据大屏

### 数据指标
| 类别 | 指标 |
|------|------|
| 概览 | 总学员数、今日活跃、周活跃、月活跃、总学习时长、签到率 |
| 考试 | 总次数、通过率、平均分、满分人数 |
| 部门 | 成员数、通过率、平均学习时长、平均等级 |
| 趋势 | 近7天活跃用户、学习时长、考试次数 |
| 分布 | 1-10级用户数量分布 |
| 动态 | 最新学习活动实时滚动 |

### 后端实现
- **服务**: `DashboardService`
  - `get_enterprise_overview()` - 企业级概览
  - `get_department_comparison()` - 部门对比
  - `get_learning_trend()` - 学习趋势
  - `get_level_distribution()` - 等级分布
  - `get_realtime_activities()` - 实时动态
  - `get_team_dashboard()` - 团队级数据
  - `get_course_ranking()` - 课程热度排行

### API 端点
| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/dashboard/enterprise/overview` | 企业概览 |
| GET | `/dashboard/enterprise/departments` | 部门对比 |
| GET | `/dashboard/enterprise/trend` | 学习趋势 |
| GET | `/dashboard/enterprise/level-distribution` | 等级分布 |
| GET | `/dashboard/enterprise/activities` | 实时动态 |
| GET | `/dashboard/enterprise/course-ranking` | 课程排行 |
| GET | `/dashboard/team` | 团队数据 |
| GET | `/dashboard/all` | 完整数据(一次性) |

### 前端实现
- **API**: `frontend/src/api/dashboard.ts`
- **页面**: `frontend/src/views/admin/data-dashboard.vue`
- **图表**: ECharts (横向柱状图、折线图、仪表盘、饼图)
- **功能**: 全屏模式、5分钟自动刷新、响应式布局

### 文件变更
- `backend/app/services/dashboard_service.py` (新增)
- `backend/app/api/v1/endpoints/dashboard.py` (新增)
- `backend/app/api/v1/__init__.py` (修改)
- `frontend/src/api/dashboard.ts` (新增)
- `frontend/src/views/admin/data-dashboard.vue` (新增)
- `frontend/src/router/index.ts` (修改)

---

## 部署说明

### 数据库迁移
需执行以下 SQL 脚本：
```bash
# 证书系统迁移
mysql -u root -p kaopeilian < backend/migrations/add_certificate_system.sql
```

### 依赖安装
后端新增依赖（用于证书图片生成）：
```bash
pip install Pillow qrcode
```

### 路由变更
新增前端路由：
- `/trainee/my-certificates` - 我的证书
- `/manager/data-dashboard` - 数据大屏

---

## 五、错误提示优化（下午更新）

### 课程名重复错误优化
**问题**：创建课程时名称重复返回 409 错误，前端提示不明确

**修复内容**：
1. **后端** `course_service.py`：课程名重复时返回 `existing_id` 和 `existing_name`
2. **前端** `edit-course.vue`：检测 409 错误后弹出确认框
   - "查看已有课程" → 跳转到已存在的课程
   - "修改名称" → 留在当前页面

### 通用错误处理增强
- `errorHandler.ts` 新增 409 冲突错误处理
- 新增工具函数：`isConflictError()`, `getConflictDetail()`, `getConflictMessage()`

### 其他页面错误提示优化
- `position-management.vue`：岗位创建/编辑错误提取详细信息
- `user-management.vue`：用户编辑错误提取详细信息

### 文件变更
- `backend/app/services/course_service.py` (修改)
- `frontend/src/utils/errorHandler.ts` (修改)
- `frontend/src/views/manager/edit-course.vue` (修改)
- `frontend/src/views/admin/position-management.vue` (修改)
- `frontend/src/views/admin/user-management.vue` (修改)

---

## 六、部署问题修复（下午更新）

### 后端导入路径修复
1. `certificate.py` 模型：`from app.core.database import Base` → `from app.models.base import Base`
2. `certificate.py` API：`from app.core.database import get_db` → `from app.core.deps import get_db`
3. `dashboard.py` API：同上
4. 合并 `get_current_user` 导入到 `app.core.deps`

### 依赖安装
```bash
docker exec kpl-backend-dev pip install Pillow qrcode
```

### 前端构建同步问题
- 问题：构建输出到 `/root/aiedu/frontend/dist/`，但容器挂载的是 `/root/aiedu/dist-test/`
- 解决：构建后需手动同步 `cp -r /root/aiedu/frontend/dist/* /root/aiedu/dist-test/`

---

## 七、钉钉免密登录问题修复（下午更新）

### 问题现象
- 钉钉环境打开应用后显示"没有访问此页面的权限"
- 后端日志显示登录实际成功

### 问题原因
登录成功后读取 URL 中的 `redirect` 参数跳转，但该参数指向用户无权限的页面（如 /admin/*）

### 修复内容
`login/index.vue`：登录成功后检查 redirect 目标是否有权限
```javascript
// 检查 redirect 目标是否有权限访问
if ((redirect.startsWith('/admin') && userRole !== 'admin') ||
    (redirect.startsWith('/manager') && !['admin', 'manager'].includes(userRole))) {
  redirect = defaultRoute  // 改为跳转到默认页面
}
```

### 调试工具
- 钉钉环境自动启用 vConsole（`main.ts` 中根据 UA 判断）
- 依赖：`npm install vconsole`

---

## 待办事项

- [ ] 证书 PDF 生成（需安装 weasyprint）
- [ ] 课程完成进度追踪（user_course_progress 表）
- [ ] 数据大屏数据缓存优化
- [ ] 钉钉环境下底部导航适配
- [ ] 移除 vConsole 调试代码（问题确认解决后）
