Files
super_video/src/components/ProgressBar/ProgressBar.vue
T

402 lines
9.7 KiB
Vue

<!--
进度条组件
用于显示制作变焦镜的步骤进度
-->
<template>
<div class="progress-bar-container">
<!-- 主进度条 -->
<div class="progress-bar">
<!-- 中间步骤指示器 -->
<div class="steps-section" v-if="currentStep > 0 || completedStep > 0">
<!-- 标题区域 -->
<div class="absolute top-0 left-40" v-if="currentStep > 0 || completedStep > 0">
<img
v-if="moduleName === 'vlog'"
:src="getStaticImage(IMAGE_PATHS.PROGRESS_BAR.TITLE)"
alt="制作变焦镜-我的志愿者手记"
class="title-image"
/>
<img
v-else-if="moduleName === 'film'"
:src="getStaticImage('film/制作变焦镜-这里是徐汇滨江')"
alt="制作变焦镜-这里是徐汇滨江"
class="title-image"
/>
<img
v-else-if="moduleName === 'interview'"
:src="getStaticImage('midterm/中期拍摄')"
alt="制作变焦镜-我的志愿者手记"
class="title-image"
/>
<img
v-else
:src="getStaticImage(IMAGE_PATHS.PROGRESS_BAR.TITLE)"
alt="制作变焦镜-我的志愿者手记"
class="title-image"
/>
</div>
<div class="flex">
<div
v-for="(step, index) in steps.slice(0, 3)"
:key="index"
class="step-item"
:class="getStepStatus(index)"
>
<span class="relative flex items-center justify-center">
<!-- <img
:src="currentStep === index ? steps[index].nameSelect : steps[index].nameselectNot"
alt=""
/> -->
<div class="flex items-center cursor-pointer" @click="handleStepClick(index + 1)">
<span
:class="[
'font-bold text-4xl ',
currentStep === index + 1 ? 'text-[#fbbf24]' : 'text-white'
]"
>{{ step.name }}</span
>
<!-- 图标 -->
<img v-if="isStepCompleted(index)" src="@/assets/images/midterm/完成.png" alt="" />
<img
v-else
:class="['ml-2', currentStep === index + 1 && 'filter-blue']"
:src="step.icon"
alt=""
/>
</div>
<!-- 连接线 -->
<template v-if="index < 2">
<img
v-if="isStepCompleted(index + 1)"
class="step-ellipsis"
src="@/assets/images/midterm/进度装饰(已完成.png"
alt=""
/>
<img
v-else
class="step-ellipsis"
src="@/assets/images/midterm/进度装饰(未完成.png"
alt=""
/>
</template>
</span>
</div>
</div>
</div>
<div class="absolute top-0 left-40" v-else>
<img
:src="getStaticImage(IMAGE_PATHS.PROGRESS_BAR.UNLOCKED)"
draggable="false"
alt="未解锁"
class="icon-image"
/>
</div>
<!-- 右侧完成按钮 -->
<div class="right-section">
<button @click="handleComplete" :disabled="!canComplete">
<img
:src="
getStaticImage(
canComplete
? IMAGE_PATHS.PROGRESS_BAR.COMPLETE_BUTTON
: IMAGE_PATHS.PROGRESS_BAR.COMPLETE_BUTTON_DISABLED
)
"
alt="完成前置-即可开启实战"
class="button-image"
/>
</button>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { computed, useModel } from 'vue'
import { getStaticImage, IMAGE_PATHS } from '@/utils'
// 定义组件属性
interface Props {
/** 已完成的最大步骤编号 (1-4),表示已完成到第几步。1=前期准备, 2=中期拍摄, 3=后期剪辑, 4=实战操作台 */
completedStep?: number
/** 当前选中的步骤索引 (1-3),对应进度条显示的三个步骤 */
currentStep?: number
moduleName?: 'film' | 'vlog' | 'interview'
}
// 定义事件
interface Emits {
/** 点击完成按钮时触发 */
complete: []
/** 修改步骤时触发 */
stepChange: [number]
/** v-model:currentStep 双向绑定时触发 */
'update:currentStep': [number]
/** v-model:completedStep 双向绑定时触发 */
'update:completedStep': [number]
}
const props = withDefaults(defineProps<Props>(), {
completedStep: 0,
currentStep: 1,
moduleName: 'vlog'
})
const [currentStep] = useModel(props, 'currentStep')
const [completedStep] = useModel(props, 'completedStep')
const emit = defineEmits<Emits>()
// 计算属性:是否可以完成(前3步都已完成,解锁实战操作台)
// 步骤:1=前期准备, 2=中期拍摄, 3=后期剪辑, 4=实战操作台
// 当 completedStep >= 3(即完成第3步)时,解锁右侧完成按钮
const canComplete = computed(() => props.completedStep >= 3)
// 判断步骤是否已完成
const isStepCompleted = (stepIndex: number) => {
// stepIndex 是数组索引 (0, 1, 2),对应步骤编号 (1, 2, 3)
// completedStep 是已完成的最大步骤编号 (1-4)
// 如果 completedStep >= stepIndex + 1,说明该步骤已完成
const stepNumber = stepIndex + 1
return props.completedStep >= stepNumber
}
// 处理完成按钮点击
const handleComplete = () => {
if (canComplete.value) {
emit('complete')
}
}
const handleStepClick = (index: number) => {
currentStep.value = index
emit('stepChange', index)
}
// 步骤配置
const steps = [
{
name: '前期准备',
icon: getStaticImage('midterm/准备图标.png'),
nameSelect: getStaticImage('midterm/前期准备-选中.png'),
nameselectNot: getStaticImage('midterm/前期准备-未选中.png')
},
{
name: '中期拍摄',
icon: getStaticImage('midterm/拍摄图标.png'),
nameSelect: getStaticImage('midterm/中期拍摄-选中.png'),
nameselectNot: getStaticImage('midterm/中期拍摄-未选中.png')
},
{
name: '后期剪辑',
icon: getStaticImage('midterm/剪辑图标.png'),
nameSelect: getStaticImage('midterm/后期剪辑-选中.png'),
nameselectNot: getStaticImage('midterm/后期剪辑-未选中.png')
}
]
// 获取步骤状态
const getStepStatus = (stepIndex: number) => {
const stepNumber = stepIndex + 1 // 转换为步骤编号 (1, 2, 3)
// 如果已完成,返回 completed
if (props.completedStep >= stepNumber) return 'completed'
// 如果当前选中,返回 current
if (props.currentStep === stepNumber) return 'current'
// 否则返回 locked
return 'locked'
}
</script>
<style scoped>
.progress-bar-container {
position: relative;
width: 100%;
margin: 0 auto;
}
.title-image {
max-width: 100%;
height: auto;
}
.progress-bar {
position: relative;
display: flex;
justify-content: center;
align-items: center;
padding-right: 160px;
height: 292px;
background-image: url('@/assets/images/components/ProgressBar-bg.png');
background-size: cover;
box-sizing: border-box;
}
.icon-image {
width: 100%;
height: 100%;
object-fit: contain;
}
.steps-section {
width: 1400px;
display: flex;
align-items: center;
justify-content: center;
margin: 30px 20px 0;
gap: 15px;
}
/* .step-item { */
/* padding-left: 300px; */
/* display: flex;
flex-direction: column;
align-items: center;
gap: 5px; */
/* flex: 1; */
/* } */
.step-text {
/* font-size: 40px; */
color: white;
font-weight: bold;
text-align: center;
white-space: nowrap;
text-shadow: 0 0 8px rgba(255, 255, 255, 0.5);
display: flex;
justify-content: start;
align-items: center;
}
.step-ellipsis {
padding: 0 40px;
}
.step-dots {
font-size: 14px;
color: rgba(255, 255, 255, 0.8);
letter-spacing: 3px;
font-weight: bold;
}
/* 步骤状态样式 */
.step-item.completed .step-text {
color: #4ade80;
font-weight: bold;
text-shadow: 0 0 8px rgba(74, 222, 128, 0.6);
}
.step-item.completed .step-dots {
color: #4ade80;
text-shadow: 0 0 4px rgba(74, 222, 128, 0.4);
}
.step-item.current .step-text {
color: #fbbf24;
font-weight: bold;
text-shadow: 0 0 8px rgba(251, 191, 36, 0.6);
}
.step-item.current .step-dots {
color: #fbbf24;
text-shadow: 0 0 4px rgba(251, 191, 36, 0.4);
}
.step-item.locked .step-text {
color: rgba(255, 255, 255, 0.7);
text-shadow: 0 0 4px rgba(255, 255, 255, 0.3);
}
.step-item.locked .step-dots {
color: rgba(255, 255, 255, 0.5);
}
.right-section {
position: absolute;
right: 20px;
top: 0;
}
button {
background: none;
border: none;
cursor: pointer;
padding: 0;
transition: transform 0.2s ease;
}
button:hover:not(:disabled) {
transform: scale(1.05);
}
button:active:not(:disabled) {
transform: scale(0.95);
}
button:disabled {
cursor: not-allowed;
}
.button-image {
height: auto;
object-fit: contain;
}
.filter-blue {
filter: brightness(0) saturate(100%) invert(79%) sepia(29%) saturate(1190%) hue-rotate(330deg)
brightness(102%) contrast(98%);
}
/* 响应式设计 */
@media (max-width: 768px) {
.progress-bar {
flex-direction: column;
gap: 15px;
padding: 20px 15px;
}
.right-section {
min-width: auto;
justify-content: center;
}
.steps-section {
flex-direction: column;
gap: 10px;
margin: 0;
}
.step-item {
flex-direction: row;
gap: 10px;
}
.step-dots {
display: none;
}
.button-image {
width: 80px;
}
}
@media (max-width: 480px) {
.progress-bar {
padding: 15px 10px;
}
.step-text {
font-size: 16px;
}
.button-image {
width: 70px;
}
}
</style>