feat: 完善课程引导与实战评价体验

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
zhongyidianandClaude Opus 4.7 committed 2026-05-09 09:53:06 +08:00
1 parent 6f8252472f
commit 1f8723aabd
122 files changed
+2595 -1325

No files matched your search

+103 -25
View File
@@ -4,7 +4,7 @@ import { getBackgroundImage, getStaticImage, IMAGE_PATHS } from '@/utils'
import { ImgButton } from '@/components/Button'
import { StarRating } from '@/components/Rating'
import { useRouter } from 'vue-router'
import BackToTopIcon from '@/assets/svg/back-to-top.svg'
import BackToTopIcon from '@/assets/images/components/置顶.png'
import { GuideSteps, GuideStepsItem } from '@/components/Guide'
import { useFilmStore } from '@/stores/modules/film'
@@ -26,7 +26,9 @@ const isLastPage = computed<boolean>(() => currentPage.value >= TOTAL_PAGES - 1)
/** 是否正在从第二页回到第一页 */
const isScrollingBackToFirst = computed<boolean>(() => {
return isAnimating.value && currentPage.value === 1 && animatingToPage.value === 0
return (
isAnimating.value && currentPage.value === 1 && animatingToPage.value === 0
)
})
/** 第一页坐着的人物是否显示 */
@@ -42,7 +44,7 @@ const guideKeyPrefix = 'film-practice-page'
const steps = ref([
'欢迎来到实战操作台! 这里的内容很丰富,滚动鼠标就能浏览到全部内容啦~',
'点击【我的创作笔记】,编写创作思路~',
'点击【查看】按钮,欣赏他人实战作品~'
'点击【查看】按钮,欣赏他人实战作品~',
])
const currentGuideKey = computed(() => `${guideKeyPrefix}-${currentPage.value}`)
@@ -75,8 +77,8 @@ const worksList = ref<WorkItem[]>([
stars: 4,
path: '/video/film/practice/film_anli1.mp4',
description:
'作品简介作品简介作品简介作品简介作品简介作品简介作品简介作品简介作品简介作品简介作品简介作品简介作品简介作品简介作品简介作品简介作品简介'
}
'作品简介作品简介作品简介作品简介作品简介作品简介作品简介作品简介作品简介作品简介作品简介作品简介作品简介作品简介作品简介作品简介作品简介',
},
])
/** 左滑动作品列表 */
@@ -168,18 +170,44 @@ const handleWheel = (event: WheelEvent): void => {
}
}
const restoreScrollPage = (): void => {
const savedPage = filmStore.practiceScrollPage
const targetPage = Math.max(0, Math.min(savedPage, TOTAL_PAGES - 1))
currentPage.value = targetPage
animatingToPage.value = targetPage
requestAnimationFrame(() => {
if (containerRef.value) {
containerRef.value.scrollTop = targetPage * PAGE_HEIGHT
}
})
}
const saveScrollPage = (): void => {
const page = containerRef.value
? Math.round(containerRef.value.scrollTop / PAGE_HEIGHT)
: currentPage.value
const targetPage = Math.max(0, Math.min(page, TOTAL_PAGES - 1))
filmStore.setPracticeScrollPage(targetPage)
}
onMounted(() => {
restoreScrollPage()
showPageGuide()
containerRef.value?.addEventListener('wheel', handleWheel, { passive: false })
})
onUnmounted(() => {
saveScrollPage()
containerRef.value?.removeEventListener('wheel', handleWheel)
})
</script>
<template>
<div ref="containerRef" class="w-full h-full overflow-auto">
<div
ref="containerRef"
class="w-full h-full overflow-auto"
>
<div class="relative min-h-full overflow-hidden">
<!-- 关闭 -->
<ImgButton
@@ -188,16 +216,19 @@ onUnmounted(() => {
@click="handleClose"
/>
<!-- 实战操作台 -->
<div
class="w-full h-[1080px] bg-cover bg-no-repeat relative"
:style="{ backgroundImage: getBackgroundImage('film/practice/实战操作台首页- 电影感') }"
>
<div class="w-full h-[1080px] bg-[#F5EFE6] relative">
<ImgButton
class="absolute left-40 bottom-28 z-10"
class="absolute left-40 bottom-14 z-10"
image="vlog/practice/查看任务要求"
@click="scrollToNextPage"
/>
<!-- img -->
<img
class="absolute left-0 top-0 z-10"
draggable="false"
:src="getStaticImage('film/practice/电影')"
alt=""
/>
<img
class="absolute right-0"
draggable="false"
@@ -212,12 +243,18 @@ onUnmounted(() => {
>
<ImgButton
class="absolute right-20 bottom-0 z-99"
image="vlog/practice/我的创作笔记"
image="vlog/practice/我的创作笔记btn"
path="/film/practice/my-notes"
/>
<div class="task-panel w-[757px] h-full ml-10 overflow-y-auto" @wheel.stop>
<img :src="getStaticImage('film/practice/滑动任务面板-电影感')" alt="" />
<div
class="task-panel w-[757px] h-full ml-10 overflow-y-auto"
@wheel.stop
>
<img
:src="getStaticImage('film/practice/任务要求')"
alt=""
/>
</div>
<img
@@ -239,7 +276,9 @@ onUnmounted(() => {
<!-- 他人实战作品 -->
<div
class="w-full h-[1080px] bg-cover bg-no-repeat pt-44 px-16"
:style="{ backgroundImage: getBackgroundImage('vlog/practice/他人实战作品') }"
:style="{
backgroundImage: getBackgroundImage('vlog/practice/他人实战作品'),
}"
>
<!-- 作品列表 -->
<div class="w-full overflow-hidden">
@@ -254,15 +293,27 @@ onUnmounted(() => {
>
<!-- 作品图片 -->
<div class="w-full h-[280px] rounded-2xl overflow-hidden">
<img class="w-full h-full object-cover" :src="work.image" :alt="work.title" />
<img
class="w-full h-full object-cover"
:src="work.image"
:alt="work.title"
/>
</div>
<!-- 作品信息 -->
<div class="flex flex-col items-center pt-8 space-y-4">
<h3 class="text-5xl font-bold text-gray-800 mb-2">{{ work.title }}</h3>
<h3 class="text-5xl font-bold text-gray-800 mb-2">
{{ work.title }}
</h3>
<!-- 星星评分 -->
<StarRating :score="work.stars" size="lg" readonly />
<StarRating
:score="work.stars"
size="lg"
readonly
/>
<!-- 简介 -->
<p class="h-[110px] text-2xl text-gray-600 mb-3 line-clamp-3 leading-relaxed">
<p
class="h-[110px] text-2xl text-gray-600 mb-3 line-clamp-3 leading-relaxed"
>
{{ work.description }}
</p>
<!-- 查看按钮 -->
@@ -275,7 +326,7 @@ onUnmounted(() => {
</div>
</div>
<!-- 指示按钮 -->
<!-- 指示按钮,暂时不要
<div class="absolute bottom-5 left-1/2 -translate-x-1/2 flex gap-16">
<img
class="cursor-pointer hover:scale-110 transition-transform"
@@ -286,19 +337,23 @@ onUnmounted(() => {
/>
<img
class="cursor-pointer hover:scale-110 transition-transform rotate-180"
:class="{ 'opacity-50 cursor-not-allowed': worksScrollIndex >= worksList.length - 3 }"
:class="{
'opacity-50 cursor-not-allowed':
worksScrollIndex >= worksList.length - 3,
}"
:src="getStaticImage('vlog/practice/滑动左')"
alt="向右"
@click="scrollWorksRight"
/>
</div>
-->
</div>
</div>
<!-- 下滑按钮 - 最后一页隐藏 -->
<ImgButton
v-show="!isLastPage"
class="absolute bottom-0 left-1/2 -translate-x-1/2"
class="absolute bottom-0 left-1/2 down-scroll-hint"
:image="'vlog/practice/下滑'"
@click="scrollToNextPage"
/>
@@ -306,13 +361,16 @@ onUnmounted(() => {
<!-- 回到顶部按钮 - 最后一页显示 -->
<img
v-show="isLastPage"
class="absolute bottom-10 right-10 cursor-pointer transition-all duration-300 hover:scale-105"
class="absolute bottom-10 left-1/2 -translate-x-1/2 cursor-pointer transition-all duration-300 hover:scale-105"
:src="BackToTopIcon"
@click="scrollToTop"
/>
<!-- 跨页人物动画 -->
<div v-show="showSittingPerson || isScrollingBackToFirst" class="character-container">
<div
v-show="showSittingPerson || isScrollingBackToFirst"
class="character-container"
>
<!-- 坐着的人物 - 第一页 -->
<Transition name="fade">
<img
@@ -335,7 +393,11 @@ onUnmounted(() => {
</Transition>
</div>
<GuideSteps v-if="visible" position-offset-y="70%" @complete="visible = false">
<GuideSteps
v-if="visible"
position-offset-y="70%"
@complete="visible = false"
>
<GuideStepsItem>{{ contentSteps }}</GuideStepsItem>
</GuideSteps>
</div>
@@ -355,6 +417,22 @@ onUnmounted(() => {
/* Chrome/Safari/Opera */
}
.down-scroll-hint {
transform: translateX(-50%);
animation: down-scroll-hint 1.4s ease-in-out infinite;
}
@keyframes down-scroll-hint {
0%,
100% {
transform: translate(-50%, 0);
}
50% {
transform: translate(-50%, 12px);
}
}
/* 第一页人物动画容器 */
.character-container {
position: fixed;