forked from zhongyidian/super_video
完善新手导航和三大创作模块的视频完成缓存、未完成提示与学习进度计算,确保进度按实际学习资源完成情况更新。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
152 lines
3.9 KiB
Vue
152 lines
3.9 KiB
Vue
<script lang="ts" setup>
|
|
import { computed, onBeforeUnmount, ref } from 'vue'
|
|
import { ImgButton } from '@/components/Button'
|
|
import { Overlay } from '@/components/Overlay'
|
|
import { StarRating } from '@/components/Rating'
|
|
import { IMAGE_PATHS, getBackgroundImage } from '@/utils'
|
|
import type { PracticeRatingItem } from './types'
|
|
|
|
interface Props {
|
|
visible: boolean
|
|
items: PracticeRatingItem[]
|
|
backgroundImage: string
|
|
topTip?: string
|
|
bottomTip?: string
|
|
itemBackground?: string
|
|
saveButtonImage?: string
|
|
}
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
itemBackground: 'vlog/practice/评分条',
|
|
saveButtonImage: 'vlog/practice/保存',
|
|
})
|
|
|
|
const emit = defineEmits<{
|
|
(e: 'close'): void
|
|
(e: 'update:items', items: PracticeRatingItem[]): void
|
|
(e: 'save', items: PracticeRatingItem[]): void
|
|
}>()
|
|
|
|
const hasTips = computed<boolean>(() =>
|
|
Boolean(props.topTip || props.bottomTip),
|
|
)
|
|
|
|
const showSuccess = ref<boolean>(false)
|
|
let successTimer: number | undefined
|
|
|
|
const handleScoreChange = (index: number, score: number): void => {
|
|
const nextItems = props.items.map((item, itemIndex) =>
|
|
itemIndex === index ? { ...item, score } : item,
|
|
)
|
|
emit('update:items', nextItems)
|
|
}
|
|
|
|
const handleSave = (): void => {
|
|
emit('save', props.items)
|
|
showSuccess.value = true
|
|
window.clearTimeout(successTimer)
|
|
successTimer = window.setTimeout(() => {
|
|
showSuccess.value = false
|
|
emit('close')
|
|
}, 1500)
|
|
}
|
|
|
|
onBeforeUnmount(() => {
|
|
window.clearTimeout(successTimer)
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<Overlay
|
|
:visible="visible"
|
|
:clickable="false"
|
|
>
|
|
<div
|
|
class="relative w-[1795px] h-[987px] bg-no-repeat bg-cover p-10 pt-[100px]"
|
|
:style="{ backgroundImage: getBackgroundImage(backgroundImage) }"
|
|
>
|
|
<ImgButton
|
|
class="absolute -top-8 -right-14 z-10"
|
|
:image="IMAGE_PATHS.COMPONENTS.CLOSE"
|
|
@click="emit('close')"
|
|
/>
|
|
|
|
<div
|
|
v-if="showSuccess"
|
|
class="absolute left-1/2 top-1/2 z-[100] -translate-x-1/2 -translate-y-1/2 rounded-2xl bg-black/70 px-16 py-8 text-5xl font-bold text-white"
|
|
>
|
|
提交成功
|
|
</div>
|
|
|
|
<div class="relative h-full overflow-y-scroll px-8">
|
|
<div class="pl-10 space-y-10 mt-10">
|
|
<p
|
|
v-if="topTip"
|
|
class="glow-text"
|
|
>
|
|
{{ topTip }}
|
|
</p>
|
|
<div
|
|
v-for="(item, index) in items"
|
|
:key="index"
|
|
class="relative w-full h-[182px] bg-no-repeat bg-cover px-12 py-3 flex justify-between"
|
|
:style="{ backgroundImage: getBackgroundImage(itemBackground) }"
|
|
>
|
|
<div class="pr-8 space-y-10">
|
|
<h4 class="text-3xl font-bold text-white mb-3">
|
|
{{ `0${index + 1}` }} {{ item.title }}
|
|
</h4>
|
|
<p class="text-3xl text-gray-800 font-bold leading-relaxed">
|
|
{{ item.description }}
|
|
</p>
|
|
</div>
|
|
<StarRating
|
|
class="absolute -bottom-5 right-10"
|
|
:score="item.score"
|
|
size="lg"
|
|
@change="handleScoreChange(index, $event)"
|
|
/>
|
|
</div>
|
|
<p
|
|
v-if="bottomTip"
|
|
class="glow-text"
|
|
>
|
|
{{ bottomTip }}
|
|
</p>
|
|
<div
|
|
class="w-full flex justify-center"
|
|
:class="{ 'mt-16': !hasTips }"
|
|
>
|
|
<ImgButton
|
|
:image="saveButtonImage"
|
|
@click="handleSave"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Overlay>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.glow-text {
|
|
color: white;
|
|
font-size: 2rem;
|
|
font-weight: bold;
|
|
text-shadow:
|
|
-1px -1px 0 #0066ff,
|
|
1px -1px 0 #0066ff,
|
|
-1px 1px 0 #0066ff,
|
|
1px 1px 0 #0066ff,
|
|
-2px -2px 0 #0066ff,
|
|
2px -2px 0 #0066ff,
|
|
-2px 2px 0 #0066ff,
|
|
2px 2px 0 #0066ff,
|
|
0 0 10px #0066ff,
|
|
0 0 20px #0066ff,
|
|
0 0 30px #0066ff,
|
|
0 0 40px #0066ff,
|
|
0 0 50px #0066ff;
|
|
}
|
|
</style>
|