Merge branch 'dev_vite' of ssh://18.167.251.121:10002/aidlab/aida_front into dev_vite

This commit is contained in:
X1627315083
2026-01-21 09:43:57 +08:00
13 changed files with 1314 additions and 1032 deletions

View File

@@ -1,5 +1,5 @@
<template> <template>
<div class="admin_page"> <div class="admin_page all-user">
<div class="admin_table_search"> <div class="admin_table_search">
<div class="admin_state"> <div class="admin_state">
<div class="admin_state_item"> <div class="admin_state_item">
@@ -16,10 +16,56 @@
</template> </template>
</a-range-picker> </a-range-picker>
</div> </div>
<!-- <div class="admin_state_item">
<span>Country or Region:</span>
<a-select
v-model:value="country"
:allowClear="true"
show-search
style="width: 230px"
:filter-option="filterOption"
placeholder="Select Item..."
max-tag-count="responsive"
:options="allCountry"
></a-select>
</div> -->
<!-- <div class="admin_state_item">
<span>Email:</span>
<input
v-model="email"
placeholder="Please enter email"
@keydown.enter="gettrialList"
type="text"
style="width: 230px"
/>
</div> -->
<div class="admin_state_item"> <div class="admin_state_item">
<span>{{ $t('admin.UserName') }}:</span> <span>{{ $t('admin.UserName') }}:</span>
<SelectUser v-model:value="ids" multiple valueKey="label" labelKey="email" /> <a-select
v-model:value="ids"
mode="multiple"
style="width: 230px"
:field-names="{ label: 'label', value: 'label' }"
:filter-option="filterOption"
:placeholder="$t('admin.selectUserName')"
max-tag-count="responsive"
:options="allUserList"
@keydown.enter="gettrialList"
></a-select>
</div> </div>
<!-- <div class="admin_state_item">
<span>User Type:</span>
<a-select
v-model:value="systemUser"
size="large"
style="width: 230px"
optionFilterProp="label"
:options="state"
placeholder="Please select"
allowClear
show-search
></a-select>
</div> -->
</div> </div>
<div class="admin_search"> <div class="admin_search">
<div class="admin_search_item" @click="searchHistoryList"> <div class="admin_search_item" @click="searchHistoryList">
@@ -70,31 +116,6 @@
</div> </div>
</div> </div>
<div class="admin_table_content" ref="historyTable"> <div class="admin_table_content" ref="historyTable">
<div class="admin_state_list plan_list">
<div
v-for="plan in planFilterOptions"
:key="plan.id"
class="plan_item"
:class="{
active: subscriptionPlanId === plan.id,
disabled: plan.status === 'PENDING'
}"
@click="plan.status !== 'PENDING' && selectPlanFilter(plan.id)"
>
<a-tooltip v-if="plan.status === 'PENDING'">
<template #title>{{ $t('admin.PlanStart') }} {{ plan.startTime }}</template>
<span class="plan_name">{{ plan.name }}</span>
<MoreOutlined class="plan_more_icon" />
</a-tooltip>
<template v-else>
<span class="plan_name">{{ plan.name }}</span>
<MoreOutlined
class="plan_more_icon"
@click.stop="plan.status !== 'PENDING' && openPlanRenameModal(plan)"
/>
</template>
</div>
</div>
<a-table <a-table
@resizeColumn="handleResizeColumn" @resizeColumn="handleResizeColumn"
:loading="tableLoading" :loading="tableLoading"
@@ -126,6 +147,12 @@
<div class="operate_item" @click="deleteAagree(record)"> <div class="operate_item" @click="deleteAagree(record)">
{{ $t('admin.Delete') }} {{ $t('admin.Delete') }}
</div> </div>
<!-- <div
class="operate_item"
@click="deleteGroup(record, index)"
>
Delete
</div> -->
</div> </div>
</template> </template>
</a-table> </a-table>
@@ -133,31 +160,7 @@
<allUserPoerationsVue <allUserPoerationsVue
ref="allUserPoerationsVue" ref="allUserPoerationsVue"
@searchHistoryList="searchHistoryList" @searchHistoryList="searchHistoryList"
:plan-options="planFilterOptions"
></allUserPoerationsVue> ></allUserPoerationsVue>
<div class="renamePlanModal" ref="renamePlanModal"></div>
<!-- 重命名订阅计划弹窗 -->
<a-modal
v-model:visible="renamePlanModalVisible"
:title="$t('admin.RenamePlan')"
@ok="confirmRenamePlan"
@cancel="cancelRenamePlan"
:ok-text="$t('admin.OK')"
:cancel-text="$t('admin.Cancel')"
:get-container="() => $refs.renamePlanModal"
>
<div class="rename-plan-form">
<div class="admin_state_item">
<span>{{ $t('admin.PlanName') }}:</span>
<a-input
v-model:value="renamePlanForm.planName"
:placeholder="$t('admin.InputPlanName')"
style="width: 250px"
@pressEnter="confirmRenamePlan"
/>
</div>
</div>
</a-modal>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
@@ -168,24 +171,20 @@ import {
computed, computed,
reactive, reactive,
toRefs, toRefs,
unref, onMounted
watch
} from 'vue' } from 'vue'
import { formatTime } from '@/tool/util' import { formatTime } from '@/tool/util'
import { useStore } from 'vuex' import { useStore } from 'vuex'
import { Https } from '@/tool/https' import { Https } from '@/tool/https'
import { Modal, message, Input } from 'ant-design-vue' import { Modal, message } from 'ant-design-vue'
import { ExclamationCircleOutlined, MoreOutlined } from '@ant-design/icons-vue' import { ExclamationCircleOutlined } from '@ant-design/icons-vue'
import allUserPoerationsVue from './addAllUser.vue' import allUserPoerationsVue from './addAllUser.vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import SelectUser from '@/component/common/SelectUser.vue' import SelectUser from '@/component/common/SelectUser.vue'
export default defineComponent({ export default defineComponent({
components: { allUserPoerationsVue, MoreOutlined, SelectUser }, components: { allUserPoerationsVue },
setup() { setup() {
const store: any = useStore() const store: any = useStore()
const currentOrganizationId = computed(
() => store.state.UserHabit.userDetail.organizationId
)
const selectedRowKeys = ref([]) as any const selectedRowKeys = ref([]) as any
const onSelectChange = (changableRowKeys: string[]) => { const onSelectChange = (changableRowKeys: string[]) => {
selectedRowKeys.value = changableRowKeys selectedRowKeys.value = changableRowKeys
@@ -193,6 +192,9 @@ export default defineComponent({
let filter: any = reactive({ let filter: any = reactive({
dataList: [], dataList: [],
tableLoading: false, tableLoading: false,
allUserList: computed(() => {
return store.state.adminPage.allUserList
}),
allCountry: [], allCountry: [],
rowSelection: computed(() => { rowSelection: computed(() => {
return { return {
@@ -201,7 +203,6 @@ export default defineComponent({
} }
}) })
}) })
const { t } = useI18n() const { t } = useI18n()
let filterData: any = reactive({ let filterData: any = reactive({
@@ -217,16 +218,10 @@ export default defineComponent({
systemUser: '', systemUser: '',
order: '', //'Ascending 升序 Descending 降序' order: '', //'Ascending 升序 Descending 降序'
orderBy: '', orderBy: '',
userName: '', userName: ''
subscriptionPlanId: ''
}) })
let renameData: any = ref({}) //修改名字选中的数据 let renameData: any = ref({}) //修改名字选中的数据
const renamePlanModalVisible = ref(false)
const renamePlanForm = reactive({
planId: null as number | null,
planName: ''
})
const columns: any = computed(() => { const columns: any = computed(() => {
return [ return [
{ {
@@ -253,6 +248,13 @@ export default defineComponent({
key: 'userName', key: 'userName',
width: 150, width: 150,
ellipsis: true ellipsis: true
// customRender: (record: any) => {
// let time = formatTime(
// record.text / 1000,
// "YYYY-MM-DD hh:mm:ss"
// );
// return time;
// },
}, },
{ {
title: t('admin.language'), title: t('admin.language'),
@@ -276,6 +278,10 @@ export default defineComponent({
{ {
title: t('admin.Credits'), title: t('admin.Credits'),
align: 'center', align: 'center',
// width: 150,
// minWidth: 100,
// maxWidth: 200,
// resizable: true,
dataIndex: 'credits', dataIndex: 'credits',
key: 'credits', key: 'credits',
width: 100, width: 100,
@@ -303,6 +309,7 @@ export default defineComponent({
width: 120, width: 120,
align: 'center', align: 'center',
fixed: 'right', fixed: 'right',
// slots:{customRender:'action'}
Operations: true Operations: true
} }
] ]
@@ -347,8 +354,7 @@ export default defineComponent({
(filterData.order = ''), //'Ascending 升序 Descending 降序' (filterData.order = ''), //'Ascending 升序 Descending 降序'
(filterData.orderBy = ''), //'Ascending 升序 Descending 降序' (filterData.orderBy = ''), //'Ascending 升序 Descending 降序'
(filterData.systemUser = ''), (filterData.systemUser = ''),
(filterData.userName = ''), (filterData.userName = '')
(filterData.subscriptionPlanId = '')
} }
let setHistoryListData = () => { let setHistoryListData = () => {
let startDate: any = filterData.rangePickerValue?.[0] let startDate: any = filterData.rangePickerValue?.[0]
@@ -371,17 +377,18 @@ export default defineComponent({
order: filterData.order, order: filterData.order,
orderBy: filterData.orderBy, orderBy: filterData.orderBy,
// userName: filterData.userName, // userName: filterData.userName,
userName: filterData.ids, userName: filterData.ids
subscriptionPlanId: filterData.subscriptionPlanId
} }
return data return data
} }
//获取列表 //获取列表
const gettrialList = () => { let gettrialList = () => {
filter.tableLoading = true filter.tableLoading = true
let data = setHistoryListData() let data = setHistoryListData()
Https.axiosPost(Https.httpUrls.subAccountList, data).then((rv: any) => { Https.axiosPost(Https.httpUrls.subAccountList, data).then((rv: any) => {
if (rv) { if (rv) {
console.log(rv)
// this.dataList = rv
filter.dataList = rv.content filter.dataList = rv.content
filterData.total = rv.total filterData.total = rv.total
filter.tableLoading = false filter.tableLoading = false
@@ -410,70 +417,6 @@ export default defineComponent({
// 使用 option.label 进行搜索 // 使用 option.label 进行搜索
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0 return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
} }
// 订阅计划筛选(按钮点击)
const selectPlanFilter = async (planId: string) => {
filterData.subscriptionPlanId = planId
// 切换管理员订阅计划
Https.axiosGet(Https.httpUrls.switchSubscribePlan, {
params: {
targetSubscriptionPlanId: planId,
adminAccId: store.state.UserHabit.userDetail.id
}
}).then((res: any) => {
console.log(res)
})
searchHistoryList()
}
const planFilterOptions = ref([])
const fetchSubscribePlanList = () => {
const orgId = currentOrganizationId.value
if (!orgId) return
Https.axiosPost(Https.httpUrls.searchSubscribeByOrg, {
organizationId: orgId,
status: ['ACTIVE', 'PENDING']
}).then(res => {
res.forEach(plan => {
plan.startTime = formatTime(plan.currentPeriodStart, 'YYYY-MM-DD hh:mm:ss')
})
// 将与当前用户 subscriptionPlanId 相同的订阅计划放到第一个
const userSubscriptionPlanId = store.state.UserHabit.userDetail.subscriptionPlanId
if (userSubscriptionPlanId && Array.isArray(res)) {
const sortedList = [...res].sort((a: any, b: any) => {
const isAUserPlan = a.id == userSubscriptionPlanId
const isBUserPlan = b.id == userSubscriptionPlanId
if (isAUserPlan && !isBUserPlan) return -1
if (!isAUserPlan && isBUserPlan) return 1
return 0
})
planFilterOptions.value = sortedList
} else {
planFilterOptions.value = res
}
console.log(planFilterOptions.value)
})
}
// 监听组织ID获取到值后再拉取订阅计划
watch(
() => currentOrganizationId.value,
orgId => {
if (orgId) {
fetchSubscribePlanList()
const userSubscriptionPlanId =
store.state.UserHabit.userDetail.subscriptionPlanId
if (userSubscriptionPlanId) {
selectPlanFilter(userSubscriptionPlanId)
}
}
},
{ immediate: true }
)
// 打开重命名弹窗(基于当前点击的计划)
const openPlanRenameModal = plan => {
renamePlanForm.planId = plan.id
renamePlanForm.planName = plan?.name || ''
renamePlanModalVisible.value = true
console.log(renamePlanForm)
}
let addhHistoryList = () => { let addhHistoryList = () => {
allUserPoerationsVue.value.init({ value: 'Add', label: t('admin.add') }, '') allUserPoerationsVue.value.init({ value: 'Add', label: t('admin.add') }, '')
} }
@@ -578,33 +521,6 @@ export default defineComponent({
gettrialList() gettrialList()
}) })
} }
// 确认重命名
const confirmRenamePlan = () => {
if (!renamePlanForm.planName || !renamePlanForm.planName.trim()) {
message.warning(t('admin.PlanNameRequired'))
return
}
Https.axiosPost(Https.httpUrls.updateSubscribePlan, {
id: renamePlanForm.planId,
name: renamePlanForm.planName.trim()
})
.then((rv: any) => {
message.success(t('admin.RenamePlanSuccess'))
renamePlanModalVisible.value = false
fetchSubscribePlanList()
})
.catch((error: any) => {
message.error(error.message || t('admin.RenamePlanFailed'))
})
}
// 取消重命名
const cancelRenamePlan = () => {
renamePlanModalVisible.value = false
renamePlanForm.planId = null
renamePlanForm.planName = ''
}
onMounted(() => { onMounted(() => {
let allCountry: any = sessionStorage.getItem('allCountry') let allCountry: any = sessionStorage.getItem('allCountry')
if (allCountry) { if (allCountry) {
@@ -629,15 +545,7 @@ export default defineComponent({
ExportAccountData, ExportAccountData,
uploadTemplate, uploadTemplate,
deleteList, deleteList,
deleteAagree, deleteAagree
planFilterOptions,
selectPlanFilter,
openPlanRenameModal,
renamePlanModalVisible,
renamePlanForm,
confirmRenamePlan,
cancelRenamePlan,
fetchSubscribePlanList
} }
}, },
data() { data() {
@@ -649,21 +557,10 @@ export default defineComponent({
} }
}, },
mounted() { mounted() {
this.updateTableHeight() let historyTable: any = this.$refs.historyTable
window.addEventListener('resize', this.updateTableHeight) this.historyTableHeight = historyTable.clientHeight - 200
}, },
beforeUnmount() { methods: {}
window.removeEventListener('resize', this.updateTableHeight)
},
methods: {
updateTableHeight() {
const historyTable: any = this.$refs.historyTable
if (historyTable) {
// 为底部分页器预留固定空间,使表格部分高度固定且分页器始终可见
this.historyTableHeight = historyTable.clientHeight - 200
}
}
}
}) })
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@@ -693,76 +590,10 @@ export default defineComponent({
font-size: 1.6rem; font-size: 1.6rem;
} }
} }
.plan_list { .all-user {
margin-top: 1rem; .admin_table_content {
display: flex; :deep(.ant-table-wrapper) {
// flex-wrap: wrap; overflow: hidden ;
padding-left: 2.8rem;
column-gap: 0.6rem;
margin-bottom: 2rem;
.plan_item {
height: 4rem;
width: auto;
min-width: 10rem;
display: flex;
align-items: center;
justify-content: center;
// font-size: 1.8rem;
font-weight: 600;
border-radius: 1.3rem;
color: #fff;
cursor: pointer;
border: 1.8px solid #000;
background-color: #000;
padding: 0 1rem 0 2rem;
&:hover {
color: #000;
background-color: #fff;
}
&.active {
background: #ffffff;
color: #000000;
}
&.disabled {
opacity: 0.5;
cursor: not-allowed;
background-color: #d9d9d9;
border-color: #d9d9d9;
color: #999;
&:hover {
background-color: #d9d9d9;
color: #999;
}
}
}
.plan_item {
column-gap: 0.6rem;
}
.plan_more_icon {
font-size: 1.6rem;
cursor: pointer;
}
}
.subscription-plan-cell {
display: flex;
align-items: center;
justify-content: center;
}
.rename-plan-form {
padding: 2rem 0;
.admin_state_item {
display: flex;
align-items: center;
> span {
width: 10rem;
margin-right: 1rem;
} }
} }
} }

View File

@@ -425,6 +425,7 @@ export class PartManager {
// this.partGroup.add(path); // this.partGroup.add(path);
this.canvas.add(path); this.canvas.add(path);
this.canvas.renderAll(); this.canvas.renderAll();
this.clearPart();
} }
/** 清空点位 */ /** 清空点位 */
clearPart() { clearPart() {

View File

@@ -145,7 +145,7 @@
const onDeleteItem = (object) => { const onDeleteItem = (object) => {
const list = [{ token: object.token, action: ACTIONS.DELETE }]; const list = [{ token: object.token, action: ACTIONS.DELETE }];
emit("change-canvas", list); emit("change-canvas", list);
canvas.remove(object); DeleteItemByToken(object.token);
canvas.renderAll(); canvas.renderAll();
}; };
const urlToCanvas = (url) => { const urlToCanvas = (url) => {
@@ -177,11 +177,12 @@
canvas.renderAll(); canvas.renderAll();
}; };
const addObject = async (item) => { const addObject = async (item) => {
const token = item.token;
const cwidth = canvas.width; const cwidth = canvas.width;
const cheight = canvas.height; const cheight = canvas.height;
let pattern = await setFill(item); let pattern = await setFill(item);
let rect = new fabric.Rect({ let rect = new fabric.Rect({
token: item.token, token: token,
width: cwidth, width: cwidth,
height: cheight, height: cheight,
fill: pattern, fill: pattern,
@@ -197,11 +198,14 @@
const cwidth = canvas.width; const cwidth = canvas.width;
const cheight = canvas.height; const cheight = canvas.height;
let image = await urlToCanvas(item.path); let image = await urlToCanvas(item.path);
let offsetX = item.location[0];
let offsetY = item.location[1];
let scaleX = ((cwidth / image.width) * item.scale[0]) / 5; let scaleX = ((cwidth / image.width) * item.scale[0]) / 5;
let scaleY = ((cheight / image.height) * item.scale[1]) / 5; let scaleY = ((cheight / image.height) * item.scale[1]) / 5;
let scale = cwidth > cheight ? scaleX : scaleY; let scale = cwidth > cheight ? scaleX : scaleY;
let offsetX =
(item.location[0] * cwidth) / props.width - (image.width * scale) / 2;
let offsetY =
(item.location[1] * cheight) / props.height -
(image.height * scale) / 2;
let angle = item.angle; let angle = item.angle;
let gapX = item.object.gapX; let gapX = item.object.gapX;
let gapY = item.object.gapY; let gapY = item.object.gapY;
@@ -298,9 +302,9 @@
list.value = list_; list.value = list_;
canvas.renderAll(); canvas.renderAll();
} else if (item.action === ACTIONS.DELETE) { } else if (item.action === ACTIONS.DELETE) {
list.value = list.value.filter((v) => v.token !== item.token); DeleteItemByToken(item.token);
if (object) canvas.remove(object);
} else if (item.action === ACTIONS.ADD) { } else if (item.action === ACTIONS.ADD) {
DeleteItemByToken(item.data.token);
list.value.push(item.data); list.value.push(item.data);
await addObject(item.data); await addObject(item.data);
} }
@@ -309,6 +313,12 @@
}; };
taskQueue.addTask(async () => await cd(list_)); taskQueue.addTask(async () => await cd(list_));
}; };
/** 删除 */
const DeleteItemByToken = (token) => {
list.value = list.value.filter((v) => v.token !== token);
const objects = canvas.getObjects().filter((o) => o.token === token);
objects.forEach((o) => canvas.remove(o));
};
defineExpose({ defineExpose({
updataList, updataList,
}); });

View File

@@ -185,7 +185,6 @@ export default defineComponent({
width:props.sketchSize.width, width:props.sketchSize.width,
height:props.sketchSize.height, height:props.sketchSize.height,
}).then((rv)=>{ }).then((rv)=>{
console.log(rv,'================',props.sketchSize)
if(oldSelectDetail?.partialDesign)oldSelectDetail.partialDesign.partialDesignBase64 = rv if(oldSelectDetail?.partialDesign)oldSelectDetail.partialDesign.partialDesignBase64 = rv
}) })

View File

@@ -907,7 +907,7 @@ export default defineComponent({
action: ACTIONS.UPDATE, action: ACTIONS.UPDATE,
token: arr[editPrintElementData.imgDomIndex].token, token: arr[editPrintElementData.imgDomIndex].token,
key: 'location[0]', key: 'location[0]',
value: offset.left, value: location[0],
}, },
]); ]);
editPrintElementDom.pingpuRef.updataList([ editPrintElementDom.pingpuRef.updataList([
@@ -915,7 +915,7 @@ export default defineComponent({
action: ACTIONS.UPDATE, action: ACTIONS.UPDATE,
token: arr[editPrintElementData.imgDomIndex].token, token: arr[editPrintElementData.imgDomIndex].token,
key: 'location[1]', key: 'location[1]',
value: offset.top, value: location[1],
}, },
]); ]);
// editPrintElementData.overallDetail.offsetX = offset.left // editPrintElementData.overallDetail.offsetX = offset.left

View File

@@ -92,6 +92,7 @@
let img = new Image(); let img = new Image();
let size = [0,0]; let size = [0,0];
img.src = props.sketchPath; img.src = props.sketchPath;
console.log(props.sketchPath)
await new Promise((resolve, reject) => { await new Promise((resolve, reject) => {
img.onload = () => { img.onload = () => {
size = [img.width, img.height] size = [img.width, img.height]

View File

@@ -0,0 +1,209 @@
<template>
<div class="apply-container container flex flex-col" ref="applyRef">
<div class="title" ref="applyTitleRef">How to Apply</div>
<div class="sub-title" ref="applySubTitleRef">Requirments</div>
<div class="requirments-list flex" ref="reqListRef">
<div class="left flex flex-col space-between">
<div class="item-box" v-for="item in leftRequirment" :key="item.type">
<div class="item-header flex align-center">
<img src="@/assets/images/award/bloom_logo.png" class="logo" />
<div class="item-title">{{ item.type }}</div>
</div>
<div class="context" v-for="el in item.desc">
{{ el }}
</div>
</div>
</div>
<div class="right">
<div class="item-box">
<div class="item-box">
<div class="item-header flex align-center">
<img src="@/assets/images/award/bloom_logo.png" class="logo" />
<div class="item-title">{{ rightRequirment.type }}</div>
</div>
<div class="context" v-for="el in rightRequirment.desc">
{{ el }}
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { nextTick, onBeforeUnmount, onMounted, ref } from 'vue'
import { gsap } from 'gsap'
const leftRequirment = ref([
{
type: 'Video',
desc: ['The process of doing design']
},
{
type: 'Design',
desc: [
'Structure: design title, moodboard and elaboration (how will you use AiDA to design)',
'Design sketch: Maximum 4 outfit design with proposed materials'
]
}
])
const rightRequirment = ref({
type: 'Submission Format',
desc: [
'Naming as “AiDA global award 2026_applicantname”',
'Mp4\n(1080x1920pixels/20mb within 1min)',
'Single PDF file\n(within 15 pages, maximum 20mb)',
'English or native language\nwith English translation'
]
})
const applyRef = ref<HTMLElement | null>(null)
const applyTitleRef = ref<HTMLElement | null>(null)
const applySubTitleRef = ref<HTMLElement | null>(null)
const reqListRef = ref<HTMLElement | null>(null)
const hasPlayedApplyAnim = ref(false)
let applyObserver: 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 headers = reqListRef.value?.querySelectorAll<HTMLElement>('.item-header')
const contexts = reqListRef.value?.querySelectorAll<HTMLElement>('.context')
gsap.set([headers, contexts], { opacity: 0 })
}
const playApplyAnimation = () => {
if (hasPlayedApplyAnim.value) return
const titleEls = [applyTitleRef.value, applySubTitleRef.value].filter(
Boolean
) as HTMLElement[]
const headers = reqListRef.value?.querySelectorAll<HTMLElement>('.item-header')
const contexts = reqListRef.value?.querySelectorAll<HTMLElement>('.context')
if (!titleEls.length) return
const tl = gsap.timeline({ defaults: { ease: 'power2.out' } })
tl.to(titleEls, {
opacity: 1,
scale: 1,
duration: 0.6,
ease: 'back.out(1.6)',
stagger: 0.1
})
if (headers?.length) {
tl.to(
headers,
{
opacity: 1,
duration: 0.4,
stagger: 0.1
},
'-=0.1'
)
}
if (contexts?.length) {
tl.to(
contexts,
{
opacity: 1,
duration: 0.4,
stagger: 0.05
},
'-=0.05'
)
}
hasPlayedApplyAnim.value = true
applyObserver?.disconnect()
}
onMounted(() => {
nextTick(() => {
setupApplyInitialState()
if ('IntersectionObserver' in window) {
applyObserver = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
playApplyAnimation()
}
})
},
{ threshold: 0.25 }
)
if (applyRef.value) applyObserver.observe(applyRef.value)
} else {
playApplyAnimation()
}
})
})
onBeforeUnmount(() => {
applyObserver?.disconnect()
})
</script>
<style scoped lang="less">
.apply-container {
flex: 1;
background: url('@/assets/images/award/apply_bg.png') no-repeat;
background-size: 100% 100%;
padding: 12.7rem 0 16.9rem;
.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;
}
.requirments-list {
flex: 1;
padding-left: 41.4rem;
column-gap: 17.7rem;
margin-top: 12rem;
.left {
color: #232323;
height: 100%;
}
.item-box {
.item-header {
column-gap: 3.2rem;
.item-title {
color: #232323;
font-family: 'PoppinsBold';
font-weight: 600;
font-size: 2.8rem;
}
}
.context {
margin-top: 4rem;
width: 46.8rem;
color: #585858;
font-family: 'Arial';
font-weight: 400;
line-height: 3rem;
font-size: 2.4rem;
padding-left: 5.6rem;
white-space: pre-line;
}
}
}
}
</style>

View File

@@ -0,0 +1,68 @@
<template>
<div class="bloom container flex flex-col align-center">
<div
class="title"
ref="titleRef"
>
Bloom Your Creativity
</div>
<div
class="season"
ref="subtitleRef"
>
Theme of 2026
</div>
<div
class="desc"
ref="textRef"
>
Where imagination meets innovation, creativity blooms. This theme celebrates
AI as a catalyst for fashion design, allowing your vision to flourish beyond
traditional boundaries. Let your ideas blossom into extraordinary designs that
merge human artistry with artificial intelligence.
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import gsap from 'gsap'
const titleRef = ref<HTMLElement | null>(null)
const subtitleRef = ref<HTMLElement | null>(null)
const textRef = ref<HTMLElement | null>(null)
</script>
<style scoped lang="less">
.bloom {
padding-top: 12.8rem;
font-family: 'Poppins';
background: url('@/assets/images/award/bloom_bg.png') no-repeat;
background-size: 100% 100%;
.title {
font-size: 4rem;
color: #232323;
margin-bottom: 2.4rem;
}
.logo {
margin-bottom: 2.2rem;
}
.season {
font-size: 3rem;
color: #c7342c;
margin-bottom: 6.6rem;
}
.desc {
font-family: 'Arial';
font-size: 2.8rem;
color: #585858;
text-align: center;
padding: 0 21.5rem;
line-height: 4.5rem;
margin-bottom: 12.3rem;
}
}
</style>

View File

@@ -0,0 +1,227 @@
<template>
<div class="judges-container flex flex-col align-center">
<div class="title" ref="judgesTitleRef">Panel of Judges</div>
<!-- <img src="@/assets/images/award/bloom_logo.png" class="logo" /> -->
<div class="sub-title" ref="judgesSubTitleRef">Expertise</div>
<div class="judgement-list" ref="judgementListRef">
<div
class="judgement-item flex flex-col align-center"
v-for="item in judgements"
:key="item.name"
>
<img :src="item.picture" class="picture" />
<div class="name">{{ item.name }}</div>
<div class="desc">{{ item.desc }}</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { onBeforeUnmount, onMounted, nextTick, ref } from 'vue'
import { gsap } from 'gsap'
import jae from '@/assets/images/award/jae.png'
import diego from '@/assets/images/award/diego.png'
import gregory from '@/assets/images/award/gregory.png'
import vincenzo from '@/assets/images/award/vincenzo.png'
import tim from '@/assets/images/award/tim.png'
import desmond from '@/assets/images/award/desmond.png'
const judgements = [
{
picture: jae,
name: 'Jae Hyuk Lim',
desc: 'Code-create\nKorea Branch Director\nBesfxxk creative director'
},
{
picture: diego,
name: 'Diego Dultzin Lacoste',
desc: 'Co-founder & Chief Father\nOfficer of OnTheList\n(Hong Kong)'
},
{
picture: gregory,
name: 'Gregory de la Hogue Moran',
desc: 'Senior Designer at\nGabriela Heasrst (Italy)'
},
{
picture: vincenzo,
name: 'Vincenzo La Torre',
desc: 'Cheif Editor of SCMP Style\n(Hong Kong)'
},
{
picture: tim,
name: 'Tim Lim',
desc: 'Group Fashion Direction of\n Modern Media Group\n(Shanghai)'
},
{
picture: desmond,
name: 'Desmond Lim',
desc: 'Cheif Editor of Vogue\n(Singapore)'
}
]
const judgesTitleRef = ref<HTMLElement | null>(null)
const judgesSubTitleRef = ref<HTMLElement | null>(null)
const judgementListRef = ref<HTMLElement | null>(null)
const hasPlayedJudgementAnim = ref(false)
let judgementObserver: IntersectionObserver | null = null
const setupJudgementInitialState = () => {
const titleEls = [judgesTitleRef.value, judgesSubTitleRef.value].filter(
Boolean
) as HTMLElement[]
if (titleEls.length) {
gsap.set(titleEls, {
opacity: 0,
scale: 0,
transformOrigin: '50% 50%'
})
}
const items =
judgementListRef.value?.querySelectorAll<HTMLElement>('.judgement-item')
if (items?.length) {
gsap.set(items, {
opacity: 0,
clipPath: 'inset(0 0 100% 0)'
})
}
}
const playJudgementAnimation = () => {
if (hasPlayedJudgementAnim.value) return
const titleEls = [judgesTitleRef.value, judgesSubTitleRef.value].filter(
Boolean
) as HTMLElement[]
const listEl = judgementListRef.value
if (!titleEls.length || !listEl) return
const items = Array.from(
listEl.querySelectorAll<HTMLElement>('.judgement-item')
)
const tl = gsap.timeline({ defaults: { ease: 'power2.out' } })
tl.to(titleEls, {
opacity: 1,
scale: 1,
duration: 0.4,
ease: 'back.out(1.6)',
stagger: 0.1
})
if (items.length) {
const firstRow = items.slice(0, 3)
const secondRow = items.slice(3)
if (firstRow.length) {
tl.to(
firstRow,
{
opacity: 1,
clipPath: 'inset(0% 0% 0% 0%)',
duration: 0.45,
stagger: 0.05
},
'-=0.2'
)
}
if (secondRow.length) {
tl.to(
secondRow,
{
opacity: 1,
clipPath: 'inset(0% 0% 0% 0%)',
duration: 0.45,
stagger: 0.05
},
'+=0.1'
)
}
}
hasPlayedJudgementAnim.value = true
judgementObserver?.disconnect()
}
onMounted(() => {
nextTick(() => {
setupJudgementInitialState()
if ('IntersectionObserver' in window) {
judgementObserver = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
playJudgementAnimation()
}
})
},
{ threshold: 0.3 }
)
if (judgementListRef.value) {
judgementObserver.observe(judgementListRef.value)
}
} else {
// Fallback: play immediately if IntersectionObserver unsupported
playJudgementAnimation()
}
})
})
onBeforeUnmount(() => {
judgementObserver?.disconnect()
})
</script>
<style scoped lang="less">
.judges-container {
height: 147.4rem;
background: url('@/assets/images/award/judges_bg.png') no-repeat;
background-size: 100% 100%;
padding-top: 12.8rem;
.title {
color: #232323;
font-family: 'PoppinsBold';
font-weight: 600;
font-size: 4rem;
}
.logo {
margin: 2.4rem 0 2.2rem;
}
.sub-title {
color: #b10000;
font-family: 'Arial';
font-weight: 400;
font-size: 3rem;
margin-bottom: 12rem;
}
.judgement-list {
display: grid;
grid-template-columns: repeat(3, 1fr);
column-gap: 23.22rem;
row-gap: 8rem;
padding: 0 25rem 0 26.6rem;
.judgement-item {
overflow: hidden;
.picture {
width: 20.2rem;
height: 26rem;
border-radius: 0.8rem;
}
.name {
margin: 3rem 0 2.4rem;
color: #232323;
font-family: 'PoppinsBold';
font-weight: 600;
font-size: 2.4rem;
}
.desc {
color: #585858;
font-family: 'Arial';
font-weight: 400;
font-size: 2rem;
white-space: pre-line;
text-align: center;
}
}
}
}
</style>

View File

@@ -0,0 +1,238 @@
<template>
<div
class="prizes-container container flex align-center space-between"
ref="prizesRef"
>
<div class="left flex flex-col flex-center">
<div
class="title"
ref="prizesTitleRef"
>
Award & Prizes
</div>
<!-- <img src="@/assets/images/award/bloom_logo.png" class="logo" /> -->
<div
class="desc"
ref="prizesSubTitleRef"
>
Recongnition
</div>
</div>
<div
class="right"
ref="prizesRightRef"
>
<div
class="prize-item flex flex-col flex-center"
v-for="item in prizes"
:key="item.name"
>
<div class="prize-money">{{ item.money }}</div>
<div class="prize-name">{{ item.name }}</div>
<div class="prize-desc flex flex-col flex-center">
<div
class="desc-item"
v-for="el in item.desc"
>
{{ el }}
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { nextTick, onBeforeUnmount, onMounted, ref } from 'vue'
import { gsap } from 'gsap'
const prizes = [
{
money: 'US$5000',
name: 'Grand Prize',
desc: ['Cash Award', 'Award Ceritificate', 'Global Media Exposure']
},
{
money: 'US$3000',
name: 'First Runner-Up',
desc: ['Cash Award', 'Award Ceritificate', 'Global Media Exposure']
},
{
money: 'US$2000',
name: 'Second Runner-Up',
desc: ['Cash Award', 'Award Ceritificate', 'Global Media Exposure']
},
{
money: 'Certification',
name: 'Finalists',
desc: ['Award Ceritificate', 'Global Media Exposure']
}
]
const prizesRef = ref<HTMLElement | null>(null)
const prizesTitleRef = ref<HTMLElement | null>(null)
const prizesSubTitleRef = ref<HTMLElement | null>(null)
const prizesRightRef = ref<HTMLElement | null>(null)
const hasPlayedPrizesAnim = ref(false)
let prizesObserver: IntersectionObserver | null = null
const setupPrizesInitialState = () => {
const titleEls = [prizesTitleRef.value, prizesSubTitleRef.value].filter(
Boolean
) as HTMLElement[]
if (titleEls.length) {
gsap.set(titleEls, {
opacity: 0,
scale: 0,
transformOrigin: '50% 50%'
})
}
if (prizesRightRef.value) {
gsap.set(prizesRightRef.value, {
'opacity': 0,
'y': 40,
'scale': 1.08,
'--prize-row-gap': '2rem',
'--prize-col-gap': '2rem'
})
}
}
const playPrizesAnimation = () => {
if (hasPlayedPrizesAnim.value) return
const titleEls = [prizesTitleRef.value, prizesSubTitleRef.value].filter(
Boolean
) as HTMLElement[]
const tl = gsap.timeline({ defaults: { ease: 'power2.out' } })
if (titleEls.length) {
tl.to(titleEls, {
opacity: 1,
scale: 1,
duration: 0.6,
ease: 'back.out(1.6)',
stagger: 0.1
})
}
if (prizesRightRef.value) {
tl.to(
prizesRightRef.value,
{
'opacity': 1,
'y': 0,
'scale': 1,
'--prize-row-gap': '4.2rem',
'--prize-col-gap': '4.4rem',
'duration': 0.55,
'ease': 'back.out(1.4)'
},
titleEls.length ? '-=0.15' : 0
)
}
hasPlayedPrizesAnim.value = true
prizesObserver?.disconnect()
}
onMounted(() => {
nextTick(() => {
setupPrizesInitialState()
if ('IntersectionObserver' in window) {
prizesObserver = new IntersectionObserver(
entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
playPrizesAnimation()
}
})
},
{ threshold: 0.25 }
)
if (prizesRef.value) prizesObserver.observe(prizesRef.value)
} else {
playPrizesAnimation()
}
})
})
onBeforeUnmount(() => {
prizesObserver?.disconnect()
})
</script>
<style scoped lang="less">
.prizes-container {
background: url('@/assets/images/award/prizes_bg.png') no-repeat;
background-size: 100% 100%;
padding: 0 21.4rem 0 34.2rem;
box-sizing: border-box;
.left {
row-gap: 3.6rem;
.title {
text-align: center;
font-family: 'PoppinsBold';
font-weight: 600;
font-size: 4rem;
color: #fff;
}
.desc {
text-align: center;
color: #f95750;
font-family: 'Poppins';
font-weight: 400;
font-size: 3rem;
}
}
.right {
// height: 45.4rem;
// padding: 4.6rem 6.1rem 4.6rem 0;
box-sizing: border-box;
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-template-rows: repeat(2, 1fr);
row-gap: var(--prize-row-gap, 4.2rem);
column-gap: var(--prize-col-gap, 4.4rem);
// flex: 1;
.prize-item {
width: 35.5rem;
height: 32.8rem;
color: #fff;
padding: 4.5rem 0 4.8rem 0;
justify-content: space-between;
background: url('@/assets/images/award/first_bg.png') no-repeat;
background-size: 100% 100%;
&:nth-of-type(2) {
background: url('@/assets/images/award/second_bg.png') no-repeat;
background-size: 100% 100%;
}
&:nth-of-type(3) {
background: url('@/assets/images/award/grand_bg.png') no-repeat;
background-size: 100% 100%;
}
&:nth-of-type(4) {
background: url('@/assets/images/award/certification_bg.png')
no-repeat;
background-size: 100% 100%;
}
.prize-money {
font-family: 'PoppinsBold';
font-weight: bold;
font-size: 4rem;
}
.prize-name {
font-family: 'PoppinsMedium';
font-weight: 500;
font-size: 2.8rem;
}
.prize-desc {
color: #e0e0e0;
font-family: 'Arial';
font-weight: 400;
font-size: 2rem;
line-height: 3rem;
height: 8.9rem;
}
}
}
}
</style>

View File

@@ -0,0 +1,173 @@
<template>
<div
class="selection-container container flex flex-col align-center"
ref="selectionRef"
>
<div class="title">Selection Criteria</div>
<!-- <img src="@/assets/images/award/bloom_logo.png" class="logo" /> -->
<div class="sub-title">Evaluation</div>
<div class="criteria-list flex" ref="criteriaListRef">
<div
class="item flex flex-col align-center"
v-for="item in criteriaList"
:key="item.name"
>
<img :src="item.icon" class="icon" :style="item.style" />
<div class="name">{{ item.name }}</div>
<div class="desc">{{ item.desc }}</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { nextTick, onBeforeUnmount, onMounted, ref } from 'vue'
import { gsap } from 'gsap'
import criteria1 from '@/assets/images/award/criteria_1.png'
import criteria2 from '@/assets/images/award/criteria_2.png'
import criteria3 from '@/assets/images/award/criteria_3.png'
import criteria4 from '@/assets/images/award/criteria_4.png'
const criteriaList = ref([
{
icon: criteria1,
name: 'Originality',
desc: 'Unique perspective and innovative approach to fashion design',
style: { width: '13rem', height: '17rem' }
},
{
icon: criteria2,
name: 'Creativity',
desc: 'Artistic vision and exceptional design excellence',
style: { width: '16rem', height: '18rem' }
},
{
icon: criteria3,
name: 'AiDA Integration',
desc: 'Effective application of AI design tools and functions',
style: { width: '16rem', height: '18rem' }
},
{
icon: criteria4,
name: 'Execution',
desc: 'Quality of presentation and technical craftsmanship',
style: { width: '18.8rem', height: '18rem' }
}
])
const selectionRef = ref<HTMLElement | null>(null)
const criteriaListRef = ref<HTMLElement | null>(null)
const hasPlayedSelectionAnim = ref(false)
let selectionObserver: IntersectionObserver | null = null
const setupSelectionInitialState = () => {
const items =
criteriaListRef.value?.querySelectorAll<HTMLElement>('.item') ?? []
if (items.length) {
gsap.set(items, {
opacity: 0,
scale: 0,
transformOrigin: '50% 50%'
})
}
}
const playSelectionAnimation = () => {
if (hasPlayedSelectionAnim.value) return
const items =
criteriaListRef.value?.querySelectorAll<HTMLElement>('.item') ?? []
if (!items.length) return
gsap.to(items, {
opacity: 1,
scale: 1,
duration: 0.6,
ease: 'back.out(1.6)',
stagger: 0.3
})
hasPlayedSelectionAnim.value = true
selectionObserver?.disconnect()
}
onMounted(() => {
nextTick(() => {
setupSelectionInitialState()
if ('IntersectionObserver' in window) {
selectionObserver = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
playSelectionAnimation()
}
})
},
{ threshold: 0.25 }
)
if (selectionRef.value) {
selectionObserver.observe(selectionRef.value)
}
} else {
playSelectionAnimation()
}
})
})
onBeforeUnmount(() => {
selectionObserver?.disconnect()
})
</script>
<style scoped lang="less">
.selection-container {
background: url('@/assets/images/award/selection_bg.png') no-repeat;
background-size: 100% 100%;
padding-top: 9.3rem;
.title {
color: #fff;
font-family: 'PoppinsBold';
font-weight: 600;
font-size: 4rem;
}
.logo {
margin: 2.3rem 0 2.3rem;
}
.sub-title {
color: #f95750;
font-family: 'Popins';
font-weight: 400;
font-size: 3rem;
margin-bottom: 11.8rem;
}
.criteria-list {
column-gap: 6rem;
.item {
height: 44rem;
width: 32.2rem;
box-sizing: border-box;
&:nth-of-type(3) {
background: url('@/assets/images/award/criteria_bg.png') no-repeat;
background-size: 100% 100%;
}
.icon {
width: 18.8rem;
height: 18rem;
}
.name {
font-family: 'PoppinsMedium';
font-weight: 500;
font-size: 2.8rem;
color: #fff;
margin: 2rem 0 5rem;
}
.desc {
font-family: 'Arial';
font-weight: 400;
font-size: 2.4rem;
color: #e0e0e0;
text-align: center;
}
}
}
}
</style>

View File

@@ -0,0 +1,280 @@
<template>
<div
ref="containerRef"
class="timeline-container container flex flex-col align-center"
>
<div class="timeline-title">Competition Timeline</div>
<div class="desc">Shaping the Future</div>
<div class="timeline-point">
<div class="labels-row flex align-center">
<div
class="item-label flex flex-col"
v-for="item in points"
:key="'label-' + item.time"
>
<div class="main-label">{{ item.label }}</div>
<div
class="sub-label"
v-if="item.subLabel"
>
{{ item.subLabel }}
</div>
</div>
</div>
<!-- Icons row -->
<div class="icons-row flex align-center">
<div class="timeline-line"></div>
<img
src="@/assets/images/award/point.png"
class="point-icon"
v-for="item in points"
:key="'icon-' + item.time"
/>
</div>
<!-- Times row -->
<div class="times-row flex align-center">
<div
class="item-time"
v-for="item in points"
:key="'time-' + item.time"
>
{{ item.time }}
</div>
</div>
<!-- Descriptions row -->
<div class="descs-row flex align-center">
<div
class="item-desc flex justify-center"
v-for="item in points"
:key="'desc-' + item.time"
>
<div class="txt">
{{ item.desc }}
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { nextTick, onBeforeUnmount, onMounted, ref } from 'vue'
import { gsap } from 'gsap'
const containerRef = ref<HTMLElement | null>(null)
const hasAnimated = ref(false)
const points = ref([
{
label: 'Select Top 20',
time: 'May',
desc: 'Submit your design concept, mood board, and initial sketch.'
},
{
label: `Top 20`,
subLabel: 'Collections Finalize',
time: 'June',
desc: 'Complete collections, physical garments, and AiDA process videos due.'
},
{
label: `Top 3`,
subLabel: 'Finalists Select',
time: 'August',
desc: 'Complete collections, physical garments, and AiDA process videos due.'
},
{
label: 'Award Ceremony',
time: 'November',
desc: 'Winners revealed with media coverage and live showcase.'
}
])
const playAnimation = () => {
if (!containerRef.value || hasAnimated.value) return
const title = containerRef.value.querySelector('.timeline-title')
const subtitle = containerRef.value.querySelector('.desc')
const line = containerRef.value.querySelector('.timeline-line')
const timeline = containerRef.value.querySelector('.timeline-point')
const tl = gsap.timeline()
if (title && subtitle) {
tl.from([title, subtitle], {
scaleX: 0,
autoAlpha: 0,
transformOrigin: '50% 50%',
duration: 0.6,
stagger: 0.1,
ease: 'power2.out'
})
}
if (timeline) {
tl.fromTo(
timeline,
{
clipPath: 'inset(0 100% 0 0)'
},
{
clipPath: 'inset(0 0% 0 0)',
duration: 1.6,
ease: 'power1.out'
}
)
}
if (line) {
tl.from(
line,
{
scaleX: 0,
transformOrigin: '0% 50%',
duration: 1.3,
ease: 'power1.out'
},
'-=1.1'
)
}
hasAnimated.value = true
}
let observer: IntersectionObserver | null = null
onMounted(async () => {
await nextTick()
if (!containerRef.value) return
observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
playAnimation()
}
})
},
{ threshold: 0.3 }
)
observer.observe(containerRef.value)
})
onBeforeUnmount(() => {
if (observer && containerRef.value) {
observer.unobserve(containerRef.value)
}
observer = null
})
</script>
<style scoped lang="less">
.timeline-container {
background: url('@/assets/images/award/timeline_bg.png') no-repeat;
background-size: 100% 100%;
position: relative;
padding-top: 12.8rem;
width: 100%;
color: #fff;
.timeline-title {
font-family: 'PoppinsBold';
font-weight: 600;
font-size: 4rem;
text-align: center;
vertical-align: middle;
}
.logo {
margin: 2.4rem 0 2.2rem 0;
}
.desc {
font-family: 'Arial';
font-size: 3rem;
font-weight: 400;
color: #f95750;
}
.timeline-point {
overflow: hidden;
will-change: clip-path;
flex: 1;
width: 100%;
margin-top: 12rem;
padding: 0 21.2rem 0 22rem;
position: relative;
z-index: 2;
.labels-row {
position: relative;
z-index: 2;
margin-bottom: 8rem;
.item-label {
flex: 1;
color: #fff;
font-family: 'PoppinsBold';
font-weight: 600;
font-size: 2.8rem;
text-align: center;
white-space: pre-line;
height: 6rem;
justify-content: center;
}
}
.icons-row {
margin-bottom: 1.6rem;
position: relative;
z-index: 2;
.point-icon {
width: 6.4rem;
height: 6.4rem;
display: block;
margin: 0 auto;
z-index: 2;
}
.timeline-line {
width: calc(100% + 22rem + 21.2rem);
left: -22rem;
height: 0.15rem;
background: linear-gradient(
90deg,
rgba(199, 52, 44, 0) 0%,
rgba(199, 52, 44, 0.719626) 25.96%,
#c7342c 51.44%,
rgba(199, 52, 44, 0.762376) 75.96%,
rgba(199, 52, 44, 0) 100%
);
position: absolute;
bottom: 50%;
transform: translateY(-50%);
z-index: 1;
}
}
.times-row {
margin-bottom: 6rem;
z-index: 2;
position: relative;
.item-time {
flex: 1;
color: #f95750;
font-family: 'Arial';
font-weight: 400;
font-size: 2.8rem;
line-height: 4.5rem;
text-align: center;
}
}
.descs-row {
.item-desc {
flex: 1;
.txt {
font-family: 'Arial';
font-weight: 400;
font-size: 2rem;
text-align: center;
color: #e0e0e0;
width: 31.2rem;
height: 10.2rem;
}
}
}
}
}
</style>

View File

@@ -25,231 +25,25 @@
<div class="line"></div> <div class="line"></div>
</div> </div>
</div> </div>
<div class="bloom container flex flex-col align-center">
<div class="title">Bloom Your Creativity</div>
<img
src="@/assets/images/award/bloom_logo.png"
class="logo"
/>
<div class="season">Theme of 2026</div>
<div class="desc">
Where imagination meets innovation, creativity blooms. This theme
celebrates AI as a catalyst for fashion design, allowing your vision to
flourish beyond traditional boundaries. Let your ideas blossom into
extraordinary designs that merge human artistry with artificial
intelligence.
</div>
</div>
<!-- <div class="design-container container">
<div class="design-title limit">Design Without Borders</div>
<div class="limit">
<img
src="@/assets/images/award/bloom_logo.png"
class="logo"
/>
</div>
<div class="global limit">Global Opportunity</div>
<div class="desc">
Open to visionary designers across the globe. From Seoul to Singapore, New
York to Shanghai, we're seeking the next generation of fashion innovators
who dare to reimagine the future of design.
</div>
</div> -->
<div class="timeline-container container flex flex-col align-center">
<div class="timeline-title">Competition Timeline</div>
<!-- <img
src="@/assets/images/award/bloom_logo.png"
alt=""
class="logo"
/> -->
<div class="desc">Shaping the Future</div>
<div class="timeline-point">
<div class="labels-row flex align-center">
<div
class="item-label flex flex-col"
v-for="item in points"
:key="'label-' + item.time"
>
<div class="main-label">{{ item.label }}</div>
<div
class="sub-label"
v-if="item.subLabel"
>
{{ item.subLabel }}
</div>
</div>
</div>
<!-- Icons row -->
<div class="icons-row flex align-center">
<div class="timeline-line"></div>
<img
src="@/assets/images/award/point.png"
class="point-icon"
v-for="item in points"
:key="'icon-' + item.time"
/>
</div>
<!-- Times row --> <Bloom />
<div class="times-row flex align-center"> <TimeLine />
<div <JudgesSection />
class="item-time" <PrizesSection />
v-for="item in points" <ApplySection />
:key="'time-' + item.time" <SelectionSection />
>
{{ item.time }}
</div>
</div>
<!-- Descriptions row -->
<div class="descs-row flex align-center">
<div
class="item-desc flex justify-center"
v-for="item in points"
:key="'desc-' + item.time"
>
<div class="txt">
{{ item.desc }}
</div>
</div>
</div>
</div>
</div>
<div class="judges-container flex flex-col align-center">
<div class="title">Panel of Judges</div>
<img
src="@/assets/images/award/bloom_logo.png"
class="logo"
/>
<div class="sub-title">Expertise</div>
<div class="judgement-list">
<div
class="judgement-item flex flex-col align-center"
v-for="item in judgements"
:key="item.name"
>
<img
:src="item.picture"
class="picture"
/>
<div class="name">{{ item.name }}</div>
<div class="desc">{{ item.desc }}</div>
</div>
</div>
</div>
<div class="prizes-container container flex align-center">
<div class="left flex flex-col flex-center">
<div class="title">Award & Prizes</div>
<!-- <img
src="@/assets/images/award/bloom_logo.png"
class="logo"
/> -->
<div class="desc">Recongnition</div>
</div>
<div class="right">
<div
class="prize-item flex flex-col flex-center"
v-for="item in prizes"
:key="item.name"
>
<div class="prize-money">{{ item.money }}</div>
<div class="prize-name">{{ item.name }}</div>
<div class="prize-desc flex flex-col flex-center">
<div
class="desc-item"
v-for="el in item.desc"
>
{{ el }}
</div>
</div>
</div>
</div>
</div>
<div class="apply-container container flex flex-col">
<div class="title">How to Apply</div>
<div class="sub-title">Requirments</div>
<div class="requirments-list flex">
<div class="left flex flex-col space-between">
<div
class="item-box"
v-for="item in leftRequirment"
:key="item.type"
>
<div class="item-header flex align-center">
<img
src="@/assets/images/award/bloom_logo.png"
class="logo"
/>
<div class="item-title">{{ item.type }}</div>
</div>
<div
class="context"
v-for="el in item.desc"
>
{{ el }}
</div>
</div>
</div>
<div class="right">
<div class="item-box">
<div class="item-box">
<div class="item-header flex align-center">
<img
src="@/assets/images/award/bloom_logo.png"
class="logo"
/>
<div class="item-title">{{ rightRequirment.type }}</div>
</div>
<div
class="context"
v-for="el in rightRequirment.desc"
>
{{ el }}
</div>
</div>
</div>
</div>
</div>
</div>
<div class="selection-container container flex flex-col align-center">
<div class="title">Selection Criteria</div>
<img
src="@/assets/images/award/bloom_logo.png"
class="logo"
/>
<div class="sub-title">Evaluation</div>
<div class="criteria-list flex">
<div
class="item flex flex-col align-center"
v-for="item in criteriaList"
:key="item.name"
>
<img
:src="item.icon"
class="icon"
:style="item.style"
/>
<div class="name">{{ item.name }}</div>
<div class="desc">{{ item.desc }}</div>
</div>
</div>
</div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import { ref } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import criteria1 from '@/assets/images/award/criteria_1.png' import JudgesSection from './components/JudgesSection.vue'
import criteria2 from '@/assets/images/award/criteria_2.png' import SelectionSection from './components/SelectionSection.vue'
import criteria3 from '@/assets/images/award/criteria_3.png' import ApplySection from './components/ApplySection.vue'
import criteria4 from '@/assets/images/award/criteria_4.png' import PrizesSection from './components/PrizesSection.vue'
import TimeLine from './components/TimeLine.vue'
import jae from '@/assets/images/award/jae.png' import Bloom from './components/Bloom.vue'
import diego from '@/assets/images/award/diego.png'
import gregory from '@/assets/images/award/gregory.png'
import vincenzo from '@/assets/images/award/vincenzo.png'
import tim from '@/assets/images/award/tim.png'
import desmond from '@/assets/images/award/desmond.png'
const router = useRouter() const router = useRouter()
@@ -275,137 +69,6 @@
label: 'for finalists to attend\naward ceremony' label: 'for finalists to attend\naward ceremony'
} }
]) ])
const points = ref([
{
label: 'Select Top 20',
time: 'May',
desc: 'Submit your design concept, mood board, and initial sketch.'
},
{
label: `Top 20`,
subLabel: 'Collections Finalize',
time: 'June',
desc: 'Complete collections, physical garments, and AiDA process videos due.'
},
{
label: `Top 3`,
subLabel: 'Finalists Select',
time: 'August',
desc: 'Complete collections, physical garments, and AiDA process videos due.'
},
{
label: 'Award Ceremony',
time: 'November',
desc: 'Winners revealed with media coverage and live showcase.'
}
])
const prizes = ref([
{
money: 'US$5000',
name: 'Grand Prize',
desc: ['Cash Award', 'Award Ceritificate', 'Global Media Exposure']
},
{
money: 'US$3000',
name: 'First Runner-Up',
desc: ['Cash Award', 'Award Ceritificate', 'Global Media Exposure']
},
{
money: 'US$2000',
name: 'Second Runner-Up',
desc: ['Cash Award', 'Award Ceritificate', 'Global Media Exposure']
},
{
money: 'Certification',
name: 'Finalists',
desc: ['Award Ceritificate', 'Global Media Exposure']
}
])
const leftRequirment = ref([
{
type: 'Video',
desc: ['The process of doing design']
},
{
type: 'Design',
desc: [
'Structure: design title, moodboard and elaboration (how will you use AiDA to design)',
'Design sketch: Maximum 4 outfit design with proposed materials'
]
}
])
const rightRequirment = ref({
type: 'Submission Format',
desc: [
'Naming as AiDA global award 2026_applicantname',
'Mp4\n(1080x1920pixels/20mb within 1min)',
'Single PDF file\n(within 15 pages, maximum 20mb)',
'English or native language\nwith English translation'
]
})
const criteriaList = ref([
{
icon: criteria1,
name: 'Originality',
desc: 'Unique perspective and innovative approach to fashion design',
style: { width: '13rem', height: '17rem' }
},
{
icon: criteria2,
name: 'Creativity',
desc: 'Artistic vision and exceptional design excellence',
style: { width: '16rem', height: '18rem' }
},
{
icon: criteria3,
name: 'AiDA Integration',
desc: 'Effective application of AI design tools and functions',
style: { width: '16rem', height: '18rem' }
},
{
icon: criteria4,
name: 'Execution',
desc: 'Quality of presentation and technical craftsmanship',
style: { width: '18.8rem', height: '18rem' }
}
])
const judgements = [
{
picture: jae,
name: 'Jae Hyuk Lim',
desc: 'Code-create\nKorea Branch Director\nBesfxxk creative director'
},
{
picture: diego,
name: 'Diego Dultzin Lacoste',
desc: 'Co-founder & Chief Father\nOfficer of OnTheList\n(Hong Kong)'
},
{
picture: gregory,
name: 'Gregory de la Hogue Moran',
desc: 'Senior Designer at\nGabriela Heasrst (Italy)'
},
{
picture: vincenzo,
name: 'Vincenzo La Torre',
desc: 'Cheif Editor of SCMP Style\n(Hong Kong)'
},
{
picture: tim,
name: 'Tim Lim',
desc: 'Group Fashion Direction of\n Modern Media Group\n(Shanghai)'
},
{
picture: desmond,
name: 'Desmond Lim',
desc: 'Cheif Editor of Vogue\n(Singapore)'
}
]
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@@ -492,422 +155,4 @@
} }
} }
} }
.bloom {
padding-top: 12.8rem;
font-family: 'Poppins';
background: url('@/assets/images/award/bloom_bg.png') no-repeat;
background-size: 100% 100%;
.title {
font-size: 4rem;
color: #232323;
margin-bottom: 2.4rem;
}
.logo {
margin-bottom: 2.2rem;
}
.season {
font-size: 3rem;
color: #c7342c;
margin-bottom: 6.6rem;
}
.desc {
font-family: 'Arial';
font-size: 2.8rem;
color: #585858;
text-align: center;
padding: 0 21.5rem;
line-height: 4.5rem;
margin-bottom: 12.3rem;
}
}
// .design-container {
// background: url('@/assets/images/award/design_bg.png') no-repeat;
// background-size: 100% 100%;
// padding-left: 21.5rem;
// padding-top: 16rem;
// .limit {
// width: 48.4rem;
// text-align: center;
// }
// .design-title {
// color: #fff;
// font-size: 4rem;
// font-weight: 600;
// font-family: 'Poppins';
// font-style: SemiBold;
// vertical-align: middle;
// text-transform: capitalize;
// }
// .logo {
// margin-top: 2.4rem;
// margin-bottom: 2.1rem;
// }
// .global {
// font-family: 'Poppins';
// font-size: 3rem;
// color: #f95750;
// margin-bottom: 19.8rem;
// }
// .desc {
// font-family: 'Arial';
// font-weight: 400;
// font-size: 2.8rem;
// color: #e0e0e0;
// width: 54rem;
// }
// }
.timeline-container {
background: url('@/assets/images/award/timeline_bg.png') no-repeat;
background-size: 100% 100%;
position: relative;
padding-top: 12.8rem;
width: 100%;
color: #fff;
.timeline-title {
font-family: 'PoppinsBold';
font-weight: 600;
font-size: 4rem;
text-align: center;
vertical-align: middle;
}
.logo {
margin: 2.4rem 0 2.2rem 0;
}
.desc {
font-family: 'Arial';
font-size: 3rem;
font-weight: 400;
color: #f95750;
}
.timeline-point {
flex: 1;
width: 100%;
margin-top: 12rem;
padding: 0 21.2rem 0 22rem;
position: relative;
z-index: 2;
.labels-row {
position: relative;
z-index: 2;
margin-bottom: 8rem;
.item-label {
flex: 1;
color: #fff;
font-family: 'PoppinsBold';
font-weight: 600;
font-size: 2.8rem;
text-align: center;
white-space: pre-line;
height: 6rem;
justify-content: center;
}
}
.icons-row {
margin-bottom: 1.6rem;
position: relative;
z-index: 2;
.point-icon {
width: 6.4rem;
height: 6.4rem;
display: block;
margin: 0 auto;
z-index: 2;
}
.timeline-line {
width: calc(100% + 22rem + 21.2rem);
left: -22rem;
height: 0.15rem;
background: linear-gradient(
90deg,
rgba(199, 52, 44, 0) 0%,
rgba(199, 52, 44, 0.719626) 25.96%,
#c7342c 51.44%,
rgba(199, 52, 44, 0.762376) 75.96%,
rgba(199, 52, 44, 0) 100%
);
position: absolute;
bottom: 50%;
transform: translateY(-50%);
z-index: 1;
}
}
.times-row {
margin-bottom: 6rem;
z-index: 2;
position: relative;
.item-time {
flex: 1;
color: #f95750;
font-family: 'Arial';
font-weight: 400;
font-size: 2.8rem;
line-height: 4.5rem;
text-align: center;
}
}
.descs-row {
.item-desc {
flex: 1;
.txt {
font-family: 'Arial';
font-weight: 400;
font-size: 2rem;
text-align: center;
color: #e0e0e0;
width: 31.2rem;
height: 10.2rem;
}
}
}
}
}
.prizes-container {
background: url('@/assets/images/award/prizes_bg.png') no-repeat;
background-size: 100% 100%;
padding: 0 21.4rem 0 33.4rem;
.left {
width: 36.2rem;
row-gap: 2.3rem;
margin-right: 27rem;
.title {
font-family: 'PoppinsBold';
font-weight: 600;
font-size: 4rem;
text-align: center;
color: #fff;
}
.desc {
font-family: 'Poppins';
font-weight: 400;
font-size: 3rem;
text-align: center;
color: #f95750;
}
}
.right {
display: grid;
grid-template-columns: repeat(2, 35.5rem);
grid-template-rows: repeat(2, 32.8rem);
gap: 4.2rem;
.prize-item {
width: 35.5rem;
height: 32.8rem;
color: #fff;
padding: 4.5rem 0 4.8rem 0;
justify-content: space-between;
background: url('@/assets/images/award/first_bg.png') no-repeat;
background-size: 100% 100%;
&:nth-of-type(2) {
background: url('@/assets/images/award/second_bg.png') no-repeat;
background-size: 100% 100%;
}
&:nth-of-type(3) {
background: url('@/assets/images/award/grand_bg.png') no-repeat;
background-size: 100% 100%;
}
&:nth-of-type(4) {
background: url('@/assets/images/award/certification_bg.png')
no-repeat;
background-size: 100% 100%;
}
.prize-money {
font-family: 'PoppinsBold';
font-weight: bold;
font-size: 4rem;
}
.prize-name {
font-family: 'PoppinsMedium';
font-weight: 500;
font-size: 2.8rem;
}
.prize-desc {
color: #e0e0e0;
font-family: 'Arial';
font-weight: 400;
font-size: 2rem;
line-height: 3rem;
height: 8.9rem;
}
}
}
}
.apply-container {
flex: 1;
background: url('@/assets/images/award/apply_bg.png') no-repeat;
background-size: 100% 100%;
padding: 12.7rem 0 16.9rem;
.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;
}
.requirments-list {
flex: 1;
padding-left: 41.4rem;
column-gap: 17.7rem;
margin-top: 12rem;
.left {
color: #232323;
height: 100%;
}
.item-box {
.item-header {
column-gap: 3.2rem;
.item-title {
color: #232323;
font-family: 'PoppinsBold';
font-weight: 600;
font-size: 2.8rem;
}
}
.context {
margin-top: 4rem;
width: 46.8rem;
color: #585858;
font-family: 'Arial';
font-weight: 400;
line-height: 3rem;
font-size: 2.4rem;
padding-left: 5.6rem;
white-space: pre-line;
}
}
}
}
.selection-container {
background: url('@/assets/images/award/selection_bg.png') no-repeat;
background-size: 100% 100%;
padding-top: 9.3rem;
.title {
color: #fff;
font-family: 'PoppinsBold';
font-weight: 600;
font-size: 4rem;
}
.logo {
margin: 2.3rem 0 2.3rem;
}
.sub-title {
color: #f95750;
font-family: 'Popins';
font-weight: 400;
font-size: 3rem;
margin-bottom: 11.8rem;
}
.criteria-list {
column-gap: 6rem;
.item {
height: 44rem;
width: 32.2rem;
box-sizing: border-box;
&:nth-of-type(3) {
background: url('@/assets/images/award/criteria_bg.png') no-repeat;
background-size: 100% 100%;
}
.icon {
width: 18.8rem;
height: 18rem;
}
.name {
font-family: 'PoppinsMedium';
font-weight: 500;
font-size: 2.8rem;
color: #fff;
margin: 2rem 0 5rem;
}
.desc {
font-family: 'Arial';
font-weight: 400;
font-size: 2.4rem;
color: #e0e0e0;
text-align: center;
}
}
}
}
.judges-container {
height: 147.4rem;
background: url('@/assets/images/award/judges_bg.png') no-repeat;
background-size: 100% 100%;
padding-top: 12.8rem;
.title {
color: #232323;
font-family: 'PoppinsBold';
font-weight: 600;
font-size: 4rem;
}
.logo {
margin: 2.4rem 0 2.2rem;
}
.sub-title {
color: #b10000;
font-family: 'Arial';
font-weight: 400;
font-size: 3rem;
margin-bottom: 12rem;
}
.judgement-list {
display: grid;
grid-template-columns: repeat(3, 1fr);
column-gap: 23.22rem;
row-gap: 8rem;
padding: 0 25rem 0 26.6rem;
.judgement-item {
.picture {
width: 20.2rem;
height: 26rem;
border-radius: 0.8rem;
}
.name {
margin: 3rem 0 2.4rem;
color: #232323;
font-family: 'PoppinsBold';
font-weight: 600;
font-size: 2.4rem;
}
.desc {
color: #585858;
font-family: 'Arial';
font-weight: 400;
font-size: 2rem;
white-space: pre-line;
text-align: center;
}
}
}
}
.footer {
height: 10rem;
padding-left: 21.5rem;
padding-right: 22rem;
background-color: #232323;
.social-list {
column-gap: 2rem;
img {
width: 2rem;
height: 2rem;
}
}
.copyright {
color: #fff;
font-family: 'Arial';
font-weight: 400;
font-size: 1.2rem;
}
}
</style> </style>