feat: 添加角色权限控制和教师筛选功能
This commit is contained in:
parent
635a74385d
commit
bb319d7946
549
API文档.md
Normal file
549
API文档.md
Normal file
@ -0,0 +1,549 @@
|
||||
# 进度管理系统 API 文档
|
||||
|
||||
## 基础信息
|
||||
|
||||
- 基础 URL: `http://localhost:1218`
|
||||
- 所有请求和响应均使用 JSON 格式
|
||||
- 所有需要认证的接口都需要在请求头中携带 `Authorization: Bearer {token}`
|
||||
|
||||
## 通用响应格式
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 10000, // 响应码
|
||||
"message": "成功", // 响应消息
|
||||
"data": {} // 响应数据
|
||||
}
|
||||
```
|
||||
|
||||
### 响应码说明
|
||||
|
||||
| 响应码 | 说明 | 消息 |
|
||||
| ------ | ---------- | ---------- |
|
||||
| 10000 | 成功 | 成功 |
|
||||
| 10001 | 参数错误 | 参数无效 |
|
||||
| 10002 | 邮箱重复 | 邮箱已存在 |
|
||||
| 10003 | 用户不存在 | 用户不存在 |
|
||||
| 10004 | 密码错误 | 密码错误 |
|
||||
| 10005 | 邮箱已存在 | 邮箱已存在 |
|
||||
| 10006 | 未授权 | 未授权 |
|
||||
| 10007 | 令牌过期 | 令牌已过期 |
|
||||
| 10008 | 令牌无效 | 无效的令牌 |
|
||||
| 10009 | 系统错误 | 系统错误 |
|
||||
|
||||
## 字段说明
|
||||
|
||||
### 用户相关字段
|
||||
|
||||
1. **角色 (roles)**
|
||||
|
||||
- 1: 教师
|
||||
- 2: 普通管理员
|
||||
- 3: 沟通联络人
|
||||
- 4: 系统管理员
|
||||
|
||||
2. **岗位 (jobs)**
|
||||
|
||||
- 1: 课程制作教师
|
||||
- 2: 课程购买方项目负责人
|
||||
- 3: 课程制作方沟通联络人
|
||||
- 4: 系统制作方项目负责人
|
||||
|
||||
3. **用户状态 (status)**
|
||||
- 1: 正常
|
||||
- 0: 禁用
|
||||
|
||||
### 课程任务相关字段
|
||||
|
||||
1. **进度状态 (progressStatus)**
|
||||
- 0: 脚本上传
|
||||
- 1: 脚本确认
|
||||
- 2: 视频拍摄
|
||||
- 3: 后期制作
|
||||
- 4: 任务完成
|
||||
|
||||
## 用户接口
|
||||
|
||||
### 1. 用户注册
|
||||
|
||||
- **接口**:`POST /api/users`
|
||||
- **描述**:创建新用户
|
||||
- **认证**:不需要
|
||||
- **请求体**:
|
||||
```json
|
||||
{
|
||||
"username": "testuser", // 用户名,不可为空
|
||||
"email": "test@example.com", // 邮箱,不可为空且唯一
|
||||
"password": "password123", // 密码,不可为空
|
||||
"departmentId": 1, // 部门ID,不可为空,关联departments表
|
||||
"roles": 1, // 角色:1-教师,2-普通管理员,3-沟通联络人,4-系统管理员
|
||||
"jobs": 1, // 岗位:1-课程制作教师,2-课程购买方项目负责人,3-课程制作方沟通联络人,4-系统制作方项目负责人
|
||||
"avatar": "http://example.com/avatar.jpg", // 头像URL,可选
|
||||
"creatorId": 1 // 创建者ID,不可为空
|
||||
}
|
||||
```
|
||||
- **成功响应**:
|
||||
```json
|
||||
{
|
||||
"code": 10000,
|
||||
"message": "成功",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
- **错误响应**:
|
||||
```json
|
||||
{
|
||||
"code": 10005,
|
||||
"message": "邮箱已存在",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 用户登录
|
||||
|
||||
- **接口**:`POST /api/users/login`
|
||||
- **描述**:用户登录获取 token
|
||||
- **认证**:不需要
|
||||
- **请求体**:
|
||||
```json
|
||||
{
|
||||
"email": "test@example.com", // 邮箱
|
||||
"password": "password123", // 密码
|
||||
"remember": true // 是否记住登录(可选)
|
||||
}
|
||||
```
|
||||
- **成功响应**:
|
||||
```json
|
||||
{
|
||||
"code": 10000,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"token": "eyJhbGciOiJIUzM4NCJ9..." // JWT令牌
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 用户登出
|
||||
|
||||
- **接口**:`POST /api/users/logout`
|
||||
- **描述**:用户登出,使当前 token 失效
|
||||
- **认证**:需要
|
||||
- **成功响应**:
|
||||
```json
|
||||
{
|
||||
"code": 10000,
|
||||
"message": "成功",
|
||||
"data": "登出成功"
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 获取当前用户信息
|
||||
|
||||
- **接口**:`GET /api/users/current`
|
||||
- **描述**:获取当前登录用户信息
|
||||
- **认证**:需要
|
||||
- **成功响应**:
|
||||
```json
|
||||
{
|
||||
"code": 10000,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"id": 12,
|
||||
"username": "testuser",
|
||||
"email": "test@example.com",
|
||||
"departmentId": 1,
|
||||
"roles": 1,
|
||||
"jobs": 1,
|
||||
"avatar": null,
|
||||
"creatorId": 1,
|
||||
"status": 1,
|
||||
"createdAt": 1734578081,
|
||||
"updatedAt": 1734578081,
|
||||
"enabled": true,
|
||||
"authorities": [
|
||||
{
|
||||
"authority": "ROLE_USER"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5. 获取用户列表
|
||||
|
||||
- **接口**:`GET /api/users/list`
|
||||
- **描述**:分页获取用户列表
|
||||
- **认证**:需要
|
||||
- **查询参数**:
|
||||
- `page`: 页码(从 1 开始)
|
||||
- `limit`: 每页数量(默认 10)
|
||||
- **成功响应**:
|
||||
```json
|
||||
{
|
||||
"code": 10000,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"list": [
|
||||
{
|
||||
"id": 1,
|
||||
"username": "admin",
|
||||
"email": "admin@example.com",
|
||||
"departmentId": 1,
|
||||
"roles": 4,
|
||||
"jobs": 4,
|
||||
"avatar": null,
|
||||
"creatorId": 1,
|
||||
"status": 1,
|
||||
"createdAt": 1734578081,
|
||||
"updatedAt": 1734578081
|
||||
}
|
||||
],
|
||||
"total": 12,
|
||||
"currentPage": 1,
|
||||
"pageSize": 10
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 6. 禁用用户
|
||||
|
||||
- **接口**:`POST /api/users/disable/{userId}`
|
||||
- **描述**:禁用指定用户
|
||||
- **认证**:需要
|
||||
- **路径参数**:
|
||||
- `userId`: 用户 ID
|
||||
- **成功响应**:
|
||||
```json
|
||||
{
|
||||
"code": 10000,
|
||||
"message": "成功",
|
||||
"data": "用户已禁用"
|
||||
}
|
||||
```
|
||||
|
||||
### 7. 查询部门用户列表
|
||||
|
||||
- **接口**:`GET /api/users/department/{departmentId}`
|
||||
- **描述**:获取指定部门下的所有正常状态用户列表
|
||||
- **认证**:需要
|
||||
- **路径参数**:
|
||||
- `departmentId`: 部门 ID
|
||||
- **成功响应**:
|
||||
```json
|
||||
{
|
||||
"code": 10000,
|
||||
"message": "成功",
|
||||
"data": [
|
||||
{
|
||||
"id": 2,
|
||||
"username": "普通管理员账号2",
|
||||
"email": "user2@qq.com",
|
||||
"password": null,
|
||||
"departmentId": 1,
|
||||
"roles": 2,
|
||||
"jobs": 2,
|
||||
"avatar": null,
|
||||
"creatorId": 1,
|
||||
"status": 1,
|
||||
"createdAt": 1734504506,
|
||||
"updatedAt": 1734504506,
|
||||
"enabled": true,
|
||||
"authorities": [
|
||||
{
|
||||
"authority": "ROLE_USER"
|
||||
}
|
||||
],
|
||||
"accountNonExpired": true,
|
||||
"accountNonLocked": true,
|
||||
"credentialsNonExpired": true
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
- **错误响应**:
|
||||
```json
|
||||
{
|
||||
"code": 50000,
|
||||
"message": "获取部门用户列表失败",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
## 课程任务接口
|
||||
|
||||
### 1. 获取课程任务列表
|
||||
|
||||
- **接口**:`GET /api/lesson-tasks`
|
||||
- **描述**:分页获取课程任务列表
|
||||
- **认证**:需要
|
||||
- **查询参数**:
|
||||
- `page`: 页码(从 1 开始)
|
||||
- `size`: 每页数量(默认 10)
|
||||
- `userId`: 用户 ID(可选)
|
||||
- **成功响应**:
|
||||
```json
|
||||
{
|
||||
"code": 10000,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"content": [
|
||||
{
|
||||
"id": 6,
|
||||
"courseName": "Test Course",
|
||||
"microLessonName": "Test Lesson",
|
||||
"userId": 12,
|
||||
"progressStatus": 1,
|
||||
"scriptUploadTime": 1734578081,
|
||||
"scriptConfirmTime": 1734578081,
|
||||
"videoCaptureTime": 1734578081,
|
||||
"videoConfirmTime": 1734578081,
|
||||
"finishTime": 1734578081,
|
||||
"advise": "Test advice",
|
||||
"createdAt": 1734578081,
|
||||
"updatedAt": 1734578081
|
||||
}
|
||||
],
|
||||
"totalElements": 4,
|
||||
"totalPages": 1,
|
||||
"size": 10,
|
||||
"number": 0,
|
||||
"first": true,
|
||||
"last": true,
|
||||
"empty": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 获取单个课程任务
|
||||
|
||||
- **接口**:`GET /api/lesson-tasks/{id}`
|
||||
- **描述**:获取指定 ID 的课程任务
|
||||
- **认证**:需要
|
||||
- **路径参数**:
|
||||
- `id`: 课程任务 ID
|
||||
- **成功响应**:
|
||||
```json
|
||||
{
|
||||
"code": 10000,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"id": 6,
|
||||
"courseName": "Test Course",
|
||||
"microLessonName": "Test Lesson",
|
||||
"userId": 12,
|
||||
"progressStatus": 1,
|
||||
"scriptUploadTime": 1734578081,
|
||||
"scriptConfirmTime": 1734578081,
|
||||
"videoCaptureTime": 1734578081,
|
||||
"videoConfirmTime": 1734578081,
|
||||
"finishTime": 1734578081,
|
||||
"advise": "Test advice",
|
||||
"createdAt": 1734578081,
|
||||
"updatedAt": 1734578081
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 创建课程任务
|
||||
|
||||
- **接口**:`POST /api/lesson-tasks`
|
||||
- **描述**:创建新的课程任务
|
||||
- **认证**:需要
|
||||
- **请求体**:
|
||||
```json
|
||||
{
|
||||
"courseName": "Java基础", // 课程名称,不可为空
|
||||
"microLessonName": "Java变量", // 微课名称,不可为空
|
||||
"userId": 1, // 负责人ID,不可为空,关联users表
|
||||
"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" // 建议
|
||||
}
|
||||
```
|
||||
- **成功响应**:
|
||||
```json
|
||||
{
|
||||
"code": 10000,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"id": 6,
|
||||
"courseName": "Updated Course",
|
||||
"microLessonName": "Updated Lesson",
|
||||
"userId": 12,
|
||||
"progressStatus": 2,
|
||||
"scriptUploadTime": 1734578081,
|
||||
"scriptConfirmTime": 1734578081,
|
||||
"videoCaptureTime": 1734578081,
|
||||
"videoConfirmTime": 1734578081,
|
||||
"finishTime": 1734578081,
|
||||
"advise": "Updated advice",
|
||||
"createdAt": 1734578081,
|
||||
"updatedAt": 1734578081
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5. 删除课程任务
|
||||
|
||||
- **接口**:`DELETE /api/lesson-tasks/{id}`
|
||||
- **描述**:删除指定 ID 的课程任务
|
||||
- **认证**:需要
|
||||
- **路径参数**:
|
||||
- `id`: 课程任务 ID
|
||||
- **成功响应**:
|
||||
```json
|
||||
{
|
||||
"code": 10000,
|
||||
"message": "成功",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
### 6. 按部门 ID 查询课程任务
|
||||
|
||||
- **接口**:`GET /api/lesson-tasks/department/{departmentId}`
|
||||
- **描述**:获取指定部门下正常状态用户的课程任务列表(分页)
|
||||
- **认证**:需要
|
||||
- **路径参数**:
|
||||
- `departmentId`: 部门 ID
|
||||
- **查询参数**:
|
||||
- `page`: 页码(从 1 开始)
|
||||
- `size`: 每页数量(默认 10)
|
||||
- **成功响应**:
|
||||
```json
|
||||
{
|
||||
"code": 10000,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"content": [
|
||||
{
|
||||
"id": 1,
|
||||
"courseName": "物理",
|
||||
"microLessonName": "微课1-1",
|
||||
"userId": 1,
|
||||
"username": "教师账号1", // 新增:用户名字段
|
||||
"progressStatus": 4,
|
||||
"scriptUploadTime": 1734498510,
|
||||
"scriptConfirmTime": 1734498510,
|
||||
"videoCaptureTime": 1734498510,
|
||||
"videoConfirmTime": 1734498510,
|
||||
"finishTime": 1734498510,
|
||||
"advise": null,
|
||||
"createdAt": 1734578081,
|
||||
"updatedAt": 1734580393
|
||||
}
|
||||
],
|
||||
"totalElements": 10,
|
||||
"totalPages": 1,
|
||||
"size": 10,
|
||||
"number": 0,
|
||||
"first": true,
|
||||
"last": true,
|
||||
"empty": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 7. 更新课程任务进度
|
||||
|
||||
- **接口**:`PUT /api/lesson-tasks/{id}`
|
||||
- **描述**:更新课程任务的进度状态和建议
|
||||
- **认证**:需要
|
||||
- **路径参数**:
|
||||
- `id`: 课程任务 ID
|
||||
- **请求体**:
|
||||
```json
|
||||
{
|
||||
"progressStatus": 2, // 进度状态(可选)
|
||||
"advise": "{\"method\":\"wechat\",\"uploaded\":true}" // 建议(可选)
|
||||
}
|
||||
```
|
||||
- **说明**:
|
||||
- 更新进度状态时会自动更新对应的时间戳:
|
||||
- 状态 1:更新 scriptUploadTime
|
||||
- 状态 2:更新 scriptConfirmTime
|
||||
- 状态 3:更新 videoCaptureTime
|
||||
- 状态 4:更新 videoConfirmTime
|
||||
- 状态 5:更新 finishTime
|
||||
- 只会更新请求体中包含的字段,未提供的字段保持不变
|
||||
- **成功响应**:
|
||||
```json
|
||||
{
|
||||
"code": 10000,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"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 令牌
|
||||
2. 所有时间戳字段均为秒级时间戳
|
||||
3. 分页接口的页码从 1 开始
|
||||
4. 用户密码在传输和存储时都会进行加密处理
|
||||
5. 课程任务的 progressStatus 字段状态码说明:
|
||||
- 0: 脚本上传
|
||||
- 1: 脚本确认
|
||||
- 2: 视频拍摄
|
||||
- 3: 后期制作
|
||||
- 4: 任务完成
|
||||
6. 用户状态说明:
|
||||
- 1: 正常
|
||||
- 0: 禁用
|
1
components.d.ts
vendored
1
components.d.ts
vendored
@ -12,6 +12,7 @@ declare module 'vue' {
|
||||
WdButton: typeof import('wot-design-uni/components/wd-button/wd-button.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']
|
||||
WdCheckbox: typeof import('wot-design-uni/components/wd-checkbox/wd-checkbox.vue')['default']
|
||||
WdCollapse: typeof import('wot-design-uni/components/wd-collapse/wd-collapse.vue')['default']
|
||||
WdCollapseItem: typeof import('wot-design-uni/components/wd-collapse-item/wd-collapse-item.vue')['default']
|
||||
WdDropMenu: typeof import('wot-design-uni/components/wd-drop-menu/wd-drop-menu.vue')['default']
|
||||
|
@ -1,59 +1,137 @@
|
||||
import http from "@/http/HttpClient";
|
||||
import { useUser } from "@/stores/useUser";
|
||||
import type { PagedData } from "@/types/api/common";
|
||||
import type { Lesson } from "@/types/api/lesson";
|
||||
import type { User } from "@/types/api/user";
|
||||
|
||||
export interface LoginRequest extends Record<string, string> {
|
||||
email: string;
|
||||
password: string;
|
||||
remember: string;
|
||||
}
|
||||
import type { CreateLessonTaskRequest, LessonTask, UpdateLessonTaskRequest } from "@/types/api/lesson";
|
||||
import type { LoginRequest } from "@/types/api/auth";
|
||||
import type { CreateUserRequest, User } from "@/types/api/user";
|
||||
|
||||
/**
|
||||
* 业务API类
|
||||
*/
|
||||
export default class BussApi {
|
||||
/**
|
||||
* 用户登录
|
||||
* @param params - 登录请求参数
|
||||
* @param params.email - 用户邮箱
|
||||
* @param params.password - 用户密码
|
||||
* @param params.remember - 是否记住登录状态
|
||||
* @returns Promise<{token: string}> 返回包含token的对象
|
||||
*/
|
||||
static login(params: LoginRequest): Promise<{ token: string }> {
|
||||
return http
|
||||
.server()
|
||||
.post("/login", params)
|
||||
.then((res) => res.data);
|
||||
.post("users/login", params)
|
||||
.then((res) => res.data.data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户信息
|
||||
* @param token - 用户认证token
|
||||
* @returns Promise<User> 返回用户信息
|
||||
*/
|
||||
static profile(token: string): Promise<User> {
|
||||
return http
|
||||
.server()
|
||||
.get("/user/online", {
|
||||
.get("users/current", {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
})
|
||||
.then((res) => res.data);
|
||||
.then((res) => res.data.data);
|
||||
}
|
||||
|
||||
static lessons(page: number = 1, limit: number = 20) {
|
||||
/**
|
||||
* 用户登出
|
||||
* @returns Promise<string> 返回登出结果
|
||||
*/
|
||||
static logout(): Promise<string> {
|
||||
const user = useUser();
|
||||
return http
|
||||
.server()
|
||||
.get<PagedData<Lesson>>("/lesson/task", {
|
||||
.post("users/logout", null, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${user.token}`,
|
||||
},
|
||||
params: { page, limit },
|
||||
})
|
||||
.then((res) => {
|
||||
if (user.hasJobTag("A")) {
|
||||
res.data.data = res.data.data.filter(
|
||||
(lesson) => lesson.user_id === user.userinfo?.id
|
||||
);
|
||||
}
|
||||
return res.data;
|
||||
});
|
||||
.then((res) => res.data.data);
|
||||
}
|
||||
|
||||
static course(id: number): Promise<Lesson> {
|
||||
/**
|
||||
* 获取课程任务列表
|
||||
* @param page - 页码,默认为1
|
||||
* @param size - 每页数量,默认为10
|
||||
* @param userId - 可选的用户ID,用于筛选特定用户的课程任务
|
||||
* @returns Promise<{
|
||||
code: number;
|
||||
message: string;
|
||||
data: {
|
||||
content: LessonTask[];
|
||||
pageable: {
|
||||
pageNumber: number;
|
||||
pageSize: number;
|
||||
sort: {
|
||||
empty: boolean;
|
||||
unsorted: boolean;
|
||||
sorted: boolean;
|
||||
};
|
||||
offset: number;
|
||||
paged: boolean;
|
||||
unpaged: boolean;
|
||||
};
|
||||
totalElements: number;
|
||||
totalPages: number;
|
||||
last: boolean;
|
||||
first: boolean;
|
||||
empty: boolean;
|
||||
number: number;
|
||||
size: number;
|
||||
numberOfElements: number;
|
||||
};
|
||||
}> 返回分页的课程任务数据
|
||||
*/
|
||||
static getLessonTasks(
|
||||
page: number = 1,
|
||||
size: number = 20,
|
||||
userId?: number
|
||||
): Promise<{
|
||||
code: number;
|
||||
message: string;
|
||||
data: {
|
||||
content: LessonTask[];
|
||||
pageable: {
|
||||
pageNumber: number;
|
||||
pageSize: number;
|
||||
sort: {
|
||||
empty: boolean;
|
||||
unsorted: boolean;
|
||||
sorted: boolean;
|
||||
};
|
||||
offset: number;
|
||||
paged: boolean;
|
||||
unpaged: boolean;
|
||||
};
|
||||
totalElements: number;
|
||||
totalPages: number;
|
||||
last: boolean;
|
||||
first: boolean;
|
||||
empty: boolean;
|
||||
number: number;
|
||||
size: number;
|
||||
numberOfElements: number;
|
||||
};
|
||||
}> {
|
||||
const user = useUser();
|
||||
const params: any = {
|
||||
page,
|
||||
size,
|
||||
};
|
||||
if (userId) {
|
||||
params.userId = userId;
|
||||
}
|
||||
return http
|
||||
.server()
|
||||
.get(`/lesson/task/${id}`, {
|
||||
.get("lesson-tasks", {
|
||||
params,
|
||||
headers: {
|
||||
Authorization: `Bearer ${user.token}`,
|
||||
},
|
||||
@ -61,11 +139,209 @@ export default class BussApi {
|
||||
.then((res) => res.data);
|
||||
}
|
||||
|
||||
static editCourse(id: number, params: Partial<Lesson>): Promise<Lesson> {
|
||||
/**
|
||||
* 获取单个课程任务详情
|
||||
* @param id - 课程任务ID
|
||||
* @returns Promise<LessonTask> 返回课程任务详情
|
||||
*/
|
||||
static getLessonTask(id: number): Promise<LessonTask> {
|
||||
const user = useUser();
|
||||
return http
|
||||
.server()
|
||||
.put(`/lesson/task/${id}`, params, {
|
||||
.get(`lesson-tasks/${id}`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${user.token}`,
|
||||
},
|
||||
})
|
||||
.then((res) => res.data.data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取课程详情
|
||||
* @param lessonId - 课程ID
|
||||
* @returns Promise<LessonTask> 返回课程详情
|
||||
*/
|
||||
static getLessonDetail(lessonId: number): Promise<LessonTask> {
|
||||
const user = useUser();
|
||||
return http
|
||||
.server()
|
||||
.get(`lesson/${lessonId}`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${user.token}`,
|
||||
},
|
||||
})
|
||||
.then((res) => res.data.data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新课程进度
|
||||
* @param lessonId - 课程ID
|
||||
* @returns Promise<void>
|
||||
*/
|
||||
static updateLessonProgress(lessonId: number): Promise<void> {
|
||||
const user = useUser();
|
||||
return http
|
||||
.server()
|
||||
.post(`lesson/${lessonId}/progress`, null, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${user.token}`,
|
||||
},
|
||||
})
|
||||
.then((res) => res.data.data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新课程任务
|
||||
* @param id - 课程任务ID
|
||||
* @param params - 更新参数,所有字段都是可选的
|
||||
* @returns Promise<LessonTask> 返回更新后的课程任务
|
||||
*/
|
||||
static updateLessonTask(id: number, params: UpdateLessonTaskRequest): Promise<LessonTask> {
|
||||
const user = useUser();
|
||||
return http
|
||||
.server()
|
||||
.put(`lesson-tasks/${id}`, params, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${user.token}`,
|
||||
},
|
||||
})
|
||||
.then((res) => res.data.data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除课程任务
|
||||
* @param id - 课程任务ID
|
||||
* @returns Promise<void>
|
||||
*/
|
||||
static deleteLessonTask(id: number): Promise<void> {
|
||||
const user = useUser();
|
||||
return http
|
||||
.server()
|
||||
.delete(`lesson-tasks/${id}`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${user.token}`,
|
||||
},
|
||||
})
|
||||
.then((res) => res.data.data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取部门课程任务列表
|
||||
* @param departmentId - 部门ID
|
||||
* @param page - 页码,默认为1
|
||||
* @param size - 每页数量,默认为20
|
||||
* @returns Promise<{
|
||||
* code: number; // 响应状态码
|
||||
* message: string; // 响应消息
|
||||
* data: {
|
||||
* list: Array<{
|
||||
* id: number; // 用户ID
|
||||
* username: string; // 用户名
|
||||
* email: string; // 邮箱
|
||||
* departmentId: number; // 部门ID
|
||||
* roles: number; // 角色
|
||||
* jobs: number; // 岗位
|
||||
* avatar: string; // 头像
|
||||
* creatorId: number; // 创建者ID
|
||||
* status: number; // 状态
|
||||
* createdAt: number; // 创建时间
|
||||
* updatedAt: number; // 更新时间
|
||||
* }>;
|
||||
* total: number; // 总数
|
||||
* currentPage: number; // 当前页
|
||||
* pageSize: number; // 每页数量
|
||||
* };
|
||||
* }> 返回分页的课程任务数据
|
||||
*/
|
||||
static getDepartmentLessonTasks(
|
||||
departmentId: number,
|
||||
page: number = 1,
|
||||
size: number = 20
|
||||
): Promise<{
|
||||
code: number;
|
||||
message: string;
|
||||
data: {
|
||||
content: LessonTask[]; // 课程任务列表
|
||||
pageable: {
|
||||
pageNumber: number; // 当前页码
|
||||
pageSize: number; // 每页大小
|
||||
sort: {
|
||||
empty: boolean; // 是否无排序
|
||||
unsorted: boolean; // 是否未排序
|
||||
sorted: boolean; // 是否已排序
|
||||
};
|
||||
offset: number; // 偏移量
|
||||
paged: boolean; // 是否分页
|
||||
unpaged: boolean; // 是否不分页
|
||||
};
|
||||
totalElements: number; // 总记录数
|
||||
totalPages: number; // 总页数
|
||||
last: boolean; // 是否最后一页
|
||||
first: boolean; // 是否第一页
|
||||
empty: boolean; // 是否为空
|
||||
number: number; // 当前页码
|
||||
size: number; // 每页大小
|
||||
numberOfElements: number; // 当前页记录数
|
||||
};
|
||||
}> {
|
||||
const user = useUser();
|
||||
const params: any = {
|
||||
page,
|
||||
size,
|
||||
departmentId
|
||||
};
|
||||
return http
|
||||
.server()
|
||||
.get("lesson-tasks/department", {
|
||||
params,
|
||||
headers: {
|
||||
Authorization: `Bearer ${user.token}`,
|
||||
},
|
||||
})
|
||||
.then((res) => res.data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取部门下的所有教师
|
||||
* @param departmentId - 部门ID
|
||||
* @returns Promise<{
|
||||
* code: number; // 响应状态码
|
||||
* message: string; // 响应消息
|
||||
* data: {
|
||||
* list: Array<{
|
||||
* id: number; // 用户ID
|
||||
* username: string; // 用户名
|
||||
* email: string; // 邮箱
|
||||
* departmentId: number; // 部门ID
|
||||
* roles: number; // 角色
|
||||
* jobs: number; // 岗位
|
||||
* status: number; // 状态
|
||||
* createdAt: number; // 创建时间
|
||||
* updatedAt: number; // 更新时间
|
||||
* }>;
|
||||
* total: number; // 总数
|
||||
* };
|
||||
* }>
|
||||
*/
|
||||
static getDepartmentTeachers(departmentId: number): Promise<{
|
||||
code: number;
|
||||
message: string;
|
||||
data: Array<{
|
||||
id: number;
|
||||
username: string;
|
||||
email: string;
|
||||
departmentId: number;
|
||||
roles: number;
|
||||
jobs: number;
|
||||
status: number;
|
||||
createdAt: number;
|
||||
updatedAt: number;
|
||||
}>;
|
||||
}> {
|
||||
const user = useUser();
|
||||
return http
|
||||
.server()
|
||||
.get(`users/department/${departmentId}`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${user.token}`,
|
||||
},
|
||||
|
@ -3,6 +3,7 @@ import { nextTick, ref } from 'vue';
|
||||
import { useRouter, useRoute } from 'uni-mini-router'
|
||||
import { onMounted, computed, watch } from 'vue';
|
||||
import { useTabbar } from '@/stores/useTabbar';
|
||||
import { useUser } from '@/stores/useUser'; // Import the useUser store
|
||||
|
||||
const props = defineProps({
|
||||
currentName: {
|
||||
@ -13,6 +14,7 @@ const props = defineProps({
|
||||
|
||||
const router = useRouter()
|
||||
const tab = useTabbar()
|
||||
const user = useUser() // Get the user store instance
|
||||
|
||||
// const isFirstTime = ref(true)
|
||||
|
||||
@ -28,25 +30,37 @@ const tabWhitelist = ['home', 'progress', 'my']
|
||||
const nameLabelIconMap = {
|
||||
home: {
|
||||
title: '进度查看',
|
||||
icon: 'dashboard'
|
||||
icon: 'dashboard',
|
||||
roles: ['teacher', 'admin', 'liaison', 'sysadmin'] as const // 使用 as const 来确保类型正确
|
||||
},
|
||||
progress: {
|
||||
title: '进度管理',
|
||||
icon: 'transfer'
|
||||
icon: 'transfer',
|
||||
roles: ['teacher', 'admin', 'sysadmin'] as const
|
||||
},
|
||||
my: {
|
||||
title: '我的',
|
||||
icon: 'user'
|
||||
icon: 'user',
|
||||
roles: ['teacher', 'admin', 'liaison', 'sysadmin'] as const
|
||||
}
|
||||
}
|
||||
|
||||
const tabList = computed(() => router.routes.filter((r: { name: string }) => tabWhitelist.includes(r.name)).map((route: { name: keyof typeof nameLabelIconMap }) => {
|
||||
return {
|
||||
name: route.name,
|
||||
title: nameLabelIconMap[route.name]?.title,
|
||||
icon: nameLabelIconMap[route.name]?.icon
|
||||
}
|
||||
}))
|
||||
const tabList = computed(() => {
|
||||
// 过滤出当前用户有权限看到的导航项
|
||||
return router.routes
|
||||
.filter((r: { name: string }) => {
|
||||
const config = nameLabelIconMap[r.name as keyof typeof nameLabelIconMap]
|
||||
// 检查该路由是否在配置中,且用户是否有权限访问
|
||||
return config && config.roles.some(role => user.hasRole(role as "teacher" | "admin" | "liaison" | "sysadmin"))
|
||||
})
|
||||
.map((route: { name: keyof typeof nameLabelIconMap }) => {
|
||||
return {
|
||||
name: route.name,
|
||||
title: nameLabelIconMap[route.name]?.title,
|
||||
icon: nameLabelIconMap[route.name]?.icon
|
||||
}
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -2,7 +2,9 @@ import { defineStore } from "pinia";
|
||||
import { ref } from "vue";
|
||||
|
||||
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://192.168.0.178:1218/api");
|
||||
|
||||
return {
|
||||
BASE_URL
|
||||
|
@ -1,11 +1,12 @@
|
||||
import { useUser } from "@/stores/useUser";
|
||||
import { useConfig } from "@/composables/useConfig"; // add this line
|
||||
import axios from "axios";
|
||||
import { uniAdapter } from "fant-axios-adapter";
|
||||
|
||||
export default class ApiClient {
|
||||
public static server() {
|
||||
const BASE_URL = "https://ppmp.fenshenzhike.com/api";
|
||||
return ApiClient.create(BASE_URL);
|
||||
const config = useConfig();
|
||||
return ApiClient.create(config.BASE_URL);
|
||||
}
|
||||
|
||||
public static create(baseUrl: string) {
|
||||
@ -19,16 +20,20 @@ export default class ApiClient {
|
||||
if (request.headers) {
|
||||
request.headers.set(
|
||||
"Content-Type",
|
||||
"application/x-www-form-urlencoded"
|
||||
"application/json;charset=UTF-8"
|
||||
);
|
||||
} else {
|
||||
request.headers = new axios.AxiosHeaders();
|
||||
request.headers.set(
|
||||
"Content-Type",
|
||||
"application/x-www-form-urlencoded"
|
||||
"application/json;charset=UTF-8"
|
||||
);
|
||||
}
|
||||
request.headers.trace_id = new Date().getTime();
|
||||
const user = useUser();
|
||||
if (user.token) {
|
||||
request.headers.set("Authorization", `Bearer ${user.token}`);
|
||||
}
|
||||
return request;
|
||||
},
|
||||
(error) => Promise.reject(error)
|
||||
|
@ -2,7 +2,7 @@ import { createSSRApp } from "vue";
|
||||
import { createPinia } from "pinia";
|
||||
import App from "./App.vue";
|
||||
import router from "./router";
|
||||
|
||||
import * as WotDesignUni from 'wot-design-uni';
|
||||
import "uno.css";
|
||||
import { persist } from "./stores/persist";
|
||||
|
||||
@ -14,6 +14,7 @@ export function createApp() {
|
||||
|
||||
app.use(pinia);
|
||||
app.use(router);
|
||||
app.use(WotDesignUni);
|
||||
|
||||
return {
|
||||
app,
|
||||
|
@ -61,5 +61,11 @@
|
||||
"navigationBarTitleText": "XSH PPMP",
|
||||
"navigationBarBackgroundColor": "#F8F8F8",
|
||||
"backgroundColor": "#F8F8F8"
|
||||
},
|
||||
"easycom": {
|
||||
"autoscan": true,
|
||||
"custom": {
|
||||
"^wd-(.*)": "wot-design-uni/components/wd-$1/wd-$1.vue"
|
||||
}
|
||||
}
|
||||
}
|
@ -2,11 +2,11 @@
|
||||
import BussApi from '@/api/BussApi';
|
||||
import pageWrapper from '@/components/page-wrapper.vue';
|
||||
import { useUser } from '@/stores/useUser';
|
||||
import type { Lesson } from '@/types/api/lesson';
|
||||
import type { LessonTask } from '@/types/api/lesson';
|
||||
import { calcLessonProgress } from '@/utils/lesson';
|
||||
import { onPageShow } from '@dcloudio/uni-app';
|
||||
import { useRouter } from 'uni-mini-router';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
import { useToast } from 'wot-design-uni';
|
||||
import type { DropMenuItemBeforeToggle } from 'wot-design-uni/components/wd-drop-menu-item/types';
|
||||
import WdTag from 'wot-design-uni/components/wd-tag/wd-tag.vue';
|
||||
@ -17,19 +17,43 @@ const router = useRouter()
|
||||
const user = useUser()
|
||||
|
||||
const teacherFilterValue = ref<number>(0)
|
||||
|
||||
const teacherFilterOptions = ref<Record<string, any>[]>([
|
||||
{ label: '老师1', value: 0 },
|
||||
{ label: '老师2', value: 1 },
|
||||
{ label: '老师3', value: 2 }
|
||||
{ label: '全部', value: 0 }
|
||||
])
|
||||
|
||||
const loadTeacherOptions = async () => {
|
||||
if (!user.userinfo?.departmentId) return
|
||||
|
||||
try {
|
||||
const res = await BussApi.getDepartmentTeachers(user.userinfo.departmentId)
|
||||
if (res.code === 10000 && Array.isArray(res.data)) {
|
||||
teacherFilterOptions.value = [
|
||||
{ label: '全部', value: 0 },
|
||||
...res.data.map(teacher => ({
|
||||
label: teacher.username,
|
||||
value: teacher.id
|
||||
}))
|
||||
]
|
||||
}
|
||||
} catch (err: unknown) {
|
||||
if (err instanceof Error) {
|
||||
toast.error({ msg: err.message })
|
||||
} else {
|
||||
toast.error({ msg: '获取教师列表失败' })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadTeacherOptions()
|
||||
})
|
||||
|
||||
const handleBeforeToggle: DropMenuItemBeforeToggle = ({ status, resolve }) => {
|
||||
resolve(true)
|
||||
}
|
||||
|
||||
const expandedCourse = ref(['lesson'])
|
||||
const groupedLessons = ref<{ [key: string]: Lesson[] }>({})
|
||||
const groupedLessons = ref<{ [key: string]: LessonTask[] }>({})
|
||||
|
||||
const openLessonDetail = (courseId: number) => {
|
||||
router.push({
|
||||
@ -40,37 +64,73 @@ const openLessonDetail = (courseId: number) => {
|
||||
})
|
||||
}
|
||||
|
||||
onPageShow(() => {
|
||||
toast.loading({
|
||||
msg: '加载中...'
|
||||
})
|
||||
BussApi.lessons(1, 512).then(res => {
|
||||
toast.close()
|
||||
const groupData = res.data.sort((a: Lesson, b: Lesson) => {
|
||||
return a.id - b.id
|
||||
}).reduce((acc: any, cur: any) => {
|
||||
if (!acc[cur.course_name]) {
|
||||
acc[cur.course_name] = []
|
||||
}
|
||||
acc[cur.course_name].push(cur)
|
||||
return acc
|
||||
}, {})
|
||||
const loadLessons = async () => {
|
||||
if (!user.userinfo) {
|
||||
toast.error({ msg: '请先登录' })
|
||||
return
|
||||
}
|
||||
|
||||
toast.loading({ msg: '加载中...' })
|
||||
|
||||
try {
|
||||
const userId = teacherFilterValue.value === 0 ? undefined : teacherFilterValue.value
|
||||
const res = await BussApi.getLessonTasks(1, 512, userId)
|
||||
|
||||
if (res.code !== 10000 || !res.data?.content || !Array.isArray(res.data.content)) {
|
||||
toast.error({ msg: res.message || '获取数据失败' })
|
||||
return
|
||||
}
|
||||
|
||||
const groupData = res.data.content
|
||||
.sort((a: LessonTask, b: LessonTask) => a.id - b.id)
|
||||
.reduce((acc: { [key: string]: LessonTask[] }, cur: LessonTask) => {
|
||||
if (!acc[cur.courseName]) {
|
||||
acc[cur.courseName] = []
|
||||
}
|
||||
acc[cur.courseName].push(cur)
|
||||
return acc
|
||||
}, {})
|
||||
|
||||
groupedLessons.value = groupData
|
||||
// expand courses with lessons in progress
|
||||
expandedCourse.value = Object.keys(groupData).filter(courseName => {
|
||||
return groupData[courseName].filter((lesson: Lesson) => calcLessonProgress(lesson) !== 0 && calcLessonProgress(lesson) !== 100).length > 0
|
||||
return groupData[courseName].filter((lesson: LessonTask) =>
|
||||
calcLessonProgress(lesson) !== 0 && calcLessonProgress(lesson) !== 100
|
||||
).length > 0
|
||||
})
|
||||
}).catch(err => {
|
||||
toast.error({ msg: err.message })
|
||||
})
|
||||
} catch (err: unknown) {
|
||||
if (err instanceof Error) {
|
||||
toast.error({ msg: err.message })
|
||||
} else {
|
||||
toast.error({ msg: '获取课程列表失败' })
|
||||
}
|
||||
} finally {
|
||||
toast.close()
|
||||
}
|
||||
}
|
||||
|
||||
// 监听教师筛选值的变化
|
||||
watch(teacherFilterValue, () => {
|
||||
loadLessons()
|
||||
})
|
||||
|
||||
onPageShow(() => {
|
||||
loadLessons()
|
||||
})
|
||||
|
||||
const getUsernameById = (userId: number) => {
|
||||
if (teacherFilterValue.value === 0) {
|
||||
const teacher = teacherFilterOptions.value.find(t => t.value === userId)
|
||||
return teacher?.label || '未知老师'
|
||||
} else
|
||||
return ''
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<page-wrapper>
|
||||
<div>
|
||||
<!-- todo teacher filter [for role B] -->
|
||||
<wd-drop-menu v-if="user.hasJobTag('B')">
|
||||
<wd-drop-menu v-if="!user.hasRole('teacher')">
|
||||
<wd-drop-menu-item v-model="teacherFilterValue" :options="teacherFilterOptions"
|
||||
:before-toggle="handleBeforeToggle" />
|
||||
</wd-drop-menu>
|
||||
@ -81,15 +141,33 @@ onPageShow(() => {
|
||||
<template #title="{ expanded, disabled, isFirst }">
|
||||
<div class="w-full flex justify-between items-center">
|
||||
<div class="flex flex-col gap-1">
|
||||
<p class="pt-1">{{ courseName || '无标题课程' }}</p>
|
||||
<p class="pt-1">
|
||||
{{ courseName || '无标题课程' }}
|
||||
<span v-if="getUsernameById(courses[0]?.userId)" class=" text-xs text-gray-400 ml-2">
|
||||
{{ getUsernameById(courses[0]?.userId) }}
|
||||
</span>
|
||||
</p>
|
||||
<div class="flex items-center gap-1">
|
||||
<wd-tag v-if="courses.filter(lesson => calcLessonProgress(lesson) !== 0 && calcLessonProgress(lesson) !==
|
||||
100).length > 0" custom-class="w-fit" type="primary">进行中</wd-tag>
|
||||
<wd-tag v-if="courses.filter(lesson => calcLessonProgress(lesson) === 100).length === courses.length"
|
||||
custom-class="w-fit" type="success">已完成</wd-tag>
|
||||
<wd-tag v-if="(() => {
|
||||
const hasInProgress = courses.some(lesson => {
|
||||
const progress = calcLessonProgress(lesson);
|
||||
return progress > 0 && progress < 100;
|
||||
});
|
||||
return hasInProgress;
|
||||
})()" custom-class="w-fit" type="primary">进行中</wd-tag>
|
||||
<wd-tag v-if="(() => {
|
||||
const allCompleted = courses.every(lesson => {
|
||||
const progress = calcLessonProgress(lesson);
|
||||
return progress === 100;
|
||||
});
|
||||
return allCompleted;
|
||||
})()" custom-class="w-fit" type="success">已完成</wd-tag>
|
||||
<wd-tag custom-class="op-60" plain>
|
||||
共{{ courses.length }}课时
|
||||
共{{ courses.length }}节微课
|
||||
</wd-tag>
|
||||
<!-- <wd-tag v-if="teacherFilterValue === 0" custom-class="w-fit" plain>
|
||||
{{ getUsernameById(courses[0]?.userId) }}
|
||||
</wd-tag> -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
@ -127,7 +205,7 @@ onPageShow(() => {
|
||||
<div v-if="calcLessonProgress(lesson) === 100" class="i-tabler-circle-check"></div>
|
||||
<div v-else-if="calcLessonProgress(lesson) === 0" class="i-tabler-circle-dashed"></div>
|
||||
<div v-else class="i-tabler-hourglass-empty"></div>
|
||||
<span>{{ lesson.m_lesson_name || '无标题视频' }}</span>
|
||||
<span>{{ lesson.microLessonName || '无标题视频' }}</span>
|
||||
</div>
|
||||
<div class="w-24 flex items-center gap-3">
|
||||
<wd-progress :percentage="calcLessonProgress(lesson)"
|
||||
|
@ -2,7 +2,8 @@
|
||||
import BussApi from '@/api/BussApi';
|
||||
import { useDayjs } from '@/composables/useDayjs';
|
||||
import { useTabbar } from '@/stores/useTabbar';
|
||||
import type { Lesson } from '@/types/api/lesson';
|
||||
import { useUser } from '@/stores/useUser'
|
||||
import type { LessonTask } from '@/types/api/lesson';
|
||||
import { calcLessonProgress, extractLessonStage, getLessonSteps } from '@/utils/lesson';
|
||||
import { useRoute, useRouter } from 'uni-mini-router';
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
@ -13,8 +14,9 @@ const router = useRouter()
|
||||
const tabbar = useTabbar()
|
||||
const toast = useToast()
|
||||
const dayjs = useDayjs()
|
||||
const user = useUser()
|
||||
|
||||
const lesson = ref<Lesson | null>(null)
|
||||
const lesson = ref<LessonTask | null>(null)
|
||||
const lessonSteps = computed(() => lesson.value ? getLessonSteps(lesson.value) : [])
|
||||
const lessonStages = computed(() => lesson.value ? extractLessonStage(lesson.value) : null)
|
||||
const lessonProgress = computed(() => lesson.value ? calcLessonProgress(lesson.value) : 0)
|
||||
@ -23,7 +25,7 @@ const goProgress = (lessonId: number) => {
|
||||
router.replaceAll({
|
||||
name: 'progress',
|
||||
params: {
|
||||
courseName: `${lesson.value?.course_name}`,
|
||||
courseName: `${lesson.value?.courseName}`,
|
||||
lessonId: `${lessonId}`
|
||||
}
|
||||
})
|
||||
@ -40,7 +42,7 @@ onMounted(() => {
|
||||
toast.loading({
|
||||
msg: '加载中...'
|
||||
})
|
||||
BussApi.course(route.params.courseId).then(courseData => {
|
||||
BussApi.getLessonTask(route.params.courseId).then(courseData => {
|
||||
toast.close()
|
||||
lesson.value = courseData
|
||||
}).catch(err => {
|
||||
@ -54,11 +56,14 @@ onMounted(() => {
|
||||
<div
|
||||
:class="`pattern p-4 flex flex-col gap-6 relative ${lessonProgress === 100 ? 'bg-emerald' : (lessonProgress === 0 ? 'bg-neutral' : 'bg-blue')}`">
|
||||
<div class="flex flex-col gap-0">
|
||||
<h2 class="text-sm text-white font-black op-50">{{ lesson?.course_name }}</h2>
|
||||
<h1 class="text-lg text-white font-bold">{{ lesson?.m_lesson_name }}</h1>
|
||||
<h2 class="text-sm text-white font-black op-50">{{ lesson?.courseName }}</h2>
|
||||
<h1 class="text-lg text-white font-bold">{{ lesson?.microLessonName }}</h1>
|
||||
</div>
|
||||
<p class="text-xs text-white font-bold op-50" style="line-height: 1;">
|
||||
创建于 {{ dayjs(lesson?.created_at).format('YYYY-MM-DD HH:mm:ss') }}
|
||||
创建于 {{ lesson?.createdAt ? dayjs(lesson.createdAt * 1000).format('YYYY-MM-DD HH:mm:ss') : '-' }}
|
||||
</p>
|
||||
<p v-if="lessonProgress === 100" class="text-xs text-white font-bold op-50" style="line-height: 1;">
|
||||
完成于 {{ lesson?.finishTime ? dayjs(lesson.finishTime * 1000).format('YYYY-MM-DD HH:mm:ss') : '-' }}
|
||||
</p>
|
||||
<div class="absolute text-white top-2 right-2 op-35 text-18">
|
||||
<div v-if="lessonProgress === 100" class="i-tabler-circle-check"></div>
|
||||
@ -73,7 +78,8 @@ onMounted(() => {
|
||||
</wd-steps>
|
||||
</div>
|
||||
<div class="px-4 pt-2">
|
||||
<wd-button type="primary" :round="false" plain block @click="goProgress(lesson?.id!)">进度处理</wd-button>
|
||||
<wd-button v-if="!user.hasRole('liaison')" type="primary" :round="false" plain block
|
||||
@click="goProgress(lesson?.id!)">进度处理</wd-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -33,7 +33,7 @@ const handleSubmit = () => {
|
||||
BussApi.login({
|
||||
email: model.email,
|
||||
password: model.password,
|
||||
remember: model.remember + '',
|
||||
remember: model.remember
|
||||
}).then(res => {
|
||||
user.token = res.token
|
||||
toast.loading({
|
||||
@ -55,7 +55,7 @@ const handleSubmit = () => {
|
||||
}
|
||||
})
|
||||
.catch((error: any) => {
|
||||
console.log(error, 'error')
|
||||
// console.log(error, 'error')
|
||||
})
|
||||
}
|
||||
</script>
|
||||
@ -68,6 +68,13 @@ const handleSubmit = () => {
|
||||
:rules="[{ required: true, message: '请填写邮箱' }]" />
|
||||
<wd-input label="密码" label-width="100px" prop="password" show-password clearable v-model="model.password"
|
||||
placeholder="请输入密码" :rules="[{ required: true, message: '请填写密码' }]" />
|
||||
<wd-cell>
|
||||
<template #title>
|
||||
<div class="pl-[5px]">
|
||||
<wd-checkbox v-model="model.remember">记住登录</wd-checkbox>
|
||||
</div>
|
||||
</template>
|
||||
</wd-cell>
|
||||
</wd-cell-group>
|
||||
<view class="p-4">
|
||||
<wd-button type="primary" size="large" @click="handleSubmit" block>登录</wd-button>
|
||||
|
@ -1,24 +1,17 @@
|
||||
<script lang="ts" setup>
|
||||
<script setup lang="ts">
|
||||
import BussApi from '@/api/BussApi';
|
||||
import pageWrapper from '@/components/page-wrapper.vue';
|
||||
import { useUser } from '@/stores/useUser';
|
||||
import type { User } from '@/types/api/user';
|
||||
import { Jobs, Roles } from '@/types/api/user';
|
||||
import { useRouter } from 'uni-mini-router';
|
||||
import { onMounted } from 'vue';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { useToast } from 'wot-design-uni';
|
||||
|
||||
const router = useRouter()
|
||||
const toast = useToast()
|
||||
const user = useUser()
|
||||
|
||||
const logout = () => {
|
||||
user.logout()
|
||||
router.replaceAll('/pages/login/index')
|
||||
}
|
||||
|
||||
const departmentMap = {
|
||||
4: '重庆眩生花科技有限公司',
|
||||
5: '重庆电子科技职业大学',
|
||||
}
|
||||
const userInfo = ref<User | null>(null)
|
||||
|
||||
onMounted(() => {
|
||||
toast.loading({
|
||||
@ -29,17 +22,75 @@ onMounted(() => {
|
||||
user.userinfo = res
|
||||
})
|
||||
})
|
||||
|
||||
const logout = async () => {
|
||||
try {
|
||||
toast.loading({ msg: '退出中...' })
|
||||
await BussApi.logout()
|
||||
toast.success('退出成功')
|
||||
user.logout()
|
||||
router.replaceAll('/pages/login/index')
|
||||
} catch (error: any) {
|
||||
toast.error(error.message || '退出失败')
|
||||
} finally {
|
||||
toast.close()
|
||||
}
|
||||
}
|
||||
|
||||
// 部门ID映射
|
||||
enum DepartmentId {
|
||||
XSH = 1, // 重庆眩生花科技有限公司
|
||||
CQEPU = 2 // 重庆电子科技职业大学
|
||||
}
|
||||
|
||||
const departmentMap: Record<DepartmentId, string> = {
|
||||
[DepartmentId.XSH]: '重庆眩生花科技有限公司',
|
||||
[DepartmentId.CQEPU]: '重庆电子科技职业大学',
|
||||
}
|
||||
|
||||
// 角色映射
|
||||
const roleMap: Record<number, string> = {
|
||||
[Roles.TEACHER]: '教师',
|
||||
[Roles.GENERAL_ADMIN]: '普通管理员',
|
||||
[Roles.CONTACTOR]: '沟通联络人',
|
||||
[Roles.SYSTEM_ADMIN]: '系统管理员',
|
||||
}
|
||||
|
||||
// 岗位映射
|
||||
const jobMap: Record<number, string> = {
|
||||
[Jobs.COURSE_TEACHER]: '课程制作教师',
|
||||
[Jobs.PROJECT_MANAGER]: '课程购买方项目负责人',
|
||||
[Jobs.COURSE_CONTACTOR]: '课程制作方沟通联络人',
|
||||
[Jobs.SYSTEM_MANAGER]: '系统制作方项目负责人',
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
toast.loading({
|
||||
msg: '加载中...'
|
||||
})
|
||||
BussApi.profile(user.token!).then(res => {
|
||||
toast.close()
|
||||
userInfo.value = res
|
||||
}).catch(error => {
|
||||
toast.close()
|
||||
toast.error(error.message || '获取用户信息失败')
|
||||
if (error.response?.data?.code === 10001) {
|
||||
router.replace('/pages/login/index')
|
||||
}
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<page-wrapper>
|
||||
<div class="p-4 flex flex-col gap-4">
|
||||
<WdCellGroup :border="true">
|
||||
<WdCell title="用户名" :value="user.userinfo?.username || '-'" />
|
||||
<WdCell title="邮箱" :value="user.userinfo?.email || '-'" />
|
||||
<WdCell title="单位" :value="user.userinfo?.department_id ? departmentMap[user.userinfo.department_id] : '-'" />
|
||||
<WdCell title="角色" :value="user.userinfo?.roles.map(i => i.role_name).join(', ') || '-'" />
|
||||
<WdCell title="权限" :value="user.userinfo?.jobs.map(i => i.job_name).join(', ') || '-'" />
|
||||
<WdCell title="用户名" :value="userInfo?.username || '-'" />
|
||||
<WdCell title="邮箱" :value="userInfo?.email || '-'" />
|
||||
<WdCell title="单位"
|
||||
:value="userInfo?.departmentId ? departmentMap[userInfo.departmentId as DepartmentId] : '-'" />
|
||||
<WdCell title="角色" :value="userInfo?.roles ? roleMap[userInfo.roles] : '-'" />
|
||||
<WdCell title="权限" :value="userInfo?.jobs ? jobMap[userInfo.jobs] : '-'" />
|
||||
</WdCellGroup>
|
||||
<div class="px-4">
|
||||
<wd-button plain hairline block type="error" @click="logout">退出账号</wd-button>
|
||||
|
@ -1,19 +1,21 @@
|
||||
<script lang="ts" setup>
|
||||
import BussApi from '@/api/BussApi';
|
||||
import { useDayjs } from '@/composables/useDayjs';
|
||||
import type { Lesson, FileUploadDestination } from '@/types/api/lesson';
|
||||
import type { LessonTask, FileUploadDestination } from '@/types/api/lesson';
|
||||
import { extractLessonStage, getLessonSteps, getScriptFile, parseCombinedFileString } from '@/utils/lesson';
|
||||
import { computed, nextTick, onMounted, ref } from 'vue';
|
||||
import { useMessage, useToast } from 'wot-design-uni';
|
||||
import StatusBlock from './StatusBlock.vue';
|
||||
import { useRoute } from 'uni-mini-router';
|
||||
import { useUser } from '@/stores/useUser';
|
||||
|
||||
const route = useRoute()
|
||||
const message = useMessage()
|
||||
const toast = useToast()
|
||||
const dayjs = useDayjs()
|
||||
const user = useUser()
|
||||
|
||||
type GroupedLessons = { [key: string]: Lesson[] }
|
||||
type GroupedLessons = { [key: string]: LessonTask[] }
|
||||
|
||||
const groupedLessons = ref<GroupedLessons>({})
|
||||
|
||||
@ -21,10 +23,10 @@ const pickerCourseColumns = ref<string[]>([])
|
||||
const pickerCourseValue = ref()
|
||||
|
||||
const pickerLessonColumns = computed(() => {
|
||||
return pickerCourseValue.value ? groupedLessons.value[pickerCourseValue.value].map((lesson: Lesson) => {
|
||||
return pickerCourseValue.value ? groupedLessons.value[pickerCourseValue.value].map((lesson: LessonTask) => {
|
||||
return {
|
||||
label: (extractLessonStage(lesson).step === 4 ? '✅ ' : '') + lesson.m_lesson_name,
|
||||
value: lesson.id
|
||||
label: (extractLessonStage(lesson).step === 4 ? '✅ ' : '') + lesson.microLessonName,
|
||||
value: lesson.id,
|
||||
}
|
||||
}) : []
|
||||
})
|
||||
@ -32,7 +34,7 @@ const pickerLessonValue = ref()
|
||||
|
||||
const selectedLesson = computed(() => {
|
||||
if (!pickerLessonValue.value) return null
|
||||
return groupedLessons.value[pickerCourseValue.value].find((lesson: Lesson) => lesson.id === pickerLessonValue.value)
|
||||
return groupedLessons.value[pickerCourseValue.value].find((lesson: LessonTask) => lesson.id === pickerLessonValue.value)
|
||||
})
|
||||
|
||||
const selectedLessonStage = computed(() => {
|
||||
@ -55,7 +57,7 @@ const onLessonPick = ({ value }: { value: number }) => {
|
||||
|
||||
const script_file_destination = ref<FileUploadDestination>('wechat')
|
||||
|
||||
const onStep0 = () => {
|
||||
const onStep1 = () => {
|
||||
message.confirm({
|
||||
title: '提交脚本',
|
||||
msg: '请确认已经通过微信或平台上传了脚本文件,再确认提交'
|
||||
@ -69,13 +71,19 @@ const onStep0 = () => {
|
||||
toast.loading({
|
||||
msg: '正在提交...'
|
||||
})
|
||||
BussApi.editCourse(selectedLesson.value.id, {
|
||||
script_file: JSON.stringify({
|
||||
const params = {
|
||||
advise: JSON.stringify({
|
||||
method: script_file_destination.value,
|
||||
uploaded: false
|
||||
}),
|
||||
script_upload_time: dayjs().unix()
|
||||
}).then(res => {
|
||||
scriptUploadTime: dayjs().unix(),
|
||||
courseName: selectedLesson.value.courseName,
|
||||
microLessonName: selectedLesson.value.microLessonName,
|
||||
userId: selectedLesson.value.userId,
|
||||
progressStatus: 1
|
||||
|
||||
}
|
||||
BussApi.updateLessonTask(selectedLesson.value.id, params).then(res => {
|
||||
toast.success({
|
||||
msg: '提交成功'
|
||||
})
|
||||
@ -102,16 +110,24 @@ const onStep2 = (rejected: boolean = false) => {
|
||||
toast.loading({
|
||||
msg: '正在处理...'
|
||||
})
|
||||
BussApi.editCourse(
|
||||
BussApi.updateLessonTask(
|
||||
selectedLesson.value.id,
|
||||
rejected ? {
|
||||
script_file: JSON.stringify({
|
||||
...parseCombinedFileString(selectedLesson.value, 'script_file'),
|
||||
advise: JSON.stringify({
|
||||
...parseCombinedFileString(selectedLesson.value, 'advise'),
|
||||
uploaded: false
|
||||
}),
|
||||
script_upload_time: 0
|
||||
scriptUploadTime: 0,
|
||||
courseName: selectedLesson.value.courseName,
|
||||
microLessonName: selectedLesson.value.microLessonName,
|
||||
userId: selectedLesson.value.userId,
|
||||
progressStatus: 0
|
||||
} : {
|
||||
script_confirm_time: dayjs().unix()
|
||||
scriptConfirmTime: dayjs().unix(),
|
||||
courseName: selectedLesson.value.courseName,
|
||||
microLessonName: selectedLesson.value.microLessonName,
|
||||
userId: selectedLesson.value.userId,
|
||||
progressStatus: 2
|
||||
}).then(res => {
|
||||
toast.success({
|
||||
msg: rejected ? '驳回成功' : '审核通过'
|
||||
@ -139,16 +155,24 @@ const onStep3 = (rejected: boolean = false) => {
|
||||
toast.loading({
|
||||
msg: '正在处理...'
|
||||
})
|
||||
BussApi.editCourse(
|
||||
BussApi.updateLessonTask(
|
||||
selectedLesson.value.id,
|
||||
rejected ? {
|
||||
capture_file: JSON.stringify({
|
||||
...parseCombinedFileString(selectedLesson.value, 'capture_file'),
|
||||
advise: JSON.stringify({
|
||||
...parseCombinedFileString(selectedLesson.value, 'advise'),
|
||||
uploaded: false
|
||||
}),
|
||||
video_capture_time: 0
|
||||
videoCaptureTime: 0,
|
||||
courseName: selectedLesson.value.courseName,
|
||||
microLessonName: selectedLesson.value.microLessonName,
|
||||
userId: selectedLesson.value.userId,
|
||||
progressStatus: 1
|
||||
} : {
|
||||
video_capture_time: dayjs().unix()
|
||||
videoConfirmTime: dayjs().unix(),
|
||||
courseName: selectedLesson.value.courseName,
|
||||
microLessonName: selectedLesson.value.microLessonName,
|
||||
userId: selectedLesson.value.userId,
|
||||
progressStatus: 3
|
||||
}).then(res => {
|
||||
toast.success({
|
||||
msg: rejected ? '驳回成功' : '审核通过'
|
||||
@ -162,19 +186,79 @@ const onStep3 = (rejected: boolean = false) => {
|
||||
})
|
||||
}
|
||||
|
||||
const updateLessons = () => {
|
||||
const onPostProduction = (rejected: boolean = false) => {
|
||||
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: JSON.stringify({
|
||||
...parseCombinedFileString(selectedLesson.value, 'advise'),
|
||||
uploaded: false
|
||||
}),
|
||||
videoCaptureTime: 0,
|
||||
courseName: selectedLesson.value.courseName,
|
||||
microLessonName: selectedLesson.value.microLessonName,
|
||||
userId: selectedLesson.value.userId,
|
||||
progressStatus: 2
|
||||
} : {
|
||||
videoConfirmTime: dayjs().unix(),
|
||||
courseName: selectedLesson.value.courseName,
|
||||
microLessonName: selectedLesson.value.microLessonName,
|
||||
userId: selectedLesson.value.userId,
|
||||
progressStatus: 4
|
||||
}).then(res => {
|
||||
toast.success({
|
||||
msg: rejected ? '驳回成功' : '审核通过'
|
||||
})
|
||||
setTimeout(() => {
|
||||
updateLessons()
|
||||
}, 1500);
|
||||
}).catch(err => {
|
||||
toast.error({ msg: err.message })
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const updateLessons = async () => {
|
||||
if (!user.userinfo) {
|
||||
toast.error({ msg: '请先登录' })
|
||||
return
|
||||
}
|
||||
|
||||
toast.loading({
|
||||
msg: '加载中...'
|
||||
})
|
||||
BussApi.lessons(1, 512).then(res => {
|
||||
toast.close()
|
||||
const groupData = res.data.sort((a: Lesson, b: Lesson) => {
|
||||
|
||||
try {
|
||||
let res
|
||||
// 如果有查看所有课程的权限
|
||||
if (user.canViewAllCourses()) {
|
||||
res = await BussApi.getLessonTasks(1, 512)
|
||||
} else {
|
||||
// 否则只获取自己的课程
|
||||
res = await BussApi.getLessonTasks(1, 512, user.userinfo.id)
|
||||
}
|
||||
|
||||
// 排序并分组课程
|
||||
const groupData = res.data.content.sort((a: LessonTask, b: LessonTask) => {
|
||||
return a.id - b.id
|
||||
}).reduce((acc: any, cur: any) => {
|
||||
if (!acc[cur.course_name]) {
|
||||
acc[cur.course_name] = []
|
||||
if (!acc[cur.courseName]) {
|
||||
acc[cur.courseName] = []
|
||||
}
|
||||
acc[cur.course_name].push(cur)
|
||||
acc[cur.courseName].push(cur)
|
||||
return acc
|
||||
}, {})
|
||||
groupedLessons.value = groupData
|
||||
@ -185,14 +269,23 @@ const updateLessons = () => {
|
||||
if (route.params?.courseName) {
|
||||
onCoursePick({ value: decodeURI(route.params.courseName) })
|
||||
if (route.params?.lessonId) {
|
||||
onLessonPick({ value: parseInt(route.params.lessonId) })
|
||||
if (typeof route.params.lessonId === 'string') {
|
||||
onLessonPick({ value: parseInt(route.params.lessonId) })
|
||||
}
|
||||
}
|
||||
}
|
||||
}).catch(err => {
|
||||
toast.error({ msg: err.message })
|
||||
})
|
||||
} catch (err: unknown) {
|
||||
if (err instanceof Error) {
|
||||
toast.error({ msg: err.message })
|
||||
} else {
|
||||
toast.error({ msg: '发生未知错误' })
|
||||
}
|
||||
} finally {
|
||||
toast.close()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
updateLessons()
|
||||
})
|
||||
@ -215,6 +308,7 @@ onMounted(() => {
|
||||
:description="(selectedLessonStage?.step || 0 <= index) ? step.description : undefined" />
|
||||
</wd-steps>
|
||||
</div>
|
||||
|
||||
<div v-if="selectedLessonStage?.step === 0" class="px-2">
|
||||
<wd-cell-group>
|
||||
<wd-cell title="脚本提交途径" :title-width="'100px'" center custom-class="mb-4">
|
||||
@ -226,10 +320,10 @@ onMounted(() => {
|
||||
</wd-cell>
|
||||
</wd-cell-group>
|
||||
|
||||
<wd-button type="primary" block @click="onStep0" custom-class="w-full">提交脚本</wd-button>
|
||||
<wd-button type="primary" block @click="onStep1" custom-class="w-full">提交脚本</wd-button>
|
||||
</div>
|
||||
<div v-if="selectedLessonStage?.step === 1">
|
||||
<StatusBlock v-if="!parseCombinedFileString(selectedLesson!, 'script_file')?.uploaded" title="脚本已提交"
|
||||
<StatusBlock v-if="!parseCombinedFileString(selectedLesson!, 'advise')?.uploaded" title="脚本已提交"
|
||||
subtitle="请耐心等待审核">
|
||||
<template #icon>
|
||||
<div class="i-tabler-progress-bolt text-7xl text-neutral-400"></div>
|
||||
@ -248,7 +342,7 @@ onMounted(() => {
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="selectedLessonStage?.step === 2">
|
||||
<StatusBlock v-if="!parseCombinedFileString(selectedLesson!, 'capture_file')?.uploaded" title="视频拍摄进行中"
|
||||
<StatusBlock v-if="!parseCombinedFileString(selectedLesson!, 'advise')?.uploaded" title="视频拍摄进行中"
|
||||
subtitle="请等待线下视频拍摄">
|
||||
<template #icon>
|
||||
<div class="i-tabler-capture text-7xl text-neutral-400"></div>
|
||||
@ -267,6 +361,23 @@ onMounted(() => {
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="selectedLessonStage?.step === 3">
|
||||
<StatusBlock v-if="!parseCombinedFileString(selectedLesson!, 'advise')?.uploaded" 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-filled text-7xl text-neutral-400"></div>
|
||||
</template>
|
||||
</StatusBlock>
|
||||
<div class="mt-4 px-4 space-y-2">
|
||||
<wd-button type="primary" :round="false" block @click="onPostProduction()">通过</wd-button>
|
||||
<wd-button type="error" :round="false" block @click="onPostProduction(true)">驳回</wd-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="selectedLessonStage?.step === 4">
|
||||
<wd-status-tip image="comment" tip="该微课已完成" />
|
||||
|
@ -8,12 +8,70 @@ export const useUser = defineStore("user", () => {
|
||||
|
||||
/**
|
||||
* 判断是否有权限
|
||||
* @param tag A: 课程制作教师(3) B: 课程制作方沟通联络人(2) C: 课程购买方项目负责人(1) D: 系统制作方项目负责人(4)
|
||||
* @param role 角色类型
|
||||
* @returns 是否有权限
|
||||
*/
|
||||
function hasJobTag(tag: "A" | "B" | "C" | "D") {
|
||||
function hasRole(role: "teacher" | "admin" | "liaison" | "sysadmin") {
|
||||
if (!userinfo.value) return false;
|
||||
return userinfo.value?.jobs.some((job) => job.description === tag) || false;
|
||||
|
||||
const roleMap = {
|
||||
teacher: 1, // 教师
|
||||
admin: 2, // 普通管理员
|
||||
liaison: 3, // 沟通联络人
|
||||
sysadmin: 4 // 系统管理员
|
||||
};
|
||||
|
||||
return userinfo.value.roles === roleMap[role];
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否有指定岗位
|
||||
* @param job 岗位类型
|
||||
* @returns 是否有权限
|
||||
*/
|
||||
function hasJob(job: "teacher" | "projectManager" | "liaison" | "sysManager") {
|
||||
if (!userinfo.value) return false;
|
||||
|
||||
const jobMap = {
|
||||
teacher: 1, // 课程制作教师
|
||||
projectManager: 2, // 课程购买方项目负责人
|
||||
liaison: 3, // 课程制作方沟通联络人
|
||||
sysManager: 4 // 系统制作方项目负责人
|
||||
};
|
||||
|
||||
return userinfo.value.jobs === jobMap[job];
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否有查看所有课程的权限
|
||||
* @returns 是否有权限
|
||||
*/
|
||||
function canViewAllCourses() {
|
||||
if (!userinfo.value) return false;
|
||||
|
||||
// 系统管理员或普通管理员可以查看所有课程
|
||||
if (userinfo.value.roles === 4 || userinfo.value.roles === 2) return true;
|
||||
|
||||
// 课程制作方沟通联络人可以查看所有课程
|
||||
if (userinfo.value.roles === 3 && userinfo.value.jobs === 3) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否有编辑课程的权限
|
||||
* @returns 是否有权限
|
||||
*/
|
||||
function canEditCourse() {
|
||||
if (!userinfo.value) return false;
|
||||
|
||||
// 教师可以编辑自己的课程
|
||||
if (userinfo.value.roles === 1) return true;
|
||||
|
||||
// 系统管理员可以编辑所有课程
|
||||
if (userinfo.value.roles === 4) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function logout() {
|
||||
@ -24,7 +82,10 @@ export const useUser = defineStore("user", () => {
|
||||
return {
|
||||
token,
|
||||
userinfo,
|
||||
hasJobTag,
|
||||
hasRole,
|
||||
hasJob,
|
||||
canViewAllCourses,
|
||||
canEditCourse,
|
||||
logout,
|
||||
};
|
||||
});
|
||||
|
11
src/types/api/auth.ts
Normal file
11
src/types/api/auth.ts
Normal file
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* 登录请求参数接口
|
||||
*/
|
||||
export interface LoginRequest {
|
||||
/** 用户邮箱 */
|
||||
email: string;
|
||||
/** 用户密码 */
|
||||
password: string;
|
||||
/** 是否记住登录状态 */
|
||||
remember?: boolean;
|
||||
}
|
@ -1,24 +1,99 @@
|
||||
export interface Lesson {
|
||||
/**
|
||||
* 课程任务接口
|
||||
*/
|
||||
export interface LessonTask {
|
||||
/** 任务ID */
|
||||
id: number;
|
||||
course_name: string;
|
||||
m_lesson_name: string;
|
||||
user_id: number;
|
||||
schedule_status: number;
|
||||
script_upload_time: number;
|
||||
script_confirm_time: number;
|
||||
video_capture_time: number;
|
||||
video_confirm_time: number;
|
||||
script_file: string;
|
||||
capture_file: string;
|
||||
material_file: string;
|
||||
finish_time: number;
|
||||
advise: null;
|
||||
created_at: string;
|
||||
/** 课程名称 */
|
||||
courseName: string;
|
||||
/** 微课名称 */
|
||||
microLessonName: string;
|
||||
/** 用户ID */
|
||||
userId: number;
|
||||
/** 进度状态 */
|
||||
progressStatus: number;
|
||||
/** 脚本上传时间(时间戳) */
|
||||
scriptUploadTime?: number;
|
||||
/** 脚本确认时间(时间戳) */
|
||||
scriptConfirmTime?: number;
|
||||
/** 视频拍摄时间(时间戳) */
|
||||
videoCaptureTime?: number;
|
||||
/** 视频确认时间(时间戳) */
|
||||
videoConfirmTime?: number;
|
||||
/** 任务完成时间(时间戳) */
|
||||
finishTime?: number;
|
||||
/** 建议/反馈信息 */
|
||||
advise?: string;
|
||||
/** 创建时间(时间戳) */
|
||||
createdAt: number;
|
||||
/** 更新时间(时间戳) */
|
||||
updatedAt: number;
|
||||
/** 脚本文件路径 */
|
||||
script_file?: string;
|
||||
/** 视频拍摄文件路径 */
|
||||
capture_file?: string;
|
||||
/** 素材文件路径 */
|
||||
material_file?: string;
|
||||
}
|
||||
|
||||
// export interface CombinedFileString {
|
||||
// method?: ScriptFileDestination;
|
||||
// reupload?: string;
|
||||
// }
|
||||
export interface LessonTaskPagination {
|
||||
content: LessonTask[];
|
||||
totalElements: number;
|
||||
totalPages: number;
|
||||
size: number;
|
||||
number: number;
|
||||
first: boolean;
|
||||
last: boolean;
|
||||
empty: boolean;
|
||||
}
|
||||
|
||||
// 进度状态枚举
|
||||
export enum ProgressStatus {
|
||||
SCRIPT_UPLOAD = 0, // 脚本上传
|
||||
SCRIPT_CONFIRM = 1, // 脚本确认
|
||||
VIDEO_CAPTURE = 2, // 视频拍摄
|
||||
POST_PRODUCTION = 3, // 后期制作
|
||||
FINISHED = 4, // 任务完成
|
||||
}
|
||||
|
||||
export type FileUploadDestination = "qq" | "wechat" | "platform";
|
||||
|
||||
/**
|
||||
* 创建课程任务请求参数接口
|
||||
*/
|
||||
export interface CreateLessonTaskRequest {
|
||||
/** 课程名称 */
|
||||
courseName: string;
|
||||
/** 微课名称 */
|
||||
microLessonName: string;
|
||||
/** 用户ID */
|
||||
userId: number;
|
||||
/** 建议信息 */
|
||||
advise?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新课程任务请求参数接口
|
||||
*/
|
||||
export interface UpdateLessonTaskRequest {
|
||||
/** 课程名称 */
|
||||
courseName?: string;
|
||||
/** 微课名称 */
|
||||
microLessonName?: string;
|
||||
/** 用户ID */
|
||||
userId?: number;
|
||||
/** 进度状态 */
|
||||
progressStatus?: number;
|
||||
/** 脚本上传时间 */
|
||||
scriptUploadTime?: number;
|
||||
/** 脚本确认时间 */
|
||||
scriptConfirmTime?: number;
|
||||
/** 视频拍摄时间 */
|
||||
videoCaptureTime?: number;
|
||||
/** 视频确认时间 */
|
||||
videoConfirmTime?: number;
|
||||
/** 完成时间 */
|
||||
finishTime?: number;
|
||||
/** 建议信息 */
|
||||
advise?: string;
|
||||
}
|
||||
|
@ -2,91 +2,69 @@ export interface User {
|
||||
id: number;
|
||||
username: string;
|
||||
email: string;
|
||||
avatar: string;
|
||||
department_id: null;
|
||||
creator_id: number;
|
||||
departmentId: number;
|
||||
roles: number;
|
||||
jobs: number;
|
||||
avatar: string | null;
|
||||
creatorId: number;
|
||||
status: number;
|
||||
login_ip: string;
|
||||
login_at: number;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
deleted_at: Date;
|
||||
permissions: Permission[];
|
||||
roles: Role[];
|
||||
jobs: Job[];
|
||||
createdAt: number;
|
||||
updatedAt: number;
|
||||
enabled: boolean;
|
||||
authorities: Authority[];
|
||||
}
|
||||
|
||||
export interface Permission {
|
||||
id: number;
|
||||
parent_id: number;
|
||||
permission_name: string;
|
||||
route: string;
|
||||
icon: string;
|
||||
module: PermissionModule;
|
||||
permission_mark: string;
|
||||
component: string;
|
||||
redirect: null | string;
|
||||
keepalive: number;
|
||||
type: number;
|
||||
hidden: boolean;
|
||||
sort: number;
|
||||
active_menu: string;
|
||||
creator_id: number;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export enum PermissionModule {
|
||||
Lesson = "lesson",
|
||||
Permissions = "permissions",
|
||||
User = "user",
|
||||
}
|
||||
|
||||
export interface Job {
|
||||
id: number;
|
||||
job_name: string;
|
||||
coding: string;
|
||||
status: number;
|
||||
sort: number;
|
||||
description: string;
|
||||
creator_id: number;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
pivot: JobPivot;
|
||||
}
|
||||
|
||||
export interface JobPivot {
|
||||
user_id: number;
|
||||
job_id: number;
|
||||
}
|
||||
|
||||
export interface Role {
|
||||
id: number;
|
||||
role_name: string;
|
||||
identify: string;
|
||||
parent_id: number;
|
||||
description: string;
|
||||
data_range: number;
|
||||
creator_id: number;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
pivot: RolePivot;
|
||||
}
|
||||
|
||||
export interface RolePivot {
|
||||
user_id: number;
|
||||
role_id: number;
|
||||
export interface Authority {
|
||||
authority: string;
|
||||
}
|
||||
|
||||
// 角色枚举
|
||||
export enum Roles {
|
||||
Teacher = 1,
|
||||
GeneralAdmin,
|
||||
Contactor,
|
||||
TEACHER = 1, // 教师
|
||||
GENERAL_ADMIN = 2, // 普通管理员
|
||||
CONTACTOR = 3, // 沟通联络人
|
||||
SYSTEM_ADMIN = 4 // 系统管理员
|
||||
}
|
||||
|
||||
// 岗位枚举
|
||||
export enum Jobs {
|
||||
BuyerManager = 1,
|
||||
MakerContactor,
|
||||
MakerTeacher,
|
||||
SystemManager,
|
||||
COURSE_TEACHER = 1, // 课程制作教师
|
||||
PROJECT_MANAGER = 2, // 课程购买方项目负责人
|
||||
COURSE_CONTACTOR = 3, // 课程制作方沟通联络人
|
||||
SYSTEM_MANAGER = 4 // 系统制作方项目负责人
|
||||
}
|
||||
|
||||
// 用户状态枚举
|
||||
export enum UserStatus {
|
||||
DISABLED = 0, // 禁用
|
||||
ENABLED = 1 // 正常
|
||||
}
|
||||
|
||||
export interface UserPagination {
|
||||
list: User[];
|
||||
total: number;
|
||||
currentPage: number;
|
||||
pageSize: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建用户请求参数接口
|
||||
*/
|
||||
export interface CreateUserRequest {
|
||||
/** 用户名 */
|
||||
username: string;
|
||||
/** 邮箱 */
|
||||
email: string;
|
||||
/** 密码 */
|
||||
password: string;
|
||||
/** 部门ID */
|
||||
departmentId: number;
|
||||
/** 角色 */
|
||||
roles: number;
|
||||
/** 岗位 */
|
||||
jobs: number;
|
||||
/** 头像 */
|
||||
avatar?: string;
|
||||
/** 创建者ID */
|
||||
creatorId: number;
|
||||
}
|
||||
|
@ -1,36 +1,56 @@
|
||||
import { useDayjs } from "@/composables/useDayjs";
|
||||
import type { FileUploadDestination, Lesson } from "@/types/api/lesson";
|
||||
import type { FileUploadDestination,LessonTask } from "@/types/api/lesson";
|
||||
|
||||
export const extractLessonStage = (lesson: Lesson) => {
|
||||
export const extractLessonStage = (lesson: LessonTask) => {
|
||||
const stages = {
|
||||
script_upload: !!lesson?.script_upload_time,
|
||||
script_confirm: !!lesson?.script_confirm_time,
|
||||
video_capture: !!lesson?.video_capture_time,
|
||||
post_production: !!lesson?.video_confirm_time,
|
||||
script_upload: !!lesson?.scriptUploadTime,
|
||||
script_confirm: !!lesson?.scriptConfirmTime,
|
||||
video_capture: !!lesson?.videoCaptureTime,
|
||||
post_production: !!lesson?.videoConfirmTime,
|
||||
step: 0,
|
||||
};
|
||||
stages.step = Object.values(stages).filter((v) => v).length;
|
||||
return stages;
|
||||
};
|
||||
|
||||
export const calcLessonProgress = (lesson: Lesson, total: number = 4) => {
|
||||
const progress = extractLessonStage(lesson);
|
||||
return Math.floor((progress.step / total) * 100);
|
||||
export const calcLessonProgress = (lesson: LessonTask) => {
|
||||
if (!lesson) return 0;
|
||||
|
||||
// 根据 progressStatus 计算进度
|
||||
// 0-脚本上传, 1-脚本确认, 2-视频拍摄, 3-后期制作, 4-任务完成
|
||||
switch (lesson.progressStatus) {
|
||||
case 4: // 任务完成
|
||||
return 100;
|
||||
case 3: // 后期制作
|
||||
return 75;
|
||||
case 2: // 视频拍摄
|
||||
return 50;
|
||||
case 1: // 脚本确认
|
||||
return 25;
|
||||
case 0: // 脚本上传
|
||||
return 0;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
export const getLessonSteps = (lesson: Lesson, simplify: boolean = false) => {
|
||||
export const getLessonSteps = (lesson: LessonTask, simplify: boolean = false) => {
|
||||
const dayjs = useDayjs();
|
||||
const dateFormat = "YYYY-MM-DD HH:mm:ss";
|
||||
const progress = extractLessonStage(lesson);
|
||||
|
||||
const formatTime = (timestamp?: number | null) => {
|
||||
if (!timestamp) return '-';
|
||||
return dayjs(timestamp * 1000).format(dateFormat);
|
||||
};
|
||||
|
||||
return [
|
||||
{
|
||||
title: progress.script_upload ? "脚本提交" : undefined,
|
||||
description: progress.script_upload
|
||||
? simplify
|
||||
? "已完成"
|
||||
: `已于 ${dayjs(lesson.script_upload_time * 1000).format(
|
||||
dateFormat
|
||||
)} 完成上传`
|
||||
: `已于 ${formatTime(lesson.scriptUploadTime)} 完成上传`
|
||||
: "脚本文件提交",
|
||||
},
|
||||
{
|
||||
@ -38,9 +58,7 @@ export const getLessonSteps = (lesson: Lesson, simplify: boolean = false) => {
|
||||
description: progress.script_confirm
|
||||
? simplify
|
||||
? "已完成"
|
||||
: `已于 ${dayjs(lesson.script_confirm_time * 1000).format(
|
||||
dateFormat
|
||||
)} 确认`
|
||||
: `已于 ${formatTime(lesson.scriptConfirmTime)} 完成确认`
|
||||
: "脚本文件确认",
|
||||
},
|
||||
{
|
||||
@ -48,9 +66,7 @@ export const getLessonSteps = (lesson: Lesson, simplify: boolean = false) => {
|
||||
description: progress.video_capture
|
||||
? simplify
|
||||
? "已完成"
|
||||
: `已于 ${dayjs(lesson.video_capture_time * 1000).format(
|
||||
dateFormat
|
||||
)} 完成上传`
|
||||
: `已于 ${formatTime(lesson.videoCaptureTime)} 完成上传`
|
||||
: "视频拍摄提交",
|
||||
},
|
||||
{
|
||||
@ -58,15 +74,13 @@ export const getLessonSteps = (lesson: Lesson, simplify: boolean = false) => {
|
||||
description: progress.post_production
|
||||
? simplify
|
||||
? "已完成"
|
||||
: `已于 ${dayjs(lesson.video_confirm_time * 1000).format(
|
||||
dateFormat
|
||||
)} 完成上传`
|
||||
: `已于 ${formatTime(lesson.videoConfirmTime)} 完成上传`
|
||||
: "视频后期制作",
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
export const getScriptFile = (lesson: Lesson) => {
|
||||
export const getScriptFile = (lesson: LessonTask) => {
|
||||
const scriptFile = lesson.script_file ? lesson.script_file.split("|") : [];
|
||||
return {
|
||||
way: scriptFile[0] || null,
|
||||
@ -75,17 +89,30 @@ export const getScriptFile = (lesson: Lesson) => {
|
||||
};
|
||||
|
||||
export const parseCombinedFileString = (
|
||||
lesson: Lesson,
|
||||
key: keyof Pick<Lesson, "script_file" | "capture_file" | "material_file">
|
||||
lesson: LessonTask,
|
||||
// key: keyof Pick<LessonTask, "script_file" | "capture_file" | "material_file">
|
||||
key: keyof Pick<LessonTask, "advise">
|
||||
): {
|
||||
method: FileUploadDestination;
|
||||
uploaded: Boolean;
|
||||
method: FileUploadDestination | undefined;
|
||||
uploaded: boolean;
|
||||
} => {
|
||||
const combined = lesson[key]
|
||||
? JSON.parse(lesson[key])
|
||||
: {
|
||||
method: null,
|
||||
uploaded: false,
|
||||
};
|
||||
return combined;
|
||||
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,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { defineConfig } from "vite";
|
||||
import uni from "@dcloudio/vite-plugin-uni";
|
||||
import { resolve } from 'path';
|
||||
|
||||
import Components from "@uni-helper/vite-plugin-uni-components";
|
||||
import { WotResolver } from "@uni-helper/vite-plugin-uni-components/resolvers";
|
||||
@ -12,10 +13,24 @@ export default defineConfig(async () => {
|
||||
plugins: [
|
||||
Components({
|
||||
resolvers: [WotResolver()],
|
||||
include: [/\.vue$/],
|
||||
exclude: [/[\\/]node_modules[\\/]/, /[\\/]\.git[\\/]/, /[\\/]\.nuxt[\\/]/],
|
||||
}),
|
||||
uni(),
|
||||
// https://github.com/unocss/unocss
|
||||
UnoCSS(),
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': resolve(__dirname, 'src'),
|
||||
}
|
||||
},
|
||||
build: {
|
||||
target: 'es2015',
|
||||
cssTarget: 'chrome61',
|
||||
commonjsOptions: {
|
||||
include: [/wot-design-uni/, /node_modules/],
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
145
数据库.md
Normal file
145
数据库.md
Normal file
@ -0,0 +1,145 @@
|
||||
# 数据库设计文档
|
||||
|
||||
## 1. 部门表 (departments)
|
||||
|
||||
用于存储组织的部门信息。
|
||||
|
||||
```sql
|
||||
CREATE TABLE departments (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(100) NOT NULL COMMENT '部门名称',
|
||||
description TEXT DEFAULT NULL COMMENT '部门描述',
|
||||
created_at BIGINT NOT NULL COMMENT '创建时间(时间戳)',
|
||||
updated_at BIGINT NOT NULL COMMENT '更新时间(时间戳)'
|
||||
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT ='部门表';
|
||||
```
|
||||
|
||||
### 字段说明
|
||||
- `id`: 部门ID,自增主键
|
||||
- `name`: 部门名称,不可为空
|
||||
- `description`: 部门描述,可为空
|
||||
- `created_at`: 创建时间,毫秒级时间戳
|
||||
- `updated_at`: 更新时间,毫秒级时间戳
|
||||
|
||||
## 2. 用户表 (users)
|
||||
|
||||
存储系统用户信息,包括教师、管理员等角色。
|
||||
|
||||
```sql
|
||||
CREATE TABLE users (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
username VARCHAR(50) NOT NULL COMMENT '用户名',
|
||||
email VARCHAR(100) NOT NULL UNIQUE COMMENT '邮箱',
|
||||
password VARCHAR(100) NOT NULL COMMENT '密码',
|
||||
department_id BIGINT NOT NULL COMMENT '所属部门',
|
||||
roles INT NOT NULL COMMENT '角色: 1-教师, 2-普通管理员, 3-沟通联络人, 4-系统管理员',
|
||||
jobs INT NOT NULL COMMENT '岗位: 1-课程制作教师, 2-课程购买方项目负责人, 3-课程制作方沟通联络人, 4-系统制作方项目负责人',
|
||||
avatar VARCHAR(255) DEFAULT NULL COMMENT '头像',
|
||||
creator_id BIGINT NOT NULL DEFAULT 1 COMMENT '创建用户的管理员ID',
|
||||
status INT NOT NULL DEFAULT 1 COMMENT '用户状态: 1-正常, 0-禁用',
|
||||
created_at BIGINT NOT NULL COMMENT '创建时间(时间戳)',
|
||||
updated_at BIGINT NOT NULL COMMENT '更新时间(时间戳)',
|
||||
FOREIGN KEY (department_id) REFERENCES departments (id) ON DELETE RESTRICT,
|
||||
INDEX idx_users_department_id (department_id)
|
||||
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT ='用户表';
|
||||
```
|
||||
|
||||
### 字段说明
|
||||
- `id`: 用户ID,自增主键
|
||||
- `username`: 用户名,不可为空
|
||||
- `email`: 邮箱地址,不可为空,唯一索引
|
||||
- `password`: 密码(加密存储),不可为空
|
||||
- `department_id`: 所属部门ID,外键关联departments表
|
||||
- `roles`: 用户角色,整数枚举:
|
||||
- 1: 教师
|
||||
- 2: 普通管理员
|
||||
- 3: 沟通联络人
|
||||
- 4: 系统管理员
|
||||
- `jobs`: 用户岗位,整数枚举:
|
||||
- 1: 课程制作教师
|
||||
- 2: 课程购买方项目负责人
|
||||
- 3: 课程制作方沟通联络人
|
||||
- 4: 系统制作方项目负责人
|
||||
- `avatar`: 用户头像URL,可为空
|
||||
- `creator_id`: 创建该用户的管理员ID
|
||||
- `status`: 用户状态:
|
||||
- 1: 正常
|
||||
- 0: 禁用
|
||||
- `created_at`: 创建时间,毫秒级时间戳
|
||||
- `updated_at`: 更新时间,毫秒级时间戳
|
||||
|
||||
### 索引
|
||||
- 主键索引:`id`
|
||||
- 外键索引:`idx_users_department_id (department_id)`
|
||||
- 唯一索引:`email`
|
||||
|
||||
## 3. 课程任务表 (lesson_tasks)
|
||||
|
||||
存储课程制作任务的信息和进度。
|
||||
|
||||
```sql
|
||||
CREATE TABLE lesson_tasks (
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
course_name VARCHAR(100) NOT NULL COMMENT '课程名称',
|
||||
micro_lesson_name VARCHAR(100) NOT NULL COMMENT '微课名称',
|
||||
user_id BIGINT NOT NULL COMMENT '负责人ID',
|
||||
progress_status INT NOT NULL DEFAULT 0 COMMENT '当前任务进度状态: 0-脚本上传, 1-脚本确认, 2-视频拍摄, 3-后期制作, 4-任务完成',
|
||||
script_upload_time BIGINT DEFAULT NULL COMMENT '脚本上传时间(时间戳)',
|
||||
script_confirm_time BIGINT DEFAULT NULL COMMENT '脚本确认时间(时间戳)',
|
||||
video_capture_time BIGINT DEFAULT NULL COMMENT '视频拍摄时间(时间戳)',
|
||||
video_confirm_time BIGINT DEFAULT NULL COMMENT '视频确认时间(时间戳)',
|
||||
finish_time BIGINT DEFAULT NULL COMMENT '任务完成时间(时间戳)',
|
||||
advise TEXT DEFAULT NULL COMMENT '任务建议或备注',
|
||||
created_at BIGINT NOT NULL COMMENT '创建时间(时间戳)',
|
||||
updated_at BIGINT NOT NULL COMMENT '更新时间(时间戳)',
|
||||
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE,
|
||||
INDEX idx_lesson_tasks_user_id (user_id),
|
||||
INDEX idx_lesson_tasks_progress_status (progress_status)
|
||||
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT ='课程任务表';
|
||||
```
|
||||
|
||||
### 字段说明
|
||||
- `id`: 任务ID,自增主键
|
||||
- `course_name`: 课程名称,不可为空
|
||||
- `micro_lesson_name`: 微课名称,不可为空
|
||||
- `user_id`: 负责人ID,外键关联users表
|
||||
- `progress_status`: 任务进度状态:
|
||||
- 0: 脚本上传
|
||||
- 1: 脚本确认
|
||||
- 2: 视频拍摄
|
||||
- 3: 后期制作
|
||||
- 4: 任务完成
|
||||
- `script_upload_time`: 脚本上传时间,毫秒级时间戳
|
||||
- `script_confirm_time`: 脚本确认时间,毫秒级时间戳
|
||||
- `video_capture_time`: 视频拍摄时间,毫秒级时间戳
|
||||
- `video_confirm_time`: 视频确认时间,毫秒级时间戳
|
||||
- `finish_time`: 任务完成时间,毫秒级时间戳
|
||||
- `advise`: 任务相关的建议或备注,文本字段
|
||||
- `created_at`: 创建时间,毫秒级时间戳
|
||||
- `updated_at`: 更新时间,毫秒级时间戳
|
||||
|
||||
### 索引
|
||||
- 主键索引:`id`
|
||||
- 外键索引:`idx_lesson_tasks_user_id (user_id)`
|
||||
- 普通索引:`idx_lesson_tasks_progress_status (progress_status)`
|
||||
|
||||
## 数据库关系
|
||||
|
||||
1. `users.department_id` -> `departments.id`
|
||||
- 一个部门可以有多个用户
|
||||
- 一个用户只能属于一个部门
|
||||
- 使用RESTRICT约束,防止删除仍有用户的部门
|
||||
|
||||
2. `lesson_tasks.user_id` -> `users.id`
|
||||
- 一个用户可以负责多个课程任务
|
||||
- 一个课程任务只能有一个负责人
|
||||
- 使用CASCADE约束,删除用户时自动删除其负责的课程任务
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 所有时间戳字段使用BIGINT类型,存储毫秒级时间戳
|
||||
2. 字符编码统一使用utf8mb4,支持完整的Unicode字符集
|
||||
3. 所有表都使用InnoDB引擎,支持事务和外键
|
||||
4. 关键字段都建立了适当的索引以提高查询性能
|
||||
5. 用户密码在存储前需要进行加密处理
|
||||
6. 删除用户时会自动删除其关联的课程任务,但不会影响部门数据
|
66
数据库测试数据.md
Normal file
66
数据库测试数据.md
Normal file
@ -0,0 +1,66 @@
|
||||
```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());
|
||||
```
|
Loading…
Reference in New Issue
Block a user