更新: 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

@ -1,9 +1,8 @@
import http from "@/http/HttpClient";
import { useUser } from "@/stores/useUser";
import type { PagedData } from "@/types/api/common";
import type { CreateLessonTaskRequest, LessonTask, UpdateLessonTaskRequest } from "@/types/api/lesson";
import type { LessonTask, UpdateLessonTaskRequest } from "@/types/api/lesson";
import type { LoginRequest } from "@/types/api/auth";
import type { CreateUserRequest, User } from "@/types/api/user";
import type { User } from "@/types/api/user";
/**
* 业务API类
@ -176,13 +175,14 @@ export default class BussApi {
/**
* 更新课程进度
* @param lessonId - 课程ID
* @param progressStatus - 进度状态 (0-未开始, 1-脚本制作, 2-脚本审核, 3-脚本确认, 4-视频制作, 5-视频确认)
* @returns Promise<void>
*/
static updateLessonProgress(lessonId: number): Promise<void> {
static updateLessonProgress(lessonId: number, progressStatus: number): Promise<void> {
const user = useUser();
return http
.server()
.post(`lesson/${lessonId}/progress`, null, {
.post(`lesson/${lessonId}/progress`, { progressStatus }, {
headers: {
Authorization: `Bearer ${user.token}`,
},

View File

@ -31,17 +31,17 @@ const nameLabelIconMap = {
home: {
title: '进度查看',
icon: 'dashboard',
roles: ['teacher', 'admin', 'liaison', 'sysadmin'] as const // 使用 as const 来确保类型正确
roles: ['teacher', 'liaison', 'admin', 'sysadmin'] as const // 校方教师、校方项目负责人、公司课程顾问、公司系统管理员
},
progress: {
title: '进度管理',
icon: 'transfer',
roles: ['teacher', 'admin', 'sysadmin'] as const
roles: ['teacher', 'admin', 'sysadmin'] as const // 校方教师、公司课程顾问、公司系统管理员
},
my: {
title: '我的',
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', () => {
// 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");
// const BASE_URL = ref<string>("http://192.30.5.16:1218/api");
const BASE_URL = ref<string>("http://192.168.0.119:1218/api");
// const BASE_URL = ref<string>("http://192.30.5.11:1218/api");
return {
BASE_URL

View File

@ -4,7 +4,7 @@ import pageWrapper from '@/components/page-wrapper.vue';
import { useUser } from '@/stores/useUser';
import type { LessonTask } from '@/types/api/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 { onMounted, ref, watch } from 'vue';
import { useToast } from 'wot-design-uni';
@ -73,8 +73,24 @@ const loadLessons = async () => {
toast.loading({ msg: '加载中...' })
try {
const userId = teacherFilterValue.value === 0 ? undefined : teacherFilterValue.value
const res = await BussApi.getLessonTasks(1, 512, userId)
let res;
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)) {
toast.error({ msg: res.message || '获取数据失败' })
@ -92,7 +108,6 @@ const loadLessons = async () => {
}, {})
groupedLessons.value = groupData
// expand courses with lessons in progress
expandedCourse.value = Object.keys(groupData).filter(courseName => {
return groupData[courseName].filter((lesson: LessonTask) =>
calcLessonProgress(lesson) !== 0 && calcLessonProgress(lesson) !== 100
@ -109,7 +124,6 @@ const loadLessons = async () => {
}
}
// 监听教师筛选值的变化
watch(teacherFilterValue, () => {
loadLessons()
})
@ -125,6 +139,26 @@ const getUsernameById = (userId: number) => {
} else
return ''
}
const refresh = async () => {
try {
uni.reLaunch({
url: '/pages/index/index'
})
} catch (err) {
} finally {
uni.stopPullDownRefresh()
}
}
onPullDownRefresh(() => {
refresh()
})
onLoad(() => {
loadLessons()
})
</script>
<template>
@ -143,9 +177,12 @@ const getUsernameById = (userId: number) => {
<div class="flex flex-col gap-1">
<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>
<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>
</p>
<div class="flex items-center gap-1">
<wd-tag v-if="(() => {
@ -162,6 +199,13 @@ const getUsernameById = (userId: number) => {
});
return allCompleted;
})()" 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>
共{{ courses.length }}节微课
</wd-tag>
@ -206,7 +250,13 @@ const getUsernameById = (userId: number) => {
<div v-else class="i-tabler-hourglass-empty"></div>
</div>
</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 class="w-24 flex items-center gap-3">
<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 { computed, onMounted, ref } from 'vue';
import { useToast } from 'wot-design-uni';
import { onPullDownRefresh, onLoad } from '@dcloudio/uni-app'
const route = useRoute()
const router = useRouter()
@ -32,7 +33,7 @@ const goProgress = (lessonId: number) => {
tabbar.activeTab = 'progress'
}
onMounted(() => {
const loadLesson = async () => {
if (!route.params?.courseId) {
toast.error({
msg: '参数错误'
@ -48,7 +49,33 @@ onMounted(() => {
}).catch(err => {
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>
<template>

View File

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

View File

@ -7,20 +7,33 @@ import { Jobs, Roles } from '@/types/api/user';
import { useRouter } from 'uni-mini-router';
import { onMounted, ref } from 'vue';
import { useToast } from 'wot-design-uni';
import { onPullDownRefresh, onLoad } from '@dcloudio/uni-app'
const router = useRouter()
const toast = useToast()
const user = useUser()
const userInfo = ref<User | null>(null)
onMounted(() => {
toast.loading({
msg: '加载中...'
})
BussApi.profile(user.token!).then(res => {
const loadUserInfo = async () => {
try {
toast.loading({
msg: '加载中...'
})
const res = await BussApi.profile(user.token!)
toast.close()
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 () => {
@ -50,34 +63,39 @@ const departmentMap: Record<DepartmentId, string> = {
// 角色映射
const roleMap: Record<number, string> = {
[Roles.TEACHER]: '教师',
[Roles.GENERAL_ADMIN]: '普通管理员',
[Roles.CONTACTOR]: '沟通联络人',
[Roles.SYSTEM_ADMIN]: '系统管理员',
[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]: '系统制作方项目负责人',
[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')
}
})
const refresh = async () => {
try {
uni.reLaunch({
url: '/pages/my/index'
})
} catch (err) {
} finally {
uni.stopPullDownRefresh()
}
}
// 添加下拉刷新处理函数
onPullDownRefresh(() => {
refresh()
})
// 页面加载时执行
onLoad(() => {
loadUserInfo()
})
</script>

View File

@ -1,8 +1,8 @@
<script lang="ts" setup>
import BussApi from '@/api/BussApi';
import { useDayjs } from '@/composables/useDayjs';
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 { useMessage, useToast } from 'wot-design-uni';
import StatusBlock from './StatusBlock.vue';
@ -12,8 +12,8 @@ import { onPullDownRefresh } from '@dcloudio/uni-app'
const route = useRoute()
const message = useMessage()
const message_reject = useMessage('wd-message-box-slot')
const toast = useToast()
const dayjs = useDayjs()
const user = useUser()
type GroupedLessons = { [key: string]: LessonTask[] }
@ -32,7 +32,7 @@ const pickerLessonColumns = computed(() => {
}) : []
})
const pickerLessonValue = ref()
const adviseText = ref('')
const adviseText = ref<string>('')
const selectedLesson = computed(() => {
if (!pickerLessonValue.value) return null
@ -59,7 +59,7 @@ const onLessonPick = ({ value }: { value: number }) => {
const script_file_destination = ref<FileUploadDestination>('wechat')
const onStep1 = () => {
const onStep0 = () => {
message.confirm({
title: '提交脚本',
msg: '请确认已经通过微信或平台上传了脚本文件,再确认提交'
@ -74,12 +74,11 @@ const onStep1 = () => {
msg: '正在提交...'
})
const params = {
// advise: adviseText.value,
// scriptUploadTime: dayjs().unix(),
advise: "",
courseName: selectedLesson.value.courseName,
microLessonName: selectedLesson.value.microLessonName,
userId: selectedLesson.value.userId,
progressStatus: 1
progressStatus: ProgressStatus.SCRIPT_CREATING//1
}
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) => {
console.log("adviseText.value: " + adviseText.value)
message.confirm({
title: rejected ? '驳回脚本' : '通过脚本',
msg: rejected ? '脚本不符合要求,驳回制作方重做' : '请确认脚本合格无误后,再确认审核通过'
const msg = rejected ? message_reject : message
msg.confirm({
title: rejected ? '驳回视频拍摄制作' : '通过视频拍摄制作',
msg: rejected ? '视频拍摄制作不符合要求,驳回拍摄制作方重做' : '请确认视频拍摄制作合格无误后,再确认审核通过'
}).then(() => {
if (!selectedLesson.value?.id) {
toast.error({
@ -114,18 +157,16 @@ const onStep2 = (rejected: boolean = false) => {
selectedLesson.value.id,
rejected ? {
advise: adviseText.value.toString(),
// scriptUploadTime: 0,
courseName: selectedLesson.value.courseName,
microLessonName: selectedLesson.value.microLessonName,
userId: selectedLesson.value.userId,
progressStatus: 0
progressStatus: ProgressStatus.SCRIPT_CREATING//1
} : {
advise: adviseText.value.toString(),
// scriptConfirmTime: dayjs().unix(),
advise: "",
courseName: selectedLesson.value.courseName,
microLessonName: selectedLesson.value.microLessonName,
userId: selectedLesson.value.userId,
progressStatus: 2
progressStatus: ProgressStatus.SCRIPT_CONFIRMED//3
}).then(res => {
toast.success({
msg: rejected ? '驳回成功' : '审核通过'
@ -140,10 +181,10 @@ const onStep2 = (rejected: boolean = false) => {
}
const onStep3 = (rejected: boolean = false) => {
console.log("adviseText.value: " + adviseText.value)
message.confirm({
title: rejected ? '驳回视频' : '通过视频',
msg: rejected ? '视频不符合要求,驳回制作方重做' : '请确认视频合格无误后,再确认审核通过'
const msg = rejected ? message_reject : message
msg.confirm({
title: rejected ? '驳回视频拍摄制作' : '通过视频拍摄制作',
msg: rejected ? '视频拍摄制作不符合要求,驳回拍摄制作方重做' : '请确认视频拍摄制作合格无误后,再确认审核通过'
}).then(() => {
if (!selectedLesson.value?.id) {
toast.error({
@ -158,62 +199,16 @@ const onStep3 = (rejected: boolean = false) => {
selectedLesson.value.id,
rejected ? {
advise: adviseText.value.toString(),
// videoCaptureTime: 0,
courseName: selectedLesson.value.courseName,
microLessonName: selectedLesson.value.microLessonName,
userId: selectedLesson.value.userId,
progressStatus: 1
progressStatus: ProgressStatus.VIDEO_CONFIRMED
} : {
advise: adviseText.value.toString(),
// videoConfirmTime: dayjs().unix(),
advise: "",
courseName: selectedLesson.value.courseName,
microLessonName: selectedLesson.value.microLessonName,
userId: selectedLesson.value.userId,
progressStatus: 3
}).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
progressStatus: ProgressStatus.VIDEO_CONFIRMED
}).then(res => {
toast.success({
msg: rejected ? '驳回成功' : '审核通过'
@ -286,16 +281,17 @@ const updateLessons = async () => {
onMounted(() => {
updateLessons()
})
const refresh = async () => {
try {
await updateLessons()
uni.reLaunch({
url: '/pages/progress/index'
})
} catch (err) {
} finally {
// 停止下拉刷新动画
uni.stopPullDownRefresh()
}
}
// 添加下拉刷新处理函数
onPullDownRefresh(() => {
refresh()
@ -310,6 +306,10 @@ onPullDownRefresh(() => {
<wd-picker :columns="pickerLessonColumns" label="微课选择" v-model="pickerLessonValue" @confirm="onLessonPick"
:columns-height="280" label-width="80px" safe-area-inset-bottom title="微课选择" :disabled="!pickerCourseValue" />
</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">
<wd-status-tip v-if="!pickerLessonValue" image="search" tip="请先选择微课" />
<div v-else class="space-y-6">
@ -322,6 +322,11 @@ onPullDownRefresh(() => {
<div v-if="selectedLessonStage?.step === 0" class="px-2">
<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 title="脚本提交途径" :title-width="'100px'" center custom-class="mb-4">
<wd-radio-group v-model="script_file_destination" shape="button">
@ -331,11 +336,11 @@ onPullDownRefresh(() => {
</wd-radio-group>
</wd-cell>
</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>
<StatusBlock v-else title="脚本还未提交" subtitle="请耐心等待审核">
<StatusBlock v-else title="脚本还未提交" subtitle="请耐心等待提交">
<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>
</StatusBlock>
</div>
@ -343,45 +348,91 @@ onPullDownRefresh(() => {
<div v-if="selectedLessonStage?.step === 1">
<StatusBlock v-if="user.hasRole('teacher')" title="脚本已提交" subtitle="请耐心等待审核">
<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>
</StatusBlock>
<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>
<div class="i-tabler-progress-check 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="onStep2()">通过</wd-button>
<wd-button type="error" block @click="onStep2(true)">驳回</wd-button>
<wd-button type="primary" block @click="onStep1()">确认</wd-button>
<wd-button type="error" block @click="onStep1(true)">驳回</wd-button>
</div>
</div>
</div>
</div>
<div v-if="selectedLessonStage?.step === 2">
<StatusBlock v-if="user.hasRole('teacher')" title="视频拍摄进行中" subtitle="请等待线下视频拍摄">
<StatusBlock v-if="!user.hasRole('teacher')" title="脚本确认进行中" subtitle="请等待脚本内容确认">
<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>
</StatusBlock>
<!-- && selectedLesson && selectedLesson?.videoCaptureTime > 0 -->
<div v-else>
<StatusBlock title="视频拍摄已完成" subtitle="请核对后审核">
<StatusBlock title="脚本审核已完成" subtitle="请确认脚本内容">
<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>
</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 />
<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 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 class="flex gap-3 px-4">
<wd-button type="primary" block @click="onStep3()">通过</wd-button>
@ -390,32 +441,8 @@ onPullDownRefresh(() => {
</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>
<!-- && 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">
<div v-else-if="selectedLessonStage?.step === 5">
<wd-status-tip image="comment" tip="该微课已完成" />
</div>
</div>

View File

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

View File

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

View File

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

View File

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