feat: 参赛表单页面i18n
This commit is contained in:
@@ -2,15 +2,15 @@
|
||||
<div class="apply-container">
|
||||
<div class="banner">
|
||||
<div class="slogan">
|
||||
BLOOM YOUR CREATIVITY • AiDA GLOBAL FASHION AWARD 2026
|
||||
{{ t('AwardApply.slogan') }}
|
||||
</div>
|
||||
<div class="title poppins-medium">Application Form</div>
|
||||
<div class="title poppins-medium">{{ t('AwardApply.applicationForm') }}</div>
|
||||
<div
|
||||
class="form-header"
|
||||
v-if="!isCompleted && !isExpired"
|
||||
>
|
||||
<div class="form-title poppins-bold">Email Verification</div>
|
||||
<div class="desc">AiDA Users Only</div>
|
||||
<div class="form-title poppins-bold">{{ t('AwardApply.emailVerification') }}</div>
|
||||
<div class="desc">{{ t('AwardApply.aidaUsersOnly') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<Success
|
||||
@@ -34,7 +34,7 @@
|
||||
<a-form-item
|
||||
name="email"
|
||||
required
|
||||
label="Email Address"
|
||||
:label="t('AwardApply.emailAddress')"
|
||||
>
|
||||
<div class="email-wrapper flex align-center">
|
||||
<a-input v-model:value="form.email" />
|
||||
@@ -47,7 +47,7 @@
|
||||
{{
|
||||
isCountingDown
|
||||
? formatCountdown(countdown)
|
||||
: 'Send Code'
|
||||
: t('AwardApply.sendCode')
|
||||
}}
|
||||
</div>
|
||||
<img
|
||||
@@ -58,13 +58,13 @@
|
||||
/>
|
||||
</div>
|
||||
<div class="tips">
|
||||
Please use the email address you registered with AiDA.
|
||||
{{ t('AwardApply.pleaseUseRegisteredEmail') }}
|
||||
</div>
|
||||
</a-form-item>
|
||||
</div>
|
||||
<div class="form-row full-row">
|
||||
<div class="form-title poppins-bold">Personal Information</div>
|
||||
<div class="desc">Tell us about yourself</div>
|
||||
<div class="form-title poppins-bold">{{ t('AwardApply.personalInformation') }}</div>
|
||||
<div class="desc">{{ t('AwardApply.tellUsAboutYourself') }}</div>
|
||||
</div>
|
||||
<div class="user-info flex">
|
||||
<template
|
||||
@@ -74,7 +74,7 @@
|
||||
<a-form-item
|
||||
v-if="item.key !== 'email'"
|
||||
:required="item.required"
|
||||
:label="item.label"
|
||||
:label="getFieldLabel(item.key)"
|
||||
:name="item.key"
|
||||
>
|
||||
<a-input
|
||||
@@ -89,7 +89,7 @@
|
||||
<a-select
|
||||
:disabled="readOnly"
|
||||
v-model:value="form[item.key]"
|
||||
:options="genderOptions"
|
||||
:options="getGenderOptions()"
|
||||
>
|
||||
<template #suffixIcon>
|
||||
<!-- <img
|
||||
@@ -109,13 +109,13 @@
|
||||
</template>
|
||||
</div>
|
||||
<div class="form-row full-row">
|
||||
<div class="form-title poppins-bold">Design Information</div>
|
||||
<div class="desc">Share your creative vision</div>
|
||||
<div class="form-title poppins-bold">{{ t('AwardApply.designInformation') }}</div>
|
||||
<div class="desc">{{ t('AwardApply.shareYourCreativeVision') }}</div>
|
||||
</div>
|
||||
<a-form-item
|
||||
class="full-row design-title"
|
||||
name="designTitle"
|
||||
label="Design Title"
|
||||
:label="t('AwardApply.designTitle')"
|
||||
required
|
||||
>
|
||||
<a-input
|
||||
@@ -126,24 +126,24 @@
|
||||
<a-form-item
|
||||
class="full-row design-desc"
|
||||
name="designDescription"
|
||||
label="Design description"
|
||||
:label="t('AwardApply.designDescription')"
|
||||
required
|
||||
>
|
||||
<a-textarea
|
||||
class="textarea"
|
||||
:disabled="readOnly"
|
||||
v-model:value="form.designDescription"
|
||||
placeholder="Briefly describe your design concept, inspiration, and creative direction..."
|
||||
:placeholder="t('AwardApply.designDescriptionPlaceholder')"
|
||||
/>
|
||||
</a-form-item>
|
||||
<div class="form-row full-row">
|
||||
<div class="form-title poppins-bold">Submission Files</div>
|
||||
<div class="desc">Upload your design materials</div>
|
||||
<div class="form-title poppins-bold">{{ t('AwardApply.submissionFiles') }}</div>
|
||||
<div class="desc">{{ t('AwardApply.uploadYourDesignMaterials') }}</div>
|
||||
</div>
|
||||
<div class="information full-row">
|
||||
<div class="information-title flex align-center">
|
||||
<div class="point"></div>
|
||||
<div class="text poppins-bold">Submission Requirements</div>
|
||||
<div class="text poppins-bold">{{ t('AwardApply.submissionRequirements') }}</div>
|
||||
</div>
|
||||
<ul class="information-list flex space-between">
|
||||
<li class="information-item">
|
||||
@@ -169,7 +169,7 @@
|
||||
name="pdfPath"
|
||||
required
|
||||
:validate-trigger="[]"
|
||||
label="How will you use AiDA in your design process?"
|
||||
:label="t('AwardApply.uploadPdfTitle')"
|
||||
>
|
||||
<div>
|
||||
<a-upload-dragger
|
||||
@@ -187,8 +187,8 @@
|
||||
alt=""
|
||||
class="upload-icon"
|
||||
/>
|
||||
<p class="desc">Click to upload or drag and drop</p>
|
||||
<p class="limit">PDF file, max 20MB</p>
|
||||
<p class="desc">{{ t('AwardApply.clickToUploadPdf') }}</p>
|
||||
<p class="limit">{{ t('AwardApply.pdfFileLimit') }}</p>
|
||||
<template #itemRender="{ file, actions }">
|
||||
<div
|
||||
class="custom-upload-list flex align-center space-between"
|
||||
@@ -240,7 +240,7 @@
|
||||
name="videoPath"
|
||||
required
|
||||
:validate-trigger="[]"
|
||||
label="How will you use AiDA in your design process?"
|
||||
:label="t('AwardApply.uploadVideoTitle')"
|
||||
>
|
||||
<div>
|
||||
<a-upload-dragger
|
||||
@@ -258,9 +258,9 @@
|
||||
alt=""
|
||||
class="upload-icon"
|
||||
/>
|
||||
<p class="desc">Click to upload or drag and drop</p>
|
||||
<p class="desc">{{ t('AwardApply.clickToUploadVideo') }}</p>
|
||||
<p class="limit">
|
||||
Video file (MP4, MOV), 1080p, max 100MB
|
||||
{{ t('AwardApply.videoFileLimit') }}
|
||||
</p>
|
||||
<template #itemRender="{ file, actions }">
|
||||
<div
|
||||
@@ -308,40 +308,39 @@
|
||||
</a-form-item>
|
||||
</div>
|
||||
</a-form>
|
||||
<div class="conditions">
|
||||
<div class="confitions-title poppins-bold">Terms & Conditions</div>
|
||||
<div class="condition-list flex flex-col">
|
||||
<div
|
||||
class="condition-item flex align-center"
|
||||
v-for="item in conditionsList"
|
||||
:key="item.id"
|
||||
>
|
||||
<a-checkbox v-model:checked="item.check" />
|
||||
<span>
|
||||
{{ item.text }}
|
||||
<span
|
||||
class="required"
|
||||
v-if="item.required"
|
||||
>
|
||||
*
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="submit-container">
|
||||
<div class="conditions">
|
||||
<div class="confitions-title poppins-bold">{{ t('AwardApply.termsAndConditions') }}</div>
|
||||
<div class="condition-list flex flex-col">
|
||||
<div
|
||||
class="submit-btn poppins-bold"
|
||||
@click="handleSubmitForm"
|
||||
class="condition-item flex align-center"
|
||||
v-for="item in conditionsList"
|
||||
:key="item.id"
|
||||
>
|
||||
Submit your Design
|
||||
</div>
|
||||
<div class="desc">
|
||||
The link in the AiDA in-platform message will save your unfinished
|
||||
form.
|
||||
<a-checkbox v-model:checked="item.check" />
|
||||
<span>
|
||||
{{ t('AwardApply.' + item.translationKey) }}
|
||||
<span
|
||||
class="required"
|
||||
v-if="item.required"
|
||||
>
|
||||
*
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="submit-container">
|
||||
<div
|
||||
class="submit-btn poppins-bold"
|
||||
@click="handleSubmitForm"
|
||||
>
|
||||
{{ t('AwardApply.submitYourDesign') }}
|
||||
</div>
|
||||
<div class="desc">
|
||||
{{ t('AwardApply.unfinishedFormTip') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a-modal
|
||||
v-model:visible="showModal"
|
||||
@@ -359,9 +358,8 @@
|
||||
class="close-icon"
|
||||
@click="handleCloseModal"
|
||||
/>
|
||||
<div class="title poppins-bold">Check your email</div>
|
||||
<div class="desc">Enter the 6-digital code sent to</div>
|
||||
<div class="email">{{ form.email }}</div>
|
||||
<div class="title poppins-bold">{{ t('AwardApply.checkYourEmail') }}</div>
|
||||
<div class="desc">{{ t('AwardApply.enterSixDigitCode') }} {{ form.email }}</div>
|
||||
<div class="code-box">
|
||||
<VerifycationCodeInput
|
||||
:ct="emailCode"
|
||||
@@ -372,7 +370,7 @@
|
||||
class="verify-btn"
|
||||
@click="handleVerifyCode"
|
||||
>
|
||||
Verify
|
||||
{{ t('AwardApply.verify') }}
|
||||
</div>
|
||||
<div
|
||||
class="cutdown"
|
||||
@@ -381,8 +379,8 @@
|
||||
>
|
||||
{{
|
||||
isCountingDown
|
||||
? `Resend Code in ${formatCountdown(countdown)}`
|
||||
: 'Resend'
|
||||
? `${t('AwardApply.resendCodeIn')} ${formatCountdown(countdown)}`
|
||||
: t('AwardApply.resendCode')
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
@@ -392,6 +390,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onUnmounted, onMounted, computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { debounce } from 'lodash-es'
|
||||
import type { Rule } from 'ant-design-vue/es/form'
|
||||
import { message, Upload } from 'ant-design-vue'
|
||||
@@ -402,13 +401,15 @@
|
||||
import UploadStatus from './components/UploadStatus.vue'
|
||||
import Success from './components/Success.vue'
|
||||
|
||||
// 是否晚于2026年7月22日
|
||||
// 是否晚于2026年7月15日
|
||||
const isExpired = computed(() => {
|
||||
const now = new Date()
|
||||
const targetDate = new Date('2026-03-15')
|
||||
const targetDate = new Date('2026-07-15')
|
||||
return now > targetDate
|
||||
})
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const route = useRoute()
|
||||
const isCompleted = ref(false)
|
||||
|
||||
@@ -451,41 +452,41 @@
|
||||
}
|
||||
const validEmail = (rule: Rule, value: string) => {
|
||||
if (!value) {
|
||||
return Promise.reject('Please input the email address')
|
||||
return Promise.reject(t('AwardApply.pleaseInputEmail'))
|
||||
}
|
||||
if (!isValidEmail(value)) {
|
||||
return Promise.reject('Please input a valid email address')
|
||||
return Promise.reject(t('AwardApply.pleaseInputValidEmail'))
|
||||
}
|
||||
return Promise.resolve()
|
||||
}
|
||||
const validateNumber = async (_rule: Rule, value: string) => {
|
||||
if (value === '') {
|
||||
return Promise.reject('Please fill in this section.')
|
||||
return Promise.reject(t('AwardApply.pleaseInputAge'))
|
||||
}
|
||||
if (!Number.isInteger(value)) {
|
||||
return Promise.reject('Please input digits')
|
||||
if (!Number.isInteger(Number(value))) {
|
||||
return Promise.reject(t('AwardApply.pleaseInputDigits'))
|
||||
}
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
const validatePhone = async (_rule: Rule, value: string) => {
|
||||
if (value === '') {
|
||||
return Promise.reject('Please enter your phone number.')
|
||||
return Promise.reject(t('AwardApply.pleaseInputPhoneNumber'))
|
||||
}
|
||||
const phoneRegex = /^[\+]?[1-9][\d]{0,15}$/
|
||||
if (!phoneRegex.test(value)) {
|
||||
return Promise.reject('Please enter a valid phone number.')
|
||||
return Promise.reject(t('AwardApply.pleaseInputValidPhone'))
|
||||
}
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
const validateVerificationCode = async (_rule: Rule, value: string) => {
|
||||
if (value === '') {
|
||||
return Promise.reject('Please enter the verification code.')
|
||||
return Promise.reject(t('AwardApply.pleaseEnterCompleteCode'))
|
||||
}
|
||||
const codeRegex = /^\d{6}$/
|
||||
if (!codeRegex.test(value)) {
|
||||
return Promise.reject('Please enter a 6-digit verification code.')
|
||||
return Promise.reject(t('AwardApply.pleaseEnterCompleteCode'))
|
||||
}
|
||||
return Promise.resolve()
|
||||
}
|
||||
@@ -596,6 +597,39 @@
|
||||
])
|
||||
|
||||
const showModal = ref(false)
|
||||
// 获取表单标签的翻译
|
||||
const getFieldLabel = (key: string) => {
|
||||
const labelMap: Record<string, string> = {
|
||||
firstName: t('AwardApply.firstName'),
|
||||
lastName: t('AwardApply.lastName'),
|
||||
gender: t('AwardApply.gender'),
|
||||
occupation: t('AwardApply.occupation'),
|
||||
age: t('AwardApply.age'),
|
||||
countryRegionCity: t('AwardApply.countryRegionCity'),
|
||||
phoneNumber: t('AwardApply.phoneNumber'),
|
||||
portfolioUrl: t('AwardApply.portfolioUrl')
|
||||
}
|
||||
return labelMap[key] || key
|
||||
}
|
||||
|
||||
// 获取性别选项的翻译
|
||||
const getGenderOptions = () => {
|
||||
return [
|
||||
{
|
||||
label: t('AwardApply.male'),
|
||||
value: 'Male'
|
||||
},
|
||||
{
|
||||
label: t('AwardApply.female'),
|
||||
value: 'Female'
|
||||
},
|
||||
{
|
||||
label: t('AwardApply.other'),
|
||||
value: 'Other'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// 倒计时相关状态
|
||||
const countdown = ref(0)
|
||||
const countdownTimer = ref<NodeJS.Timeout | null>(null)
|
||||
@@ -662,7 +696,7 @@
|
||||
console.log(verifyCode.value)
|
||||
|
||||
if (verifyCode.value.length !== 6) {
|
||||
message.error('Please enter the complete 6-digit verification code')
|
||||
message.error(t('AwardApply.pleaseEnterCompleteCode'))
|
||||
return
|
||||
}
|
||||
Https.axiosGet(Https.httpUrls.checkOTP, {
|
||||
@@ -676,21 +710,21 @@
|
||||
|
||||
form.value.secureToken = res.data.secureToken
|
||||
|
||||
message.success('Verification successful!')
|
||||
message.success(t('AwardApply.verificationSuccess'))
|
||||
showModal.value = false
|
||||
})
|
||||
}
|
||||
|
||||
const handleSubmitForm = () => {
|
||||
if (!hasValidEmail.value) {
|
||||
message.error('Please verify your email first')
|
||||
message.error(t('AwardApply.pleaseVerifyEmailFirst'))
|
||||
return
|
||||
}
|
||||
const validCondition = conditionsList.value.filter(
|
||||
item => item.required && !item.check
|
||||
)
|
||||
if (validCondition.length > 0) {
|
||||
message.error('Please check the terms and conditions')
|
||||
message.error(t('AwardApply.pleaseCheckTerms'))
|
||||
return
|
||||
}
|
||||
formRef.value
|
||||
@@ -704,7 +738,7 @@
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(err)
|
||||
message.error('Please fill in all the required fields')
|
||||
message.error(t('AwardApply.pleaseFillRequiredFields'))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -728,7 +762,7 @@
|
||||
// 统一的文件上传前验证
|
||||
const beforeUploadFile = (type: FileType, file: File) => {
|
||||
if (!hasValidEmail.value) {
|
||||
message.error('Please verify your email first')
|
||||
message.error(t('AwardApply.pleaseVerifyEmailFirst'))
|
||||
return Upload.LIST_IGNORE
|
||||
}
|
||||
let maxSize: number
|
||||
@@ -740,12 +774,12 @@
|
||||
maxSize = 20 * 1024 * 1024 // 20MB
|
||||
allowedExtensions = ['pdf']
|
||||
allowedMimeTypes = ['application/pdf']
|
||||
errorMessage = 'Please upload a PDF file only.'
|
||||
errorMessage = t('AwardApply.uploadPdfOnly')
|
||||
} else if (type === 'video') {
|
||||
maxSize = 100 * 1024 * 1024 // 100MB
|
||||
allowedExtensions = ['mp4', 'mov']
|
||||
allowedMimeTypes = ['video/mp4', 'video/quicktime']
|
||||
errorMessage = 'Please upload a MP4 or MOV file only.'
|
||||
errorMessage = t('AwardApply.uploadVideoOnly')
|
||||
} else {
|
||||
return Upload.LIST_IGNORE
|
||||
}
|
||||
@@ -777,7 +811,7 @@
|
||||
if (file.size > maxSize) {
|
||||
const sizeLimit = type === 'pdf' ? '20MB' : '100MB'
|
||||
message.error(
|
||||
`File size exceeds ${sizeLimit} limit. Please upload a smaller file.`
|
||||
t('AwardApply.fileSizeExceeds', { sizeLimit })
|
||||
)
|
||||
// 从文件列表中移除
|
||||
// if (type === 'pdf') {
|
||||
@@ -899,7 +933,7 @@
|
||||
const status = info.file.status
|
||||
|
||||
if (status === 'done') {
|
||||
message.success(`${info.file.name} file uploaded successfully.`)
|
||||
message.success(t('AwardApply.fileUploadedSuccess', { fileName: info.file.name }))
|
||||
if (type === 'pdf') {
|
||||
isUploadingPdf.value = false
|
||||
uploadProgressPdf.value = 0
|
||||
@@ -910,7 +944,7 @@
|
||||
videoUploadStatus.value = 'success'
|
||||
}
|
||||
} else if (status === 'error') {
|
||||
message.error(`${info.file.name} file upload failed.`)
|
||||
message.error(t('AwardApply.fileUploadFailed', { fileName: info.file.name }))
|
||||
if (type === 'pdf') {
|
||||
isUploadingPdf.value = false
|
||||
uploadProgressPdf.value = 0
|
||||
@@ -928,7 +962,7 @@
|
||||
const file = option.file as File
|
||||
|
||||
if (!form.value.email) {
|
||||
message.error('Please input the email address first')
|
||||
message.error(t('AwardApply.pleaseInputEmail'))
|
||||
option.onError?.(new Error('Email required'))
|
||||
return
|
||||
}
|
||||
@@ -973,7 +1007,7 @@
|
||||
option.onSuccess?.({ uploadId }, option.file)
|
||||
} catch (error: any) {
|
||||
console.error('Upload error:', error)
|
||||
message.error(error?.message || 'Upload failed')
|
||||
message.error(error?.message || t('AwardApply.uploadFailed'))
|
||||
option.onError?.(error)
|
||||
|
||||
if (type === 'pdf') {
|
||||
@@ -1017,23 +1051,27 @@
|
||||
check: false,
|
||||
required: true,
|
||||
text: 'I confirm that all submitted work is original and created by me.',
|
||||
translationKey: 'conditionFirst',
|
||||
id: 'first'
|
||||
},
|
||||
{
|
||||
check: false,
|
||||
required: true,
|
||||
text: 'I understand that Code-Create has marketing and promotional rights to all submitted designs and videos.',
|
||||
translationKey: 'conditionSecond',
|
||||
id: 'second'
|
||||
},
|
||||
{
|
||||
check: false,
|
||||
required: true,
|
||||
text: 'I agree to participate in finalist activities if selected, including AiDA training and award ceremony.',
|
||||
translationKey: 'conditionThird',
|
||||
id: 'third'
|
||||
},
|
||||
{
|
||||
check: false,
|
||||
text: 'I would like to receive updates about AiDA products and future competitions. (Optional)',
|
||||
translationKey: 'conditionFourth',
|
||||
id: 'forth'
|
||||
}
|
||||
])
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
alt=""
|
||||
class="icon-img"
|
||||
/>
|
||||
<div class="title">{{ info.title }}</div>
|
||||
<div class="title">{{ $t(info.title) }}</div>
|
||||
<div class="desc">
|
||||
{{ info.desc }}
|
||||
{{ $t(info.desc) }}
|
||||
<!-- <div>
|
||||
Please review your submitted information in the AiDA in-platform message.
|
||||
</div>
|
||||
@@ -37,14 +37,14 @@
|
||||
if (props.isExpired) {
|
||||
return {
|
||||
icon: expiredIcon,
|
||||
title: t('AwardsPage.deadlinePassed'),
|
||||
desc: t('AwardsPage.deadlinePassedDesc')
|
||||
title: 'AwardsPage.deadlinePassed',
|
||||
desc: 'AwardsPage.deadlinePassedDesc'
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
icon: successIcon,
|
||||
title: t('AwardsPage.submissionSuccessful'),
|
||||
desc: t('AwardsPage.submissionSuccessfulDesc')
|
||||
title: 'AwardsPage.submissionSuccessful',
|
||||
desc: 'AwardsPage.submissionSuccessfulDesc'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
class="progress-icon successful-icon"
|
||||
/>
|
||||
</div>
|
||||
<div class="text">{{ text }}</div>
|
||||
<div class="tips">{{ tips }}</div>
|
||||
<div class="text">{{ $t(text) }}</div>
|
||||
<div class="tips">{{ $t(tips) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -32,16 +32,16 @@
|
||||
|
||||
const textMap: Record<string, string> = {
|
||||
idle: '',
|
||||
uploading: computed(() => t('AwardsPage.uploadInProgress')).value,
|
||||
success: computed(() => t('AwardsPage.uploadSuccess')).value,
|
||||
error: computed(() => t('AwardsPage.uploadFailed')).value
|
||||
uploading: 'AwardsPage.uploadInProgress',
|
||||
success:'AwardsPage.uploadSuccess',
|
||||
error: 'AwardsPage.fileUploadFailed'
|
||||
}
|
||||
|
||||
const tips = computed(() => {
|
||||
if (props.type === 'pdf') {
|
||||
return t('AwardsPage.pdfFileTip')
|
||||
return 'AwardsPage.pdfFileTip'
|
||||
} else if (props.type === 'video') {
|
||||
return t('AwardsPage.videoFileTip')
|
||||
return 'AwardsPage.videoFileTip'
|
||||
}
|
||||
return ''
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user