forked from zhongyidian/super_video
feat: 完善课程引导与实战评价体验
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
6f8252472f
commit
1f8723aabd
122 files changed
+2595
-1325
No files matched your search
@@ -4,7 +4,8 @@ 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 { useInterviewStore } from '@/stores/modules/interview'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
@@ -12,6 +13,8 @@ const TOTAL_PAGES = 3
|
||||
const PAGE_HEIGHT = 1080
|
||||
const SCROLL_DURATION = 1000
|
||||
|
||||
const interviewStore = useInterviewStore()
|
||||
|
||||
const currentPage = ref<number>(0)
|
||||
const worksScrollIndex = ref<number>(0)
|
||||
const animatingToPage = ref<number>(0)
|
||||
@@ -23,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
|
||||
)
|
||||
})
|
||||
|
||||
/** 第一页坐着的人物是否显示 */
|
||||
@@ -45,13 +50,13 @@ interface WorkItem {
|
||||
const worksList = ref<WorkItem[]>([
|
||||
{
|
||||
id: 1,
|
||||
image: 'https://picsum.photos/400/300?random=1',
|
||||
title: '作品名称',
|
||||
image: getStaticImage('interview/practice/访谈-匠心育人润物无声'),
|
||||
title: '匠心育人润物无声',
|
||||
stars: 4,
|
||||
path: '/video/interview/practice/fangtan_cace1_batch.mp4',
|
||||
path: 'video/interview/practice/fangtan_cace1_batch.mp4',
|
||||
description:
|
||||
'作品简介作品简介作品简介作品简介作品简介作品简介作品简介作品简介作品简介作品简介作品简介作品简介作品简介作品简介作品简介作品简介作品简介'
|
||||
}
|
||||
'作品简介作品简介作品简介作品简介作品简介作品简介作品简介作品简介作品简介作品简介作品简介作品简介作品简介作品简介作品简介作品简介作品简介',
|
||||
},
|
||||
// {
|
||||
// id: 2,
|
||||
// image: 'https://picsum.photos/400/300?random=2',
|
||||
@@ -150,17 +155,43 @@ const handleWheel = (event: WheelEvent): void => {
|
||||
}
|
||||
}
|
||||
|
||||
const restoreScrollPage = (): void => {
|
||||
const savedPage = interviewStore.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))
|
||||
interviewStore.setPracticeScrollPage(targetPage)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
restoreScrollPage()
|
||||
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
|
||||
@@ -169,16 +200,19 @@ onUnmounted(() => {
|
||||
@click="handleClose"
|
||||
/>
|
||||
<!-- 实战操作台 -->
|
||||
<div
|
||||
class="w-full h-[1080px] bg-cover bg-no-repeat relative"
|
||||
:style="{ backgroundImage: getBackgroundImage('vlog/practice/实战操作台首页') }"
|
||||
>
|
||||
<div class="w-full h-[1080px] bg-[#F5EFE6] relative">
|
||||
<ImgButton
|
||||
class="absolute left-40 bottom-28 z-10"
|
||||
class="absolute left-40 bottom-12 z-10"
|
||||
image="vlog/practice/查看任务要求"
|
||||
@click="scrollToNextPage"
|
||||
/>
|
||||
<!-- img -->
|
||||
<img
|
||||
class="absolute left-0 top-0 z-10"
|
||||
draggable="false"
|
||||
:src="getStaticImage('interview/practice/访谈')"
|
||||
alt=""
|
||||
/>
|
||||
<img
|
||||
class="absolute right-0"
|
||||
draggable="false"
|
||||
@@ -193,12 +227,18 @@ onUnmounted(() => {
|
||||
>
|
||||
<ImgButton
|
||||
class="absolute right-20 bottom-0 z-99"
|
||||
image="vlog/practice/我的创作笔记"
|
||||
path="/practice/my-notes"
|
||||
image="vlog/practice/我的创作笔记btn"
|
||||
path="/interview/practice/my-notes"
|
||||
/>
|
||||
|
||||
<div class="task-panel w-[757px] h-full ml-10 overflow-y-auto" @wheel.stop>
|
||||
<img :src="getStaticImage('vlog/practice/滑动任务面板')" alt="" />
|
||||
<div
|
||||
class="task-panel w-[757px] h-full ml-10 overflow-y-auto"
|
||||
@wheel.stop
|
||||
>
|
||||
<img
|
||||
:src="getStaticImage('interview/practice/任务要求')"
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
|
||||
<img
|
||||
@@ -220,7 +260,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">
|
||||
@@ -235,28 +277,40 @@ 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>
|
||||
<!-- 查看按钮 -->
|
||||
<ImgButton
|
||||
image="vlog/practice/查看"
|
||||
:path="`/practice/video/${encodeURIComponent(work.path)}`"
|
||||
:path="`/interview/practice/video/${encodeURIComponent(work.path)}`"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</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"
|
||||
@@ -267,19 +321,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"
|
||||
/>
|
||||
@@ -287,13 +345,24 @@ 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"
|
||||
/>
|
||||
|
||||
<!-- 老师点评按钮 - 最后一页显示 -->
|
||||
<ImgButton
|
||||
v-show="isLastPage"
|
||||
class="absolute right-2 bottom-10 z-30"
|
||||
image="components/教师点评按钮"
|
||||
path="/interview/practice/teacher-comment"
|
||||
/>
|
||||
|
||||
<!-- 跨页人物动画 -->
|
||||
<div v-show="showSittingPerson || isScrollingBackToFirst" class="character-container">
|
||||
<div
|
||||
v-show="showSittingPerson || isScrollingBackToFirst"
|
||||
class="character-container"
|
||||
>
|
||||
<!-- 坐着的人物 - 第一页 -->
|
||||
<Transition name="fade">
|
||||
<img
|
||||
@@ -332,6 +401,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;
|
||||
|
||||
Reference in new issue
Block a user