Files
aida_front/src/views/AwardPage/apply.vue

1471 lines
33 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">
<div class="banner">
<div class="slogan">
BLOOM YOUR CREATIVITY AiDA GLOBAL FASHION AWARD 2026
</div>
<div class="title poppins-medium">Application Form</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>
</div>
<Success :isExpired="isExpired" v-if="isCompleted || isExpired" />
<div
class="form-container"
v-if="!isCompleted && !isExpired"
>
<div class="form-content">
<a-form
name="form"
ref="formRef"
layout="vertical"
:model="form"
:rules="rulesRef"
autocomplete="off"
>
<div class="email-box full-row">
<a-form-item
name="email"
required
label="Email Address"
>
<div class="email-wrapper flex align-center">
<a-input v-model:value="form.email" />
<div
class="code-btn"
:class="{ disabled: isCountingDown }"
@click="handleSendCode"
>
{{
isCountingDown
? formatCountdown(countdown)
: 'Send Code'
}}
</div>
<img
v-if="hasValidEmail"
src="@/assets/images/award/checked.png"
alt=""
class="checked-icon"
/>
</div>
<div class="tips">
Please use the email address you registered with AiDA.
</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>
<div class="user-info flex">
<template
v-for="item in formKeys"
:key="item.key"
>
<a-form-item
v-if="item.key !== 'email'"
:required="item.required"
:label="item.label"
:name="item.key"
>
<a-input
v-if="item.type === 'input'"
:disabled="readOnly"
v-model:value="form[item.key]"
/>
<a-select
v-if="item.type === 'select'"
:disabled="readOnly"
v-model:value="form[item.key]"
:options="genderOptions"
>
<template #suffixIcon>
<img
class="arrow-down-icon"
src="@/assets/images/award/arrow_down.svg"
/>
</template>
</a-select>
</a-form-item>
</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>
<a-form-item
class="full-row design-title"
name="designTitle"
label="Design Title"
required
>
<a-input
v-model:value="form.designTitle"
:disabled="readOnly"
/>
</a-form-item>
<a-form-item
class="full-row design-desc"
name="designDescription"
label="Design description"
required
>
<a-textarea
class="textarea"
:disabled="readOnly"
v-model:value="form.designDescription"
placeholder="Briefly describe your design concept, inspiration, and creative direction..."
/>
</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>
<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>
<ul class="information-list flex space-between">
<li class="information-item">
{{
` Single PDF file\n Title, mood board, elaboration\n+ 4 outfit design with materials (max 15 pages)`
}}
</li>
<div class="right">
<li class="information-item">
Format: Single PDF file, 15pages, maximum 20MB
</li>
<li class="information-item">
{{
` Video: Design process, 1080×1920 pixels (9:16 ratio),\nmaximum 60 seconds`
}}
</li>
</div>
</ul>
</div>
<div class="upload-container full-row">
<a-form-item
class="full-row"
name="pdfPath"
required
:validate-trigger="[]"
label="How will you use AiDA in your design process?"
>
<div
v-if="
pdfUploadStatus === 'idle' ||
pdfUploadStatus === 'error'
"
>
<a-upload-dragger
v-model:fileList="pdfList"
:disabled="readOnly"
:showUploadList="false"
@change="info => handleFileChange(info, 'pdf')"
:customRequest="handleUploadPdf"
:beforeUpload="beforeUploadPdf"
accept=".pdf"
>
<img
src="@/assets/images/award/upload.png"
alt=""
class="upload-icon"
/>
<p class="desc">Click to upload or drag and drop</p>
<p class="limit">PDF file, max 20MB</p>
</a-upload-dragger>
</div>
<div
v-else
class="uploading-container"
>
<UploadStatus
:status="pdfUploadStatus"
type="pdf"
/>
</div>
<div
class="progress-bar-container"
v-if="pdfUploadStatus === 'uploading'"
>
<div
class="progress-bar"
:style="{ width: `${uploadProgressPdf}%` }"
></div>
</div>
</a-form-item>
</div>
<div class="upload-container full-row">
<a-form-item
class="full-row"
name="videoPath"
required
:validate-trigger="[]"
label="How will you use AiDA in your design process?"
>
<div
v-if="
videoUploadStatus === 'idle' ||
videoUploadStatus === 'error'
"
>
<a-upload-dragger
v-model:fileList="videoList"
:showUploadList="false"
:disabled="readOnly"
@change="info => handleFileChange(info, 'video')"
:customRequest="handleUploadVideo"
:beforeUpload="beforeUploadVideo"
accept=".mp4,.mov"
>
<img
src="@/assets/images/award/upload.png"
alt=""
class="upload-icon"
/>
<p class="desc">Click to upload or drag and drop</p>
<p class="limit">
Video file (MP4, MOV), 1080p, max 100MB
</p>
</a-upload-dragger>
</div>
<div
v-else
class="uploading-container"
>
<UploadStatus
:status="videoUploadStatus"
type="video"
/>
</div>
<div
class="progress-bar-container"
v-if="videoUploadStatus === 'uploading'"
>
<div
class="progress-bar"
:style="{ width: `${uploadProgressVideo}%` }"
></div>
</div>
</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="submit-btn poppins-bold"
@click="handleSubmitForm"
>
Submit your Design
</div>
<div class="desc">
The link in the AiDA in-platform message will save your unfinished
form.
</div>
</div>
</div>
</div>
<a-modal
v-model:visible="showModal"
:footer="null"
:maskClosable="false"
:closable="false"
wrapClassName="code-modal"
forceRender
:keyboard="false"
:style="{ top: '29.3rem' }"
>
<div class="verify-container flex flex-col align-center">
<img
src="@/assets/images/award/close.svg"
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="code-box">
<VerifycationCodeInput
:ct="emailCode"
@sendCaptcha="setVerifyCode"
/>
</div>
<div
class="verify-btn"
@click="handleVerifyCode"
>
Verify
</div>
<div
class="cutdown"
:class="{ disabled: isCountingDown }"
@click="handleSendCode"
>
{{
isCountingDown
? `Resend Code in ${formatCountdown(countdown)}`
: 'Resend'
}}
</div>
</div>
</a-modal>
</div>
</template>
<script setup lang="ts">
import { ref, onUnmounted, onMounted, computed } from 'vue'
import { debounce } from 'lodash-es'
import type { Rule } from 'ant-design-vue/es/form'
import { message } from 'ant-design-vue'
import { Https } from '@/tool/https'
import { useRoute } from 'vue-router'
import type { UploadChangeParam } from 'ant-design-vue'
import VerifycationCodeInput from './components/VerificationCodeInput.vue'
import UploadStatus from './components/UploadStatus.vue'
import Success from './components/Success.vue'
// 是否晚于2026年7月22日
const isExpired = computed(() => {
const now = new Date()
const targetDate = new Date('2026-03-15')
return now > targetDate
})
const route = useRoute()
const isCompleted = ref(false)
const hasValidEmail = ref(false)
const readOnly = computed(() => {
if (route.query.id && !hasValidEmail.value) {
return true
}
return false
})
const formRef = ref(null)
const form = ref({
email: '',
firstName: '',
lastName: '',
gender: '',
occupation: '',
age: '',
countryRegionCity: '',
phoneNumber: '',
portfolioUrl: '',
// code: '',
designTitle: '',
designDescription: '',
pdfPath: '',
videoPath: '',
secureToken: null
})
// 验证码输入组件引用
const codeInputRef = ref()
const emailCode = ref(['', '', '', '', '', ''])
const isValidEmail = email => {
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
return emailRegex.test(email)
}
const validEmail = (rule: Rule, value: string) => {
if (!value) {
return Promise.reject('Please input the email address')
}
if (!isValidEmail(value)) {
return Promise.reject('Please input a valid email address')
}
return Promise.resolve()
}
const validateNumber = async (_rule: Rule, value: string) => {
if (value === '') {
return Promise.reject('Please fill in this section.')
}
if (!Number.isInteger(value)) {
return Promise.reject('Please input digits')
}
return Promise.resolve()
}
const validatePhone = async (_rule: Rule, value: string) => {
if (value === '') {
return Promise.reject('Please enter your phone number.')
}
const phoneRegex = /^[\+]?[1-9][\d]{0,15}$/
if (!phoneRegex.test(value)) {
return Promise.reject('Please enter a valid phone number.')
}
return Promise.resolve()
}
const validateVerificationCode = async (_rule: Rule, value: string) => {
if (value === '') {
return Promise.reject('Please enter the verification code.')
}
const codeRegex = /^\d{6}$/
if (!codeRegex.test(value)) {
return Promise.reject('Please enter a 6-digit verification code.')
}
return Promise.resolve()
}
const rulesRef = {
email: [{ required: true, validator: validEmail }],
firstName: [
{ required: true, message: 'Please input your first name', trigger: 'blur' }
],
lastName: [
{ required: true, message: 'Please input your last name', trigger: 'blur' }
],
gender: [
{ required: true, message: 'Please select your gender', trigger: 'blur' }
],
occupation: [
{ required: true, message: 'Please input your occupation', trigger: 'blur' }
],
age: [{ required: true, message: 'Please input your age', trigger: 'blur' }],
countryRegionCity: [
{
required: true,
message: 'Please input your country/region and city',
trigger: 'blur'
}
],
phoneNumber: [{ required: true, validator: validatePhone, trigger: 'blur' }],
designTitle: [
{ required: true, message: 'Please input your design title', trigger: 'blur' }
],
designDescription: [
{
required: true,
message: 'Please input your design description',
trigger: 'blur'
}
],
pdfPath: [{ required: true, message: 'Please upload your PDF', trigger: 'null' }],
videoPath: [
{ required: true, message: 'Please upload your video', trigger: 'null' }
]
}
const genderOptions = [
{
label: 'Male',
value: 'Male'
},
{
label: 'Female',
value: 'Female'
},
{
label: 'Other',
value: 'Other'
}
]
const formKeys = ref([
{
label: 'First Name',
required: true,
type: 'input',
key: 'firstName'
},
{
label: 'Last Name',
required: true,
type: 'input',
key: 'lastName'
},
{
label: 'Gender',
required: true,
type: 'select',
key: 'gender'
},
{
label: 'Occupation',
required: true,
type: 'input',
key: 'occupation'
},
{
label: 'Age',
required: true,
type: 'input',
key: 'age'
},
{
label: 'Country/Region and City',
required: true,
type: 'input',
key: 'countryRegionCity'
},
{
label: 'Phone Number',
required: true,
type: 'input',
key: 'phoneNumber'
},
{
label: 'Portfoilo Website/Instagram(Optional)',
required: false,
type: 'input',
key: 'portfolioUrl'
}
])
const showModal = ref(false)
// 倒计时相关状态
const countdown = ref(0)
const countdownTimer = ref<NodeJS.Timeout | null>(null)
const isCountingDown = ref(false)
const formatCountdown = (seconds: number) => {
const minutes = Math.floor(seconds / 60)
const remainingSeconds = seconds % 60
return `${minutes.toString().padStart(2, '0')}:${remainingSeconds
.toString()
.padStart(2, '0')}`
}
const startCountdown = () => {
countdown.value = 60
isCountingDown.value = true
countdownTimer.value = setInterval(() => {
countdown.value--
if (countdown.value <= 0) {
clearCountdown()
}
}, 1000)
}
const clearCountdown = () => {
if (countdownTimer.value) {
clearInterval(countdownTimer.value)
countdownTimer.value = null
}
countdown.value = 0
isCountingDown.value = false
}
const handleSendCode = debounce(async () => {
// 如果正在倒计时,不允许再次发送
if (isCountingDown.value) {
return
}
try {
await formRef.value.validateFields(['email'])
// TODO: 发送验证码的逻辑
await Https.axiosGet(Https.httpUrls.checkEmail, {
params: {
email: form.value.email
}
})
startCountdown()
showModal.value = true
} catch (error) {}
}, 300)
const verifyCode = ref(null)
const setVerifyCode = value => {
verifyCode.value = value
}
const handleCloseModal = () => {
showModal.value = false
}
const handleVerifyCode = () => {
console.log(verifyCode.value)
if (verifyCode.value.length !== 6) {
message.error('Please enter the complete 6-digit verification code')
return
}
Https.axiosGet(Https.httpUrls.checkOTP, {
params: {
email: form.value.email,
code: verifyCode.value
},
fullData: true
}).then(res => {
console.log('coderes', res)
form.value.secureToken = res.data.secureToken
hasValidEmail.value = true
message.success('Verification successful!')
showModal.value = false
})
}
const handleSubmitForm = () => {
if (!hasValidEmail.value) {
message.error('Please verify your email first')
return
}
const validCondition = conditionsList.value.filter(
item => item.required && !item.check
)
if (validCondition.length > 0) {
message.error('Please check the terms and conditions')
return
}
formRef.value
.validate()
.then(res => {
console.log(res)
Https.axiosPost(Https.httpUrls.submitForm, form.value).then(res => {
console.log('res', res)
isCompleted.value = true
})
})
.catch(err => {
console.log(err)
message.error('Please fill in all the required fields')
})
}
const pdfList = ref([])
const videoList = ref([])
const isUploadingPdf = ref(false)
const uploadProgressPdf = ref(0)
const isUploadingVideo = ref(false)
const uploadProgressVideo = ref(0)
const pdfUploadStatus = ref<'idle' | 'uploading' | 'success' | 'error'>('idle')
const videoUploadStatus = ref<'idle' | 'uploading' | 'success' | 'error'>('idle')
const chunkUploadState: Record<
FileType,
{ uploadId: string | null; chunkSize: number; uploadedChunks: Set<number> }
> = {
pdf: { uploadId: null, chunkSize: 0, uploadedChunks: new Set() },
video: { uploadId: null, chunkSize: 0, uploadedChunks: new Set() }
}
// 统一的文件上传前验证
const beforeUploadFile = (type: FileType, file: File) => {
if (!hasValidEmail.value) {
message.error('Please verify your email first')
return false
}
let maxSize: number
let allowedExtensions: string[]
let allowedMimeTypes: string[]
let errorMessage: string
if (type === 'pdf') {
maxSize = 20 * 1024 * 1024 // 20MB
allowedExtensions = ['pdf']
allowedMimeTypes = ['application/pdf']
errorMessage = 'Please upload a PDF file only.'
} 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.'
} else {
return false
}
// 验证文件类型
const fileExtension = file.name.split('.').pop()?.toLowerCase()
const isValidType =
allowedExtensions.includes(fileExtension || '') ||
allowedMimeTypes.includes(file.type)
if (!isValidType) {
message.error(errorMessage)
// 从文件列表中移除
if (type === 'pdf') {
const index = pdfList.value.findIndex(item => item.uid === file.uid)
if (index > -1) {
pdfList.value.splice(index, 1)
}
} else {
const index = videoList.value.findIndex(item => item.uid === file.uid)
if (index > -1) {
videoList.value.splice(index, 1)
}
}
return false // 阻止上传
}
// 验证文件大小
if (file.size > maxSize) {
const sizeLimit = type === 'pdf' ? '20MB' : '100MB'
message.error(
`File size exceeds ${sizeLimit} limit. Please upload a smaller file.`
)
// 从文件列表中移除
if (type === 'pdf') {
const index = pdfList.value.findIndex(item => item.uid === file.uid)
if (index > -1) {
pdfList.value.splice(index, 1)
}
} else {
const index = videoList.value.findIndex(item => item.uid === file.uid)
if (index > -1) {
videoList.value.splice(index, 1)
}
}
return false // 阻止上传
}
return true // 允许上传
}
// PDF文件上传前验证
const beforeUploadPdf = (file: File) => {
return beforeUploadFile('pdf', file)
}
// Video文件上传前验证
const beforeUploadVideo = (file: File) => {
return beforeUploadFile('video', file)
}
const initializeChunkUpload = async (type: FileType, file: File) => {
const endpoint =
type === 'pdf' ? Https.httpUrls.initPdfUpload : Https.httpUrls.initVideoUpload
const data = await Https.axiosPost(endpoint, {
fileName: file.name,
fileSize: file.size,
fileType: file.type,
email: form.value.email,
secureToken: form.value.secureToken
})
return {
uploadId: data?.uploadId as string,
chunkSize: data?.chunkSize as number
}
}
const createFileChunks = (file: File, chunkSize: number) => {
const chunks: Blob[] = []
const totalChunks = Math.ceil(file.size / chunkSize)
for (let i = 0; i < totalChunks; i++) {
const start = i * chunkSize
const end = Math.min(start + chunkSize, file.size)
const chunk = file.slice(start, end)
chunks.push(chunk)
}
return chunks
}
const updateUploadProgress = (
type: FileType,
uploadedCount: number,
total: number
) => {
const percent = Math.round((uploadedCount / total) * 100)
if (type === 'pdf') {
uploadProgressPdf.value = percent
} else {
uploadProgressVideo.value = percent
}
}
const uploadChunk = async (
type: FileType,
chunk: Blob,
chunkIndex: number,
totalChunks: number
) => {
const endpoint =
type === 'pdf' ? Https.httpUrls.uploadPDF : Https.httpUrls.uploadVideo
const formData = new FormData()
formData.append('chunk', chunk)
formData.append('uploadId', chunkUploadState[type].uploadId || '')
formData.append('chunkIndex', String(chunkIndex))
formData.append('totalChunks', String(totalChunks))
formData.append('secureToken', form.value.secureToken)
await Https.axiosPost(endpoint, formData, {
headers: { 'Content-Type': 'multipart/form-data' }
})
chunkUploadState[type].uploadedChunks.add(chunkIndex)
}
const completeChunkUpload = async (type: FileType, file: File) => {
const endpoint =
type === 'pdf'
? Https.httpUrls.uploadPDFComplete
: Https.httpUrls.uploadVideoComplete
return Https.axiosPost(endpoint, {
uploadId: chunkUploadState[type].uploadId,
email: form.value.email,
fileName: file.name,
totalSize: file.size,
secureToken: form.value.secureToken
})
}
type FileType = 'pdf' | 'video'
const handleFileChange = (info: UploadChangeParam, type: FileType) => {
const status = info.file.status
if (status === 'done') {
message.success(`${info.file.name} file uploaded successfully.`)
if (type === 'pdf') {
isUploadingPdf.value = false
uploadProgressPdf.value = 0
pdfUploadStatus.value = 'success'
} else if (type === 'video') {
isUploadingVideo.value = false
uploadProgressVideo.value = 0
videoUploadStatus.value = 'success'
}
} else if (status === 'error') {
message.error(`${info.file.name} file upload failed.`)
if (type === 'pdf') {
isUploadingPdf.value = false
uploadProgressPdf.value = 0
pdfUploadStatus.value = 'error'
} else if (type === 'video') {
isUploadingVideo.value = false
uploadProgressVideo.value = 0
videoUploadStatus.value = 'error'
}
}
}
// 统一的上传处理函数
const handleUploadFile = async (option: any, type: FileType) => {
const file = option.file as File
if (!form.value.email) {
message.error('Please input the email address first')
option.onError?.(new Error('Email required'))
return
}
if (type === 'pdf') {
isUploadingPdf.value = true
uploadProgressPdf.value = 0
pdfUploadStatus.value = 'uploading'
} else {
isUploadingVideo.value = true
uploadProgressVideo.value = 0
videoUploadStatus.value = 'uploading'
}
try {
const { uploadId, chunkSize } = await initializeChunkUpload(type, file)
chunkUploadState[type].uploadId = uploadId
chunkUploadState[type].chunkSize = chunkSize
chunkUploadState[type].uploadedChunks = new Set()
const chunks = createFileChunks(file, chunkSize)
for (let i = 0; i < chunks.length; i++) {
await uploadChunk(type, chunks[i], i, chunks.length)
updateUploadProgress(type, i + 1, chunks.length)
}
const res = await completeChunkUpload(type, file)
console.log('上传完成-----', res)
if (type === 'pdf') {
pdfUploadStatus.value = 'success'
uploadProgressPdf.value = 100
isUploadingPdf.value = false
form.value.pdfPath = res.fileUrl
} else {
videoUploadStatus.value = 'success'
uploadProgressVideo.value = 100
isUploadingVideo.value = false
form.value.videoPath = res.fileUrl
}
option.onSuccess?.({ uploadId }, option.file)
} catch (error: any) {
console.error('Upload error:', error)
message.error(error?.message || 'Upload failed')
option.onError?.(error)
if (type === 'pdf') {
isUploadingPdf.value = false
uploadProgressPdf.value = 0
pdfUploadStatus.value = 'error'
} else {
isUploadingVideo.value = false
uploadProgressVideo.value = 0
videoUploadStatus.value = 'error'
}
}
}
// PDF上传处理
const handleUploadPdf = (option: any) => {
return handleUploadFile(option, 'pdf')
}
// Video上传处理
const handleUploadVideo = (option: any) => {
return handleUploadFile(option, 'video')
}
const conditionsList = ref([
{
check: false,
required: true,
text: 'I confirm that all submitted work is original and created by me.',
id: 'first'
},
{
check: false,
required: true,
text: 'I understand that Code-Create has marketing and promotional rights to all submitted designs and videos.',
id: 'second'
},
{
check: false,
required: true,
text: 'I agree to participate in finalist activities if selected, including AiDA training and award ceremony.',
id: 'third'
},
{
check: false,
text: 'I would like to receive updates about AiDA products and future competitions. (Optional)',
id: 'forth'
}
])
const handleEchoForm = () => {
Https.axiosGet(Https.httpUrls.getContestantByID + route.query.id).then(res => {
Object.assign(form.value, res)
})
}
onMounted(() => {
if (route.query.id) {
handleEchoForm()
}
})
onUnmounted(() => {
clearCountdown()
})
</script>
<style lang="less" scoped>
.poppins-bold {
font-family: 'PoppinsBold';
font-weight: 600;
}
.poppins-medium {
font-family: 'PoppinsMedium';
font-weight: 500;
}
.full-row {
width: 100%;
}
.arrow-down-icon {
width: 2rem;
height: 1rem;
}
.apply-container {
min-height: calc(100% - 18rem);
background-color: #f5f5f5;
box-sizing: border-box;
}
.banner {
height: 54.8rem;
background: url('@/assets/images/award/form_bg.png') no-repeat;
background-size: 100% 100%;
text-align: center;
padding: 12rem 21.4rem 0;
position: relative;
.slogan {
color: #585858;
font-family: 'ArialBold';
font-weight: 700;
font-size: 2.8rem;
margin-bottom: 4rem;
}
.title {
color: #c7342c;
font-size: 8rem;
}
.form-header {
height: 16.8rem;
width: calc(100% - 42.8rem);
left: 21.4rem;
bottom: 0;
position: absolute;
background-color: #fff;
border-top-left-radius: 0.8rem;
border-top-right-radius: 0.8rem;
text-align: left;
padding: 6rem 6rem 0;
.form-title {
color: #232323;
font-size: 3rem;
margin-bottom: 1rem;
}
.desc {
color: #b10000;
font-family: 'Instrument';
font-weight: 500;
font-size: 2.4rem;
}
}
}
.form-row {
margin-top: 12rem;
margin-bottom: 6rem;
.form-title {
color: #232323;
font-size: 3rem;
margin-bottom: 1rem;
}
.desc {
color: #b10000;
font-family: 'Instrument';
font-weight: 500;
font-size: 2.4rem;
}
}
.form-container {
padding: 0 21.4rem 12rem;
background-color: #f5f5f5;
.form-content {
padding: 4rem 6rem 6rem;
background-color: #fff;
border-bottom-left-radius: 0.8rem;
border-bottom-right-radius: 0.8rem;
.ant-form {
display: flex;
flex-wrap: wrap;
column-gap: 4rem;
.user-info {
flex-wrap: wrap;
row-gap: 6rem;
justify-content: space-between;
}
.ant-form-item {
width: 66.5rem;
margin-bottom: 0;
&.full-row {
width: 100%;
}
&.design-title {
margin-bottom: 9rem;
}
:deep(label) {
// display: none;
flex-direction: row-reverse;
color: #232323;
&,
&::before {
font-family: 'Arial';
font-weight: 400;
font-size: 2rem;
}
}
:deep(.ant-input),
:deep(.ant-select-selector) {
border: 0.2rem solid #d5d5d5;
height: 6rem;
border-radius: 0.8rem;
font-family: 'Arial';
font-weight: 400;
font-size: 1.8rem;
&.textarea {
height: 20rem;
}
.ant-select-selection-search {
height: 100%;
}
.ant-select-selection-item {
line-height: 6rem;
}
}
:deep(.ant-select-arrow) {
height: 4rem;
width: 6.2rem;
justify-content: center;
display: flex;
align-items: center;
border-left: 0.1rem solid #d5d5d5;
}
}
}
.email-box {
:deep(.ant-input) {
border: none !important;
&:focus {
box-shadow: none;
}
}
.email-wrapper {
border-radius: 0.8rem;
border: 0.2rem solid #d5d5d5;
position: relative;
.checked-icon {
position: absolute;
right: -6rem;
width: 4rem;
height: 4rem;
}
}
.code-btn {
width: 13rem;
height: 4rem;
color: #585858;
font-family: 'Arial';
font-weight: 400;
font-size: 1.8rem;
line-height: 4rem;
text-align: center;
cursor: pointer;
border-left: 0.1rem solid #d5d5d5;
&.disabled {
cursor: not-allowed;
opacity: 0.6;
}
}
}
.tips {
font-family: 'Arial';
font-weight: 400;
font-size: 1.6rem;
color: #585858;
margin-top: 1rem;
}
}
}
:deep(.ant-form-item-label) {
padding: 0 0 1rem;
}
.information {
padding-left: 2.4rem;
.information-title {
font-size: 2.8rem;
color: #232323;
column-gap: 2.4rem;
margin-bottom: 5rem;
.point {
width: 1.2rem;
height: 1.2rem;
border-radius: 50%;
background-color: #c7342c;
}
}
.information-list {
padding-left: 2.4rem;
column-gap: 4.8rem;
.information-item {
font-family: 'Arial';
font-weight: 400;
font-size: 2.4rem;
color: #585858;
position: relative;
list-style: disc;
// line-height: 3rem;
white-space: pre-line;
}
}
}
.upload-container {
margin-top: 6rem;
:deep(.ant-upload-drag) {
height: 32rem;
.ant-upload-btn {
padding: 0;
display: flex;
flex-direction: column;
align-items: center;
padding-top: 5.89rem;
.upload-icon {
width: 12rem;
height: 12rem;
}
.desc {
color: #585858;
font-family: 'Arial';
font-weight: 400;
font-size: 2.4rem;
margin: 1rem 0 2rem;
}
.limit {
color: #aaa;
font-family: 'Arial';
font-weight: 400;
font-size: 1.8rem;
}
}
}
.uploading-container {
height: 32rem;
display: flex;
align-items: center;
justify-content: center;
border: 0.2rem solid #d5d5d5;
border-radius: 0.8rem;
background-color: #fafafa;
.uploading-text {
color: #585858;
font-family: 'Arial';
font-weight: 400;
font-size: 2.4rem;
}
}
.progress-bar-container {
width: 100%;
height: 1rem;
background-color: #f0f0f0;
border-radius: 0.8rem;
margin-top: 2rem;
overflow: hidden;
position: absolute;
bottom: 0;
.progress-bar {
height: 100%;
background-color: #c7342c;
border-radius: 0.8rem;
transition: width 0.3s ease;
}
}
}
.conditions {
margin-top: 12rem;
&-title {
color: #232323;
font-size: 3rem;
}
.condition-list {
margin-top: 6rem;
row-gap: 3rem;
.condition-item {
border: 0.2rem solid #d5d5d5;
height: 6rem;
line-height: 6rem;
border-radius: 0.8rem;
color: #585858;
font-family: 'Arial';
font-weight: 400;
font-size: 1.8rem;
padding-left: 1.5rem;
column-gap: 2.5rem;
.required {
color: #c7342c;
}
}
:deep(.ant-checkbox-wrapper) {
.ant-checkbox-inner {
width: 2rem;
height: 2rem;
border: 0.2rem solid #585858 !important;
border-radius: 0.4rem;
}
.ant-checkbox-checked .ant-checkbox-inner {
background-color: #fff !important;
border-color: #585858 !important;
}
.ant-checkbox-checked .ant-checkbox-inner::after {
border-bottom: 0.2rem solid #585858;
border-right: 0.2rem solid #585858;
width: 0.65rem;
height: 1rem;
left: 21.895%;
}
}
}
}
.submit-container {
margin-top: 12rem;
.submit-btn {
height: 5rem;
background-color: #454545;
border-radius: 0.8rem;
color: #fff;
line-height: 5rem;
text-align: center;
font-size: 1.6rem;
cursor: pointer;
}
.desc {
font-family: 'Instrument';
font-weight: 400;
font-size: 1.6rem;
color: #6d6d6d;
text-align: center;
margin-top: 2rem;
}
}
.verify-container {
width: 60rem;
height: 49.4rem;
padding: 6rem 8.6rem;
background-color: #fff;
position: relative;
.close-icon {
position: absolute;
width: 2.4rem;
height: 2.4rem;
top: 2rem;
right: 2rem;
cursor: pointer;
}
.title {
color: #232323;
font-size: 3rem;
line-height: 5rem;
}
.desc {
color: #585858;
font-family: 'Arial';
font-weight: 400;
font-size: 1.6rem;
line-height: 3.4rem;
margin-top: 1.4rem;
}
.email {
font-family: 'ArialBold';
font-weight: 700;
font-size: 1.6rem;
line-height: 3rem;
color: #232323;
}
.code-box {
width: 100%;
margin: 5rem 0;
}
.verify-btn {
background-color: #232323;
height: 4.2rem;
border-radius: 8px;
line-height: 4.2rem;
width: 100%;
color: #fff;
text-align: center;
font-family: 'ArialBold';
font-weight: 700;
font-size: 1.6rem;
cursor: pointer;
}
.cutdown {
font-family: 'Arial';
font-weight: 400;
font-size: 1.4rem;
color: #585858;
margin-top: 2rem;
text-decoration: underline solid #585858;
&:not(.disabled) {
cursor: pointer;
}
&.disabled {
cursor: not-allowed;
}
}
}
</style>
<style lang="less">
.code-modal {
.ant-modal-content .ant-modal-body {
padding: 0;
// width: 60rem;
}
}
</style>