forked from zhongyidian/super_video
402 lines
9.7 KiB
Vue
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>
|