Files
gloabl_award_front/src/views/AwardPage/components/ApplySection.vue
2026-02-06 15:42:57 +08:00

530 lines
12 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>
<div
class="apply-container flex flex-col"
id="apply"
ref="applyRef"
>
<div
class="title animation-element"
ref="applyTitleRef"
>
{{ $t('AwardsPage.howToApply') }}
</div>
<div
class="sub-title animation-element"
ref="applySubTitleRef"
>
{{ $t('AwardsPage.stepByStep') }}
</div>
<div
class="requirments-list flex flex-col"
ref="reqListRef"
>
<div class="top flex">
<div
class="item-box animation-element"
v-for="(item, index) in leftRequirment"
:key="item.type"
:ref="el => { if(el) itemRefs[index] = el }"
:style="{ background: item.background || '#fff' }"
>
<div class="item-header flex flex-center">
<div class="item-title">{{ $t(item.type) }}</div>
</div>
<div class="context-container flex flex-center">
<div
class="context"
v-for="el in item.desc"
>
{{ $t(el) }}
</div>
<div
class="list"
v-if="item.listTitle"
>
<div class="list-title">{{ $t(item.listTitle) }}</div>
<ul class="list-items">
<li
class="list-item"
v-for="el in item.list"
>
{{ $t(el) }}
</li>
</ul>
</div>
</div>
</div>
</div>
<div class="bottom flex">
<div class="step-3 flex flex-col animation-element" ref="step3Ref">
<div class="header">{{ $t('AwardsPage.step3Title') }}</div>
<div class="content flex">
<div class="content-left flex flex-col space-between">
<div class="content-item">
<div class="item-header flex align-center">
<div class="point"></div>
<div>{{ $t('AwardsPage.processVideo') }}</div>
</div>
<div class="desc-wrapper flex flex-col space-between">
<div class="item-desc">
{{ $t('AwardsPage.processVideoDesc') }}
</div>
<ul class="desc-lists">
<div class="desc-lists-title">
{{ $t('AwardsPage.videoRequirements') }}
</div>
<li>{{ $t('AwardsPage.videoFormat') }}</li>
<li>{{ $t('AwardsPage.videoResolution') }}</li>
<li>{{ $t('AwardsPage.videoDuration') }}</li>
<li>{{ $t('AwardsPage.videoSize') }}</li>
</ul>
</div>
</div>
<div class="content-item">
<div class="item-header flex align-center">
<div class="point"></div>
<div>{{ $t('AwardsPage.fileName') }}</div>
</div>
<div class="item-desc indent">
{{ $t('AwardsPage.fileNameDesc') }}
</div>
</div>
</div>
<div class="content-right">
<div class="content-item flex flex-col">
<div class="item-header flex align-center">
<div class="point"></div>
<div>{{ $t('AwardsPage.designPortfolio') }}</div>
</div>
<div
class="desc-wrapper flex-1 flex flex-col space-between"
>
<ul class="desc-lists">
<div class="desc-lists-title">
<p>
{{ $t('AwardsPage.submitPdf') }}
</p>
<p>{{ $t('AwardsPage.requiredStructure') }}</p>
</div>
<li>{{ $t('AwardsPage.pdfDesignTitle') }}</li>
<li>{{ $t('AwardsPage.pdfMoodboard') }}</li>
<li>{{ $t('AwardsPage.pdfConcept') }}</li>
<div>{{ $t('AwardsPage.pdfConceptDesc') }}</div>
</ul>
<ul class="desc-lists">
<div class="desc-lists-title">
<p>{{ $t('AwardsPage.pdfRequirements') }}</p>
</div>
<li>{{ $t('AwardsPage.pdfMaxPages') }}</li>
<li>{{ $t('AwardsPage.pdfMaxSize') }}</li>
<li>
{{ $t('AwardsPage.pdfLanguage') }}
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<div class="step-4 animation-element" ref="step4Ref">
<div class="header flex flex-col flex-center">
<p>{{ $t('AwardsPage.step4Title') }}</p>
<p class="sub-title">{{ $t('AwardsPage.step4Subtitle') }}</p>
</div>
<div class="content">
<div class="content-item">
<div class="desc-wrapper flex-1 flex flex-col space-between">
<ul class="desc-lists">
<div class="desc-lists-title">
{{ $t('AwardsPage.step4Desc') }}
</div>
<li>{{ $t('AwardsPage.finalistPieces') }}</li>
<li>
{{ $t('AwardsPage.finalistBasedOn') }}
</li>
<li>
{{ $t('AwardsPage.finalistShipping') }}
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { nextTick, onBeforeUnmount, onMounted, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { gsap } from 'gsap'
const { t } = useI18n()
const leftRequirment = ref([
{
type: 'AwardsPage.step1Title',
desc: ['AwardsPage.step1Desc']
},
{
type: 'AwardsPage.step2Title',
desc: ['AwardsPage.step2Desc'],
listTitle: 'AwardsPage.step2ListTitle',
list: [
'AwardsPage.step2List[0]',
'AwardsPage.step2List[1]',
'AwardsPage.step2List[2]'
],
background: '#F9F9F9'
}
])
const applyRef = ref()
const applyTitleRef = ref()
const applySubTitleRef = ref()
const reqListRef = ref()
const itemRefs = ref<HTMLElement[]>([])
const step3Ref = ref()
const step4Ref = ref()
const hasPlayedAnim = ref(false)
let timeline: gsap.core.Timeline | null = null
let observer: IntersectionObserver | null = null
const setupApplyInitialState = () => {
// 设置标题和副标题的初始状态
const titleEls = [applyTitleRef.value, applySubTitleRef.value].filter(
Boolean
) as HTMLElement[]
if (titleEls.length) {
gsap.set(titleEls, {
opacity: 0,
scale: 0,
transformOrigin: '50% 50%'
})
}
// 设置步骤元素的初始状态
const allStepElements: HTMLElement[] = []
if (itemRefs.value && itemRefs.value.length > 0) {
allStepElements.push(...itemRefs.value)
}
if (step3Ref.value) {
allStepElements.push(step3Ref.value as HTMLElement)
}
if (step4Ref.value) {
allStepElements.push(step4Ref.value as HTMLElement)
}
if (allStepElements.length > 0) {
gsap.set(allStepElements, {
opacity: 0,
y: 50
})
}
}
const initAnimations = () => {
if (hasPlayedAnim.value) return
timeline = gsap.timeline({
defaults: { ease: 'back.out(1.7)' }
})
if (applyTitleRef.value && applySubTitleRef.value) {
timeline.to([applyTitleRef.value, applySubTitleRef.value], {
scale: 1,
opacity: 1,
duration: 0.6,
stagger: 0.1
})
}
const allStepElements: HTMLElement[] = []
if (itemRefs.value && itemRefs.value.length > 0) {
allStepElements.push(...itemRefs.value)
}
if (step3Ref.value) {
allStepElements.push(step3Ref.value as HTMLElement)
}
if (step4Ref.value) {
allStepElements.push(step4Ref.value as HTMLElement)
}
if (allStepElements.length > 0) {
timeline.to(allStepElements, {
opacity: 1,
y: 0,
duration: 0.6,
stagger: 0.2
}, '>')
}
hasPlayedAnim.value = true
}
onMounted(() => {
nextTick(() => {
setupApplyInitialState()
observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
initAnimations()
observer?.disconnect()
}
})
},
{
threshold: 0.3,
rootMargin: '0px 0px -100px 0px'
}
)
// Start observing the component root element
if (applyRef.value) {
observer.observe(applyRef.value)
}
})
})
onBeforeUnmount(() => {
// Cleanup animation timeline
if (timeline) {
timeline.kill()
}
// Cleanup IntersectionObserver
if (observer) {
observer.disconnect()
}
})
</script>
<style scoped lang="less">
p {
margin: 0;
padding: 0;
}
ul {
margin: 0;
padding: 0;
}
.animation-element{
will-change: opacity transform;
}
.apply-container {
flex: 1;
height: 143.3rem;
background: url('@/assets/images/award/apply_bg.png') no-repeat;
background-size: 100% 100%;
padding: 12.7rem 21.4rem 12rem;
.title {
text-align: center;
color: #232323;
font-family: 'PoppinsBold';
font-weight: 600;
font-size: 4rem;
margin-bottom: 3rem;
}
.sub-title {
text-align: center;
color: #b10000;
font-size: 3rem;
font-family: 'Arial';
font-weight: 400;
margin-bottom: 8.2rem;
}
.requirments-list {
flex: 1;
row-gap: 8.2rem;
.top {
height: 27.4rem;
color: #585858;
column-gap: 4.6rem;
.item-box {
height: 27.4rem;
}
}
.item-box {
border-radius: 0.8rem;
&:nth-of-type(1) {
width: 47rem;
flex-grow: initial;
}
&:nth-of-type(2) {
flex: 1;
}
.item-header {
background-color: #424242;
border-radius: 0.8rem;
height: 7.8rem;
.item-title {
color: #fff;
font-family: 'PoppinsBold';
font-weight: 600;
font-size: 2.4rem;
text-align: center;
white-space: pre-line;
}
}
.context-container {
margin-top: 4rem;
column-gap: 7rem;
.list {
font-family: 'Instrument';
font-weight: 400;
font-size: 2.4rem;
line-height: 3rem;
}
}
.context {
// margin-top: 4rem;
// width: 46.8rem;
text-align: center;
color: #585858;
font-family: 'Arial';
font-weight: 400;
line-height: 3rem;
font-size: 2.4rem;
// padding-left: 5.6rem;
white-space: pre-line;
}
}
.bottom {
column-gap: 4.6rem;
height: 63.4rem;
.step-3 {
flex: 1;
}
.step-3,
.step-4 {
.header {
font-family: 'PoppinsBold';
font-weight: 600;
font-size: 2.4rem;
text-align: center;
height: 7.4rem;
line-height: 7.4rem;
color: #fff;
background-color: #b10000;
border-radius: 0.8rem;
}
.content {
padding: 4rem;
border-radius: 0.8rem;
column-gap: 6.4rem;
background: linear-gradient(
180deg,
#ffe8e8 0%,
#feefef 25%,
#f9f9f9 100%
);
flex: 1;
.content-left {
flex: 1;
}
.content-item {
.item-header {
column-gap: 2rem;
color: #585858;
font-family: 'InstrumentBold';
font-weight: 700;
font-size: 2.4rem;
line-height: 3rem;
.point {
width: 1.2rem;
height: 1.2rem;
background-color: #b10000;
border-radius: 50%;
}
}
.item-desc {
font-family: 'Instrument';
font-weight: 400;
font-size: 2.4rem;
color: #585858;
&.indent {
padding-left: 3.8rem;
line-height: 3rem;
padding-top: 2rem;
}
}
.desc-wrapper {
margin-top: 3rem;
/* 基线行高变量,供子元素计算方块垂直偏移以对齐首行 */
--desc-line-height: 3rem;
font-family: 'Instrument';
font-weight: 400;
color: #585858;
font-size: 2.4rem;
line-height: 3rem;
row-gap: 3rem;
.desc-lists {
/* 使用自定义方块代替浏览器 marker保证大小为 1rem 并与文字垂直居中 */
padding-left: 0;
list-style: none;
li {
list-style: none;
/* 使内容对齐到首行顶部,方块通过 margin-top 调整到首行中间 */
display: flex;
align-items: flex-start;
gap: 1rem;
padding: 0;
margin: 0.4rem 0;
&::before {
content: '';
/* 固定为 1rem 方块 */
width: 0.5rem;
height: 0.5rem;
background-color: #585858;
flex: 0 0 0.5rem;
border-radius: 0;
/* 让方块垂直居中于第一行文字:(line-height - square)/2 */
margin-top: calc(
(var(--desc-line-height, 3rem) - 1rem) / 2
);
}
}
}
}
}
.content-right {
.content-item {
height: 100%;
}
}
}
}
.step-4 {
width: 45.1rem;
.header {
color: #fff;
text-align: center;
background-color: #424242;
border-radius: 0.8rem;
height: 7.8rem;
white-space: pre-line;
font-family: 'PoppinsBold';
font-weight: 600;
font-size: 2.4rem;
line-height: 1.5;
.sub-title {
font-family: 'Poppins';
font-weight: 400;
font-size: 1.8rem;
color: #fff;
margin: 0;
}
}
.content {
background: #fff;
}
}
}
}
}
</style>