Files
super_video/src/components/Guide/StepNavigation.vue
T
zhongyidianandClaude Opus 4.7 3e258b87d9 feat: 完善课程学习与技能树体验
完善课程资源、学习进度与技能树解锁体验,统一视频播放与引导相关交互。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 10:30:18 +08:00

273 lines
7.7 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<Transition
name="step-navigation"
appear
>
<!-- 抽屉遮罩层 -->
<div
v-if="visible"
class="fixed inset-0 z-50 bg-black/30 backdrop-blur-md"
>
<ImgButton
class="absolute right-0 top-0 z-[10]"
image="components/close"
@click="handleClose"
/>
<!-- 抽屉内容 -->
<div
class="step-navigation-drawer relative flex h-full w-full justify-end"
@click="handleClose"
>
<!-- 右侧步骤面板 -->
<div
class="relative flex h-full w-[648px] flex-col py-[100px]"
:class="'steps-panel-bg'"
@click.stop
>
<!-- 顶部分页箭头(上一页) -->
<div
class="relative z-10 flex items-center justify-center pointer-events-none"
>
<button
class="pointer-events-auto flex h-20 w-20 items-center justify-center border-none bg-transparent transition-all duration-200 disabled:cursor-not-allowed disabled:opacity-40 hover:enabled:scale-110"
@click="goPrevPage"
:disabled="!canPrevPage"
>
<svg
width="64"
height="64"
viewBox="0 0 40 40"
fill="none"
>
<path
d="M5 30L35 30L20 6Z"
fill="white"
/>
</svg>
</button>
</div>
<!-- 步骤列表(分页,每页最多5条) -->
<div class="scrollbar-hide z-10 h-[800px] flex-1 px-6 py-4">
<div class="h-auto space-y-4">
<div
v-for="(step, index) in pagedSteps"
:key="step.id"
class="step-item-bg relative flex h-[122px] w-[831px] cursor-pointer items-center px-8 py-4 transition-all duration-300 hover:-translate-x-[23%]"
:class="{
'step-item-active-bg h-[230px] w-[1034px] pl-[180px] -translate-x-[23%]':
currentPageStartIndex + index === currentStepIndex,
}"
@click="handleStepClick(index)"
>
<!-- 步骤标签 -->
<div
class="mr-0 min-w-[5rem] rounded-full px-16 py-4 text-center text-2xl font-semibold shadow-md"
:class="getTagColor(step.label)"
>
{{ step.label }}
</div>
<!-- 步骤内容 -->
<div
class="mr-4 flex-1 text-3xl font-bold leading-[1.5] flex items-center"
>
<img
class="pointer-events-none h-12 w-12 object-contain transition-all duration-300"
:src="
completedSteps?.[currentPageStartIndex + index]
? completedIcon
: incompleteIcon
"
alt=""
/>
{{ step.content }}
</div>
<!-- 步骤数字(全局序号) -->
<div
class="absolute right-[15%] z-[1] flex items-center justify-center text-6xl font-semibold text-white/30 transition-all duration-300"
:class="{
'mb-20 text-[150px] text-white drop-shadow-[5px_10px_5px_rgba(0,0,0,0.1)]':
currentPageStartIndex + index === currentStepIndex,
}"
>
{{ currentPageStartIndex + index + 1 }}
</div>
</div>
</div>
</div>
<!-- 底部分页箭头(下一页) -->
<div class="relative z-10 flex flex-col items-center justify-center">
<button
class="flex h-20 w-20 items-center justify-center border-none bg-transparent transition-all duration-200 disabled:cursor-not-allowed disabled:opacity-40 hover:enabled:scale-110"
@click="goNextPage"
:disabled="!canNextPage"
>
<svg
width="64"
height="64"
viewBox="0 0 40 40"
fill="none"
>
<path
d="M5 10L35 10L20 34Z"
fill="white"
/>
</svg>
</button>
</div>
</div>
</div>
</div>
</Transition>
</template>
<script setup lang="ts">
import { ImgButton } from '@/components'
import { ref, computed, watch } from 'vue'
import { getTagColor } from '@/utils'
import completedIcon from '@/assets/images/midterm/完成.png'
import incompleteIcon from '@/assets/images/midterm/未完成.png'
export interface StepItem {
id: string
label: string
content: string
type?: 'shooting' | 'planning' | 'post' | 'other'
}
interface Props {
/** 是否显示抽屉 */
visible: boolean
/** 步骤列表 */
steps: StepItem[]
/** 当前步骤索引 */
currentStepIndex?: number
/** 步骤完成状态 */
completedSteps?: boolean[]
/** 是否显示关闭按钮 */
showClose?: boolean
}
interface Emits {
(e: 'step-click', index: number, step: StepItem): void
(e: 'close'): void
}
const props = withDefaults(defineProps<Props>(), {
currentStepIndex: 0,
showClose: true,
})
const emit = defineEmits<Emits>()
// 分页:每页最多 5 条
const pageSize = 5
const currentPage = ref(0)
const totalPages = computed(() =>
Math.max(1, Math.ceil((props.steps?.length ?? 0) / pageSize)),
)
const currentPageStartIndex = computed(() => currentPage.value * pageSize)
const pagedSteps = computed(() =>
props.steps.slice(
currentPageStartIndex.value,
currentPageStartIndex.value + pageSize,
),
)
const canPrevPage = computed(() => currentPage.value > 0)
const canNextPage = computed(() => currentPage.value < totalPages.value - 1)
const goPrevPage = () => {
if (!canPrevPage.value) return
currentPage.value -= 1
}
const goNextPage = () => {
if (!canNextPage.value) return
currentPage.value += 1
}
// 处理步骤点击
const handleStepClick = (index: number) => {
const globalIndex = currentPageStartIndex.value + index
emit('step-click', globalIndex, props.steps[globalIndex])
}
// 处理关闭
const handleClose = () => {
emit('close')
}
// 当外部当前步骤改变时,自动定位到对应页
watch(
() => props.currentStepIndex,
(newVal) => {
const idx = newVal ?? 0
const targetPage = Math.floor(idx / pageSize)
if (targetPage !== currentPage.value) {
currentPage.value = Math.min(
Math.max(targetPage, 0),
totalPages.value - 1,
)
}
},
{ immediate: true },
)
</script>
<style scoped>
.step-navigation-enter-active,
.step-navigation-leave-active {
transition: opacity 300ms ease;
}
.step-navigation-enter-from,
.step-navigation-leave-to {
opacity: 0;
}
.step-navigation-enter-active .step-navigation-drawer,
.step-navigation-leave-active .step-navigation-drawer {
transition: transform 300ms ease-out;
}
.step-navigation-enter-from .step-navigation-drawer,
.step-navigation-leave-to .step-navigation-drawer {
transform: translateX(100%);
}
.step-navigation-enter-to .step-navigation-drawer,
.step-navigation-leave-from .step-navigation-drawer {
transform: translateX(0);
}
.steps-panel-bg {
background-image: url('@/assets/images/vlog/专业剖析侧边.png');
background-repeat: no-repeat;
background-position: center center;
background-size: cover;
}
.step-item-bg {
background-image: url('@/assets/images/vlog/侧边-底板未选中.png');
}
.step-item-active-bg {
background-image: url('@/assets/images/vlog/侧边-选中底板.png');
}
.scrollbar-hide {
scrollbar-width: none;
-ms-overflow-style: none;
}
.scrollbar-hide::-webkit-scrollbar {
display: none;
}
</style>