feat: 完善视频学习进度缓存
完善新手导航和三大创作模块的视频完成缓存、未完成提示与学习进度计算,确保进度按实际学习资源完成情况更新。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
1f8723aabd
commit
6f289959be
63 files changed
+3139
-1126
No files matched your search
@@ -1,13 +1,29 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue'
|
||||
import { getBackgroundImage } from '@/utils'
|
||||
import { useBasicElementsStore, useFilmStore, useInterviewStore, useVlogStore } from '@/stores'
|
||||
import { AnchoredProgressBar, ImgButton, type ProgressAnchor } from '@/components'
|
||||
import { studyModuleAnchors, type StudyProgressModuleKey } from './studyProgressConfig'
|
||||
import { getProductionModuleProgress } from './studyProgressUtils'
|
||||
import {
|
||||
useBasicElementsStore,
|
||||
useFilmStore,
|
||||
useInterviewStore,
|
||||
useVlogStore,
|
||||
} from '@/stores'
|
||||
import {
|
||||
AnchoredProgressBar,
|
||||
ImgButton,
|
||||
type ProgressAnchor,
|
||||
} from '@/components'
|
||||
import {
|
||||
studyModuleAnchors,
|
||||
type StudyProgressModuleKey,
|
||||
} from './studyProgressConfig'
|
||||
import {
|
||||
getBasicElementsProgress,
|
||||
getOverallStudyProgress,
|
||||
getProductionModuleProgress,
|
||||
} from './studyProgressUtils'
|
||||
|
||||
defineOptions({
|
||||
name: 'StudyProgressPopup'
|
||||
name: 'StudyProgressPopup',
|
||||
})
|
||||
|
||||
type StudyProgressItem = {
|
||||
@@ -27,42 +43,68 @@ const rowPositions = {
|
||||
basic: { height: 168 },
|
||||
vlog: { height: 148 },
|
||||
interview: { height: 146 },
|
||||
film: { height: 146 }
|
||||
film: { height: 146 },
|
||||
} as const
|
||||
const labelTextShadow =
|
||||
'0 0 5px rgba(255,255,255,0.95), 0 0 10px rgba(54,111,255,0.75), 0 3px 0 rgba(0, 48, 151, 0.75)'
|
||||
const totalTextShadow = '4px 5px 0 rgba(40, 130, 253, 0.75)'
|
||||
const childTextShadow = '0 0 6px rgba(54,250,255,0.9), 0 2px 0 rgba(0, 68, 151, 0.7)'
|
||||
const childTextShadow =
|
||||
'0 0 6px rgba(54,250,255,0.9), 0 2px 0 rgba(0, 68, 151, 0.7)'
|
||||
|
||||
const basicProgress = computed(() =>
|
||||
basicElements.isBasicElementsUnlock ? 100 : basicElements.basicElementsUnlockProgress
|
||||
getBasicElementsProgress(
|
||||
basicElements.basicElementsUnlockProgress,
|
||||
basicElements.isBasicElementsUnlock,
|
||||
basicElements.learningVideosWatched,
|
||||
),
|
||||
)
|
||||
const vlogProgress = computed(() =>
|
||||
getProductionModuleProgress('vlog', vlog.viewed, vlog.workVideosWatched),
|
||||
)
|
||||
const interviewProgress = computed(() =>
|
||||
getProductionModuleProgress(
|
||||
'interview',
|
||||
interview.viewed,
|
||||
interview.workVideosWatched,
|
||||
),
|
||||
)
|
||||
const filmViewed = computed(() => ({
|
||||
...film.viewed,
|
||||
FilmOnsiteShooting: film.allOnsiteScenesViewed,
|
||||
FilmEditing: film.allEditingVideosWatched,
|
||||
}))
|
||||
const filmProgress = computed(() =>
|
||||
getProductionModuleProgress('film', filmViewed.value, film.workVideosWatched),
|
||||
)
|
||||
const totalProgress = computed(() =>
|
||||
getOverallStudyProgress([
|
||||
basicProgress.value,
|
||||
vlogProgress.value,
|
||||
interviewProgress.value,
|
||||
filmProgress.value,
|
||||
]),
|
||||
)
|
||||
const totalProgress = computed(() => {
|
||||
const total =
|
||||
basicProgress.value + vlog.progressPercent + interview.progressPercent + film.progressPercent
|
||||
return Math.round(total / 4)
|
||||
})
|
||||
const progressItems = computed<StudyProgressItem[]>(() => [
|
||||
{
|
||||
key: 'basic',
|
||||
progress: basicProgress.value,
|
||||
anchors: studyModuleAnchors.basic
|
||||
anchors: studyModuleAnchors.basic,
|
||||
},
|
||||
{
|
||||
key: 'vlog',
|
||||
progress: getProductionModuleProgress('vlog', vlog.viewed),
|
||||
anchors: studyModuleAnchors.vlog
|
||||
progress: vlogProgress.value,
|
||||
anchors: studyModuleAnchors.vlog,
|
||||
},
|
||||
{
|
||||
key: 'interview',
|
||||
progress: getProductionModuleProgress('interview', interview.viewed),
|
||||
anchors: studyModuleAnchors.interview
|
||||
progress: interviewProgress.value,
|
||||
anchors: studyModuleAnchors.interview,
|
||||
},
|
||||
{
|
||||
key: 'film',
|
||||
progress: getProductionModuleProgress('film', film.viewed),
|
||||
anchors: studyModuleAnchors.film
|
||||
}
|
||||
progress: filmProgress.value,
|
||||
anchors: studyModuleAnchors.film,
|
||||
},
|
||||
])
|
||||
|
||||
const handleClose = () => {
|
||||
@@ -95,10 +137,14 @@ const handleClose = () => {
|
||||
:style="{ textShadow: totalTextShadow }"
|
||||
>
|
||||
<span class="text-[clamp(36px,4.5vw,72px)] leading-none">总进度</span>
|
||||
<span class="text-[clamp(36px,4.5vw,72px)] leading-none">{{ totalProgress }}%</span>
|
||||
<span class="text-[clamp(36px,4.5vw,72px)] leading-none">
|
||||
{{ totalProgress }}%
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="w-[1580px] mx-auto mt-[215px] flex flex-col pr-16 pl-64 gap-2">
|
||||
<div
|
||||
class="w-[1580px] mx-auto mt-[215px] flex flex-col pr-16 pl-64 gap-2"
|
||||
>
|
||||
<AnchoredProgressBar
|
||||
v-for="item in progressItems"
|
||||
:key="item.key"
|
||||
|
||||
@@ -1,41 +1,102 @@
|
||||
import { moduleStepMap } from '@/config/moduleStepMap'
|
||||
|
||||
type ProductionModuleKey = 'vlog' | 'interview' | 'film'
|
||||
export type ProductionModuleKey = 'vlog' | 'interview' | 'film'
|
||||
|
||||
type StepKey = 1 | 2 | 3
|
||||
|
||||
const countViewedRoutes = (routes: readonly string[], viewed: Record<string, boolean>) => {
|
||||
const countViewedRoutes = (
|
||||
routes: readonly string[],
|
||||
viewed: Record<string, boolean>,
|
||||
) => {
|
||||
if (routes.length === 0) return 0
|
||||
return routes.filter(routeName => viewed[routeName]).length
|
||||
return routes.filter((routeName) => viewed[routeName]).length
|
||||
}
|
||||
|
||||
const getStepProgress = (routes: readonly string[], viewed: Record<string, boolean>) => {
|
||||
const getStepProgress = (
|
||||
routes: readonly string[],
|
||||
viewed: Record<string, boolean>,
|
||||
) => {
|
||||
if (routes.length === 0) return 0
|
||||
return countViewedRoutes(routes, viewed) / routes.length
|
||||
}
|
||||
|
||||
const interpolate = (start: number, end: number, progress: number) =>
|
||||
start + (end - start) * progress
|
||||
const WORK_COMPLETION_KEYS = ['case', 'profession', 'idea'] as const
|
||||
|
||||
const getWorkProgress = (workVideosWatched: Record<string, boolean>) =>
|
||||
countViewedRoutes(WORK_COMPLETION_KEYS, workVideosWatched) /
|
||||
WORK_COMPLETION_KEYS.length
|
||||
|
||||
export const getProductionModuleProgress = (
|
||||
moduleKey: ProductionModuleKey,
|
||||
viewed: Record<string, boolean>
|
||||
viewed: Record<string, boolean>,
|
||||
workVideosWatched: Record<string, boolean> = {},
|
||||
) => {
|
||||
const stepRoutes = moduleStepMap[moduleKey]
|
||||
const hasAnyViewed = Object.values(stepRoutes).some(routes =>
|
||||
routes.some(routeName => viewed[routeName])
|
||||
)
|
||||
const taskProgress = getWorkProgress(workVideosWatched) * 20
|
||||
const stepProgress = (step: StepKey) =>
|
||||
getStepProgress(stepRoutes[step], viewed)
|
||||
const step1Progress = stepProgress(1) * 22
|
||||
const step2Progress = stepProgress(2) * 22
|
||||
const step3Progress = stepProgress(3) * 21
|
||||
const progress = taskProgress + step1Progress + step2Progress + step3Progress
|
||||
|
||||
if (!hasAnyViewed) return 0
|
||||
if (
|
||||
progress >= 85 &&
|
||||
step1Progress === 22 &&
|
||||
step2Progress === 22 &&
|
||||
step3Progress === 21
|
||||
) {
|
||||
return 100
|
||||
}
|
||||
|
||||
const stepProgress = (step: StepKey) => getStepProgress(stepRoutes[step], viewed)
|
||||
const step1Progress = stepProgress(1)
|
||||
const step2Progress = stepProgress(2)
|
||||
const step3Progress = stepProgress(3)
|
||||
|
||||
if (step1Progress < 1) return Math.round(interpolate(20, 42, step1Progress))
|
||||
if (step2Progress < 1) return Math.round(interpolate(42, 64, step2Progress))
|
||||
if (step3Progress < 1) return Math.round(interpolate(64, 85, step3Progress))
|
||||
|
||||
return 100
|
||||
return Math.round(progress)
|
||||
}
|
||||
|
||||
const BASIC_ELEMENTS_VIDEO_KEYS = [
|
||||
'basic-composition:step1',
|
||||
'basic-composition:step2',
|
||||
'basic-light:step2',
|
||||
'basic-light:step3',
|
||||
'basic-light:step4',
|
||||
'basic-camera-movement:step2',
|
||||
'basic-camera-movement:step3',
|
||||
'basic-shooting-skills:step2',
|
||||
'basic-shooting-skills:step3',
|
||||
'basic-shooting-skills:step4',
|
||||
'basic-shooting-skills:step5',
|
||||
'basic-shooting-skills:step6',
|
||||
'basic-shooting-skills:step7',
|
||||
'basic-short-video-editing-skills:step2',
|
||||
'basic-short-video-editing-skills:step3',
|
||||
'basic-short-video-editing-skills:step4',
|
||||
'basic-short-video-editing-skills:step5',
|
||||
'basic-short-video-editing-skills:step6',
|
||||
'basic-short-video-editing-skills:step7',
|
||||
'basic-short-video-editing-skills:step8',
|
||||
'basic-short-video-editing-skills:step9',
|
||||
'basic-short-video-editing-skills:step10',
|
||||
'basic-short-video-editing-skills:step11',
|
||||
] as const
|
||||
|
||||
export const getBasicElementsProgress = (
|
||||
unlockProgress: number,
|
||||
isUnlocked: boolean,
|
||||
learningVideosWatched: Record<string, boolean> = {},
|
||||
) => {
|
||||
if (isUnlocked) return 100
|
||||
|
||||
const watchedProgress =
|
||||
(countViewedRoutes(BASIC_ELEMENTS_VIDEO_KEYS, learningVideosWatched) /
|
||||
BASIC_ELEMENTS_VIDEO_KEYS.length) *
|
||||
100
|
||||
|
||||
return Math.round(
|
||||
Math.max(0, Math.min(100, Math.max(unlockProgress, watchedProgress))),
|
||||
)
|
||||
}
|
||||
|
||||
export const getOverallStudyProgress = (progresses: readonly number[]) => {
|
||||
if (progresses.length === 0) return 0
|
||||
const total = progresses.reduce((sum, progress) => sum + progress, 0)
|
||||
return Math.round(total / progresses.length)
|
||||
}
|
||||
Reference in new issue
Block a user