新增一键催办功能、驳回显示时间戳

This commit is contained in:
2025-09-11 19:32:18 +08:00
parent 583691266f
commit 518d4d3a20
13 changed files with 1417 additions and 482 deletions

1
.vercel/project.json Normal file
View File

@ -0,0 +1 @@
{"projectName":"trae_ppms-uni-vue3_8m55"}

3
components.d.ts vendored
View File

@ -23,7 +23,9 @@ declare module 'vue' {
WdForm: typeof import('wot-design-uni/components/wd-form/wd-form.vue')['default']
WdIcon: typeof import('wot-design-uni/components/wd-icon/wd-icon.vue')['default']
WdInput: typeof import('wot-design-uni/components/wd-input/wd-input.vue')['default']
WdLoading: typeof import('wot-design-uni/components/wd-loading/wd-loading.vue')['default']
WdMessageBox: typeof import('wot-design-uni/components/wd-message-box/wd-message-box.vue')['default']
WdNoticeBar: typeof import('wot-design-uni/components/wd-notice-bar/wd-notice-bar.vue')['default']
WdPicker: typeof import('wot-design-uni/components/wd-picker/wd-picker.vue')['default']
WdProgress: typeof import('wot-design-uni/components/wd-progress/wd-progress.vue')['default']
WdRadio: typeof import('wot-design-uni/components/wd-radio/wd-radio.vue')['default']
@ -32,6 +34,7 @@ declare module 'vue' {
WdStatusTip: typeof import('wot-design-uni/components/wd-status-tip/wd-status-tip.vue')['default']
WdStep: typeof import('wot-design-uni/components/wd-step/wd-step.vue')['default']
WdSteps: typeof import('wot-design-uni/components/wd-steps/wd-steps.vue')['default']
WdSwipeAction: typeof import('wot-design-uni/components/wd-swipe-action/wd-swipe-action.vue')['default']
WdTabbar: typeof import('wot-design-uni/components/wd-tabbar/wd-tabbar.vue')['default']
WdTabbarItem: typeof import('wot-design-uni/components/wd-tabbar-item/wd-tabbar-item.vue')['default']
WdTextarea: typeof import('wot-design-uni/components/wd-textarea/wd-textarea.vue')['default']

View File

@ -60,9 +60,9 @@
"@dcloudio/uni-quickapp-webview": "3.0.0-4020420240722002",
"dayjs": "^1.11.13",
"pinia": "^2.2.2",
"vue": "^3.4.21",
"vue": "^3.5.18",
"vue-i18n": "^9.1.9",
"wot-design-uni": "^1.5.1"
"wot-design-uni": "^1.11.0"
},
"devDependencies": {
"@dcloudio/types": "^3.4.8",

646
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -360,4 +360,44 @@ export default class BussApi {
})
.then((res) => res.data);
}
/**
* 检查当前用户是否需要任务提醒
* @returns Promise<boolean> 返回是否需要提醒
* - 教师:可以催办管理员(状态 1、3或收到管理员消息状态 0、2、4 且有 msg时返回 true
* - 管理员:可以催办教师(状态 0、2、4或有消息需要处理时返回 true
*/
static checkNeedReminder(): Promise<boolean> {
const user = useUser();
return http
.server()
.get("lesson-tasks/need-reminder", {
headers: {
Authorization: `Bearer ${user.token}`,
},
})
.then((res) => res.data.data);
}
/**
* 一键催办 - 发送催办提醒
* @param id - 课程任务ID
* @returns Promise<LessonTask> 返回更新后的课程任务
*/
static sendReminder(id: number): Promise<LessonTask> {
return this.updateLessonTask(id, {
expediteStatus: 1
});
}
/**
* 清除催办提醒
* @param id - 课程任务ID
* @returns Promise<LessonTask> 返回更新后的课程任务
*/
static clearReminder(id: number): Promise<LessonTask> {
return this.updateLessonTask(id, {
expediteStatus: 0
});
}
}

View File

@ -64,6 +64,130 @@ const expandedCourse = ref(['lesson'])
const groupedLessons = ref<{ [key: string]: LessonTask[] }>({})
const refreshKey = ref(0)
// 催办消息展示相关
const reminderMessages = ref<LessonTask[]>([])
/**
* 判断当前用户是否负责当前进度
*/
const isUserResponsibleForCurrentProgress = (lesson: LessonTask): boolean => {
if (!lesson || !user.userinfo) return false
const progressStatus = lesson.progressStatus
const userRoles = user.userinfo.roles
const userJobs = user.userinfo.jobs
// 判断进度负责人
switch (progressStatus) {
case 0: // 脚本制作 - 教师负责
case 1: // 脚本提交 - 教师负责
return userRoles === 1 && userJobs === 1 // 校方教师 + 课程制作教师
case 2: // 脚本审核 - 审核人员负责
return userJobs === 2 // 课程审核人员
case 3: // 脚本确认 - 教师负责
return userRoles === 1 && userJobs === 1 // 校方教师 + 课程制作教师
case 4: // 视频制作审核 - 审核人员负责
return userJobs === 2 // 课程审核人员
case 5: // 已完成
return false
default:
return false
}
}
/**
* 判断是否应该显示催办消息
*/
const shouldShowExpediteNotice = (lesson: LessonTask): boolean => {
if (!lesson || !user.userinfo) return false
// 条件1: 当前用户所负责的课程通过userId判断
const isUserCourse = lesson.userId === user.userinfo.id
// 条件2: 课程当前所在的进度是当前用户所负责的进度
const isResponsibleForProgress = isUserResponsibleForCurrentProgress(lesson)
// 条件3: 催办信息为1
const isExpedited = lesson.expediteStatus === 1
return isUserCourse && isResponsibleForProgress && isExpedited
}
/**
* 调试函数:打印当前账户所负责的课程状态
*/
const debugUserCourses = () => {
console.log('🔍 [首页] 执行debugUserCourses函数...')
if (!user.userinfo) {
console.log('❌ 用户未登录')
return
}
console.log('👤 当前用户ID:', user.userinfo.id)
const allLessons = Object.values(groupedLessons.value).flat()
console.log('📚 所有课程数量:', allLessons.length)
// 过滤出当前用户负责的课程
const userCourses = allLessons.filter(lesson => lesson.userId === user.userinfo!.id)
console.log('🎯 当前用户负责的课程数量:', userCourses.length)
// 生成简洁的ID和状态对应关系
const courseStatusMap = userCourses.reduce((acc, lesson) => {
acc[`ID:${lesson.id}`] = `状态:${lesson.progressStatus},催办:${lesson.expediteStatus}`
return acc
}, {} as Record<string, string>)
console.log('📋 当前用户负责的课程状态:', courseStatusMap)
// 需要处理的催办课程(满足三个条件)
const needAttentionMap = userCourses
.filter(lesson => shouldShowExpediteNotice(lesson))
.reduce((acc, lesson) => {
acc[`ID:${lesson.id}`] = `需处理催办`
return acc
}, {} as Record<string, string>)
if (Object.keys(needAttentionMap).length > 0) {
console.log('⚠️ 需要处理的催办课程:', needAttentionMap)
} else {
console.log('✅ 当前没有需要处理的催办课程')
}
}
// 检查催办消息
const checkReminderMessages = () => {
const allLessons = Object.values(groupedLessons.value).flat()
const filteredLessons = allLessons.filter(lesson => {
// 检查是否有催办状态
if (lesson.expediteStatus !== 1) {
return false
}
// 根据用户角色和任务状态判断是否显示催办信息
const progressStatus = lesson.progressStatus
if (user.hasRole('teacher')) {
// 教师:任务状态是 0、2、4 时显示催办信息
return [0, 2, 4].includes(progressStatus)
} else if (user.hasRole('admin')) {
// 公司课程顾问(课程审核人员):任务状态是 1、3 时显示催办信息
return [1, 3].includes(progressStatus)
} else {
return false
}
})
reminderMessages.value = filteredLessons
}
const openLessonDetail = (courseId: number) => {
router.push({
name: 'lesson',
@ -75,11 +199,22 @@ const openLessonDetail = (courseId: number) => {
const loadLessons = async () => {
console.log('🔄 [首页] 开始加载课程列表...')
if (!user.hasValidToken() || !user.userinfo) {
console.log('❌ [首页] 用户未登录,跳转到登录页')
toast.error({ msg: '请先登录' })
router.replace('/pages/login/index')
return
}
console.log('✅ [首页] 用户已登录,用户信息:', {
id: user.userinfo.id,
username: user.userinfo.username,
roles: user.userinfo.roles,
jobs: user.userinfo.jobs
})
toast.loading({ msg: '加载中...' })
try {
let res;
@ -118,6 +253,28 @@ const loadLessons = async () => {
return acc
}, {})
// 在每个章节内部按徽标状态排序
Object.keys(groupData).forEach(courseName => {
// 使用冒泡排序,将有徽标的课程排在前面
const lessons = groupData[courseName]
for (let i = 0; i < lessons.length; i++) {
for (let j = 0; j < lessons.length - i - 1; j++) {
// 检查徽标是否显示:根据用户角色和课程角色判断
const currentHasBadge = (getLessonRole(lessons[j]) === 'teacher' && user.hasRole('teacher')) ||
(getLessonRole(lessons[j]) === 'admin' && user.hasRole('admin'))
const nextHasBadge = (getLessonRole(lessons[j + 1]) === 'teacher' && user.hasRole('teacher')) ||
(getLessonRole(lessons[j + 1]) === 'admin' && user.hasRole('admin'))
// 如果当前元素没有徽标但下一个元素有徽标,则交换位置
if (!currentHasBadge && nextHasBadge) {
const temp = lessons[j]
lessons[j] = lessons[j + 1]
lessons[j + 1] = temp
}
}
}
})
if (value.value !== 0) {
Object.keys(groupData).forEach(courseName => {
groupData[courseName].sort((a: LessonTask, b: LessonTask) => {
@ -132,8 +289,26 @@ const loadLessons = async () => {
groupedLessons.value = groupData
// 调试:打印当前账户所负责的课程状态
console.log('🚀 [首页] 开始调试用户课程状态...')
debugUserCourses()
console.log('✅ [首页] 调试完成')
const expandedGroups = Object.keys(groupData).filter(courseName => {
const courses = groupData[courseName]
// 检查是否有徽标需要显示 - 新增逻辑
const hasBadge = courses.some(lesson =>
(getLessonRole(lesson) === 'teacher' && user.hasRole('teacher')) ||
(getLessonRole(lesson) === 'admin' && user.hasRole('admin'))
)
// 如果没有徽标需要显示,则不展开 - 新增逻辑
if (!hasBadge) {
return false
}
// 原有的进度逻辑
const hasCompleted = courses.some(lesson => calcLessonProgress(lesson) === 100)
const hasNotStarted = courses.some(lesson => calcLessonProgress(lesson) === 0)
const hasInProgress = courses.some(lesson => {
@ -144,6 +319,9 @@ const loadLessons = async () => {
})
expandedCourse.value = expandedGroups
// 检查催办消息
checkReminderMessages()
} catch (err: unknown) {
if (err instanceof Error) {
toast.error({ msg: err.message })
@ -195,11 +373,31 @@ onPullDownRefresh(() => {
onLoad(() => {
loadLessons()
})
const handleSwipeAction = (action: string, lesson: LessonTask) => {
switch (action) {
case 'edit':
toast.show(`编辑微课: ${lesson.microLessonName}`)
// 这里可以添加编辑逻辑
break
case 'delete':
toast.show(`删除微课: ${lesson.microLessonName}`)
// 这里可以添加删除逻辑
break
default:
break
}
}
</script>
<template>
<page-wrapper>
<div>
<!-- 催办消息展示 -->
<div v-if="reminderMessages.length > 0" class="mb-2">
<wd-notice-bar text="您有课程需要完成,请及时处理" type="warning" />
</div>
<div v-if="!user.hasRole('teacher')" class="flex items-center justify-around gap-4 px-4">
<wd-drop-menu>
<wd-drop-menu-item v-model="teacherFilterValue" :options="teacherFilterOptions"
@ -217,14 +415,9 @@ onLoad(() => {
<template #title="{ expanded, disabled, isFirst }">
<div class="w-full flex justify-between items-center">
<div class="flex flex-col gap-1">
<!-- <wd-badge is-dot :right="230" :hidden="!courses.some(lesson =>
(getLessonRole(lesson) === 'teacher' && user.hasRole('teacher')) ||
(getLessonRole(lesson) === 'admin' && user.hasRole('admin'))
)"> -->
<p class="pt-1">
{{ courseName || '无标题课程' }}
</p>
<!-- </wd-badge> -->
<div class="flex items-center gap-1">
<wd-tag v-if="(() => {
const hasCompleted = courses.some(lesson => calcLessonProgress(lesson) === 100)
@ -254,10 +447,10 @@ onLoad(() => {
共{{ courses.length }}节微课
</wd-tag>
<wd-tag custom-class="op-60" plain>
已完成{{ courses.filter(lesson => calcLessonProgress(lesson) === 100).length }}·
进行中{{ courses.filter(lesson => calcLessonProgress(lesson) !== 0 && calcLessonProgress(lesson)
!== 100).length }}·
未开始{{ courses.filter(lesson => calcLessonProgress(lesson) === 0).length }}
已完成{{courses.filter(lesson => calcLessonProgress(lesson) === 100).length}}·
进行中{{courses.filter(lesson => calcLessonProgress(lesson) !== 0 && calcLessonProgress(lesson)
!== 100).length}}·
未开始{{courses.filter(lesson => calcLessonProgress(lesson) === 0).length}}
</wd-tag>
</div>
</div>
@ -266,20 +459,20 @@ onLoad(() => {
<div class="flex items-center gap-0.5">
<div class="w-1.5 aspect-square rounded-full bg-emerald"></div>
<span class="text-xs text-emerald font-bold font-mono">
{{ courses.filter(lesson => calcLessonProgress(lesson) === 100).length }}
{{courses.filter(lesson => calcLessonProgress(lesson) === 100).length}}
</span>
</div>
<div class="flex items-center gap-0.5">
<div class="w-1.5 aspect-square rounded-full bg-blue"></div>
<span class="text-xs text-blue font-bold font-mono">
{{ courses.filter(lesson => calcLessonProgress(lesson) !== 0 && calcLessonProgress(lesson) !==
100).length }}
{{courses.filter(lesson => calcLessonProgress(lesson) !== 0 && calcLessonProgress(lesson) !==
100).length}}
</span>
</div>
<div class="flex items-center gap-0.5">
<div class="w-1.5 aspect-square rounded-full bg-neutral"></div>
<span class="text-xs text-neutral font-bold font-mono">
{{ courses.filter(lesson => calcLessonProgress(lesson) === 0).length }}
{{courses.filter(lesson => calcLessonProgress(lesson) === 0).length}}
</span>
</div>
</div>
@ -292,8 +485,11 @@ onLoad(() => {
<wd-status-tip v-if="courses.length === 0"
image="https://registry.npmmirror.com/wot-design-uni-assets/*/files/content.png" tip="没有课程小节" />
<div v-else v-for="(lesson, i) in courses" :key="`${lesson.id}-${refreshKey}`"
@click="openLessonDetail(lesson.id)"
class="w-full py-2 gap-12 flex justify-between items-center border-b border-b-solid border-neutral-100 last:border-b-0 first:pt-0 last:pb-0">
class="w-full border-b border-b-solid border-neutral-100 last:border-b-0">
<!-- 所有用户都使用 SwipeAction 组件 -->
<wd-swipe-action :disabled="!(user.hasRole('admin') || user.hasRole('sysadmin'))">
<div @click="openLessonDetail(lesson.id)"
class="w-full py-2 gap-12 flex justify-between items-center first:pt-0 last:pb-0">
<div class="flex items-center gap-1 self-center">
<div>
<div v-if="calcLessonProgress(lesson) === 100" class="i-tabler-circle-check text-emerald"></div>
@ -318,6 +514,17 @@ onLoad(() => {
<div class="i-tabler-dots text-neutral-400 text-xl"></div>
</div>
</div>
<!-- 右滑操作按钮 - 仅管理员可见 -->
<template #right>
<view class="action">
<!-- <view class="button" style="background: #f59e0b;" @click.stop="handleSwipeAction('edit', lesson)">编辑
</view> -->
<view class="button" style="background: #ef4444;" @click.stop="handleSwipeAction('delete', lesson)">
删除</view>
</view>
</template>
</wd-swipe-action>
</div>
</div>
</wd-collapse-item>
</wd-collapse>
@ -329,4 +536,21 @@ onLoad(() => {
.wd-collapse-item__header>view {
width: 100%;
}
/* SwipeAction 样式 */
.action {
height: 100%;
display: flex;
}
.button {
display: flex;
align-items: center;
justify-content: center;
padding: 0 16px;
height: 100%;
color: white;
font-size: 14px;
min-width: 60px;
}
</style>

View File

@ -20,6 +20,37 @@ const message = useMessage()
const lesson = ref<LessonTask | null>(null)
// 判断当前用户是否应该处理该课程
const shouldCurrentUserHandle = () => {
if (!lesson.value) return false
const role = getLessonRole(lesson.value)
return (role === 'teacher' && user.hasRole('teacher')) ||
(role === 'admin' && user.hasRole('admin'))
}
// 发送催办提醒
const sendReminder = async () => {
if (!lesson.value) return
try {
toast.loading({ msg: '发送催办中...' })
await BussApi.sendReminder(lesson.value.id)
toast.success({ msg: '催办成功,已通知相关人员处理' })
// 重新加载课程详情
await loadLesson()
} catch (err: unknown) {
if (err instanceof Error) {
toast.error({ msg: err.message })
} else {
toast.error({ msg: '催办失败' })
}
} finally {
toast.close()
}
}
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)
@ -39,31 +70,61 @@ const goProgress = (lessonId: number) => {
return
}
router.replaceAll({
name: 'progress',
params: {
courseName: `${lesson.value.courseName}`,
lessonId: `${lessonId}`
}
// 更新导航栏状态
tabbar.activeTab = 'progress'
// 先清除之前的缓存参数,确保每次使用最新的参数
uni.removeStorageSync('progress_params')
// 使用本地存储传递参数
const courseNameParam = lesson.value.courseName
const lessonIdParam = lessonId.toString()
// 将参数存储到本地缓存
uni.setStorageSync('progress_params', {
courseName: courseNameParam,
lessonId: lessonIdParam
})
// 不带参数跳转到tabBar页面
router.pushTab({
name: 'progress'
})
}
const loadLesson = async () => {
if (!route.params?.courseId) {
toast.error({
msg: '参数错误'
msg: '参数错误缺少课程ID'
})
console.error('Missing courseId in route params:', route.params)
return
}
console.log('Loading lesson with courseId:', route.params.courseId)
toast.loading({
msg: '加载中...'
})
BussApi.getLessonTask(route.params.courseId).then(courseData => {
toast.close()
try {
// 确保courseId是数字类型
const courseId = parseInt(route.params.courseId as string)
if (isNaN(courseId)) {
throw new Error('课程ID格式错误')
}
const courseData = await BussApi.getLessonTask(courseId)
console.log('Lesson data loaded:', courseData)
lesson.value = courseData
}).catch(err => {
toast.error({ msg: err.message })
toast.close()
} catch (err: any) {
console.error('Failed to load lesson:', err)
toast.error({
msg: err?.message || '加载课程数据失败,请重试'
})
}
}
onMounted(() => {
@ -92,6 +153,9 @@ onPullDownRefresh(() => {
// 页面加载时执行
onLoad(() => {
// 清除进度页面的缓存参数,避免缓存累积
uni.removeStorageSync('progress_params')
loadLesson()
})
@ -100,17 +164,27 @@ onLoad(() => {
<template>
<div>
<wd-message-box></wd-message-box>
<!-- 加载状态 -->
<div v-if="!lesson" class="p-4 text-center">
<wd-loading vertical>
加载课程信息中...
</wd-loading>
</div>
<!-- 课程信息 -->
<div v-else>
<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?.courseName }}</h2>
<h1 class="text-lg text-white font-bold">{{ lesson?.microLessonName }}</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;">
创建于 {{ lesson?.createdAt ? dayjs(lesson.createdAt * 1000).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') : '-' }}
完成于 {{ 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>
@ -118,12 +192,15 @@ onLoad(() => {
<div v-else class="i-tabler-hourglass-empty"></div>
</div>
</div>
<div class="p-4">
<wd-steps :active="lessonStages?.step || 0" vertical>
<wd-step v-for="(step, index) in lessonSteps" :key="index" :title="step.title"
:description="step.description" />
</wd-steps>
</div>
<!-- 进度处理按钮区域 -->
<div v-if="lessonProgress !== 100" class="px-4 pt-2 ">
<wd-button v-if="!user.hasRole('liaison')" type="primary" :round="false" plain block
@click="goProgress(lesson?.id!)">进度处理</wd-button>
@ -131,6 +208,17 @@ onLoad(() => {
<div v-else class="px-4 pt-2">
<wd-button v-if="!user.hasRole('liaison')" disabled type="success" :round="false" plain block>已完成</wd-button>
</div>
<!-- 催办按钮区域 -->
<div
v-if="lesson && !shouldCurrentUserHandle() && lessonProgress !== 100 && (user.hasRole('teacher') || user.hasRole('admin'))"
class="px-4 pt-2">
<wd-button type="warning" :round="false" plain block @click="sendReminder">
<wd-icon name="bell" class="mr-1"></wd-icon>
催办
</wd-button>
</div>
</div>
</div>
</template>

View File

@ -37,6 +37,14 @@ const handleSubmit = () => {
remember: model.remember
}).then(res => {
user.setToken(res.token)
// 如果用户勾选了记住密码,保存登录凭证
if (model.remember) {
user.saveCredentials(model.email, model.password)
} else {
user.clearCredentials()
}
toast.loading({
msg: '加载资料...'
})
@ -74,6 +82,15 @@ onPullDownRefresh(() => {
refresh()
})
// 初始化时加载登录凭证
onLoad(() => {
const credentials = user.getCredentials()
if (credentials) {
model.email = credentials.email
model.password = credentials.password
model.remember = true
}
})
</script>
<template>
@ -87,7 +104,7 @@ onPullDownRefresh(() => {
<wd-cell>
<template #title>
<div class="pl-[5px]">
<wd-checkbox v-model="model.remember">记住登录</wd-checkbox>
<wd-checkbox v-model="model.remember">记住密码</wd-checkbox>
</div>
</template>
</wd-cell>

View File

@ -9,7 +9,7 @@ import StatusBlock from './StatusBlock.vue';
import { useRoute } from 'uni-mini-router';
import { useUser } from '@/stores/useUser';
import { useTabbar } from '@/stores/useTabbar'
import { onPullDownRefresh } from '@dcloudio/uni-app'
import { onPullDownRefresh, onLoad, onShow } from '@dcloudio/uni-app'
import { useRouter } from 'uni-mini-router';
const router = useRouter()
@ -29,8 +29,16 @@ const pickerCourseValue = ref()
const pickerLessonColumns = computed(() => {
return pickerCourseValue.value ? groupedLessons.value[pickerCourseValue.value].map((lesson: LessonTask) => {
const isCompleted = extractLessonStage(lesson).step === 5
const shouldShowExpedite = shouldShowExpediteNotice(lesson)
let prefix = ''
if (isCompleted) {
prefix = '✅ '
} else if (shouldShowExpedite) {
prefix = '🔔 '
}
return {
label: (extractLessonStage(lesson).step === 5 ? '✅ ' : '') + lesson.microLessonName,
label: prefix + lesson.microLessonName,
value: lesson.id,
}
}) : []
@ -48,6 +56,142 @@ const selectedLessonStage = computed(() => {
return extractLessonStage(selectedLesson.value)
})
const isExpedited = computed(() => {
return selectedLesson.value?.expediteStatus === 1
})
/**
* 判断当前用户是否负责当前进度
* @param lesson 课程任务
* @returns 是否负责当前进度
*/
const isUserResponsibleForCurrentProgress = (lesson: LessonTask): boolean => {
if (!lesson || !user.userinfo) return false
const progressStatus = lesson.progressStatus
const userRoles = user.userinfo.roles
const userJobs = user.userinfo.jobs
// 判断进度负责人
switch (progressStatus) {
case 0: // 脚本制作 - 教师负责
case 1: // 脚本提交 - 教师负责
return userRoles === 1 && userJobs === 1 // 校方教师 + 课程制作教师
case 2: // 脚本审核 - 审核人员负责
return userJobs === 2 // 课程审核人员
case 3: // 脚本确认 - 教师负责
return userRoles === 1 && userJobs === 1 // 校方教师 + 课程制作教师
case 4: // 视频制作审核 - 审核人员负责
return userJobs === 2 // 课程审核人员
case 5: // 已完成
return false
default:
return false
}
}
/**
* 判断是否应该显示催办消息
* @param lesson 课程任务
* @returns 是否显示催办消息
*/
const shouldShowExpediteNotice = (lesson: LessonTask): boolean => {
if (!lesson || !user.userinfo) return false
// 条件1: 当前用户所负责的课程通过userId判断
const isUserCourse = lesson.userId === user.userinfo.id
// 条件2: 课程当前所在的进度是当前用户所负责的进度
const isResponsibleForProgress = isUserResponsibleForCurrentProgress(lesson)
// 条件3: 催办信息为1
const isExpedited = lesson.expediteStatus === 1
return isUserCourse && isResponsibleForProgress && isExpedited
}
const shouldShowExpediteNoticeForSelected = computed(() => {
return selectedLesson.value ? shouldShowExpediteNotice(selectedLesson.value) : false
})
/**
* 调试函数:打印当前账户所负责的课程状态
*/
const debugUserCourses = () => {
console.log('🔍 执行debugUserCourses函数...')
if (!user.userinfo) {
console.log('❌ 用户未登录')
return
}
console.log('👤 当前用户ID:', user.userinfo.id)
const allLessons = Object.values(groupedLessons.value).flat()
console.log('📚 所有课程数量:', allLessons.length)
// 过滤出当前用户负责的课程
const userCourses = allLessons.filter(lesson => lesson.userId === user.userinfo!.id)
console.log('🎯 当前用户负责的课程数量:', userCourses.length)
// 生成简洁的ID和状态对应关系
const courseStatusMap = userCourses.reduce((acc, lesson) => {
acc[`ID:${lesson.id}`] = `状态:${lesson.progressStatus}`
return acc
}, {} as Record<string, string>)
console.log('📋 当前用户负责的课程状态:', courseStatusMap)
// 催办状态的课程
const expeditedCourseMap = userCourses
.filter(lesson => lesson.expediteStatus === 1)
.reduce((acc, lesson) => {
acc[`ID:${lesson.id}`] = `催办:${lesson.expediteStatus}`
return acc
}, {} as Record<string, string>)
if (Object.keys(expeditedCourseMap).length > 0) {
console.log('🔔 催办状态的课程:', expeditedCourseMap)
}
// 需要处理的催办课程
const needAttentionMap = userCourses
.filter(lesson => shouldShowExpediteNotice(lesson))
.reduce((acc, lesson) => {
acc[`ID:${lesson.id}`] = `需处理:是`
return acc
}, {} as Record<string, string>)
if (Object.keys(needAttentionMap).length > 0) {
console.log('⚠️ 需要处理的催办课程:', needAttentionMap)
}
}
// 自动清除催办消息的辅助函数
const autoClearReminder = async (lessonId: number) => {
try {
console.log('开始清除催办消息课程ID:', lessonId)
await BussApi.clearReminder(lessonId)
console.log('催办消息清除成功课程ID:', lessonId)
// 显示清除成功的提示
toast.success({
msg: '催办消息已清除'
})
} catch (err: any) {
console.error('自动清除催办消息失败:', err)
// 显示清除失败的提示
toast.error({
msg: `清除催办消息失败: ${err?.message || '未知错误'}`
})
}
}
/**
* 判断advise是否为驳回状态
* @param advise 修改建议字符串
@ -57,7 +201,7 @@ const isRejectAdvise = (advise: string | undefined): boolean => {
try {
if (!advise) return false
const adviseObj = JSON.parse(advise)
return 'reject' in adviseObj
return 'type' in adviseObj && 'data' in adviseObj
} catch {
return false
}
@ -67,24 +211,76 @@ const adviseText = computed(() => {
try {
if (!selectedLesson.value?.advise) return '暂无修改建议'
const adviseObj = JSON.parse(selectedLesson.value.advise)
return adviseObj.reject || '暂无修改建议'
return adviseObj.data || '暂无修改建议'
} catch {
return selectedLesson?.value?.advise || '暂无修改建议'
}
})
const hasValidAdvise = computed(() => {
try {
if (!selectedLesson.value?.advise) return false
const adviseObj = JSON.parse(selectedLesson.value.advise)
const dataText = adviseObj.data
return dataText && dataText.trim() !== ''
} catch {
const adviseText = selectedLesson?.value?.advise
return adviseText && adviseText.trim() !== ''
}
})
const adviseTimestamp = computed(() => {
try {
if (!selectedLesson.value?.advise) return null
const adviseObj = JSON.parse(selectedLesson.value.advise)
return adviseObj.time || null
} catch {
return null
}
})
const formatTimestamp = (timestamp: string | null) => {
if (!timestamp) return ''
try {
const date = new Date(timestamp)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const hour = String(date.getHours()).padStart(2, '0')
const minute = String(date.getMinutes()).padStart(2, '0')
const formatted = `${year}/${month}/${day} ${hour}:${minute}`
console.log('格式化时间:', timestamp, '->', formatted)
return formatted
} catch (error) {
console.error('时间格式化错误:', error)
return ''
}
}
const getAdviseText = (adviseString: string | undefined): string => {
try {
if (!adviseString) return ''
const adviseObj = JSON.parse(adviseString)
const rejectText = adviseObj.reject || ''
return rejectText
const dataText = adviseObj.data || ''
return dataText
} catch (e) {
console.error('JSON解析失败:', e)
return ''
}
}
const getAdviseTimestamp = (adviseString: string | undefined): string | null => {
try {
if (!adviseString) return null
const adviseObj = JSON.parse(adviseString)
return adviseObj.time || null
} catch (e) {
console.error('JSON解析失败:', e)
return null
}
}
const onCoursePick = ({ value }: { value: string }) => {
pickerCourseValue.value = value
pickerLessonValue.value = undefined
@ -92,6 +288,28 @@ const onCoursePick = ({ value }: { value: string }) => {
const onLessonPick = ({ value }: { value: number }) => {
pickerLessonValue.value = value
// 调试:打印选中课程的详细信息
if (selectedLesson.value) {
const lesson = selectedLesson.value
const isResponsible = isUserResponsibleForCurrentProgress(lesson)
const shouldShowExpedite = shouldShowExpediteNotice(lesson)
console.log('🎯 选中课程详情:', {
id: lesson.id,
courseName: lesson.courseName,
microLessonName: lesson.microLessonName,
progressStatus: lesson.progressStatus,
expediteStatus: lesson.expediteStatus,
userId: lesson.userId,
currentUserId: user.userinfo?.id,
userRoles: user.userinfo?.roles,
userJobs: user.userinfo?.jobs,
isUserCourse: lesson.userId === user.userinfo?.id,
isUserResponsibleForProgress: isResponsible,
shouldShowExpediteNotice: shouldShowExpedite
})
}
}
/**
@ -119,10 +337,19 @@ const onStep0 = () => {
courseName: selectedLesson.value.courseName,
microLessonName: selectedLesson.value.microLessonName,
userId: selectedLesson.value.userId,
progressStatus: ProgressStatus.SCRIPT_CREATING//1
progressStatus: ProgressStatus.SCRIPT_CREATING,//1
msg: "" // 提交时清除催办消息
}
BussApi.updateLessonTask(selectedLesson.value.id, params).then(res => {
console.log('onStep0 请求体:', params)
console.log('onStep0 课程ID:', selectedLesson.value.id)
BussApi.updateLessonTask(selectedLesson.value.id, params).then(async res => {
// 清理催办提醒
if (selectedLesson.value?.id) {
await autoClearReminder(selectedLesson.value.id)
}
toast.success({
msg: '提交成功'
})
@ -152,28 +379,47 @@ const onStep1 = (rejected: boolean = false) => {
toast.loading({
msg: '正在处理...'
})
BussApi.updateLessonTask(
selectedLesson.value.id,
rejected ? {
advise: JSON.stringify({ reject: adviseTextValue.value.toString() }),
const requestData = rejected ? {
advise: JSON.stringify({
type: user.hasRole('teacher') ? 'teacher' : 'general_admin',
data: adviseTextValue.value.toString(),
time: new Date().toISOString()
}),
courseName: selectedLesson.value.courseName,
microLessonName: selectedLesson.value.microLessonName,
userId: selectedLesson.value.userId,
progressStatus: ProgressStatus.NOT_STARTED//0
progressStatus: ProgressStatus.NOT_STARTED,//0
msg: "" // 驳回时清除催办消息
} : {
advise: JSON.stringify({ confirm: "" }),
courseName: selectedLesson.value.courseName,
microLessonName: selectedLesson.value.microLessonName,
userId: selectedLesson.value.userId,
progressStatus: ProgressStatus.SCRIPT_REVIEW//2
}).then(res => {
progressStatus: ProgressStatus.SCRIPT_REVIEW,//2
msg: "" // 通过时清除催办消息
}
console.log('onStep1 请求体:', requestData)
console.log('onStep1 课程ID:', selectedLesson.value.id)
BussApi.updateLessonTask(selectedLesson.value.id, requestData).then(async res => {
console.log('onStep1 任务状态更新成功:', res)
// 清理催办提醒
if (selectedLesson.value?.id) {
await autoClearReminder(selectedLesson.value.id)
}
toast.success({
msg: rejected ? '驳回成功' : '审核通过'
})
setTimeout(() => {
updateLessons()
}, 1500);
}).catch(err => {
console.error('onStep1 任务状态更新失败:', err)
toast.error({ msg: err.message })
})
})
@ -194,28 +440,47 @@ const onStep2 = (rejected: boolean = false) => {
toast.loading({
msg: '正在处理...'
})
BussApi.updateLessonTask(
selectedLesson.value.id,
rejected ? {
advise: JSON.stringify({ reject: adviseTextValue.value.toString() }),
const requestData = rejected ? {
advise: JSON.stringify({
type: user.hasRole('teacher') ? 'teacher' : 'general_admin',
data: adviseTextValue.value.toString(),
time: new Date().toISOString()
}),
courseName: selectedLesson.value.courseName,
microLessonName: selectedLesson.value.microLessonName,
userId: selectedLesson.value.userId,
progressStatus: ProgressStatus.SCRIPT_CREATING//1
progressStatus: ProgressStatus.SCRIPT_CREATING,//1
msg: "" // 驳回时清除催办消息
} : {
advise: JSON.stringify({ confirm: "" }),
courseName: selectedLesson.value.courseName,
microLessonName: selectedLesson.value.microLessonName,
userId: selectedLesson.value.userId,
progressStatus: ProgressStatus.SCRIPT_CONFIRMED//3
}).then(res => {
progressStatus: ProgressStatus.SCRIPT_CONFIRMED,//3
msg: "" // 通过时清除催办消息
}
console.log('onStep2 请求体:', requestData)
console.log('onStep2 课程ID:', selectedLesson.value.id)
BussApi.updateLessonTask(selectedLesson.value.id, requestData).then(async res => {
console.log('onStep2 任务状态更新成功:', res)
// 清理催办提醒
if (selectedLesson.value?.id) {
await autoClearReminder(selectedLesson.value.id)
}
toast.success({
msg: rejected ? '驳回成功' : '审核通过'
})
setTimeout(() => {
updateLessons()
}, 1500);
}).catch(err => {
console.error('onStep2 任务状态更新失败:', err)
toast.error({ msg: err.message })
})
})
@ -236,28 +501,47 @@ const onStep3 = (rejected: boolean = false) => {
toast.loading({
msg: '正在处理...'
})
BussApi.updateLessonTask(
selectedLesson.value.id,
rejected ? {
advise: JSON.stringify({ reject: 'huertian' + adviseTextValue.value.toString() }),
const requestData = rejected ? {
advise: JSON.stringify({
type: user.hasRole('teacher') ? 'teacher' : 'general_admin',
data: adviseTextValue.value.toString(),
time: new Date().toISOString()
}),
courseName: selectedLesson.value.courseName,
microLessonName: selectedLesson.value.microLessonName,
userId: selectedLesson.value.userId,
progressStatus: ProgressStatus.SCRIPT_CONFIRMED//3
progressStatus: ProgressStatus.SCRIPT_REVIEW,//2
msg: "" // 驳回时清除催办消息
} : {
advise: JSON.stringify({ confirm: "" }),
courseName: selectedLesson.value.courseName,
microLessonName: selectedLesson.value.microLessonName,
userId: selectedLesson.value.userId,
progressStatus: ProgressStatus.VIDEO_CREATING//4
}).then(res => {
progressStatus: ProgressStatus.VIDEO_CREATING,//4
msg: "" // 通过时清除催办消息
}
console.log('onStep3 请求体:', requestData)
console.log('onStep3 课程ID:', selectedLesson.value.id)
BussApi.updateLessonTask(selectedLesson.value.id, requestData).then(async res => {
console.log('onStep3 任务状态更新成功:', res)
// 清理催办提醒
if (selectedLesson.value?.id) {
await autoClearReminder(selectedLesson.value.id)
}
toast.success({
msg: rejected ? '驳回成功' : '审核通过'
})
setTimeout(() => {
updateLessons()
}, 1500);
}).catch(err => {
console.error('onStep3 任务状态更新失败:', err)
toast.error({ msg: err.message })
})
})
@ -277,41 +561,69 @@ const onStep4 = (rejected: boolean = false) => {
toast.loading({
msg: '正在处理...'
})
BussApi.updateLessonTask(
selectedLesson.value.id,
rejected ? {
advise: JSON.stringify({ reject: adviseTextValue.value.toString() }),
const requestData = rejected ? {
advise: JSON.stringify({
type: user.hasRole('teacher') ? 'teacher' : 'general_admin',
data: adviseTextValue.value.toString(),
time: new Date().toISOString()
}),
courseName: selectedLesson.value.courseName,
microLessonName: selectedLesson.value.microLessonName,
userId: selectedLesson.value.userId,
progressStatus: ProgressStatus.SCRIPT_CONFIRMED//3
progressStatus: ProgressStatus.SCRIPT_CONFIRMED,//3
msg: "" // 驳回时清除催办消息
} : {
advise: JSON.stringify({ confirm: "" }),
courseName: selectedLesson.value.courseName,
microLessonName: selectedLesson.value.microLessonName,
userId: selectedLesson.value.userId,
progressStatus: ProgressStatus.VIDEO_CONFIRMED//5
}).then(res => {
progressStatus: ProgressStatus.VIDEO_CONFIRMED,//5
msg: "" // 通过时清除催办消息
}
console.log('onStep4 请求体:', requestData)
console.log('onStep4 课程ID:', selectedLesson.value.id)
BussApi.updateLessonTask(selectedLesson.value.id, requestData).then(async res => {
console.log('onStep4 任务状态更新成功:', res)
// 清理催办提醒
if (selectedLesson.value?.id) {
await autoClearReminder(selectedLesson.value.id)
}
toast.success({
msg: rejected ? '驳回成功' : '审核通过'
})
setTimeout(() => {
updateLessons()
}, 1500);
}).catch(err => {
console.error('onStep4 任务状态更新失败:', err)
toast.error({ msg: err.message })
})
})
}
const updateLessons = async () => {
console.log('🔄 开始更新课程列表...')
adviseTextValue.value = ""
if (!user.hasValidToken() || !user.userinfo) {
console.log('❌ 用户未登录,跳转到登录页')
toast.error({ msg: '请先登录' })
router.replace('/pages/login/index')
return
}
console.log('✅ 用户已登录,用户信息:', {
id: user.userinfo.id,
username: user.userinfo.username,
roles: user.userinfo.roles,
jobs: user.userinfo.jobs
})
toast.loading({
msg: '加载中...'
})
@ -338,11 +650,24 @@ const updateLessons = async () => {
...Object.keys(groupData)
]
if (route.params?.courseName) {
onCoursePick({ value: decodeURI(route.params.courseName) })
if (route.params?.lessonId) {
if (typeof route.params.lessonId === 'string') {
onLessonPick({ value: parseInt(route.params.lessonId) })
// 调试:打印当前账户所负责的课程状态
console.log('🚀 开始调试用户课程状态...')
debugUserCourses()
console.log('✅ 调试完成')
// 从本地缓存获取参数
const progress_params = uni.getStorageSync('progress_params')
if (progress_params && progress_params.courseName && progress_params.lessonId) {
// 选择课程
if (Object.keys(groupData).includes(progress_params.courseName)) {
onCoursePick({ value: progress_params.courseName })
// 选择课程ID
const lessonIdValue = parseInt(progress_params.lessonId)
if (!isNaN(lessonIdValue)) {
onLessonPick({ value: lessonIdValue })
}
}
}
@ -359,9 +684,24 @@ const updateLessons = async () => {
onMounted(() => {
console.log('📱 页面已挂载,开始加载数据...')
// 从本地缓存获取参数
const progress_params = uni.getStorageSync('progress_params')
updateLessons()
})
// 添加onShow钩子确保每次显示页面时都刷新数据
onShow(() => {
const progress_params = uni.getStorageSync('progress_params')
// 如果有缓存参数,则需要重新加载数据
if (progress_params && progress_params.courseName && progress_params.lessonId) {
updateLessons()
}
})
const refresh = async () => {
try {
const startTime = Date.now()
@ -405,6 +745,11 @@ const handleClick = () => {
<wd-status-tip v-if="!pickerLessonValue"
image="https://registry.npmmirror.com/wot-design-uni-assets/*/files/search.png" tip="请先选择微课" />
<div v-else class="space-y-6">
<!-- 催办状态提示 -->
<!-- <div v-if="shouldShowExpediteNoticeForSelected" class="mx-2">
<wd-notice-bar text="🔔 此课程已被催办,请加快处理进度" type="warning" />
</div> -->
<div>
<wd-steps :active="selectedLessonStage?.step || 0" align-center>
<wd-step v-for="(step, index) in getLessonSteps(selectedLesson!, true)" :key="index" :title="step.title"
@ -414,20 +759,25 @@ const handleClick = () => {
<div v-if="selectedLessonStage?.step === 0" class="px-2">
<div v-if="user.hasRole('teacher')">
<div v-if="isRejectAdvise(selectedLesson?.advise)" class="m-2 text-sm">
<div v-if="isRejectAdvise(selectedLesson?.advise) && hasValidAdvise" class="m-2 text-sm">
<wd-card title="修改建议">
{{ adviseText }}
<div class="space-y-2">
<div class="break-words whitespace-pre-wrap">{{ adviseText }}</div>
<div v-if="adviseTimestamp" class="text-xs text-gray-500">
时间{{ formatTimestamp(adviseTimestamp) }}
</div>
</div>
</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">
<wd-radio value="wechat">微信</wd-radio>
<wd-radio value="qq">QQ</wd-radio>
<wd-radio value="platform">平台</wd-radio>
<div class="mb-4">
<div class="text-sm font-semibold text-gray-700 mb-4 ">脚本提交途径</div>
<wd-radio-group v-model="script_file_destination" class="flex justify-center">
<wd-radio value="wechat" shape="button" class="mx-1">微信</wd-radio>
<wd-radio value="qq" shape="button" class="mx-1">QQ</wd-radio>
<wd-radio value="baidu" shape="button" class="mx-1">百度网盘</wd-radio>
<wd-radio value="platform" shape="button" class="mx-1">平台</wd-radio>
</wd-radio-group>
</wd-cell>
</wd-cell-group>
</div>
<wd-button type="primary" block @click="onStep0" custom-class="w-full">提交脚本</wd-button>
</div>
<StatusBlock v-else title="脚本还未提交" subtitle="请耐心等待提交">
@ -446,9 +796,14 @@ const handleClick = () => {
</StatusBlock>
</div>
<div v-else>
<div v-if="isRejectAdvise(selectedLesson?.advise) === true" class=" m-2 text-sm">
<div v-if="isRejectAdvise(selectedLesson?.advise) && hasValidAdvise" class=" m-2 text-sm">
<wd-card title="修改建议">
{{ adviseText }}
<div class="space-y-2">
<div class="break-words whitespace-pre-wrap">{{ adviseText }}</div>
<div v-if="adviseTimestamp" class="text-xs text-gray-500">
时间{{ formatTimestamp(adviseTimestamp) }}
</div>
</div>
</wd-card>
</div>
<StatusBlock title="脚本已提交" subtitle="请耐心等待审核">
@ -457,7 +812,7 @@ const handleClick = () => {
</template>
</StatusBlock>
<div class="mt-4 space-y-4">
<div class="flex gap-3 px-4">
<div class="flex gap-3 px-4 justify-center">
<wd-button type="primary" block @click="onStep1()">确认</wd-button>
<wd-button type="error" block @click="onStep1(true)">驳回</wd-button>
</div>
@ -467,13 +822,23 @@ const handleClick = () => {
<div v-if="selectedLessonStage?.step === 2">
<div v-if="user.hasRole('teacher')">
<div v-if="isRejectAdvise(selectedLesson?.advise) && hasValidAdvise" class="m-2 text-sm">
<wd-card title="修改建议">
<div class="space-y-2">
<div class="break-words whitespace-pre-wrap">{{ adviseText }}</div>
<div v-if="adviseTimestamp" class="text-xs text-gray-500">
时间{{ formatTimestamp(adviseTimestamp) }}
</div>
</div>
</wd-card>
</div>
<StatusBlock title="脚本审核已完成" subtitle="请确认脚本内容">
<template #icon>
<div class="i-tabler-file-text text-7xl text-neutral-400"></div>
</template>
</StatusBlock>
<div class="mt-4 space-y-4">
<div class="flex gap-3 px-4">
<div class="flex gap-3 px-4 justify-center">
<wd-button type="primary" block @click="onStep2()">确认</wd-button>
<wd-button type="error" block @click="onStep2(true)">驳回</wd-button>
</div>
@ -490,32 +855,30 @@ const handleClick = () => {
<div v-if="selectedLessonStage?.step === 3">
<div v-if="user.hasRole('teacher')">
<div v-if="getAdviseText(selectedLesson?.advise).startsWith('huertian')" class=" m-2 text-sm">
<wd-card title="修改建议">
{{ getAdviseText(selectedLesson?.advise).substring('huertian'.length) }}
</wd-card>
</div>
<StatusBlock title="视频拍摄制作进行中" subtitle="请等待视频拍摄制作">
<StatusBlock title="后期制作进行中" subtitle="请等待后期制作">
<template #icon>
<div class="i-tabler-video text-7xl text-neutral-400"></div>
</template>
</StatusBlock>
</div>
<div v-else>
<div v-if="!getAdviseText(selectedLesson?.advise).startsWith('huertian')">
<div v-if="isRejectAdvise(selectedLesson?.advise) === true" class=" m-2 text-sm">
<div v-if="isRejectAdvise(selectedLesson?.advise) && hasValidAdvise" class=" m-2 text-sm">
<wd-card title="修改建议">
{{ adviseText }}
<div class="space-y-2">
<div class="break-words whitespace-pre-wrap">{{ adviseText }}</div>
<div v-if="adviseTimestamp" class="text-xs text-gray-500">
时间{{ formatTimestamp(adviseTimestamp) }}
</div>
</div>
</wd-card>
</div>
</div>
<StatusBlock title="视频拍摄制作进行中" subtitle="完成后请确认">
<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="flex gap-3 px-4 center">
<div class="flex gap-3 px-4 justify-center">
<wd-button type="primary" block @click="onStep3()">通过</wd-button>
<wd-button type="error" block @click="onStep3(true)">驳回</wd-button>
</div>
@ -525,20 +888,20 @@ const handleClick = () => {
<div v-if="selectedLessonStage?.step === 4">
<div v-if="user.hasRole('teacher')">
<StatusBlock title="视频拍摄制作已完成" subtitle="请确认视频内容">
<StatusBlock title="后期制作已完成" subtitle="请确认资源内容">
<template #icon>
<div class="i-tabler-brand-parsinta text-7xl text-neutral-400"></div>
</template>
</StatusBlock>
<div class="mt-4 space-y-4">
<div class="flex gap-3 px-4">
<div class="flex gap-3 px-4 justify-center">
<wd-button type="primary" block @click="onStep4()">通过</wd-button>
<wd-button type="error" block @click="onStep4(true)">驳回</wd-button>
</div>
</div>
</div>
<div v-else>
<StatusBlock title="视频内容确认中" subtitle="请等待视频内容确认">
<StatusBlock title="资源内容确认中" subtitle="请等待资源内容确认">
<template #icon>
<div class="i-tabler-brand-parsinta text-7xl text-neutral-400"></div>
</template>

View File

@ -84,6 +84,22 @@ export const useUser = defineStore("user", () => {
}
}
// 保存登录凭证
function saveCredentials(email: string, password: string) {
uni.setStorageSync('credentials', { email, password });
}
// 获取登录凭证
function getCredentials(): { email: string, password: string } | null {
const credentials = uni.getStorageSync('credentials');
return credentials || null;
}
// 清除登录凭证
function clearCredentials() {
uni.removeStorageSync('credentials');
}
// 检查 token 是否有效
function hasValidToken(): boolean {
return !!token.value && token.value.length > 0;
@ -92,6 +108,8 @@ export const useUser = defineStore("user", () => {
function logout() {
token.value = null;
userinfo.value = null;
uni.removeStorageSync('token');
// 不要清除登录凭证,以便用户下次能够快速登录
}
return {
@ -103,6 +121,9 @@ export const useUser = defineStore("user", () => {
canEditCourse,
logout,
setToken,
hasValidToken
hasValidToken,
saveCredentials,
getCredentials,
clearCredentials
};
});

View File

@ -24,8 +24,12 @@ export interface LessonTask {
videoConfirmTime?: number;
/** 任务完成时间(时间戳) */
finishTime?: number;
/** 催办状态 0:未催办 1:催办 */
expediteStatus: number;
/** 建议/反馈信息 */
advise?: string;
/** 催办消息 */
msg?: string | null;
/** 创建时间(时间戳) */
createdAt: number;
/** 更新时间(时间戳) */
@ -47,8 +51,8 @@ export interface LessonTaskPagination {
* NOT_STARTED = 0, // 脚本制作
SCRIPT_CREATING = 1, // 脚本审核
SCRIPT_REVIEW = 2, // 脚本审核
SCRIPT_CONFIRMED = 3, // 视频拍摄与制作
VIDEO_CREATING = 4, // 视频确认
SCRIPT_CONFIRMED = 3, // 后期制作
VIDEO_CREATING = 4, // 资源确认
VIDEO_CONFIRMED = 5 // 任务完成
*/
// 进度状态枚举
@ -56,12 +60,12 @@ export enum ProgressStatus {
NOT_STARTED = 0, // 脚本制作
SCRIPT_CREATING = 1, // 脚本审核
SCRIPT_REVIEW = 2, // 脚本审核
SCRIPT_CONFIRMED = 3, // 视频拍摄与制作
VIDEO_CREATING = 4, // 视频确认
SCRIPT_CONFIRMED = 3, // 后期制作
VIDEO_CREATING = 4, // 资源确认
VIDEO_CONFIRMED = 5 // 任务完成
}
export type FileUploadDestination = "qq" | "wechat" | "platform";
export type FileUploadDestination = "qq" | "wechat" | "platform" | "baidu";
/**
* 创建课程任务请求参数接口
@ -101,6 +105,10 @@ export interface UpdateLessonTaskRequest {
videoConfirmTime?: number;
/** 任务完成时间 */
finishTime?: number;
/** 催办状态 0:未催办 1:催办 */
expediteStatus?: number;
/** 建议信息 */
advise?: string;
/** 催办消息 */
msg?: string | null;
}

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 // 系统管理
COURSE_TEACHER = 1, // 课程制作教师
PROJECT_MANAGER = 2, // 课程审核人员
COURSE_CONTACTOR = 3, // 校方项目负责人
SYSTEM_MANAGER = 4 // 公司系统管理
}
// 用户状态枚举

View File

@ -7,8 +7,8 @@ export const extractLessonStage = (lesson: LessonTask) => {
script_creating: currentStep >= 1, // 脚本制作
script_review: currentStep >= 2, // 脚本审核
script_confirmed: currentStep >= 3, // 脚本确认
video_creating: currentStep >= 4, // 视频制作
video_confirmed: currentStep >= 5, // 视频确认
video_creating: currentStep >= 4, // 后期制作
video_confirmed: currentStep >= 5, // 资源确认
step: 0,
};
stages.step = currentStep;
@ -21,7 +21,7 @@ export const getLessonRole = (lesson: LessonTask): 'teacher' | 'admin' | '' => {
switch (lesson.progressStatus) {
case 0: // 未开始
case 2: // 脚本审核
case 4: // 视频拍摄与制作
case 4: // 后期制作
return 'teacher';
case 1: // 脚本制作
@ -37,11 +37,11 @@ export const calcLessonProgress = (lesson: LessonTask) => {
if (!lesson) return 0;
// 根据 progressStatus 计算进度
// 0-未开始, 1-脚本制作, 2-脚本审核, 3-脚本确认, 4-视频拍摄与制作, 5-视频确认
// 0-未开始, 1-脚本制作, 2-脚本审核, 3-脚本确认, 4-后期制作, 5-资源确认
switch (lesson.progressStatus) {
case 5: // 视频确认
case 5: // 资源确认
return 100;
case 4: // 视频拍摄与制作
case 4: // 后期制作
return 80;
case 3: // 脚本确认
return 60;
@ -92,20 +92,20 @@ export const getLessonSteps = (lesson: LessonTask, simplify: boolean = false) =>
: "确认脚本内容",
},
{
title: progress.video_creating ? "拍摄制作" : undefined,
title: progress.video_creating ? "后期制作" : undefined,
description: progress.video_creating
? simplify
? "已完成"
: `已于 ${formatTime(lesson.videoCreateTime)} 完成拍摄制作`
: "拍摄制作",
: `已于 ${formatTime(lesson.videoCreateTime)} 完成后期制作`
: "后期制作",
},
{
title: progress.video_confirmed ? "视频确认" : undefined,
title: progress.video_confirmed ? "资源确认" : undefined,
description: progress.video_confirmed
? simplify
? "已完成"
: `已于 ${formatTime(lesson.videoConfirmTime)} 完成视频确认`
: "确认视频内容",
: `已于 ${formatTime(lesson.videoConfirmTime)} 完成资源确认`
: "确认资源内容",
},
];
};