更新: API文档和数据库文档,优化课程和用户相关功能

This commit is contained in:
huertian 2025-01-02 14:06:20 +08:00
parent 5babb8f930
commit 53e5b5cd85
17 changed files with 610 additions and 558 deletions

View File

@ -56,11 +56,12 @@
### 课程任务相关字段 ### 课程任务相关字段
1. **进度状态 (progressStatus)** 1. **进度状态 (progressStatus)**
- 0: 脚本上传 - 0: 未开始
- 1: 脚本确认 - 1: 脚本制作
- 2: 视频拍摄 - 2: 脚本审核
- 3: 后期制作 - 3: 脚本确认
- 4: 任务完成 - 4: 视频拍摄与制作
- 5: 视频确认
## 用户接口 ## 用户接口
@ -274,12 +275,12 @@
### 1. 获取课程任务列表 ### 1. 获取课程任务列表
- **接口**`GET /api/lesson-tasks` - **接口**`GET /api/lesson-tasks`
- **描述**:分页获取课程任务列表 - **描述**:分页获取课程任务列表,可按用户筛选
- **认证**:需要 - **认证**:需要
- **查询参数** - **查询参数**
- `page`: 页码(从 1 开始) - `page`: 页码(从 1 开始)
- `size`: 每页数量(默认 10 - `size`: 每页数量(默认 10
- `userId`: 用户 ID可选 - `userId`: 用户ID可选
- **成功响应** - **成功响应**
```json ```json
{ {
@ -288,27 +289,35 @@
"data": { "data": {
"content": [ "content": [
{ {
"id": 6, "id": 1,
"courseName": "Test Course", "courseName": "数学",
"microLessonName": "Test Lesson", "microLessonName": "数学1-1",
"userId": 12, "userId": 1,
"progressStatus": 1, "progressStatus": 4,
"scriptUploadTime": 1734578081, "scriptCreateTime": 1734940587,
"scriptConfirmTime": 1734578081, "scriptReviewTime": null,
"videoCaptureTime": 1734578081, "scriptConfirmTime": 1734940825,
"videoConfirmTime": 1734578081, "videoCreateTime": 1734940832,
"finishTime": 1734578081, "videoConfirmTime": 1734940837,
"advise": "Test advice", "finishTime": 1734940837,
"createdAt": 1734578081, "advise": "",
"updatedAt": 1734578081 "createdAt": 1734674726,
"updatedAt": 1734940837
} }
], ],
"totalElements": 4, "pageable": {
"pageNumber": 0,
"pageSize": 10,
"sort": {
"empty": true,
"unsorted": true,
"sorted": false
}
},
"totalElements": 7,
"totalPages": 1, "totalPages": 1,
"size": 10,
"number": 0,
"first": true,
"last": true, "last": true,
"first": true,
"empty": false "empty": false
} }
} }
@ -317,29 +326,30 @@
### 2. 获取单个课程任务 ### 2. 获取单个课程任务
- **接口**`GET /api/lesson-tasks/{id}` - **接口**`GET /api/lesson-tasks/{id}`
- **描述**:获取指定 ID 的课程任务 - **描述**根据ID获取课程任务详情
- **认证**:需要 - **认证**:需要
- **路径参数** - **路径参数**
- `id`: 课程任务 ID - `id`: 课程任务ID
- **成功响应** - **成功响应**
```json ```json
{ {
"code": 10000, "code": 10000,
"message": "成功", "message": "成功",
"data": { "data": {
"id": 6, "id": 1,
"courseName": "Test Course", "courseName": "数学",
"microLessonName": "Test Lesson", "microLessonName": "数学1-1",
"userId": 12, "userId": 1,
"progressStatus": 1, "progressStatus": 4,
"scriptUploadTime": 1734578081, "scriptCreateTime": 1734940587,
"scriptConfirmTime": 1734578081, "scriptReviewTime": null,
"videoCaptureTime": 1734578081, "scriptConfirmTime": 1734940825,
"videoConfirmTime": 1734578081, "videoCreateTime": 1734940832,
"finishTime": 1734578081, "videoConfirmTime": 1734940837,
"advise": "Test advice", "finishTime": 1734940837,
"createdAt": 1734578081, "advise": "",
"updatedAt": 1734578081 "createdAt": 1734674726,
"updatedAt": 1734940837
} }
} }
``` ```
@ -352,55 +362,10 @@
- **请求体** - **请求体**
```json ```json
{ {
"courseName": "Java基础", // 课程名称,不可为空 "courseName": "测试课程",
"microLessonName": "Java变量", // 微课名称,不可为空 "microLessonName": "测试微课",
"userId": 1, // 负责人ID不可为空关联users表 "userId": 1,
"advise": "请注意讲解速度" // 任务建议或备注,可选 "advise": "任务说明"
}
```
- **成功响应**
```json
{
"code": 10000,
"message": "成功",
"data": {
"id": 6,
"courseName": "Java基础",
"microLessonName": "Java变量",
"userId": 1,
"progressStatus": 1,
"scriptUploadTime": ,
"scriptConfirmTime": ,
"videoCaptureTime": ,
"videoConfirmTime": ,
"finishTime": ,
"advise": "请注意讲解速度",
"createdAt": 1734578081,
"updatedAt": 1734578081
}
}
```
### 4. 更新课程任务
- **接口**`PUT /api/lesson-tasks/{id}`
- **描述**:更新指定 ID 的课程任务
- **认证**:需要
- **路径参数**
- `id`: 课程任务 ID
- **请求体**
```json
{
"courseName": "Updated Course", // 课程名称
"microLessonName": "Updated Lesson", // 微课名称
"userId": 12, // 用户ID
"progressStatus": 2, // 进度状态
"scriptUploadTime": 1734578081, // 脚本上传时间
"scriptConfirmTime": 1734578081, // 脚本确认时间
"videoCaptureTime": 1734578081, // 视频录制时间
"videoConfirmTime": 1734578081, // 视频确认时间
"finishTime": 1734578081, // 完成时间
"advise": "Updated advice" // 建议
} }
``` ```
- **成功响应** - **成功响应**
@ -409,46 +374,69 @@
"code": 10000, "code": 10000,
"message": "成功", "message": "成功",
"data": { "data": {
"id": 6, "id": 11,
"courseName": "Updated Course", "courseName": "测试课程",
"microLessonName": "Updated Lesson", "microLessonName": "测试微课",
"userId": 12, "userId": 1,
"progressStatus": 2, "progressStatus": 0,
"scriptUploadTime": 1734578081, "scriptCreateTime": null,
"scriptConfirmTime": 1734578081, "scriptReviewTime": null,
"videoCaptureTime": 1734578081, "scriptConfirmTime": null,
"videoConfirmTime": 1734578081, "videoCreateTime": null,
"finishTime": 1734578081, "videoConfirmTime": null,
"advise": "Updated advice", "finishTime": null,
"createdAt": 1734578081, "advise": "任务说明",
"updatedAt": 1734578081 "createdAt": 1735003870,
"updatedAt": 1735003870
} }
} }
``` ```
### 5. 删除课程任务 ### 4. 更新课程任务
- **接口**`DELETE /api/lesson-tasks/{id}` - **接口**`PUT /api/lesson-tasks/{id}`
- **描述**删除指定 ID 的课程任务 - **描述**更新课程任务信息
- **认证**:需要 - **认证**:需要
- **路径参数** - **路径参数**
- `id`: 课程任务 ID - `id`: 课程任务ID
- **请求体**
```json
{
"progressStatus": 1,
"advise": "更新的任务说明"
}
```
- **成功响应** - **成功响应**
```json ```json
{ {
"code": 10000, "code": 10000,
"message": "成功", "message": "成功",
"data": null "data": {
"id": 11,
"courseName": "测试课程",
"microLessonName": "测试微课",
"userId": 1,
"progressStatus": 1,
"scriptCreateTime": 1735003922,
"scriptReviewTime": null,
"scriptConfirmTime": null,
"videoCreateTime": null,
"videoConfirmTime": null,
"finishTime": null,
"advise": "更新的任务说明",
"createdAt": 1735003870,
"updatedAt": 1735003922
}
} }
``` ```
### 6. 按部门 ID 查询课程任务 ### 5. 获取部门课程任务
- **接口**`GET /api/lesson-tasks/department/{departmentId}` - **接口**`GET /api/lesson-tasks/department/{departmentId}`
- **描述**:获取指定部门下正常状态用户的课程任务列表(分页) - **描述**:获取指定部门的课程任务列表
- **认证**:需要 - **认证**:需要
- **路径参数** - **路径参数**
- `departmentId`: 部门 ID - `departmentId`: 部门ID
- **查询参数** - **查询参数**
- `page`: 页码(从 1 开始) - `page`: 页码(从 1 开始)
- `size`: 每页数量(默认 10 - `size`: 每页数量(默认 10
@ -461,89 +449,51 @@
"content": [ "content": [
{ {
"id": 1, "id": 1,
"courseName": "物理", "courseName": "数学",
"microLessonName": "微课1-1", "microLessonName": "数学1-1",
"userId": 1, "userId": 1,
"username": "教师账号1", // 新增:用户名字段 "username": "教师账号1",
"progressStatus": 4, "progressStatus": 4,
"scriptUploadTime": 1734498510, "scriptCreateTime": 1734940587,
"scriptConfirmTime": 1734498510, "scriptReviewTime": null,
"videoCaptureTime": 1734498510, "scriptConfirmTime": 1734940825,
"videoConfirmTime": 1734498510, "videoCreateTime": 1734940832,
"finishTime": 1734498510, "videoConfirmTime": 1734940837,
"advise": null, "finishTime": 1734940837,
"createdAt": 1734578081, "advise": "",
"updatedAt": 1734580393 "createdAt": 1734674726,
"updatedAt": 1734940837
} }
], ],
"totalElements": 10, "pageable": {
"totalPages": 1, "pageNumber": 0,
"size": 10, "pageSize": 10
"number": 0, },
"first": true, "totalElements": 11,
"last": true, "totalPages": 2
"empty": false
} }
} }
``` ```
### 7. 更新课程任务进度 ### 6. 删除课程任务
- **接口**`PUT /api/lesson-tasks/{id}` - **接口**`DELETE /api/lesson-tasks/{id}`
- **描述**更新课程任务的进度状态和建议 - **描述**删除指定的课程任务
- **认证**:需要 - **认证**:需要
- **路径参数** - **路径参数**
- `id`: 课程任务 ID - `id`: 课程任务ID
- **请求体**
```json
{
"progressStatus": 2, // 进度状态(可选)
"advise": "{\"method\":\"wechat\",\"uploaded\":true}" // 建议(可选)
}
```
- **说明**
- 更新进度状态时会自动更新对应的时间戳:
- 状态 1更新 scriptUploadTime
- 状态 2更新 scriptConfirmTime
- 状态 3更新 videoCaptureTime
- 状态 4更新 videoConfirmTime
- 状态 5更新 finishTime
- 只会更新请求体中包含的字段,未提供的字段保持不变
- **成功响应** - **成功响应**
```json ```json
{ {
"code": 10000, "code": 10000,
"message": "成功", "message": "成功",
"data": { "data": null
"id": 10,
"courseName": "测试课程",
"microLessonName": "测试微课",
"userId": 1,
"progressStatus": 2,
"scriptUploadTime": null,
"scriptConfirmTime": 1734663755,
"videoCaptureTime": null,
"videoConfirmTime": null,
"finishTime": null,
"advise": "{\"method\":\"wechat\",\"uploaded\":true}",
"createdAt": 1734602440,
"updatedAt": 1734663755
}
} }
``` ```
## 注意事项 ## 注意事项
1. 所有需要认证的接口必须在请求头中携带有效的 JWT 令牌 1. 所有时间戳字段均使用秒级时间戳10位
2. 所有时间戳字段均为秒级时间戳 2. 课程任务状态变更时会自动记录相应的时间戳
3. 分页接口的页码从 1 开始 3. 部门课程任务列表会额外返回用户名信息
4. 用户密码在传输和存储时都会进行加密处理 4. 分页参数中的页码从1开始
5. 课程任务的 progressStatus 字段状态码说明:
- 0: 脚本上传
- 1: 脚本确认
- 2: 视频拍摄
- 3: 后期制作
- 4: 任务完成
6. 用户状态说明:
- 1: 正常
- 0: 禁用

2
components.d.ts vendored
View File

@ -11,6 +11,7 @@ declare module 'vue' {
TabBar: typeof import('./src/components/TabBar.vue')['default'] TabBar: typeof import('./src/components/TabBar.vue')['default']
WdBadge: typeof import('wot-design-uni/components/wd-badge/wd-badge.vue')['default'] WdBadge: typeof import('wot-design-uni/components/wd-badge/wd-badge.vue')['default']
WdButton: typeof import('wot-design-uni/components/wd-button/wd-button.vue')['default'] WdButton: typeof import('wot-design-uni/components/wd-button/wd-button.vue')['default']
WdCard: typeof import('wot-design-uni/components/wd-card/wd-card.vue')['default']
WdCell: typeof import('wot-design-uni/components/wd-cell/wd-cell.vue')['default'] WdCell: typeof import('wot-design-uni/components/wd-cell/wd-cell.vue')['default']
WdCellGroup: typeof import('wot-design-uni/components/wd-cell-group/wd-cell-group.vue')['default'] WdCellGroup: typeof import('wot-design-uni/components/wd-cell-group/wd-cell-group.vue')['default']
WdCheckbox: typeof import('wot-design-uni/components/wd-checkbox/wd-checkbox.vue')['default'] WdCheckbox: typeof import('wot-design-uni/components/wd-checkbox/wd-checkbox.vue')['default']
@ -31,6 +32,7 @@ declare module 'vue' {
WdSteps: typeof import('wot-design-uni/components/wd-steps/wd-steps.vue')['default'] WdSteps: typeof import('wot-design-uni/components/wd-steps/wd-steps.vue')['default']
WdTabbar: typeof import('wot-design-uni/components/wd-tabbar/wd-tabbar.vue')['default'] WdTabbar: typeof import('wot-design-uni/components/wd-tabbar/wd-tabbar.vue')['default']
WdTabbarItem: typeof import('wot-design-uni/components/wd-tabbar-item/wd-tabbar-item.vue')['default'] WdTabbarItem: typeof import('wot-design-uni/components/wd-tabbar-item/wd-tabbar-item.vue')['default']
WdTextarea: typeof import('wot-design-uni/components/wd-textarea/wd-textarea.vue')['default']
WdToast: typeof import('wot-design-uni/components/wd-toast/wd-toast.vue')['default'] WdToast: typeof import('wot-design-uni/components/wd-toast/wd-toast.vue')['default']
} }
} }

9
pnpm-lock.yaml generated
View File

@ -1509,55 +1509,46 @@ packages:
resolution: {integrity: sha512-btVgIsCjuYFKUjopPoWiDqmoUXQDiW2A4C3Mtmp5vACm7/GnyuprqIDPNczeyR5W8rTXEbkmrJux7cJmD99D2g==} resolution: {integrity: sha512-btVgIsCjuYFKUjopPoWiDqmoUXQDiW2A4C3Mtmp5vACm7/GnyuprqIDPNczeyR5W8rTXEbkmrJux7cJmD99D2g==}
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm-musleabihf@4.21.3': '@rollup/rollup-linux-arm-musleabihf@4.21.3':
resolution: {integrity: sha512-zmjbSphplZlau6ZTkxd3+NMtE4UKVy7U4aVFMmHcgO5CUbw17ZP6QCgyxhzGaU/wFFdTfiojjbLG3/0p9HhAqA==} resolution: {integrity: sha512-zmjbSphplZlau6ZTkxd3+NMtE4UKVy7U4aVFMmHcgO5CUbw17ZP6QCgyxhzGaU/wFFdTfiojjbLG3/0p9HhAqA==}
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
libc: [musl]
'@rollup/rollup-linux-arm64-gnu@4.21.3': '@rollup/rollup-linux-arm64-gnu@4.21.3':
resolution: {integrity: sha512-nSZfcZtAnQPRZmUkUQwZq2OjQciR6tEoJaZVFvLHsj0MF6QhNMg0fQ6mUOsiCUpTqxTx0/O6gX0V/nYc7LrgPw==} resolution: {integrity: sha512-nSZfcZtAnQPRZmUkUQwZq2OjQciR6tEoJaZVFvLHsj0MF6QhNMg0fQ6mUOsiCUpTqxTx0/O6gX0V/nYc7LrgPw==}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm64-musl@4.21.3': '@rollup/rollup-linux-arm64-musl@4.21.3':
resolution: {integrity: sha512-MnvSPGO8KJXIMGlQDYfvYS3IosFN2rKsvxRpPO2l2cum+Z3exiExLwVU+GExL96pn8IP+GdH8Tz70EpBhO0sIQ==} resolution: {integrity: sha512-MnvSPGO8KJXIMGlQDYfvYS3IosFN2rKsvxRpPO2l2cum+Z3exiExLwVU+GExL96pn8IP+GdH8Tz70EpBhO0sIQ==}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [musl]
'@rollup/rollup-linux-powerpc64le-gnu@4.21.3': '@rollup/rollup-linux-powerpc64le-gnu@4.21.3':
resolution: {integrity: sha512-+W+p/9QNDr2vE2AXU0qIy0qQE75E8RTwTwgqS2G5CRQ11vzq0tbnfBd6brWhS9bCRjAjepJe2fvvkvS3dno+iw==} resolution: {integrity: sha512-+W+p/9QNDr2vE2AXU0qIy0qQE75E8RTwTwgqS2G5CRQ11vzq0tbnfBd6brWhS9bCRjAjepJe2fvvkvS3dno+iw==}
cpu: [ppc64] cpu: [ppc64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-riscv64-gnu@4.21.3': '@rollup/rollup-linux-riscv64-gnu@4.21.3':
resolution: {integrity: sha512-yXH6K6KfqGXaxHrtr+Uoy+JpNlUlI46BKVyonGiaD74ravdnF9BUNC+vV+SIuB96hUMGShhKV693rF9QDfO6nQ==} resolution: {integrity: sha512-yXH6K6KfqGXaxHrtr+Uoy+JpNlUlI46BKVyonGiaD74ravdnF9BUNC+vV+SIuB96hUMGShhKV693rF9QDfO6nQ==}
cpu: [riscv64] cpu: [riscv64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-s390x-gnu@4.21.3': '@rollup/rollup-linux-s390x-gnu@4.21.3':
resolution: {integrity: sha512-R8cwY9wcnApN/KDYWTH4gV/ypvy9yZUHlbJvfaiXSB48JO3KpwSpjOGqO4jnGkLDSk1hgjYkTbTt6Q7uvPf8eg==} resolution: {integrity: sha512-R8cwY9wcnApN/KDYWTH4gV/ypvy9yZUHlbJvfaiXSB48JO3KpwSpjOGqO4jnGkLDSk1hgjYkTbTt6Q7uvPf8eg==}
cpu: [s390x] cpu: [s390x]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-gnu@4.21.3': '@rollup/rollup-linux-x64-gnu@4.21.3':
resolution: {integrity: sha512-kZPbX/NOPh0vhS5sI+dR8L1bU2cSO9FgxwM8r7wHzGydzfSjLRCFAT87GR5U9scj2rhzN3JPYVC7NoBbl4FZ0g==} resolution: {integrity: sha512-kZPbX/NOPh0vhS5sI+dR8L1bU2cSO9FgxwM8r7wHzGydzfSjLRCFAT87GR5U9scj2rhzN3JPYVC7NoBbl4FZ0g==}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-musl@4.21.3': '@rollup/rollup-linux-x64-musl@4.21.3':
resolution: {integrity: sha512-S0Yq+xA1VEH66uiMNhijsWAafffydd2X5b77eLHfRmfLsRSpbiAWiRHV6DEpz6aOToPsgid7TI9rGd6zB1rhbg==} resolution: {integrity: sha512-S0Yq+xA1VEH66uiMNhijsWAafffydd2X5b77eLHfRmfLsRSpbiAWiRHV6DEpz6aOToPsgid7TI9rGd6zB1rhbg==}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [musl]
'@rollup/rollup-win32-arm64-msvc@4.21.3': '@rollup/rollup-win32-arm64-msvc@4.21.3':
resolution: {integrity: sha512-9isNzeL34yquCPyerog+IMCNxKR8XYmGd0tHSV+OVx0TmE0aJOo9uw4fZfUuk2qxobP5sug6vNdZR6u7Mw7Q+Q==} resolution: {integrity: sha512-9isNzeL34yquCPyerog+IMCNxKR8XYmGd0tHSV+OVx0TmE0aJOo9uw4fZfUuk2qxobP5sug6vNdZR6u7Mw7Q+Q==}

View File

@ -1,9 +1,8 @@
import http from "@/http/HttpClient"; import http from "@/http/HttpClient";
import { useUser } from "@/stores/useUser"; import { useUser } from "@/stores/useUser";
import type { PagedData } from "@/types/api/common"; import type { LessonTask, UpdateLessonTaskRequest } from "@/types/api/lesson";
import type { CreateLessonTaskRequest, LessonTask, UpdateLessonTaskRequest } from "@/types/api/lesson";
import type { LoginRequest } from "@/types/api/auth"; import type { LoginRequest } from "@/types/api/auth";
import type { CreateUserRequest, User } from "@/types/api/user"; import type { User } from "@/types/api/user";
/** /**
* API类 * API类
@ -176,13 +175,14 @@ export default class BussApi {
/** /**
* *
* @param lessonId - ID * @param lessonId - ID
* @param progressStatus - (0-, 1-, 2-, 3-, 4-, 5-)
* @returns Promise<void> * @returns Promise<void>
*/ */
static updateLessonProgress(lessonId: number): Promise<void> { static updateLessonProgress(lessonId: number, progressStatus: number): Promise<void> {
const user = useUser(); const user = useUser();
return http return http
.server() .server()
.post(`lesson/${lessonId}/progress`, null, { .post(`lesson/${lessonId}/progress`, { progressStatus }, {
headers: { headers: {
Authorization: `Bearer ${user.token}`, Authorization: `Bearer ${user.token}`,
}, },

View File

@ -31,17 +31,17 @@ const nameLabelIconMap = {
home: { home: {
title: '进度查看', title: '进度查看',
icon: 'dashboard', icon: 'dashboard',
roles: ['teacher', 'admin', 'liaison', 'sysadmin'] as const // 使 as const roles: ['teacher', 'liaison', 'admin', 'sysadmin'] as const //
}, },
progress: { progress: {
title: '进度管理', title: '进度管理',
icon: 'transfer', icon: 'transfer',
roles: ['teacher', 'admin', 'sysadmin'] as const roles: ['teacher', 'admin', 'sysadmin'] as const //
}, },
my: { my: {
title: '我的', title: '我的',
icon: 'user', icon: 'user',
roles: ['teacher', 'admin', 'liaison', 'sysadmin'] as const roles: ['teacher', 'liaison', 'admin', 'sysadmin'] as const //
} }
} }

View File

@ -4,8 +4,8 @@ import { ref } from "vue";
export const useConfig = defineStore('config', () => { export const useConfig = defineStore('config', () => {
// const BASE_URL = ref<string>("https://ppmp.fenshenzhike.com/api"); // const BASE_URL = ref<string>("https://ppmp.fenshenzhike.com/api");
// const BASE_URL = ref<string>("http://localhost:1218/api"); // const BASE_URL = ref<string>("http://localhost:1218/api");
const BASE_URL = ref<string>("http://192.168.0.178:1218/api"); const BASE_URL = ref<string>("http://192.168.0.119:1218/api");
// const BASE_URL = ref<string>("http://192.30.5.16:1218/api"); // const BASE_URL = ref<string>("http://192.30.5.11:1218/api");
return { return {
BASE_URL BASE_URL

View File

@ -4,7 +4,7 @@ import pageWrapper from '@/components/page-wrapper.vue';
import { useUser } from '@/stores/useUser'; import { useUser } from '@/stores/useUser';
import type { LessonTask } from '@/types/api/lesson'; import type { LessonTask } from '@/types/api/lesson';
import { calcLessonProgress } from '@/utils/lesson'; import { calcLessonProgress } from '@/utils/lesson';
import { onPageShow } from '@dcloudio/uni-app'; import { onPageShow, onLoad, onPullDownRefresh } from '@dcloudio/uni-app';
import { useRouter } from 'uni-mini-router'; import { useRouter } from 'uni-mini-router';
import { onMounted, ref, watch } from 'vue'; import { onMounted, ref, watch } from 'vue';
import { useToast } from 'wot-design-uni'; import { useToast } from 'wot-design-uni';
@ -73,8 +73,24 @@ const loadLessons = async () => {
toast.loading({ msg: '加载中...' }) toast.loading({ msg: '加载中...' })
try { try {
const userId = teacherFilterValue.value === 0 ? undefined : teacherFilterValue.value let res;
const res = await BussApi.getLessonTasks(1, 512, userId)
if (user.hasRole('teacher')) {
res = await BussApi.getLessonTasks(1, 512, user.userinfo.id)
} else if (user.hasRole('admin') || user.hasRole('liaison')) {
const userId = teacherFilterValue.value === 0 ? undefined : teacherFilterValue.value
if (userId) {
res = await BussApi.getLessonTasks(1, 512, userId)
} else {
res = await BussApi.getLessonTasks(1, 512)
}
} else if (user.hasRole('sysadmin')) {
const userId = teacherFilterValue.value === 0 ? undefined : teacherFilterValue.value
res = await BussApi.getLessonTasks(1, 512, userId)
} else {
toast.error({ msg: '无权限访问' })
return
}
if (res.code !== 10000 || !res.data?.content || !Array.isArray(res.data.content)) { if (res.code !== 10000 || !res.data?.content || !Array.isArray(res.data.content)) {
toast.error({ msg: res.message || '获取数据失败' }) toast.error({ msg: res.message || '获取数据失败' })
@ -92,7 +108,6 @@ const loadLessons = async () => {
}, {}) }, {})
groupedLessons.value = groupData groupedLessons.value = groupData
// expand courses with lessons in progress
expandedCourse.value = Object.keys(groupData).filter(courseName => { expandedCourse.value = Object.keys(groupData).filter(courseName => {
return groupData[courseName].filter((lesson: LessonTask) => return groupData[courseName].filter((lesson: LessonTask) =>
calcLessonProgress(lesson) !== 0 && calcLessonProgress(lesson) !== 100 calcLessonProgress(lesson) !== 0 && calcLessonProgress(lesson) !== 100
@ -109,7 +124,6 @@ const loadLessons = async () => {
} }
} }
//
watch(teacherFilterValue, () => { watch(teacherFilterValue, () => {
loadLessons() loadLessons()
}) })
@ -125,6 +139,26 @@ const getUsernameById = (userId: number) => {
} else } else
return '' return ''
} }
const refresh = async () => {
try {
uni.reLaunch({
url: '/pages/index/index'
})
} catch (err) {
} finally {
uni.stopPullDownRefresh()
}
}
onPullDownRefresh(() => {
refresh()
})
onLoad(() => {
loadLessons()
})
</script> </script>
<template> <template>
@ -143,9 +177,12 @@ const getUsernameById = (userId: number) => {
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-1">
<p class="pt-1"> <p class="pt-1">
{{ courseName || '无标题课程' }} {{ courseName || '无标题课程' }}
<span v-if="getUsernameById(courses[0]?.userId)" class=" text-xs text-gray-400 ml-2"> <wd-badge hidden is-dot :top="-10">
{{ getUsernameById(courses[0]?.userId) }} <span v-if="!user.hasRole('teacher') && getUsernameById(courses[0]?.userId)"
</span> class=" text-xs text-gray-400 ml-2">
<!-- {{ getUsernameById(courses[0]?.userId) }} -->
</span>
</wd-badge>
</p> </p>
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
<wd-tag v-if="(() => { <wd-tag v-if="(() => {
@ -162,6 +199,13 @@ const getUsernameById = (userId: number) => {
}); });
return allCompleted; return allCompleted;
})()" custom-class="w-fit" type="success">已完成</wd-tag> })()" custom-class="w-fit" type="success">已完成</wd-tag>
<wd-tag v-if="(() => {
const allNotStarted = courses.every(lesson => {
const progress = calcLessonProgress(lesson);
return progress === 0;
});
return allNotStarted;
})()" custom-class="w-fit" type="default">未开始</wd-tag>
<wd-tag custom-class="op-60" plain> <wd-tag custom-class="op-60" plain>
{{ courses.length }}节微课 {{ courses.length }}节微课
</wd-tag> </wd-tag>
@ -206,7 +250,13 @@ const getUsernameById = (userId: number) => {
<div v-else class="i-tabler-hourglass-empty"></div> <div v-else class="i-tabler-hourglass-empty"></div>
</div> </div>
</wd-badge> </wd-badge>
<span>{{ lesson.microLessonName || '无标题视频' }}</span> <span>{{ lesson.microLessonName || '无标题微课' }}</span>
<wd-badge hidden is-dot :top="-10">
<span v-if="!user.hasRole('teacher') && getUsernameById(courses[0]?.userId)"
class=" text-xs text-gray-400 ml-2">
{{ getUsernameById(courses[0]?.userId) }}
</span>
</wd-badge>
</div> </div>
<div class="w-24 flex items-center gap-3"> <div class="w-24 flex items-center gap-3">
<wd-progress :percentage="calcLessonProgress(lesson)" <wd-progress :percentage="calcLessonProgress(lesson)"

View File

@ -8,6 +8,7 @@ import { calcLessonProgress, extractLessonStage, getLessonSteps } from '@/utils/
import { useRoute, useRouter } from 'uni-mini-router'; import { useRoute, useRouter } from 'uni-mini-router';
import { computed, onMounted, ref } from 'vue'; import { computed, onMounted, ref } from 'vue';
import { useToast } from 'wot-design-uni'; import { useToast } from 'wot-design-uni';
import { onPullDownRefresh, onLoad } from '@dcloudio/uni-app'
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
@ -32,7 +33,7 @@ const goProgress = (lessonId: number) => {
tabbar.activeTab = 'progress' tabbar.activeTab = 'progress'
} }
onMounted(() => { const loadLesson = async () => {
if (!route.params?.courseId) { if (!route.params?.courseId) {
toast.error({ toast.error({
msg: '参数错误' msg: '参数错误'
@ -48,7 +49,33 @@ onMounted(() => {
}).catch(err => { }).catch(err => {
toast.error({ msg: err.message }) toast.error({ msg: err.message })
}) })
}
onMounted(() => {
loadLesson()
}) })
const refresh = async () => {
try {
uni.reLaunch({
url: `/pages/lesson/index?courseId=${route.params?.courseId}`
})
} catch (err) {
} finally {
uni.stopPullDownRefresh()
}
}
//
onPullDownRefresh(() => {
refresh()
})
//
onLoad(() => {
loadLesson()
})
</script> </script>
<template> <template>

View File

@ -5,6 +5,7 @@ import { useTabbar } from '@/stores/useTabbar';
import { useRouter } from 'uni-mini-router'; import { useRouter } from 'uni-mini-router';
import { reactive, ref } from 'vue'; import { reactive, ref } from 'vue';
import { useToast } from 'wot-design-uni'; import { useToast } from 'wot-design-uni';
import { onPullDownRefresh, onLoad } from '@dcloudio/uni-app'
const router = useRouter() const router = useRouter()
const toast = useToast() const toast = useToast()
@ -58,6 +59,21 @@ const handleSubmit = () => {
// console.log(error, 'error') // console.log(error, 'error')
}) })
} }
const refresh = async () => {
try {
uni.reLaunch({
url: '/pages/login/index'
})
} catch (err) {
} finally {
uni.stopPullDownRefresh()
}
}
//
onPullDownRefresh(() => {
refresh()
})
</script> </script>
<template> <template>

View File

@ -7,20 +7,33 @@ import { Jobs, Roles } from '@/types/api/user';
import { useRouter } from 'uni-mini-router'; import { useRouter } from 'uni-mini-router';
import { onMounted, ref } from 'vue'; import { onMounted, ref } from 'vue';
import { useToast } from 'wot-design-uni'; import { useToast } from 'wot-design-uni';
import { onPullDownRefresh, onLoad } from '@dcloudio/uni-app'
const router = useRouter() const router = useRouter()
const toast = useToast() const toast = useToast()
const user = useUser() const user = useUser()
const userInfo = ref<User | null>(null) const userInfo = ref<User | null>(null)
onMounted(() => { const loadUserInfo = async () => {
toast.loading({ try {
msg: '加载中...' toast.loading({
}) msg: '加载中...'
BussApi.profile(user.token!).then(res => { })
const res = await BussApi.profile(user.token!)
toast.close() toast.close()
user.userinfo = res user.userinfo = res
}) userInfo.value = res
} catch (error: any) {
toast.close()
toast.error(error.message || '获取用户信息失败')
if (error.response?.data?.code === 10001) {
router.replace('/pages/login/index')
}
}
}
onMounted(() => {
loadUserInfo()
}) })
const logout = async () => { const logout = async () => {
@ -50,34 +63,39 @@ const departmentMap: Record<DepartmentId, string> = {
// //
const roleMap: Record<number, string> = { const roleMap: Record<number, string> = {
[Roles.TEACHER]: '教师', [Roles.TEACHER]: '校方教师',
[Roles.GENERAL_ADMIN]: '普通管理员', [Roles.GENERAL_ADMIN]: '公司课程顾问',
[Roles.CONTACTOR]: '沟通联络人', [Roles.CONTACTOR]: '校方项目负责人',
[Roles.SYSTEM_ADMIN]: '系统管理员', [Roles.SYSTEM_ADMIN]: '公司系统管理员',
} }
// //
const jobMap: Record<number, string> = { const jobMap: Record<number, string> = {
[Jobs.COURSE_TEACHER]: '课程制作教师', [Jobs.COURSE_TEACHER]: '课程制作教师',
[Jobs.PROJECT_MANAGER]: '课程购买方项目负责人', [Jobs.PROJECT_MANAGER]: '课程审核人员',
[Jobs.COURSE_CONTACTOR]: '课程制作方沟通联络人', [Jobs.COURSE_CONTACTOR]: '校方项目负责人',
[Jobs.SYSTEM_MANAGER]: '系统制作方项目负责人', [Jobs.SYSTEM_MANAGER]: '公司系统管理员',
} }
onMounted(() => { const refresh = async () => {
toast.loading({ try {
msg: '加载中...' uni.reLaunch({
}) url: '/pages/my/index'
BussApi.profile(user.token!).then(res => { })
toast.close() } catch (err) {
userInfo.value = res } finally {
}).catch(error => { uni.stopPullDownRefresh()
toast.close() }
toast.error(error.message || '获取用户信息失败') }
if (error.response?.data?.code === 10001) {
router.replace('/pages/login/index') //
} onPullDownRefresh(() => {
}) refresh()
})
//
onLoad(() => {
loadUserInfo()
}) })
</script> </script>

View File

@ -1,8 +1,8 @@
<script lang="ts" setup> <script lang="ts" setup>
import BussApi from '@/api/BussApi'; import BussApi from '@/api/BussApi';
import { useDayjs } from '@/composables/useDayjs';
import type { LessonTask, FileUploadDestination } from '@/types/api/lesson'; import type { LessonTask, FileUploadDestination } from '@/types/api/lesson';
import { extractLessonStage, getLessonSteps, getScriptFile, parseCombinedFileString } from '@/utils/lesson'; import { extractLessonStage, getLessonSteps } from '@/utils/lesson';
import { ProgressStatus } from '@/types/api/lesson';
import { computed, nextTick, onMounted, ref } from 'vue'; import { computed, nextTick, onMounted, ref } from 'vue';
import { useMessage, useToast } from 'wot-design-uni'; import { useMessage, useToast } from 'wot-design-uni';
import StatusBlock from './StatusBlock.vue'; import StatusBlock from './StatusBlock.vue';
@ -12,8 +12,8 @@ import { onPullDownRefresh } from '@dcloudio/uni-app'
const route = useRoute() const route = useRoute()
const message = useMessage() const message = useMessage()
const message_reject = useMessage('wd-message-box-slot')
const toast = useToast() const toast = useToast()
const dayjs = useDayjs()
const user = useUser() const user = useUser()
type GroupedLessons = { [key: string]: LessonTask[] } type GroupedLessons = { [key: string]: LessonTask[] }
@ -32,7 +32,7 @@ const pickerLessonColumns = computed(() => {
}) : [] }) : []
}) })
const pickerLessonValue = ref() const pickerLessonValue = ref()
const adviseText = ref('') const adviseText = ref<string>('')
const selectedLesson = computed(() => { const selectedLesson = computed(() => {
if (!pickerLessonValue.value) return null if (!pickerLessonValue.value) return null
@ -59,7 +59,7 @@ const onLessonPick = ({ value }: { value: number }) => {
const script_file_destination = ref<FileUploadDestination>('wechat') const script_file_destination = ref<FileUploadDestination>('wechat')
const onStep1 = () => { const onStep0 = () => {
message.confirm({ message.confirm({
title: '提交脚本', title: '提交脚本',
msg: '请确认已经通过微信或平台上传了脚本文件,再确认提交' msg: '请确认已经通过微信或平台上传了脚本文件,再确认提交'
@ -74,12 +74,11 @@ const onStep1 = () => {
msg: '正在提交...' msg: '正在提交...'
}) })
const params = { const params = {
// advise: adviseText.value, advise: "",
// scriptUploadTime: dayjs().unix(),
courseName: selectedLesson.value.courseName, courseName: selectedLesson.value.courseName,
microLessonName: selectedLesson.value.microLessonName, microLessonName: selectedLesson.value.microLessonName,
userId: selectedLesson.value.userId, userId: selectedLesson.value.userId,
progressStatus: 1 progressStatus: ProgressStatus.SCRIPT_CREATING//1
} }
BussApi.updateLessonTask(selectedLesson.value.id, params).then(res => { BussApi.updateLessonTask(selectedLesson.value.id, params).then(res => {
@ -95,11 +94,55 @@ const onStep1 = () => {
}) })
} }
const onStep1 = (rejected: boolean = false) => {
const msg = rejected ? message_reject : message
msg
.confirm({
title: rejected ? '驳回脚本' : '通过脚本',
msg: rejected ? '脚本不符合要求,驳回制作方重做' : '请确认脚本合格无误后,再确认审核通过'
}).then(() => {
if (!selectedLesson.value?.id) {
toast.error({
msg: '参数错误'
})
return
}
toast.loading({
msg: '正在处理...'
})
BussApi.updateLessonTask(
selectedLesson.value.id,
rejected ? {
advise: adviseText.value.toString(),
courseName: selectedLesson.value.courseName,
microLessonName: selectedLesson.value.microLessonName,
userId: selectedLesson.value.userId,
progressStatus: ProgressStatus.NOT_STARTED//0
} : {
advise: "",
courseName: selectedLesson.value.courseName,
microLessonName: selectedLesson.value.microLessonName,
userId: selectedLesson.value.userId,
progressStatus: ProgressStatus.SCRIPT_REVIEW//2
}).then(res => {
toast.success({
msg: rejected ? '驳回成功' : '审核通过'
})
setTimeout(() => {
updateLessons()
}, 1500);
}).catch(err => {
toast.error({ msg: err.message })
})
})
}
const onStep2 = (rejected: boolean = false) => { const onStep2 = (rejected: boolean = false) => {
console.log("adviseText.value: " + adviseText.value) const msg = rejected ? message_reject : message
message.confirm({ msg.confirm({
title: rejected ? '驳回脚本' : '通过脚本', title: rejected ? '驳回视频拍摄制作' : '通过视频拍摄制作',
msg: rejected ? '脚本不符合要求,驳回制作方重做' : '请确认脚本合格无误后,再确认审核通过' msg: rejected ? '视频拍摄制作不符合要求,驳回拍摄制作方重做' : '请确认视频拍摄制作合格无误后,再确认审核通过'
}).then(() => { }).then(() => {
if (!selectedLesson.value?.id) { if (!selectedLesson.value?.id) {
toast.error({ toast.error({
@ -114,18 +157,16 @@ const onStep2 = (rejected: boolean = false) => {
selectedLesson.value.id, selectedLesson.value.id,
rejected ? { rejected ? {
advise: adviseText.value.toString(), advise: adviseText.value.toString(),
// scriptUploadTime: 0,
courseName: selectedLesson.value.courseName, courseName: selectedLesson.value.courseName,
microLessonName: selectedLesson.value.microLessonName, microLessonName: selectedLesson.value.microLessonName,
userId: selectedLesson.value.userId, userId: selectedLesson.value.userId,
progressStatus: 0 progressStatus: ProgressStatus.SCRIPT_CREATING//1
} : { } : {
advise: adviseText.value.toString(), advise: "",
// scriptConfirmTime: dayjs().unix(),
courseName: selectedLesson.value.courseName, courseName: selectedLesson.value.courseName,
microLessonName: selectedLesson.value.microLessonName, microLessonName: selectedLesson.value.microLessonName,
userId: selectedLesson.value.userId, userId: selectedLesson.value.userId,
progressStatus: 2 progressStatus: ProgressStatus.SCRIPT_CONFIRMED//3
}).then(res => { }).then(res => {
toast.success({ toast.success({
msg: rejected ? '驳回成功' : '审核通过' msg: rejected ? '驳回成功' : '审核通过'
@ -140,10 +181,10 @@ const onStep2 = (rejected: boolean = false) => {
} }
const onStep3 = (rejected: boolean = false) => { const onStep3 = (rejected: boolean = false) => {
console.log("adviseText.value: " + adviseText.value) const msg = rejected ? message_reject : message
message.confirm({ msg.confirm({
title: rejected ? '驳回视频' : '通过视频', title: rejected ? '驳回视频拍摄制作' : '通过视频拍摄制作',
msg: rejected ? '视频不符合要求,驳回制作方重做' : '请确认视频合格无误后,再确认审核通过' msg: rejected ? '视频拍摄制作不符合要求,驳回拍摄制作方重做' : '请确认视频拍摄制作合格无误后,再确认审核通过'
}).then(() => { }).then(() => {
if (!selectedLesson.value?.id) { if (!selectedLesson.value?.id) {
toast.error({ toast.error({
@ -158,62 +199,16 @@ const onStep3 = (rejected: boolean = false) => {
selectedLesson.value.id, selectedLesson.value.id,
rejected ? { rejected ? {
advise: adviseText.value.toString(), advise: adviseText.value.toString(),
// videoCaptureTime: 0,
courseName: selectedLesson.value.courseName, courseName: selectedLesson.value.courseName,
microLessonName: selectedLesson.value.microLessonName, microLessonName: selectedLesson.value.microLessonName,
userId: selectedLesson.value.userId, userId: selectedLesson.value.userId,
progressStatus: 1 progressStatus: ProgressStatus.VIDEO_CONFIRMED
} : { } : {
advise: adviseText.value.toString(), advise: "",
// videoConfirmTime: dayjs().unix(),
courseName: selectedLesson.value.courseName, courseName: selectedLesson.value.courseName,
microLessonName: selectedLesson.value.microLessonName, microLessonName: selectedLesson.value.microLessonName,
userId: selectedLesson.value.userId, userId: selectedLesson.value.userId,
progressStatus: 3 progressStatus: ProgressStatus.VIDEO_CONFIRMED
}).then(res => {
toast.success({
msg: rejected ? '驳回成功' : '审核通过'
})
setTimeout(() => {
updateLessons()
}, 1500);
}).catch(err => {
toast.error({ msg: err.message })
})
})
}
const onPostProduction = (rejected: boolean = false) => {
console.log("adviseText.value: " + adviseText.value)
message.confirm({
title: rejected ? '驳回后期制作' : '通过后期制作',
msg: rejected ? '后期制作不符合要求,驳回制作方重做' : '请确认后期制作合格无误后,再确认审核通过'
}).then(() => {
if (!selectedLesson.value?.id) {
toast.error({
msg: '参数错误'
})
return
}
toast.loading({
msg: '正在处理...'
})
BussApi.updateLessonTask(
selectedLesson.value.id,
rejected ? {
advise: adviseText.value.toString(),
// videoCaptureTime: 0,
courseName: selectedLesson.value.courseName,
microLessonName: selectedLesson.value.microLessonName,
userId: selectedLesson.value.userId,
progressStatus: 2
} : {
advise: adviseText.value.toString(),
// videoConfirmTime: dayjs().unix(),
courseName: selectedLesson.value.courseName,
microLessonName: selectedLesson.value.microLessonName,
userId: selectedLesson.value.userId,
progressStatus: 4
}).then(res => { }).then(res => {
toast.success({ toast.success({
msg: rejected ? '驳回成功' : '审核通过' msg: rejected ? '驳回成功' : '审核通过'
@ -286,16 +281,17 @@ const updateLessons = async () => {
onMounted(() => { onMounted(() => {
updateLessons() updateLessons()
}) })
const refresh = async () => { const refresh = async () => {
try { try {
await updateLessons() uni.reLaunch({
url: '/pages/progress/index'
})
} catch (err) { } catch (err) {
} finally { } finally {
//
uni.stopPullDownRefresh() uni.stopPullDownRefresh()
} }
} }
// //
onPullDownRefresh(() => { onPullDownRefresh(() => {
refresh() refresh()
@ -310,6 +306,10 @@ onPullDownRefresh(() => {
<wd-picker :columns="pickerLessonColumns" label="微课选择" v-model="pickerLessonValue" @confirm="onLessonPick" <wd-picker :columns="pickerLessonColumns" label="微课选择" v-model="pickerLessonValue" @confirm="onLessonPick"
:columns-height="280" label-width="80px" safe-area-inset-bottom title="微课选择" :disabled="!pickerCourseValue" /> :columns-height="280" label-width="80px" safe-area-inset-bottom title="微课选择" :disabled="!pickerCourseValue" />
</div> </div>
<wd-message-box selector="wd-message-box-slot">
<wd-textarea v-model="adviseText" type="text" clear-trigger="focus" clearable auto-height show-word-limit
:focus-when-clear="false" :maxlength="100" placeholder="请输入审核建议..." block />
</wd-message-box>
<div class="p-2 pt-4"> <div class="p-2 pt-4">
<wd-status-tip v-if="!pickerLessonValue" image="search" tip="请先选择微课" /> <wd-status-tip v-if="!pickerLessonValue" image="search" tip="请先选择微课" />
<div v-else class="space-y-6"> <div v-else class="space-y-6">
@ -322,6 +322,11 @@ onPullDownRefresh(() => {
<div v-if="selectedLessonStage?.step === 0" class="px-2"> <div v-if="selectedLessonStage?.step === 0" class="px-2">
<div v-if="user.hasRole('teacher')"> <div v-if="user.hasRole('teacher')">
<div v-if="selectedLesson?.advise && selectedLesson.advise.length > 0" class=" m-2 text-sm">
<wd-card title="修改建议">
{{ selectedLesson?.advise }}
</wd-card>
</div>
<wd-cell-group> <wd-cell-group>
<wd-cell title="脚本提交途径" :title-width="'100px'" center custom-class="mb-4"> <wd-cell title="脚本提交途径" :title-width="'100px'" center custom-class="mb-4">
<wd-radio-group v-model="script_file_destination" shape="button"> <wd-radio-group v-model="script_file_destination" shape="button">
@ -331,11 +336,11 @@ onPullDownRefresh(() => {
</wd-radio-group> </wd-radio-group>
</wd-cell> </wd-cell>
</wd-cell-group> </wd-cell-group>
<wd-button type="primary" block @click="onStep1" custom-class="w-full">提交脚本</wd-button> <wd-button type="primary" block @click="onStep0" custom-class="w-full">提交脚本</wd-button>
</div> </div>
<StatusBlock v-else title="脚本还未提交" subtitle="请耐心等待审核"> <StatusBlock v-else title="脚本还未提交" subtitle="请耐心等待提交">
<template #icon> <template #icon>
<div class="i-tabler-progress-bolt text-7xl text-neutral-400"></div> <div class="i-tabler-file-upload text-7xl text-neutral-400"></div>
</template> </template>
</StatusBlock> </StatusBlock>
</div> </div>
@ -343,45 +348,91 @@ onPullDownRefresh(() => {
<div v-if="selectedLessonStage?.step === 1"> <div v-if="selectedLessonStage?.step === 1">
<StatusBlock v-if="user.hasRole('teacher')" title="脚本已提交" subtitle="请耐心等待审核"> <StatusBlock v-if="user.hasRole('teacher')" title="脚本已提交" subtitle="请耐心等待审核">
<template #icon> <template #icon>
<div class="i-tabler-progress-bolt text-7xl text-neutral-400"></div> <div class="i-tabler-progress-bolt text-7xl text-neutral-400 "></div>
</template> </template>
</StatusBlock> </StatusBlock>
<div v-else> <div v-else>
<StatusBlock title="脚本处理已完成" subtitle="请核对并审核"> <div v-if="selectedLesson?.advise && selectedLesson.advise.length > 0" class=" m-2 text-sm">
<wd-card title="修改建议">
{{ selectedLesson?.advise }}
</wd-card>
</div>
<StatusBlock title="脚本制作已完成" subtitle="请核对并审核">
<template #icon> <template #icon>
<div class="i-tabler-progress-check text-7xl text-neutral-400"></div> <div class="i-tabler-progress-check text-7xl text-neutral-400"></div>
</template> </template>
</StatusBlock> </StatusBlock>
<div class="mt-4 space-y-4"> <div class="mt-4 space-y-4">
<div class="bg-neutral000 rounded-lg px-4 py-3">
<wd-input v-model="adviseText" title="审核建议" type="text" show-clear show-word-limit :maxlength="50"
placeholder="请输入审核建议..." block />
</div>
<div class="flex gap-3 px-4"> <div class="flex gap-3 px-4">
<wd-button type="primary" block @click="onStep2()">通过</wd-button> <wd-button type="primary" block @click="onStep1()">确认</wd-button>
<wd-button type="error" block @click="onStep2(true)">驳回</wd-button> <wd-button type="error" block @click="onStep1(true)">驳回</wd-button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div v-if="selectedLessonStage?.step === 2"> <div v-if="selectedLessonStage?.step === 2">
<StatusBlock v-if="user.hasRole('teacher')" title="视频拍摄进行中" subtitle="请等待线下视频拍摄"> <StatusBlock v-if="!user.hasRole('teacher')" title="脚本确认进行中" subtitle="请等待脚本内容确认">
<template #icon> <template #icon>
<div class="i-tabler-capture text-7xl text-neutral-400"></div> <div class="i-tabler-file-text text-7xl text-neutral-400"></div>
</template> </template>
</StatusBlock> </StatusBlock>
<!-- && selectedLesson && selectedLesson?.videoCaptureTime > 0 -->
<div v-else> <div v-else>
<StatusBlock title="视频拍摄已完成" subtitle="请核对后审核"> <StatusBlock title="脚本审核已完成" subtitle="请确认脚本内容">
<template #icon> <template #icon>
<div class="i-tabler-capture-filled text-7xl text-neutral-400"></div> <div class="i-tabler-file-text text-7xl text-neutral-400"></div>
</template>
</StatusBlock>
</div>
<div v-if="user.hasRole('teacher')" class="mt-4 space-y-4">
<div class="flex gap-3 px-4">
<wd-button type="primary" block @click="onStep2()">确认</wd-button>
<wd-button type="error" block @click="onStep2(true)">驳回</wd-button>
</div>
</div>
</div>
<div v-if="selectedLessonStage?.step === 3">
<StatusBlock v-if="user.hasRole('teacher')" title="视频拍摄制作进行中" subtitle="请等待视频拍摄制作">
<template #icon>
<div class="i-tabler-video text-7xl text-neutral-400"></div>
</template>
</StatusBlock>
<div v-else>
<StatusBlock title="视频拍摄制作进行中" subtitle="完成后请确认">
<template #icon>
<div class="i-tabler-video text-7xl text-neutral-400"></div>
</template> </template>
</StatusBlock> </StatusBlock>
<div class="mt-4 space-y-4"> <div class="mt-4 space-y-4">
<div class="bg-neutral000 rounded-lg px-4 py-3"> <div class="bg-neutral000 rounded-lg px-4 py-3">
<wd-input v-model="adviseText" title="审核建议" type="text" show-clear show-word-limit :maxlength="50" <wd-textarea v-model="adviseText" title="审核建议" type="text" clear-trigger="focus" clearable auto-height
placeholder="请输入审核建议..." block /> show-word-limit :focus-when-clear="false" :maxlength="100" placeholder="请输入审核建议..." block />
</div>
<div class="flex gap-3 px-4 center">
<wd-button type="primary" block @click="onStep3()">通过</wd-button>
<!-- <wd-button type="error" block @click="onStep3(true)">驳回</wd-button> -->
</div>
</div>
</div>
</div>
<div v-if="selectedLessonStage?.step === 4">
<StatusBlock v-if="user.hasRole('teacher')" title="视频拍摄制作进行中" subtitle="请等待视频拍摄制作">
<template #icon>
<div class="i-tabler-video text-7xl text-neutral-400"></div>
</template>
</StatusBlock>
<div v-else>
<StatusBlock title="视频拍摄制作进行中" subtitle="完成后请确认">
<template #icon>
<div class="i-tabler-video text-7xl text-neutral-400"></div>
</template>
</StatusBlock>
<div class="mt-4 space-y-4">
<div class="bg-neutral000 rounded-lg px-4 py-3">
<wd-textarea v-model="adviseText" title="审核建议" type="text" clear-trigger="focus" clearable auto-height
show-word-limit :focus-when-clear="false" :maxlength="100" placeholder="请输入审核建议..." block />
</div> </div>
<div class="flex gap-3 px-4"> <div class="flex gap-3 px-4">
<wd-button type="primary" block @click="onStep3()">通过</wd-button> <wd-button type="primary" block @click="onStep3()">通过</wd-button>
@ -390,32 +441,8 @@ onPullDownRefresh(() => {
</div> </div>
</div> </div>
</div> </div>
<div v-if="selectedLessonStage?.step === 3">
<StatusBlock v-if="user.hasRole('teacher')" title="后期制作进行中" subtitle="请等待视频后期制作"> <div v-else-if="selectedLessonStage?.step === 5">
<template #icon>
<div class="i-tabler-video text-7xl text-neutral-400"></div>
</template>
</StatusBlock>
<!-- && selectedLesson && selectedLesson?.videoPostProductionTime > 0 -->
<div v-else>
<StatusBlock title="后期制作已完成" subtitle="请核对后审核">
<template #icon>
<div class="i-tabler-video text-7xl text-neutral-400"></div>
</template>
</StatusBlock>
<div class="mt-4 space-y-4">
<div class="bg-neutral000 rounded-lg px-4 py-3">
<wd-input v-model="adviseText" title="审核建议" type="text" show-clear show-word-limit :maxlength="50"
placeholder="请输入审核建议..." block />
</div>
<div class="flex gap-3 px-4">
<wd-button type="primary" block @click="onPostProduction()">通过</wd-button>
<wd-button type="error" block @click="onPostProduction(true)">驳回</wd-button>
</div>
</div>
</div>
</div>
<div v-else-if="selectedLessonStage?.step === 4">
<wd-status-tip image="comment" tip="该微课已完成" /> <wd-status-tip image="comment" tip="该微课已完成" />
</div> </div>
</div> </div>

View File

@ -34,9 +34,9 @@ export const useUser = defineStore("user", () => {
const jobMap = { const jobMap = {
teacher: 1, // 课程制作教师 teacher: 1, // 课程制作教师
projectManager: 2, // 课程购买方项目负责人 projectManager: 2, // 课程审核人员
liaison: 3, // 课程制作方沟通联络 liaison: 3, // 校方项目负责
sysManager: 4 // 系统制作方项目负责人 sysManager: 4 // 公司系统管理员
}; };
return userinfo.value.jobs === jobMap[job]; return userinfo.value.jobs === jobMap[job];

View File

@ -12,13 +12,15 @@ export interface LessonTask {
userId: number; userId: number;
/** 进度状态 */ /** 进度状态 */
progressStatus: number; progressStatus: number;
/** 脚本上传时间(时间戳) */ /** 脚本开始制作时间(时间戳) */
scriptUploadTime?: number; scriptCreateTime?: number;
/** 脚本确认时间(时间戳) */ /** 脚本提交审核时间(时间戳) */
scriptReviewTime?: number;
/** 脚本审核通过时间(时间戳) */
scriptConfirmTime?: number; scriptConfirmTime?: number;
/** 视频拍摄时间(时间戳) */ /** 开始视频制作时间(时间戳) */
videoCaptureTime?: number; videoCreateTime?: number;
/** 视频确认时间(时间戳) */ /** 视频审核通过时间(时间戳) */
videoConfirmTime?: number; videoConfirmTime?: number;
/** 任务完成时间(时间戳) */ /** 任务完成时间(时间戳) */
finishTime?: number; finishTime?: number;
@ -41,13 +43,22 @@ export interface LessonTaskPagination {
empty: boolean; empty: boolean;
} }
/**
* NOT_STARTED = 0, // 脚本制作
SCRIPT_CREATING = 1, // 脚本审核
SCRIPT_REVIEW = 2, // 脚本审核
SCRIPT_CONFIRMED = 3, // 视频拍摄与制作
VIDEO_CREATING = 4, // 视频确认
VIDEO_CONFIRMED = 5 // 任务完成
*/
// 进度状态枚举 // 进度状态枚举
export enum ProgressStatus { export enum ProgressStatus {
SCRIPT_UPLOAD = 0, // 脚本上传 NOT_STARTED = 0, // 脚本制作
SCRIPT_CONFIRM = 1, // 脚本确认 SCRIPT_CREATING = 1, // 脚本审核
VIDEO_CAPTURE = 2, // 视频拍摄 SCRIPT_REVIEW = 2, // 脚本审核
POST_PRODUCTION = 3, // 后期制作 SCRIPT_CONFIRMED = 3, // 视频拍摄与制作
FINISHED = 4, // 任务完成 VIDEO_CREATING = 4, // 视频确认
VIDEO_CONFIRMED = 5 // 任务完成
} }
export type FileUploadDestination = "qq" | "wechat" | "platform"; export type FileUploadDestination = "qq" | "wechat" | "platform";
@ -78,15 +89,17 @@ export interface UpdateLessonTaskRequest {
userId?: number; userId?: number;
/** 进度状态 */ /** 进度状态 */
progressStatus?: number; progressStatus?: number;
/** 脚本上传时间 */ /** 脚本开始制作时间 */
scriptUploadTime?: number; scriptCreateTime?: number;
/** 脚本确认时间 */ /** 脚本提交审核时间 */
scriptReviewTime?: number;
/** 脚本审核通过时间 */
scriptConfirmTime?: number; scriptConfirmTime?: number;
/** 视频拍摄时间 */ /** 开始视频制作时间 */
videoCaptureTime?: number; videoCreateTime?: number;
/** 视频确认时间 */ /** 视频审核通过时间 */
videoConfirmTime?: number; videoConfirmTime?: number;
/** 完成时间 */ /** 任务完成时间 */
finishTime?: number; finishTime?: number;
/** 建议信息 */ /** 建议信息 */
advise?: string; advise?: string;

View File

@ -20,18 +20,18 @@ export interface Authority {
// 角色枚举 // 角色枚举
export enum Roles { export enum Roles {
TEACHER = 1, // 教师 TEACHER = 1, // 校方教师
GENERAL_ADMIN = 2, // 普通管理员 GENERAL_ADMIN = 2, // 公司课程顾问
CONTACTOR = 3, // 沟通联络 CONTACTOR = 3, // 校方项目负责
SYSTEM_ADMIN = 4 // 系统管理员 SYSTEM_ADMIN = 4 // 公司系统管理员
} }
// 岗位枚举 // 岗位枚举
export enum Jobs { export enum Jobs {
COURSE_TEACHER = 1, // 课程制作教师 COURSE_TEACHER = 1, // 课程制作教师
PROJECT_MANAGER = 2, // 课程购买方项目负责人 PROJECT_MANAGER = 2, // 课程审核人员
COURSE_CONTACTOR = 3, // 课程制作方沟通联络 COURSE_CONTACTOR = 3, // 校方项目负责
SYSTEM_MANAGER = 4 // 系统制作方项目负责人 SYSTEM_MANAGER = 4 // 公司系统管理员
} }
// 用户状态枚举 // 用户状态枚举

View File

@ -2,14 +2,16 @@ import { useDayjs } from "@/composables/useDayjs";
import type { FileUploadDestination,LessonTask } from "@/types/api/lesson"; import type { FileUploadDestination,LessonTask } from "@/types/api/lesson";
export const extractLessonStage = (lesson: LessonTask) => { export const extractLessonStage = (lesson: LessonTask) => {
const currentStep = lesson?.progressStatus || 0;
const stages = { const stages = {
script_upload: !!lesson?.scriptUploadTime, script_creating: currentStep >= 1, // 脚本制作
script_confirm: !!lesson?.scriptConfirmTime, script_review: currentStep >= 2, // 脚本审核
video_capture: !!lesson?.videoCaptureTime, script_confirmed: currentStep >= 3, // 脚本确认
post_production: !!lesson?.videoConfirmTime, video_creating: currentStep >= 4, // 视频制作
video_confirmed: currentStep >= 5, // 视频确认
step: 0, step: 0,
}; };
stages.step = Object.values(stages).filter((v) => v).length; stages.step = currentStep;
return stages; return stages;
}; };
@ -17,18 +19,19 @@ export const calcLessonProgress = (lesson: LessonTask) => {
if (!lesson) return 0; if (!lesson) return 0;
// 根据 progressStatus 计算进度 // 根据 progressStatus 计算进度
// 0-脚本上传, 1-脚本确认, 2-视频拍摄, 3-后期制作, 4-任务完成 // 0-未开始, 1-脚本制作, 2-脚本审核, 3-脚本确认, 4-视频拍摄与制作, 5-视频确认
switch (lesson.progressStatus) { switch (lesson.progressStatus) {
case 4: // 任务完成 case 5: // 视频确认
return 100; return 100;
case 3: // 后期制作 case 4: // 视频拍摄与制作
return 75; return 80;
case 2: // 视频拍摄 case 3: // 脚本确认
return 50; return 60;
case 1: // 脚本确认 case 2: // 脚本审核
return 25; return 40;
case 0: // 脚本上传 case 1: // 脚本制作
return 0; return 20;
case 0: // 未开始
default: default:
return 0; return 0;
} }
@ -40,79 +43,51 @@ export const getLessonSteps = (lesson: LessonTask, simplify: boolean = false) =>
const progress = extractLessonStage(lesson); const progress = extractLessonStage(lesson);
const formatTime = (timestamp?: number | null) => { const formatTime = (timestamp?: number | null) => {
if (!timestamp) return '-'; if (!timestamp || isNaN(timestamp) || timestamp <= 0) return '-';
return dayjs(timestamp * 1000).format(dateFormat); const date = dayjs(timestamp * 1000);
return date.isValid() ? date.format(dateFormat) : '-';
}; };
return [ return [
{ {
title: progress.script_upload ? "脚本提交" : undefined, title: progress.script_creating ? "脚本制作" : undefined,
description: progress.script_upload description: progress.script_creating
? simplify ? simplify
? "已完成" ? "已完成"
: `已于 ${formatTime(lesson.scriptUploadTime)} 完成上传` : `已于 ${formatTime(lesson.scriptCreateTime)} 完成脚本制作`
: "脚本文件提交", : "开始制作脚本",
}, },
{ {
title: progress.script_confirm ? "脚本确认" : undefined, title: progress.script_review ? "脚本审核" : undefined,
description: progress.script_confirm description: progress.script_review
? simplify ? simplify
? "已完成" ? "已完成"
: `已于 ${formatTime(lesson.scriptConfirmTime)} 完成确认` : `已于 ${formatTime(lesson.scriptReviewTime)} 完成脚本审核`
: "脚本文件确认", : "提交脚本审核",
}, },
{ {
title: progress.video_capture ? "视频拍摄" : undefined, title: progress.script_confirmed ? "脚本确认" : undefined,
description: progress.video_capture description: progress.script_confirmed
? simplify ? simplify
? "已完成" ? "已完成"
: `已于 ${formatTime(lesson.videoCaptureTime)} 完成上传` : `已于 ${formatTime(lesson.scriptConfirmTime)} 完成脚本确认`
: "视频拍摄提交", : "确认脚本内容",
}, },
{ {
title: progress.post_production ? "后期制作" : undefined, title: progress.video_creating ? "视频拍摄制作" : undefined,
description: progress.post_production description: progress.video_creating
? simplify ? simplify
? "已完成" ? "已完成"
: `已于 ${formatTime(lesson.videoConfirmTime)} 完成上传` : `已于 ${formatTime(lesson.videoCreateTime)} 完成拍摄制作`
: "视频后期制作", : "视频拍摄制作",
},
{
title: progress.video_confirmed ? "视频确认" : undefined,
description: progress.video_confirmed
? simplify
? "已完成"
: `已于 ${formatTime(lesson.videoConfirmTime)} 完成视频确认`
: "确认视频内容",
}, },
]; ];
}; };
export const getScriptFile = (lesson: LessonTask) => {
const scriptFile = lesson.script_file ? lesson.script_file.split("|") : [];
return {
way: scriptFile[0] || null,
reupload: scriptFile[1] || null,
};
};
export const parseCombinedFileString = (
lesson: LessonTask,
// key: keyof Pick<LessonTask, "script_file" | "capture_file" | "material_file">
key: keyof Pick<LessonTask, "advise">
): {
method: FileUploadDestination | undefined;
uploaded: boolean;
} => {
const value = lesson[key];
if (!value) {
return {
method: undefined,
uploaded: false,
};
}
try {
const parsed = JSON.parse(value);
return {
method: (parsed.method as FileUploadDestination) || undefined,
uploaded: !!parsed.uploaded,
};
} catch {
return {
method: undefined,
uploaded: false,
};
}
};

View File

@ -15,7 +15,8 @@ CREATE TABLE departments (
``` ```
### 字段说明 ### 字段说明
- `id`: 部门ID自增主键
- `id`: 部门 ID自增主键
- `name`: 部门名称,不可为空 - `name`: 部门名称,不可为空
- `description`: 部门描述,可为空 - `description`: 部门描述,可为空
- `created_at`: 创建时间,毫秒级时间戳 - `created_at`: 创建时间,毫秒级时间戳
@ -32,8 +33,8 @@ CREATE TABLE users (
email VARCHAR(100) NOT NULL UNIQUE COMMENT '邮箱', email VARCHAR(100) NOT NULL UNIQUE COMMENT '邮箱',
password VARCHAR(100) NOT NULL COMMENT '密码', password VARCHAR(100) NOT NULL COMMENT '密码',
department_id BIGINT NOT NULL COMMENT '所属部门', department_id BIGINT NOT NULL COMMENT '所属部门',
roles INT NOT NULL COMMENT '角色: 1-教师, 2-普通管理员, 3-沟通联络人, 4-系统管理员', roles INT NOT NULL COMMENT '角色: 1-校方教师, 2-公司课程顾问, 3-校方项目负责人, 4-公司系统管理员',
jobs INT NOT NULL COMMENT '岗位: 1-课程制作教师, 2-课程购买方项目负责人, 3-课程制作方沟通联络人, 4-系统制作方项目负责人', jobs INT NOT NULL COMMENT '岗位: 1-课程制作教师, 2-课程审核人员, 3-校方项目负责人, 4-公司系统管理员',
avatar VARCHAR(255) DEFAULT NULL COMMENT '头像', avatar VARCHAR(255) DEFAULT NULL COMMENT '头像',
creator_id BIGINT NOT NULL DEFAULT 1 COMMENT '创建用户的管理员ID', creator_id BIGINT NOT NULL DEFAULT 1 COMMENT '创建用户的管理员ID',
status INT NOT NULL DEFAULT 1 COMMENT '用户状态: 1-正常, 0-禁用', status INT NOT NULL DEFAULT 1 COMMENT '用户状态: 1-正常, 0-禁用',
@ -45,11 +46,12 @@ CREATE TABLE users (
``` ```
### 字段说明 ### 字段说明
- `id`: 用户ID自增主键
- `id`: 用户 ID自增主键
- `username`: 用户名,不可为空 - `username`: 用户名,不可为空
- `email`: 邮箱地址,不可为空,唯一索引 - `email`: 邮箱地址,不可为空,唯一索引
- `password`: 密码(加密存储),不可为空 - `password`: 密码(加密存储),不可为空
- `department_id`: 所属部门ID外键关联departments表 - `department_id`: 所属部门 ID外键关联 departments
- `roles`: 用户角色,整数枚举: - `roles`: 用户角色,整数枚举:
- 1: 教师 - 1: 教师
- 2: 普通管理员 - 2: 普通管理员
@ -60,8 +62,8 @@ CREATE TABLE users (
- 2: 课程购买方项目负责人 - 2: 课程购买方项目负责人
- 3: 课程制作方沟通联络人 - 3: 课程制作方沟通联络人
- 4: 系统制作方项目负责人 - 4: 系统制作方项目负责人
- `avatar`: 用户头像URL可为空 - `avatar`: 用户头像 URL可为空
- `creator_id`: 创建该用户的管理员ID - `creator_id`: 创建该用户的管理员 ID
- `status`: 用户状态: - `status`: 用户状态:
- 1: 正常 - 1: 正常
- 0: 禁用 - 0: 禁用
@ -69,6 +71,7 @@ CREATE TABLE users (
- `updated_at`: 更新时间,毫秒级时间戳 - `updated_at`: 更新时间,毫秒级时间戳
### 索引 ### 索引
- 主键索引:`id` - 主键索引:`id`
- 外键索引:`idx_users_department_id (department_id)` - 外键索引:`idx_users_department_id (department_id)`
- 唯一索引:`email` - 唯一索引:`email`
@ -78,68 +81,114 @@ CREATE TABLE users (
存储课程制作任务的信息和进度。 存储课程制作任务的信息和进度。
```sql ```sql
CREATE TABLE lesson_tasks ( CREATE TABLE lesson_tasks
id BIGINT AUTO_INCREMENT PRIMARY KEY, (
course_name VARCHAR(100) NOT NULL COMMENT '课程名称', id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID系统自动生成的唯一标识',
micro_lesson_name VARCHAR(100) NOT NULL COMMENT '微课名称', course_name VARCHAR(100) NOT NULL COMMENT '所属课程名称,标识任务所属的主课程',
user_id BIGINT NOT NULL COMMENT '负责人ID', micro_lesson_name VARCHAR(100) NOT NULL COMMENT '具体微课名称,标识任务所属的具体微课单元',
progress_status INT NOT NULL DEFAULT 0 COMMENT '当前任务进度状态: 0-脚本上传, 1-脚本确认, 2-视频拍摄, 3-后期制作, 4-任务完成', user_id BIGINT NOT NULL COMMENT '任务负责人ID关联users表的主键id',
script_upload_time BIGINT DEFAULT NULL COMMENT '脚本上传时间(时间戳)', progress_status INT NOT NULL DEFAULT 0 COMMENT '任务进度状态:
script_confirm_time BIGINT DEFAULT NULL COMMENT '脚本确认时间(时间戳)', 0-未开始:任务创建后的初始状态
video_capture_time BIGINT DEFAULT NULL COMMENT '视频拍摄时间(时间戳)', 1-脚本制作:正在编写课程脚本
video_confirm_time BIGINT DEFAULT NULL COMMENT '视频确认时间(时间戳)', 2-脚本审核:脚本提交审核阶段
finish_time BIGINT DEFAULT NULL COMMENT '任务完成时间(时间戳)', 3-脚本确认:脚本审核通过确认
advise TEXT DEFAULT NULL COMMENT '任务建议或备注', 4-视频拍摄与制作:进行视频录制和后期制作
created_at BIGINT NOT NULL COMMENT '创建时间(时间戳)', 5-视频确认:最终视频审核确认',
updated_at BIGINT NOT NULL COMMENT '更新时间(时间戳)', script_create_time BIGINT DEFAULT NULL COMMENT '脚本开始制作的时间戳状态1时记录',
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE, script_review_time BIGINT DEFAULT NULL COMMENT '脚本提交审核的时间戳状态2时记录',
INDEX idx_lesson_tasks_user_id (user_id), script_confirm_time BIGINT DEFAULT NULL COMMENT '脚本审核通过的时间戳状态3时记录',
INDEX idx_lesson_tasks_progress_status (progress_status) video_create_time BIGINT DEFAULT NULL COMMENT '开始视频制作的时间戳状态4时记录',
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT ='课程任务表'; video_confirm_time BIGINT DEFAULT NULL COMMENT '视频审核通过的时间戳状态5时记录',
finish_time BIGINT DEFAULT NULL COMMENT '整个任务完成的时间戳,最终确认后记录',
advise TEXT DEFAULT NULL COMMENT '任务相关的建议、修改意见或其他重要备注信息',
created_at BIGINT NOT NULL COMMENT '记录创建的时间戳,系统自动生成',
updated_at BIGINT NOT NULL COMMENT '记录最后更新的时间戳,系统自动更新',
-- 外键约束确保user_id关联到users表的有效用户
CONSTRAINT fk_lesson_tasks_user FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE ON UPDATE CASCADE,
-- 索引设计:
-- 1. 用户ID索引用于快速查找特定用户的所有任务
INDEX idx_user_id (user_id),
-- 2. 进度状态索引:用于按状态筛选和统计任务
INDEX idx_progress_status (progress_status),
-- 3. 课程名称索引:用于按课程分组和查询
INDEX idx_course_name (course_name),
-- 4. 复合索引:用于同时按用户和状态查询
INDEX idx_user_progress (user_id, progress_status)
) ENGINE=InnoDB
DEFAULT CHARSET=utf8mb4
COMMENT='课程任务管理表:记录微课制作的完整流程,包括脚本编写、审核、视频制作等各个环节的进度和时间节点';
``` ```
### 字段说明 ### 字段说明
- `id`: 任务ID自增主键
- `id`: 任务 ID自增主键
- `course_name`: 课程名称,不可为空 - `course_name`: 课程名称,不可为空
- `micro_lesson_name`: 微课名称,不可为空 - `micro_lesson_name`: 微课名称,不可为空
- `user_id`: 负责人ID外键关联users表 - `user_id`: 负责人 ID外键关联 users
- `progress_status`: 任务进度状态: - `progress_status`: 任务进度状态:
- 0: 脚本上传 - 0: 未开始
- 1: 脚本确认 - 1: 脚本制作
- 2: 视频拍摄 - 2: 脚本审核
- 3: 后期制作 - 3: 脚本确认
- 4: 任务完成 - 4: 视频拍摄与制作
- `script_upload_time`: 脚本上传时间,毫秒级时间戳 - 5: 视频确认
- `script_confirm_time`: 脚本确认时间,毫秒级时间戳 - `script_create_time`: 脚本开始制作的时间戳,状态 1 时记录
- `video_capture_time`: 视频拍摄时间,毫秒级时间戳 - `script_review_time`: 脚本提交审核的时间戳,状态 2 时记录
- `video_confirm_time`: 视频确认时间,毫秒级时间戳 - `script_confirm_time`: 脚本审核通过的时间戳,状态 3 时记录
- `finish_time`: 任务完成时间,毫秒级时间戳 - `video_create_time`: 开始视频制作的时间戳,状态 4 时记录
- `advise`: 任务相关的建议或备注,文本字段 - `video_confirm_time`: 视频审核通过的时间戳,状态 5 时记录
- `created_at`: 创建时间,毫秒级时间戳 - `finish_time`: 整个任务完成的时间戳,最终确认后记录
- `updated_at`: 更新时间,毫秒级时间戳 - `advise`: 任务相关的建议、修改意见或其他重要备注信息
- `created_at`: 记录创建的时间戳,系统自动生成
- `updated_at`: 记录最后更新的时间戳,系统自动更新
### 索引 ### 索引
- 主键索引:`id` - 主键索引:`id`
- 外键索引:`idx_lesson_tasks_user_id (user_id)` - 外键索引:`idx_user_id (user_id)`
- 普通索引:`idx_lesson_tasks_progress_status (progress_status)` - 普通索引:`idx_progress_status (progress_status)`
- 普通索引:`idx_course_name (course_name)`
- 复合索引:`idx_user_progress (user_id, progress_status)`
## 数据库关系 ## 数据库关系
1. `users.department_id` -> `departments.id` 1. `users.department_id` -> `departments.id`
- 一个部门可以有多个用户 - 一个部门可以有多个用户
- 一个用户只能属于一个部门 - 一个用户只能属于一个部门
- 使用RESTRICT约束防止删除仍有用户的部门 - 使用 RESTRICT 约束,防止删除仍有用户的部门
2. `lesson_tasks.user_id` -> `users.id` 2. `lesson_tasks.user_id` -> `users.id`
- 一个用户可以负责多个课程任务 - 一个用户可以负责多个课程任务
- 一个课程任务只能有一个负责人 - 一个课程任务只能有一个负责人
- 使用CASCADE约束删除用户时自动删除其负责的课程任务 - 使用 CASCADE 约束,删除用户时自动删除其负责的课程任务
3. `lesson_tasks``users` 表关系:
- 一对多关系一个用户users可以负责多个课程任务lesson_tasks
- 通过 `user_id` 外键关联,确保任务负责人的有效性
- 使用 CASCADE 级联删除:当用户被删除时,相关任务也会被自动删除
- 使用 CASCADE 级联更新:当用户 ID 更新时,相关任务的 user_id 也会自动更新
4. 索引说明:
- `idx_user_id`: 优化按负责人查询任务的性能
- `idx_progress_status`: 优化按任务状态筛选和统计的性能
- `idx_course_name`: 优化按课程名称查询和分组的性能
- `idx_user_progress`: 优化同时按用户和任务状态查询的性能,适用于查看特定用户的特定状态任务
5. 时间节点追踪:
- 系统通过各个时间戳字段script_create_time, script_review_time 等)完整记录任务的每个阶段
- 可以通过这些时间戳计算各阶段的耗时,用于任务进度分析和效率优化
## 注意事项 ## 注意事项
1. 所有时间戳字段使用BIGINT类型存储毫秒级时间戳 1. 所有时间戳字段使用 BIGINT 类型,存储毫秒级时间戳
2. 字符编码统一使用utf8mb4支持完整的Unicode字符集 2. 字符编码统一使用 utf8mb4支持完整的 Unicode 字符集
3. 所有表都使用InnoDB引擎支持事务和外键 3. 所有表都使用 InnoDB 引擎,支持事务和外键
4. 关键字段都建立了适当的索引以提高查询性能 4. 关键字段都建立了适当的索引以提高查询性能
5. 用户密码在存储前需要进行加密处理 5. 用户密码在存储前需要进行加密处理
6. 删除用户时会自动删除其关联的课程任务,但不会影响部门数据 6. 删除用户时会自动删除其关联的课程任务,但不会影响部门数据

View File

@ -1,66 +0,0 @@
```sql
-- 插入角色:教师,岗位:课程制作教师
INSERT INTO users (username, email, password, department_id, roles, jobs, avatar, creator_id, status, created_at,
updated_at)
VALUES ('教师账号1', 'user1@qq.com', '$2a$10$6RzWwYoBa/ZFWc6U9LmshemE801Yc/aUw.KxgT6JAihcYRfDgaoZq', 2, 1, 1, NULL, 1,
1, UNIX_TIMESTAMP(), UNIX_TIMESTAMP());
-- 插入角色:普通管理员,岗位:课程购买方项目负责人
INSERT INTO users (username, email, password, department_id, roles, jobs, avatar, creator_id, status, created_at,
updated_at)
VALUES ('普通管理员账号2', 'user2@qq.com', '$2a$10$6RzWwYoBa/ZFWc6U9LmshemE801Yc/aUw.KxgT6JAihcYRfDgaoZq', 2, 2, 2,
NULL, 1, 1, UNIX_TIMESTAMP(), UNIX_TIMESTAMP());
-- 插入角色:沟通联络人,岗位:课程制作方沟通联络人
INSERT INTO users (username, email, password, department_id, roles, jobs, avatar, creator_id, status, created_at,
updated_at)
VALUES ('沟通联络人账号3', 'user3@qq.com', '$2a$10$6RzWwYoBa/ZFWc6U9LmshemE801Yc/aUw.KxgT6JAihcYRfDgaoZq', 1, 3, 3,
NULL, 1, 1, UNIX_TIMESTAMP(),
UNIX_TIMESTAMP());
-- 插入角色:系统管理员
INSERT INTO users (username, email, password, department_id, roles, jobs, avatar, creator_id, status, created_at,
updated_at)
VALUES ('系统管理员', 'admin@qq.com', '$2a$10$6RzWwYoBa/ZFWc6U9LmshemE801Yc/aUw.KxgT6JAihcYRfDgaoZq', 1, 4, 4, NULL, 1,
1, UNIX_TIMESTAMP(), UNIX_TIMESTAMP());
-- 课程名数学微课名微课1-1
INSERT INTO lesson_tasks (course_name, micro_lesson_name, user_id, progress_status, created_at, updated_at)
VALUES ('数学', '微课1-1', 1, 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP());
-- 课程名数学微课名微课1-2
INSERT INTO lesson_tasks (course_name, micro_lesson_name, user_id, progress_status, created_at, updated_at)
VALUES ('数学', '微课1-2', 1, 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP());
-- 课程名数学微课名微课1-3
INSERT INTO lesson_tasks (course_name, micro_lesson_name, user_id, progress_status, created_at, updated_at)
VALUES ('数学', '微课1-3', 1, 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP());
-- 课程名物理微课名微课2-1
INSERT INTO lesson_tasks (course_name, micro_lesson_name, user_id, progress_status, created_at, updated_at)
VALUES ('物理', '微课2-1', 1, 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP());
-- 课程名物理微课名微课2-2
INSERT INTO lesson_tasks (course_name, micro_lesson_name, user_id, progress_status, created_at, updated_at)
VALUES ('物理', '微课2-2', 1, 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP());
-- 课程名物理微课名微课2-3
INSERT INTO lesson_tasks (course_name, micro_lesson_name, user_id, progress_status, created_at, updated_at)
VALUES ('物理', '微课2-3', 1, 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP());
-- 课程名英语微课名微课3-1
INSERT INTO lesson_tasks (course_name, micro_lesson_name, user_id, progress_status, created_at, updated_at)
VALUES ('英语', '微课3-1', 1, 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP());
-- 课程名英语微课名微课3-2
INSERT INTO lesson_tasks (course_name, micro_lesson_name, user_id, progress_status, created_at, updated_at)
VALUES ('英语', '微课3-2', 1, 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP());
-- 课程名英语微课名微课3-3
INSERT INTO lesson_tasks (course_name, micro_lesson_name, user_id, progress_status, created_at, updated_at)
VALUES ('英语', '微课3-3', 1, 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP());
INSERT INTO departments (name, description, created_at, updated_at)
VALUES ('重庆眨生花科技有限公司', '重庆眨生花科技有限公司', NOW(), NOW()),
('重庆电子科技职业大学', '重庆电子科技职业大学', NOW(), NOW());
```