Merge branch 'main' of ssh://18.167.251.121:10002/aidlab/Aida_Purchaser_Front
25
package-lock.json
generated
@@ -19,6 +19,7 @@
|
||||
"pinia": "^2.0.32",
|
||||
"pinia-persistedstate-plugin": "^0.1.0",
|
||||
"pinia-plugin-persistedstate": "^3.1.0",
|
||||
"swiper": "^12.1.3",
|
||||
"vue": "^3.2.47",
|
||||
"vue-i18n": "^11.2.8",
|
||||
"vue-router": "^4.1.6"
|
||||
@@ -7611,6 +7612,25 @@
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/swiper": {
|
||||
"version": "12.1.3",
|
||||
"resolved": "https://registry.npmmirror.com/swiper/-/swiper-12.1.3.tgz",
|
||||
"integrity": "sha512-XcWlVmkHFICI4fuoJKgbp8PscDcS4i7pBH8nwJRBi3dpQvhCySwsWRYm4bOf/BzKVWkHOYaFw7qz9uBSrY3oug==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/swiperjs"
|
||||
},
|
||||
{
|
||||
"type": "open_collective",
|
||||
"url": "http://opencollective.com/swiper"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 4.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/text-table": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/text-table/-/text-table-0.2.0.tgz",
|
||||
@@ -14245,6 +14265,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"swiper": {
|
||||
"version": "12.1.3",
|
||||
"resolved": "https://registry.npmmirror.com/swiper/-/swiper-12.1.3.tgz",
|
||||
"integrity": "sha512-XcWlVmkHFICI4fuoJKgbp8PscDcS4i7pBH8nwJRBi3dpQvhCySwsWRYm4bOf/BzKVWkHOYaFw7qz9uBSrY3oug=="
|
||||
},
|
||||
"text-table": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/text-table/-/text-table-0.2.0.tgz",
|
||||
|
||||
@@ -17,14 +17,15 @@
|
||||
"crypto-js": "^4.2.0",
|
||||
"element-plus": "^2.13.2",
|
||||
"gsap": "^3.13.0",
|
||||
"vue-i18n": "^11.2.8",
|
||||
"markdown-it": "^14.1.0",
|
||||
"md5": "^2.3.0",
|
||||
"normalize.css": "^8.0.1",
|
||||
"pinia": "^2.0.32",
|
||||
"pinia-persistedstate-plugin": "^0.1.0",
|
||||
"pinia-plugin-persistedstate": "^3.1.0",
|
||||
"swiper": "^12.1.3",
|
||||
"vue": "^3.2.47",
|
||||
"vue-i18n": "^11.2.8",
|
||||
"vue-router": "^4.1.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -57,4 +58,4 @@
|
||||
"npm run lint"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
27
src/App.vue
@@ -1,16 +1,34 @@
|
||||
<template>
|
||||
<main-header />
|
||||
<div class="view"><RouteCache /></div>
|
||||
<div class="view" ref="viewRef" :style="viewStyle"><RouteCache /></div>
|
||||
<login-dialog />
|
||||
<div id="loading" v-if="loading" v-loading="true"></div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref, onBeforeUnmount } from 'vue'
|
||||
import RouteCache from '@/components/RouteCache.vue'
|
||||
import MainHeader from '@/views/main-header.vue'
|
||||
import { computed } from 'vue'
|
||||
import LoginDialog from '@/views/login/login-dialog.vue'
|
||||
import { useGlobalStore } from '@/stores'
|
||||
const globalStore = useGlobalStore()
|
||||
const loading = computed(() => globalStore.state.loading)
|
||||
const viewRef = ref()
|
||||
const viewStyle = ref({
|
||||
'--app-view-width': '',
|
||||
'--app-view-height': ''
|
||||
})
|
||||
const observer = new ResizeObserver((entries) => {
|
||||
const { width, height } = entries[0].contentRect
|
||||
viewStyle.value['--app-view-width'] = width + 'px'
|
||||
viewStyle.value['--app-view-height'] = height + 'px'
|
||||
})
|
||||
onMounted(() => {
|
||||
observer.observe(viewRef.value)
|
||||
})
|
||||
onBeforeUnmount(() => {
|
||||
observer.disconnect()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@@ -18,7 +36,6 @@
|
||||
font-size: 1.6rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-family: 'KaiseiOpti-Regular', sans-serif;
|
||||
}
|
||||
#loading {
|
||||
position: fixed;
|
||||
@@ -34,6 +51,10 @@
|
||||
}
|
||||
</style>
|
||||
<style lang="less" scoped>
|
||||
* {
|
||||
--header-height: 8rem;
|
||||
--footer-height: 7rem;
|
||||
}
|
||||
.view {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
|
||||
@@ -19,6 +19,7 @@ body,
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
font-family: 'KaiseiOpti-Medium';
|
||||
}
|
||||
@keyframes loading {
|
||||
0% {
|
||||
@@ -106,35 +107,62 @@ body,
|
||||
--mosaic-bg-size: 1rem;
|
||||
--mosaic-bg-color1: #efefef;
|
||||
--mosaic-bg-color2: #fff;
|
||||
background-image: repeating-conic-gradient(var(--mosaic-bg-color1) 0% 25%, var(--mosaic-bg-color2) 0% 50%);
|
||||
background-image: repeating-conic-gradient(
|
||||
var(--mosaic-bg-color1) 0% 25%,
|
||||
var(--mosaic-bg-color2) 0% 50%
|
||||
);
|
||||
background-repeat: repeat;
|
||||
background-position: 50% 50%;
|
||||
background-size: var(--mosaic-bg-size) var(--mosaic-bg-size);
|
||||
}
|
||||
|
||||
|
||||
.flex{
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
.flex-center{
|
||||
.flex-center {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.flex-1{
|
||||
.flex-1 {
|
||||
flex: 1;
|
||||
}
|
||||
.flex-col{
|
||||
.flex-col {
|
||||
flex-direction: column;
|
||||
}
|
||||
.align-center{
|
||||
.align-center {
|
||||
align-items: center;
|
||||
}
|
||||
.space-between{
|
||||
.space-between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
.justify-center{
|
||||
.justify-center {
|
||||
justify-content: center;
|
||||
}
|
||||
.relative{
|
||||
.relative {
|
||||
position: relative;
|
||||
}
|
||||
button[custom],
|
||||
button[custom='white'] {
|
||||
min-width: 19.4rem;
|
||||
height: 5rem;
|
||||
padding: 0 1rem;
|
||||
border-radius: 0;
|
||||
border: none;
|
||||
font-family: KaiseiOpti-Bold;
|
||||
font-size: var(--button-font-size, 2rem);
|
||||
color: var(--button-color, #232323);
|
||||
background: var(--button-bgcolor, #fff);
|
||||
cursor: pointer;
|
||||
}
|
||||
button[custom]:active,
|
||||
button[custom='white']:active {
|
||||
background: var(--button-click-bgcolor, #e4e4e4);
|
||||
color: var(--button-click-color, #232323);
|
||||
}
|
||||
button[custom='black'] {
|
||||
--button-bgcolor: #232323;
|
||||
--button-color: #fff;
|
||||
--button-click-bgcolor: #333;
|
||||
--button-click-color: #fff;
|
||||
--button-font-size: 1.6rem;
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ body,
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
// font-family: 'Medium';
|
||||
font-family: 'KaiseiOpti-Medium';
|
||||
}
|
||||
|
||||
@keyframes loading {
|
||||
@@ -88,22 +88,31 @@ body,
|
||||
}
|
||||
|
||||
.el-overlay {
|
||||
--el-color-primary: #ff7a51; // 主橙红色
|
||||
--el-color-primary-light-3: #ffa785; // 较浅的橙红(混合20%白)
|
||||
--el-color-primary-light-5: #ffc2aa; // 更浅的橙红(混合33%白)
|
||||
--el-color-primary-light-7: #ffddcf; // 浅橙红(混合47%白)
|
||||
--el-color-primary-light-8: #ffe8df; // 很浅的橙红(混合53%白)
|
||||
--el-color-primary-light-9: #fff2ec; // 极浅的橙红(混合60%白)
|
||||
--el-color-primary-dark-2: #cc6241; // 深橙红(加深20%)
|
||||
--el-color-primary: #ff7a51; // 主橙红色
|
||||
--el-color-primary-light-3: #ffa785; // 较浅的橙红(混合20%白)
|
||||
--el-color-primary-light-5: #ffc2aa; // 更浅的橙红(混合33%白)
|
||||
--el-color-primary-light-7: #ffddcf; // 浅橙红(混合47%白)
|
||||
--el-color-primary-light-8: #ffe8df; // 很浅的橙红(混合53%白)
|
||||
--el-color-primary-light-9: #fff2ec; // 极浅的橙红(混合60%白)
|
||||
--el-color-primary-dark-2: #cc6241; // 深橙红(加深20%)
|
||||
}
|
||||
.el-select, .el-popper{
|
||||
--el-color-primary: #6c6c6c; /* 主灰色 */
|
||||
--el-color-primary-light-3: #8a8a8a; /* 较浅的灰色(混合20%白) */
|
||||
--el-color-primary-light-5: #a8a8a8; /* 更浅的灰色(混合33%白) */
|
||||
--el-color-primary-light-7: #c6c6c6; /* 浅灰色(混合47%白) */
|
||||
--el-color-primary-light-8: #d4d4d4; /* 很浅的灰色(混合53%白) */
|
||||
--el-color-primary-light-9: #e3e3e3; /* 极浅的灰色(混合60%白) */
|
||||
--el-color-primary-dark-2: #565656; /* 深灰色(加深20%) */
|
||||
|
||||
.el-select,
|
||||
.el-popper {
|
||||
--el-color-primary: #6c6c6c;
|
||||
/* 主灰色 */
|
||||
--el-color-primary-light-3: #8a8a8a;
|
||||
/* 较浅的灰色(混合20%白) */
|
||||
--el-color-primary-light-5: #a8a8a8;
|
||||
/* 更浅的灰色(混合33%白) */
|
||||
--el-color-primary-light-7: #c6c6c6;
|
||||
/* 浅灰色(混合47%白) */
|
||||
--el-color-primary-light-8: #d4d4d4;
|
||||
/* 很浅的灰色(混合53%白) */
|
||||
--el-color-primary-light-9: #e3e3e3;
|
||||
/* 极浅的灰色(混合60%白) */
|
||||
--el-color-primary-dark-2: #565656;
|
||||
/* 深灰色(加深20%) */
|
||||
}
|
||||
|
||||
|
||||
@@ -112,13 +121,14 @@ body,
|
||||
&::-webkit-scrollbar {
|
||||
width: 0.4rem;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
border-radius: 0.4rem;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
.mosaic-bg{
|
||||
.mosaic-bg {
|
||||
--mosaic-bg-size: 1rem;
|
||||
--mosaic-bg-color1: #efefef;
|
||||
--mosaic-bg-color2: #fff;
|
||||
@@ -126,4 +136,33 @@ body,
|
||||
background-repeat: repeat;
|
||||
background-position: 50% 50%;
|
||||
background-size: var(--mosaic-bg-size) var(--mosaic-bg-size);
|
||||
}
|
||||
|
||||
|
||||
// 自定义button按钮
|
||||
button[custom],
|
||||
button[custom="white"] {
|
||||
min-width: 19.4rem;
|
||||
height: 5rem;
|
||||
padding: 0 1rem;
|
||||
border-radius: 0;
|
||||
border: none;
|
||||
font-family: KaiseiOpti-Bold;
|
||||
font-size: var(--button-font-size, 2rem);
|
||||
color: var(--button-color, #232323);
|
||||
background: var(--button-bgcolor, #fff);
|
||||
cursor: pointer;
|
||||
|
||||
&:active {
|
||||
background: var(--button-click-bgcolor, #e4e4e4);
|
||||
color: var(--button-click-color, #232323);
|
||||
}
|
||||
}
|
||||
|
||||
button[custom="black"] {
|
||||
--button-bgcolor: #232323;
|
||||
--button-color: #fff;
|
||||
--button-click-bgcolor: #333;
|
||||
--button-click-color: #fff;
|
||||
--button-font-size: 1.6rem;
|
||||
}
|
||||
4
src/assets/icons/add.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M20.5433 11.5224H3.45666C3.20445 11.5224 3 11.7269 3 11.9791C3 12.2313 3.20445 12.4357 3.45666 12.4357H20.5433C20.7955 12.4357 21 12.2313 21 11.9791C21 11.7269 20.7955 11.5224 20.5433 11.5224Z" fill="black"/>
|
||||
<path d="M12.4688 20.5433V3.45666C12.4688 3.20445 12.2643 3 12.0121 3C11.7599 3 11.5554 3.20445 11.5554 3.45666V20.5433C11.5554 20.7955 11.7599 21 12.0121 21C12.2643 21 12.4688 20.7955 12.4688 20.5433Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 539 B |
4
src/assets/icons/arrow_right.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="27" height="25" viewBox="0 0 27 25" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0.998359 12.0798L24.1266 12.0798" stroke="#232323" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M14.1094 1.0332L25.3052 12.2291L14.0357 23.2039" stroke="#232323" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 371 B |
3
src/assets/icons/back.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="10" height="17" viewBox="0 0 10 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1.56121 8.28356L8.95682 0.887943C9.16023 0.684537 9.16023 0.355959 8.95682 0.152554C8.75342 -0.0508513 8.42484 -0.0508513 8.22143 0.152554L0.0852156 8.28877C-0.0295258 8.40351 -0.0295258 8.59127 0.0852156 8.71123L8.22143 16.8474C8.42484 17.0509 8.75342 17.0509 8.95682 16.8474C9.16023 16.644 9.16023 16.3155 8.95682 16.1121L1.56121 8.71644C1.44647 8.6017 1.44647 8.41394 1.56121 8.29399V8.28356Z" fill="#232323"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 527 B |
3
src/assets/icons/brand/call.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.29289 7.20711L8.09256 8.40744C7.74975 8.75025 7.7025 9.29107 7.98245 9.68688C10.3435 13.025 11.3355 14.027 14.32 16.045C14.7142 16.3115 15.2419 16.2581 15.5783 15.9217L17.2929 14.2071C17.6834 13.8166 18.3166 13.8166 18.7071 14.2071L20.5144 16.0144C20.8069 16.3069 20.8892 16.7492 20.686 17.1096C20.2788 17.8317 19.5007 18.9997 18.5 19.5C17.2062 20.1469 15.9123 20.0498 15.5811 20.0109C15.5264 20.0045 15.4735 20.0001 15.4185 19.9972C14.8712 19.9681 11.6429 19.643 8 16C4 12 4 8.5 4 8.5C4 8.5 4 7.5 4.5 6C4.81932 5.04205 5.75042 4.28802 6.38166 3.86816C6.74005 3.62978 7.20529 3.70529 7.50965 4.00965L9.29289 5.79289C9.68342 6.18342 9.68342 6.81658 9.29289 7.20711Z" stroke="#232323" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 823 B |
3
src/assets/icons/brand/email.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M19.1571 5H4.84294C3.82753 5 3 5.82753 3 6.84294V17.5785C3 18.5939 3.82753 19.4215 4.84294 19.4215H19.1571C20.1725 19.4215 21 18.5939 21 17.5785V6.84742C21 5.83201 20.1725 5.00447 19.1571 5.00447V5ZM4.00199 7.6168L11.6421 15.257C11.83 15.4493 12.1655 15.4448 12.3534 15.257L19.9935 7.6168V17.583C19.9935 18.0437 19.6178 18.424 19.1526 18.424H4.84294C4.38221 18.424 4.00199 18.0482 4.00199 17.583V7.6168ZM19.5507 6.63718L12.2773 13.9105C12.1297 14.0582 11.8703 14.0582 11.7227 13.9105L4.4493 6.63718C4.36431 6.55219 4.32406 6.41799 4.35089 6.29722C4.37326 6.18539 4.4493 6.1004 4.54771 6.06014C4.64165 6.02435 4.74006 6.00646 4.83847 6.00646H19.1526C19.251 6.00646 19.3494 6.02435 19.4433 6.06014C19.5462 6.1004 19.6178 6.18539 19.6446 6.29722C19.6715 6.41799 19.6312 6.54771 19.5462 6.63718H19.5507Z" fill="#232323"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 930 B |
4
src/assets/icons/brand/link.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.93 17.1099L10.07 18.9699C9.4 19.6399 8.5 20.0199 7.55 20.0199C6.6 20.0199 5.7 19.6499 5.03 18.9699C4.36 18.2999 3.98 17.3999 3.98 16.4499C3.98 15.4999 4.35 14.5999 5.03 13.9299L8.74 10.2199C9.41 9.54994 10.31 9.16994 11.26 9.16994C12.21 9.16994 13.11 9.53994 13.78 10.2199C14.21 10.6499 14.53 11.1899 14.69 11.7699C14.73 11.8999 14.81 11.9999 14.92 12.0699C15.03 12.1399 15.17 12.1499 15.29 12.1099C15.55 12.0399 15.71 11.7599 15.63 11.4999C15.42 10.7499 15.02 10.0699 14.47 9.51994C13.61 8.65994 12.47 8.18994 11.25 8.18994C10.03 8.18994 8.89 8.65994 8.03 9.51994L4.32 13.2299C3.47 14.0799 3 15.2299 3 16.4499C3 17.6699 3.47 18.8099 4.33 19.6699C5.19 20.5299 6.33 20.9999 7.55 20.9999C8.77 20.9999 9.91 20.5299 10.77 19.6699L12.63 17.8099C12.72 17.7199 12.77 17.5899 12.77 17.4599C12.77 17.3299 12.72 17.1999 12.63 17.1099C12.44 16.9199 12.12 16.9199 11.93 17.1099Z" fill="#232323"/>
|
||||
<path d="M19.6687 4.33C18.7987 3.47 17.6587 3 16.4487 3C15.2387 3 14.0887 3.47 13.2287 4.33L11.3787 6.18C11.2887 6.27 11.2387 6.4 11.2387 6.53C11.2387 6.66 11.2887 6.79 11.3787 6.88C11.5687 7.07 11.8787 7.07 12.0787 6.88L13.9387 5.03C14.6087 4.36 15.5087 3.98 16.4587 3.98C17.4087 3.98 18.3087 4.35 18.9787 5.03C19.6487 5.7 20.0287 6.6 20.0287 7.55C20.0287 8.5 19.6587 9.4 18.9787 10.07L15.2687 13.78C14.5987 14.45 13.6987 14.83 12.7487 14.83C11.7987 14.83 10.8987 14.46 10.2287 13.78C9.79874 13.35 9.47874 12.81 9.31874 12.23C9.27874 12.1 9.19874 12 9.07874 11.93C8.96874 11.87 8.82874 11.85 8.70874 11.89C8.44874 11.96 8.29874 12.24 8.36874 12.5C8.57874 13.25 8.97874 13.93 9.52874 14.48C10.3887 15.34 11.5287 15.81 12.7487 15.81C13.9687 15.81 15.1087 15.34 15.9687 14.48L19.6787 10.77C20.5387 9.91 21.0087 8.77 21.0087 7.55C21.0087 6.33 20.5387 5.19 19.6787 4.33H19.6687Z" fill="#232323"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
3
src/assets/icons/close.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1.28289 16.25L0 14.9671L6.8421 8.125L0 1.28289L1.28289 0L8.125 6.8421L14.9671 0L16.25 1.28289L9.40789 8.125L16.25 14.9671L14.9671 16.25L8.125 9.40789L1.28289 16.25Z" fill="#232323"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 296 B |
3
src/assets/icons/collectionStory/back.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="13" height="22" viewBox="0 0 13 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.6387 20.0371C11.9849 20.3836 11.985 20.9436 11.6387 21.29C11.2922 21.6365 10.7313 21.6365 10.3848 21.29L0.430664 11.335L0.426758 11.3311C0.196025 11.0898 0.19053 10.705 0.430664 10.4648L10.3848 0.509766C10.7313 0.163262 11.2922 0.163262 11.6387 0.509766C11.9852 0.856269 11.9852 1.41717 11.6387 1.76367L2.66309 10.7383V10.748L2.59375 10.8203C2.5437 10.8726 2.5492 10.9476 2.58984 10.9883L11.6387 20.0371Z" fill="white" stroke="white" stroke-width="0.5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 571 B |
3
src/assets/icons/collectionStory/scrollingLearnMore.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M23.5416 31.9844L7.88035 16.3231C7.44961 15.8923 6.7538 15.8923 6.32306 16.3231C5.89231 16.7538 5.89231 17.4496 6.32306 17.8803L23.5527 35.11C23.7957 35.353 24.1933 35.353 24.4473 35.11L41.6769 17.8803C42.1077 17.4496 42.1077 16.7538 41.6769 16.3231C41.2462 15.8923 40.5504 15.8923 40.1196 16.3231L24.4584 31.9844C24.2154 32.2273 23.8178 32.2273 23.5637 31.9844H23.5416Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 499 B |
2
src/assets/icons/collectionStory/swiperRight.svg
Normal file
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" id="Bold" viewBox="0 0 24 24" width="512" height="512"><path d="M6.079,22.5a1.5,1.5,0,0,1,.44-1.06l7.672-7.672a2.5,2.5,0,0,0,0-3.536L6.529,2.565A1.5,1.5,0,0,1,8.65.444l7.662,7.661a5.506,5.506,0,0,1,0,7.779L8.64,23.556A1.5,1.5,0,0,1,6.079,22.5Z"/></svg>
|
||||
|
After Width: | Height: | Size: 332 B |
4
src/assets/icons/logout.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.6233 6.32175L15.7098 9.40833C15.7338 9.43232 15.7418 9.4683 15.7298 9.50029C15.7178 9.53227 15.6858 9.55226 15.6539 9.55226L8.14532 9.55226C7.89743 9.55226 7.69752 9.75217 7.69752 10.0001C7.69752 10.2479 7.89743 10.4478 8.14532 10.4478L15.6539 10.4478C15.6898 10.4478 15.7178 10.4678 15.7298 10.4998C15.7418 10.5318 15.7378 10.5678 15.7098 10.5918L12.6233 13.6784C12.4553 13.8503 12.4553 14.1461 12.6233 14.3141C12.7072 14.398 12.8232 14.446 12.9431 14.446C13.063 14.446 13.175 14.398 13.263 14.3141L17.449 10.128C17.517 10.06 17.517 9.93609 17.449 9.86812L13.263 5.68205C13.179 5.59809 13.063 5.55011 12.9431 5.55011C12.8232 5.55011 12.7112 5.59809 12.6233 5.68205C12.4553 5.84997 12.4553 6.14983 12.6233 6.31776L12.6233 6.32175Z" fill="#232323"/>
|
||||
<path d="M7.39453 17.1967C7.39453 16.9488 7.19462 16.7489 6.94674 16.7489L3.74821 16.7489C3.5563 16.7489 3.39638 16.593 3.39638 16.397L3.39638 3.60295C3.39638 3.41104 3.5523 3.25112 3.74821 3.25112L6.94674 3.25112C7.19462 3.25112 7.39453 3.05121 7.39453 2.80332C7.39453 2.55544 7.19462 2.35553 6.94674 2.35553L3.74821 2.35553C3.06053 2.35553 2.50079 2.91527 2.50079 3.60295L2.50079 16.397C2.50079 17.0847 3.06053 17.6445 3.74821 17.6445L6.94674 17.6445C7.19462 17.6445 7.39453 17.4446 7.39453 17.1967Z" fill="#232323"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
3
src/assets/icons/my_wardrobe.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="19" height="12" viewBox="0 0 19 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.88916 2.181C7.79027 1.48222 8.35808 0.400024 9.37247 0.400024C11.245 0.400024 11.5927 2.86742 9.86691 3.49818C9.51283 3.62804 9.26402 3.93724 9.26402 4.30209V4.96687L0.593832 9.8089C0.246132 10.0099 0.392868 10.8664 0.794797 10.8664H17.6407C18.0395 10.8664 18.1894 10.0192 17.8513 9.81199L14.017 7.65843L12.0998 6.58165" stroke="#585858" stroke-width="0.8" stroke-miterlimit="10" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 520 B |
9
src/assets/icons/notifications.svg
Normal file
|
After Width: | Height: | Size: 11 KiB |
9
src/assets/icons/settings.svg
Normal file
@@ -0,0 +1,9 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13.0759 3.39874H16.3041C16.5037 3.39874 16.6673 3.56227 16.6673 3.76194V17.9701C16.6673 18.1698 16.5037 18.3333 16.3041 18.3333H4.30851C4.10884 18.3333 3.94531 18.1698 3.94531 17.9701V3.76285C3.94531 3.56318 4.10884 3.39965 4.30851 3.39965H7.39753" stroke="#585858" stroke-width="0.666667" stroke-miterlimit="10"/>
|
||||
<path d="M14.0938 5.15686H15.1337C15.2069 5.15686 15.2675 5.21739 15.2675 5.29058V16.3736C15.2675 16.4468 15.2069 16.5073 15.1337 16.5073H5.28215C5.20897 16.5073 5.14844 16.4468 5.14844 16.3736V5.29058C5.14844 5.21739 5.20897 5.15686 5.28215 5.15686H6.29948" stroke="#585858" stroke-width="0.666667" stroke-miterlimit="10"/>
|
||||
<path d="M9.59802 2.08328L6.48732 3.94084C6.42047 3.9806 6.37891 4.05378 6.37891 4.13148V5.33582C6.37891 5.4587 6.47829 5.55808 6.60116 5.55808H13.867C13.9899 5.55808 14.0892 5.4587 14.0892 5.33582V4.13148C14.0892 4.05287 14.0486 3.9806 13.9808 3.94084L10.8701 2.08328C10.478 1.84928 9.98923 1.84928 9.59712 2.08328H9.59802Z" stroke="#585858" stroke-width="0.666667" stroke-miterlimit="10"/>
|
||||
<path d="M6.89062 7.74353H13.3032" stroke="#585858" stroke-width="0.666667" stroke-miterlimit="10" stroke-linecap="round"/>
|
||||
<path d="M6.89062 9.59393H13.3032" stroke="#585858" stroke-width="0.666667" stroke-miterlimit="10" stroke-linecap="round"/>
|
||||
<path d="M6.89062 11.4443H13.3032" stroke="#585858" stroke-width="0.666667" stroke-miterlimit="10" stroke-linecap="round"/>
|
||||
<path d="M6.89062 13.385H13.3032" stroke="#585858" stroke-width="0.666667" stroke-miterlimit="10" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
4
src/assets/icons/share.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 16.1998L15.85 8.34981" stroke="#585858" stroke-width="1.375" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M8.70312 7.99995H16.3031L16.2031 15.55" stroke="#585858" stroke-width="1.375" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 361 B |
5
src/assets/icons/warning.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.41667 5.83333C6.41667 5.51117 6.1555 5.25 5.83333 5.25C5.51117 5.25 5.25 5.51117 5.25 5.83333V8.16667C5.25 8.48883 5.51117 8.75 5.83333 8.75C6.1555 8.75 6.41667 8.48883 6.41667 8.16667V5.83333Z" fill="#9F9F9F"/>
|
||||
<path d="M5.83333 0C2.61167 0 0 2.61167 0 5.83333C0 9.05499 2.61167 11.6667 5.83333 11.6667C9.05499 11.6667 11.6667 9.05499 11.6667 5.83333C11.6667 2.61167 9.05499 0 5.83333 0ZM1.16667 5.83333C1.16667 3.256 3.256 1.16667 5.83333 1.16667C8.41066 1.16667 10.5 3.256 10.5 5.83333C10.5 8.41066 8.41066 10.5 5.83333 10.5C3.256 10.5 1.16667 8.41066 1.16667 5.83333Z" fill="#9F9F9F"/>
|
||||
<path d="M5.83333 4.25836C6.20382 4.25836 6.50417 3.95802 6.50417 3.58753C6.50417 3.21704 6.20382 2.91669 5.83333 2.91669C5.46284 2.91669 5.1625 3.21704 5.1625 3.58753C5.1625 3.95802 5.46284 4.25836 5.83333 4.25836Z" fill="#9F9F9F"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 939 B |
BIN
src/assets/images/brand/brandBg.png
Normal file
|
After Width: | Height: | Size: 713 KiB |
BIN
src/assets/images/collectionStory/Rectangle.png
Normal file
|
After Width: | Height: | Size: 71 KiB |
BIN
src/assets/images/collectionStory/collection_story_banner.png
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
BIN
src/assets/images/collectionStory/coreconcept_1.png
Normal file
|
After Width: | Height: | Size: 532 KiB |
BIN
src/assets/images/collectionStory/coreconcept_2.png
Normal file
|
After Width: | Height: | Size: 384 KiB |
BIN
src/assets/images/collectionStory/coreconcept_3.png
Normal file
|
After Width: | Height: | Size: 220 KiB |
BIN
src/assets/images/collectionStory/coreconcept_4.png
Normal file
|
After Width: | Height: | Size: 449 KiB |
BIN
src/assets/images/collectionStory/coreconcept_5.png
Normal file
|
After Width: | Height: | Size: 1.0 MiB |
BIN
src/assets/images/collectionStory/feeling_1.png
Normal file
|
After Width: | Height: | Size: 220 KiB |
BIN
src/assets/images/collectionStory/feeling_2.png
Normal file
|
After Width: | Height: | Size: 52 KiB |
BIN
src/assets/images/collectionStory/inspiration_1.png
Normal file
|
After Width: | Height: | Size: 451 KiB |
BIN
src/assets/images/collectionStory/inspiration_2.png
Normal file
|
After Width: | Height: | Size: 401 KiB |
|
Before Width: | Height: | Size: 851 KiB After Width: | Height: | Size: 851 KiB |
BIN
src/assets/images/home/design-bg.jpg
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
BIN
src/assets/images/home/designers-left.jpg
Normal file
|
After Width: | Height: | Size: 569 KiB |
BIN
src/assets/images/home/designers-right.jpg
Normal file
|
After Width: | Height: | Size: 151 KiB |
BIN
src/assets/images/home/digital-items-1.jpg
Normal file
|
After Width: | Height: | Size: 376 KiB |
BIN
src/assets/images/home/digital-items-2.jpg
Normal file
|
After Width: | Height: | Size: 382 KiB |
BIN
src/assets/images/icons/douyin.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
src/assets/images/icons/facebook.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
src/assets/images/icons/instagram.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
src/assets/images/icons/linkedin.png
Normal file
|
After Width: | Height: | Size: 755 B |
BIN
src/assets/images/icons/wechat.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
src/assets/images/icons/xiaohongshu.png
Normal file
|
After Width: | Height: | Size: 1011 B |
BIN
src/assets/images/login/bg.jpg
Normal file
|
After Width: | Height: | Size: 1.0 MiB |
BIN
src/assets/images/login/google.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
src/assets/images/login/wechat.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
src/assets/images/logos/aida-black.png
Normal file
|
After Width: | Height: | Size: 649 B |
BIN
src/assets/images/logos/aida.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
src/assets/images/logos/code-create-black.png
Normal file
|
After Width: | Height: | Size: 8.9 KiB |
BIN
src/assets/images/logos/stylish-arade-black.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
src/assets/images/profile-content-bg.jpg
Normal file
|
After Width: | Height: | Size: 32 KiB |
82
src/components/CommodityItem.vue
Normal file
@@ -0,0 +1,82 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
|
||||
const props = defineProps({
|
||||
url: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: 'aaa'
|
||||
},
|
||||
price: {
|
||||
type: String,
|
||||
default: '111'
|
||||
}
|
||||
})
|
||||
const emit = defineEmits([
|
||||
'addShopping'
|
||||
])
|
||||
let data = reactive({
|
||||
})
|
||||
const addShopping = () => {
|
||||
emit('addShopping')
|
||||
}
|
||||
|
||||
onMounted(()=>{
|
||||
})
|
||||
onUnmounted(()=>{
|
||||
})
|
||||
defineExpose({})
|
||||
const {} = toRefs(data);
|
||||
</script>
|
||||
<template>
|
||||
<div class="commodity-item">
|
||||
<img :src="props.url" alt="">
|
||||
<div class="detail">
|
||||
<div calss="text">
|
||||
<div class="name">
|
||||
{{ props.name }}
|
||||
</div>
|
||||
<div class="price">
|
||||
{{ props.price }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn" @click="addShopping">
|
||||
<div class="text">
|
||||
<SvgIcon name="add" size="24"></SvgIcon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="less" scoped>
|
||||
.commodity-item{
|
||||
width: var(--commodity-width,100%);
|
||||
> img{
|
||||
width: 100%;
|
||||
height: var(--commodity-height,auto);
|
||||
margin-bottom: var(--commodity-marginBottom,1rem);
|
||||
}
|
||||
> .detail{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
.text{
|
||||
color: #232323;
|
||||
> .name{
|
||||
font-family: "KaiseiOpti-Regular";
|
||||
font-weight: 400;
|
||||
font-size: var(--commodity-name-fontSize,1.6rem);
|
||||
line-height: var(--commodity-name-lineHeight,2.3rem);
|
||||
}
|
||||
> .price{
|
||||
font-family: "KaiseiOpti-Regular";
|
||||
font-weight: 400;
|
||||
font-size: var(--commodity-price-fontSize,1.4rem);
|
||||
line-height: var(--commodity-price-lineHeight,2.3rem);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
62
src/components/Footer.vue
Normal file
@@ -0,0 +1,62 @@
|
||||
<template>
|
||||
<section class="section-footer">
|
||||
<div class="footer">
|
||||
<div class="left">
|
||||
<div>About</div>
|
||||
<div>Privacy Policy</div>
|
||||
<div>Terms of Use</div>
|
||||
<div>Disclaimer</div>
|
||||
<div>Site Map</div>
|
||||
</div>
|
||||
<div class="right">
|
||||
<img src="@/assets/images/icons/xiaohongshu.png" />
|
||||
<img src="@/assets/images/icons/linkedin.png" />
|
||||
<img src="@/assets/images/icons/instagram.png" />
|
||||
<img src="@/assets/images/icons/facebook.png" />
|
||||
<img src="@/assets/images/icons/douyin.png" />
|
||||
<img src="@/assets/images/icons/wechat.png" />
|
||||
<div class="text">© Code-Create 2026</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.section-footer {
|
||||
background-color: #f6f6f6;
|
||||
border-top: 0.1rem solid #232323;
|
||||
> .footer {
|
||||
padding: 0 9rem;
|
||||
height: var(--footer-height);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
> .left {
|
||||
font-family: KaiseiOpti-Regular;
|
||||
font-size: 1.4rem;
|
||||
color: #585858;
|
||||
display: flex;
|
||||
gap: 4.5rem;
|
||||
}
|
||||
> .right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
> img {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
margin-left: 2rem;
|
||||
}
|
||||
.text{
|
||||
margin-left: 4rem;
|
||||
font-family: KaiseiOpti-Regular;
|
||||
font-size: 1.2rem;
|
||||
color: #585858;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
109
src/components/input-code.vue
Normal file
@@ -0,0 +1,109 @@
|
||||
<template>
|
||||
<div class="input-code">
|
||||
<input
|
||||
ref="inputRef"
|
||||
type="tel"
|
||||
maxlength="1"
|
||||
v-for="(v, i) in props.length"
|
||||
:key="i"
|
||||
v-model="code[i]"
|
||||
@input="handleInput(i)"
|
||||
@keydown.delete="handleDelete(i)"
|
||||
@paste="handlePaste"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref, computed, watch, nextTick } from 'vue'
|
||||
const emit = defineEmits(['submit', 'update:modelValue'])
|
||||
const props = defineProps({
|
||||
length: {
|
||||
type: Number,
|
||||
default: 6
|
||||
}
|
||||
})
|
||||
const inputRef = ref('')
|
||||
const code = ref([])
|
||||
const codeStr = computed(() => code.value.join(''))
|
||||
watch(codeStr, (newVal) => {
|
||||
emit('update:modelValue', newVal)
|
||||
})
|
||||
const resetCode = (size = props.length) => {
|
||||
code.value = []
|
||||
for (let i = 0; i < size; i++) {
|
||||
code.value.push('')
|
||||
}
|
||||
}
|
||||
resetCode(props.length)
|
||||
const handleInput = (index: number) => {
|
||||
const value = code.value[index]
|
||||
if (value) {
|
||||
if (/[0-9]/.test(value)) {
|
||||
code.value[index] = value
|
||||
focusLast()
|
||||
} else {
|
||||
code.value[index] = ''
|
||||
}
|
||||
}
|
||||
submit()
|
||||
}
|
||||
const handleDelete = (index: number) => {
|
||||
if (code.value[index].length == 0) {
|
||||
focusLast(-1)
|
||||
}
|
||||
}
|
||||
const handlePaste = (e: ClipboardEvent) => {
|
||||
const text = e.clipboardData?.getData('text')
|
||||
if (text) {
|
||||
const nums = text.match(/[0-9]/g) || []
|
||||
if (nums.length === code.value.length) {
|
||||
code.value = [...nums]
|
||||
focusLast()
|
||||
nextTick(submit)
|
||||
}
|
||||
}
|
||||
}
|
||||
// 聚焦最后一个没有输入的
|
||||
const focusLast = (step = 0) => {
|
||||
let index = code.value.findIndex((item) => !item) + step
|
||||
index < 0 && (index = 0)
|
||||
if (index >= 0 && index < props.length) {
|
||||
inputRef.value[index]?.focus?.()
|
||||
}
|
||||
if (code.value.every((item) => item.length)) {
|
||||
inputRef.value?.forEach((item) => item.blur?.())
|
||||
}
|
||||
}
|
||||
const submit = () => {
|
||||
if (codeStr.value.length === props.length) {
|
||||
emit('submit', codeStr.value)
|
||||
}
|
||||
}
|
||||
onMounted(() => {
|
||||
focusLast()
|
||||
})
|
||||
defineExpose({
|
||||
resetCode
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.input-code {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: var(--input-code-justify-content, space-between);
|
||||
gap: var(--input-code-input-gap, 0);
|
||||
> input {
|
||||
width: var(--input-code-input-width, 4.8rem);
|
||||
height: var(--input-code-input-height, 4.8rem);
|
||||
border-radius: var(--input-code-input-radius, 0);
|
||||
border-width: var(--input-code-input-border-width, 0.15rem);
|
||||
border-color: var(--input-code-input-border-color, #c4c4c4);
|
||||
border-style: var(--input-code-input-border-style, solid);
|
||||
background-color: var(--input-code-input-bg-color, #fff);
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -2,5 +2,40 @@ export default {
|
||||
Login: {
|
||||
login: 'Log in',
|
||||
register: 'Register',
|
||||
}
|
||||
loginTo: 'Log on to <span>FiDA</span>',
|
||||
loginTitle: 'A multi-agent canvas for rapid, trend driven design iteration.',
|
||||
name: 'Name',
|
||||
email: 'Email',
|
||||
password: 'Password',
|
||||
enterName: 'Enter your name',
|
||||
enterEmail: 'Enter your email',
|
||||
enterPassword: 'Enter your password',
|
||||
forgotPassword: 'Forget password?',
|
||||
pleaseInputName: 'Please input the name',
|
||||
nameLengthError: 'Name length must be between {min} and {max} characters',
|
||||
passwordSpecial: 'Must contain special characters',
|
||||
passwordCase: 'Mix of uppercase, lowercase and numbers',
|
||||
pleaseInputEmail: 'Please input the email',
|
||||
emailFormatError: 'Please input the email again',
|
||||
pleaseInputPassword: 'Please input the password',
|
||||
passwordLengthError: 'Password length must be between {min} and {max} characters',
|
||||
pleaseTermsPolicy: 'Please agree to the Terms, Policy and Fees',
|
||||
agreeTermsPolicy:
|
||||
'I agree to the <span onclick="onClickPrivacy()">Terms, Policy</span> and Fees.',
|
||||
noAccountToSignUp: `Don't have an account? <span onclick="onClickRegister()">Sign up</span>`,
|
||||
signUpFor: 'Sign up for <span>FiDA</span>',
|
||||
registerTip: 'A multi-agent canvas for rapid, trend driven design iteration.',
|
||||
havenAccountToLogin: `Already have an account? <span onclick="onClickLogin()">Log in</span>`,
|
||||
verifyEmail: 'Verify your email address',
|
||||
verifyCodeHasSent: 'A verification code has been sent to<br><span>{email}</span>',
|
||||
verify: 'Verify',
|
||||
resendCode: 'Resend Code',
|
||||
resendCodeIn: 'Resend Code in {time}',
|
||||
orContinueWith: 'or continue with',
|
||||
googleLogin: 'Sign in with Google',
|
||||
wechatLogin: 'Sign in with Wechat',
|
||||
indexTip: 'A multi-agent canvas for rapid, trend driven design iteration.',
|
||||
sendCodeError: 'Send code error',
|
||||
retrievePassword: 'Retrieve password'
|
||||
},
|
||||
}
|
||||
|
||||
@@ -2,5 +2,41 @@ export default {
|
||||
Login: {
|
||||
login: '登录',
|
||||
register: '注册',
|
||||
}
|
||||
signUp: '注册',
|
||||
loginTo: '登录到 <span>FiDA</span',
|
||||
loginTitle: '一个多智能体画布,用于快速、趋势驱动的设计迭代。',
|
||||
name: '姓名',
|
||||
email: '邮箱',
|
||||
password: '密码',
|
||||
enterName: '请输入姓名',
|
||||
enterEmail: '请输入邮箱',
|
||||
enterPassword: '请输入密码',
|
||||
forgotPassword: '忘记密码?',
|
||||
pleaseInputName: '请输入姓名',
|
||||
nameLengthError: '姓名长度必须在 {min} 到 {max} 个字符之间',
|
||||
passwordSpecial: '必须包含特殊符号',
|
||||
passwordCase: '大小写字母与数字混合组合',
|
||||
pleaseInputEmail: '请输入邮箱',
|
||||
emailFormatError: '请输入正确的邮箱',
|
||||
pleaseInputPassword: '请输入密码',
|
||||
passwordLengthError: '密码长度必须在 {min} 到 {max} 个字符之间',
|
||||
pleaseTermsPolicy: '请同意条款、政策和费用',
|
||||
agreeTermsPolicy: '我同意 <span onclick="onClickPrivacy()">条款、政策</span> 和费用。',
|
||||
noAccountToSignUp: `还没有账号? <span onclick="onClickRegister()">注册</span>`,
|
||||
signUpFor: '注册账号',
|
||||
registerTip: '一个多智能体画布,用于快速、趋势驱动的设计迭代。',
|
||||
havenAccountToLogin: `已经有账号? <span onclick="onClickLogin()">登录</span>`,
|
||||
verifyEmail: '验证您的邮箱地址',
|
||||
verifyCodeHasSent: '已发送验证码到<br><span>{email}</span>',
|
||||
verifyCode: '请输入验证码',
|
||||
verify: '验证',
|
||||
resendCode: '重新发送验证码',
|
||||
resendCodeIn: '重新发送验证码倒计时 {time}',
|
||||
orContinueWith: '或者使用',
|
||||
googleLogin: '使用 Google 登录',
|
||||
wechatLogin: '使用微信登录',
|
||||
indexTip: '一个多智能体画布,用于快速、趋势驱动的设计迭代。',
|
||||
sendCodeError: '发送验证码失败',
|
||||
retrievePassword: '找回密码'
|
||||
},
|
||||
}
|
||||
|
||||
@@ -41,6 +41,30 @@ const router = createRouter({
|
||||
component: () => import('../views/404.vue')
|
||||
}
|
||||
]
|
||||
history: createWebHistory('/'),
|
||||
// history: createWebHistory(import.meta.env.VITE_APP_URL),
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
name: 'home',
|
||||
component: () => import('../views/home/index.vue'),
|
||||
},
|
||||
{
|
||||
path: '/collectionStory',
|
||||
name: 'collectionStory',
|
||||
component: () => import('../views/collectionStory/index.vue'),
|
||||
},
|
||||
{
|
||||
path: '/brand',
|
||||
name: 'brand',
|
||||
component: () => import('../views/brand/index.vue'),
|
||||
},
|
||||
{
|
||||
path: '/:pathMatch(.*)',
|
||||
name: '404',
|
||||
component: () => import('../views/404.vue'),
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
|
||||
@@ -183,4 +183,15 @@ export async function shareImageToWhatsapp (url: string){
|
||||
const whatsappLink = `https://api.whatsapp.com/send/?text=${encodeURIComponent(message)}`
|
||||
window.open(whatsappLink, '_blank')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 倒计时
|
||||
* @param time 倒计时时间,单位秒
|
||||
* @returns 倒计时字符串,格式为 mm:ss
|
||||
*/
|
||||
export function CountDown(time: number) {
|
||||
const mm = String(Math.floor(time / 60)).padStart(2, '0');
|
||||
const ss = String(time % 60).padStart(2, '0');
|
||||
return `${mm}:${ss}`;
|
||||
}
|
||||
|
||||
169
src/views/brand/commodity-list.vue
Normal file
@@ -0,0 +1,169 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
|
||||
import img from "@/assets/images/collectionStory/Rectangle.png";
|
||||
//const props = defineProps({
|
||||
//})
|
||||
const emit = defineEmits([
|
||||
'addShopping'
|
||||
])
|
||||
let data = reactive({
|
||||
})
|
||||
const list = ref([
|
||||
{
|
||||
url: img,
|
||||
title: "Windswept Burden",
|
||||
price: "$100.00",
|
||||
},{
|
||||
url: img,
|
||||
title: "Windswept Burden",
|
||||
price: "$100.00",
|
||||
},{
|
||||
url: img,
|
||||
title: "Windswept Burden",
|
||||
price: "$100.00",
|
||||
},{
|
||||
url: img,
|
||||
title: "Windswept Burden",
|
||||
price: "$100.00",
|
||||
},{
|
||||
url: img,
|
||||
title: "Windswept Burden",
|
||||
price: "$100.00",
|
||||
},{
|
||||
url: img,
|
||||
title: "Windswept Burden",
|
||||
price: "$100.00",
|
||||
},{
|
||||
url: img,
|
||||
title: "Windswept Burden",
|
||||
price: "$100.00",
|
||||
},{
|
||||
url: img,
|
||||
title: "Windswept Burden",
|
||||
price: "$100.00",
|
||||
},
|
||||
])
|
||||
const type = ref('All')
|
||||
const addShopping = (item) => {
|
||||
emit('addShopping', item)
|
||||
}
|
||||
onMounted(()=>{
|
||||
})
|
||||
onUnmounted(()=>{
|
||||
})
|
||||
defineExpose({})
|
||||
const {} = toRefs(data);
|
||||
</script>
|
||||
<template>
|
||||
<div class="commodityList">
|
||||
<div class="header">
|
||||
<div class="title">
|
||||
Items
|
||||
</div>
|
||||
<div class="menu">
|
||||
<div :class="{'active': type === 'All'}" @click="type = 'All'">All</div>
|
||||
<div :class="{'active': type === 'Male'}" @click="type = 'Male'">Male</div>
|
||||
<div :class="{'active': type === 'Female'}" @click="type = 'Female'">Female</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list">
|
||||
<div class="item" v-for="item in list" :key="item.url">
|
||||
<CommodityItem :url="item.url" :name="item.title" :price="item.price" @addShopping="addShopping(item)"></CommodityItem>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="less" scoped>
|
||||
.commodityList{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.header{
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 2;
|
||||
background-color: #fff;
|
||||
.title{
|
||||
font-family: "KaiseiOpti-Bold";
|
||||
font-weight: 700;
|
||||
font-size: 3.6rem;
|
||||
line-height: 6rem;
|
||||
color: #121212;
|
||||
padding: 4rem 0 3.6rem 1.2rem;
|
||||
}
|
||||
.menu{
|
||||
padding: 0 1.2rem;
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
margin-bottom: 6rem;
|
||||
> div{
|
||||
min-width: 6rem;
|
||||
text-align: center;
|
||||
font-family: "KaiseiOpti-Bold";
|
||||
font-weight: 700;
|
||||
font-size: 1.98rem;
|
||||
line-height: 100%;
|
||||
position: relative;
|
||||
color: #7B7B7B;
|
||||
cursor: pointer;
|
||||
&::after{
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -5px;
|
||||
left: 0;
|
||||
display: block;
|
||||
width: 100%;
|
||||
border-bottom: 1px solid #232323;
|
||||
display: none;
|
||||
}
|
||||
&.active{
|
||||
color: #232323;
|
||||
&::after{
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.list{
|
||||
border-top: 0.5px solid #585858;
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
display: grid;
|
||||
align-content: start;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
overflow-y: auto;
|
||||
/* 垂直线(右边框) */
|
||||
.item{
|
||||
position: relative;
|
||||
padding: 1.2rem;
|
||||
}
|
||||
.item::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
border-right: 0.5px solid #585858;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* 水平线(下边框) */
|
||||
.item::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
border-bottom: 0.5px solid #585858;
|
||||
z-index: 1;
|
||||
}
|
||||
/* 移除最后一列的右边框 */
|
||||
.item:nth-child(3n)::before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
72
src/views/brand/index.vue
Normal file
@@ -0,0 +1,72 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
|
||||
import CommodityList from "./commodity-list.vue";
|
||||
import MerchantInfo from "./merchant-info.vue";
|
||||
//const props = defineProps({
|
||||
//})
|
||||
//const emit = defineEmits([
|
||||
//])
|
||||
let data = reactive({
|
||||
})
|
||||
const addShopping = (item) => {}
|
||||
onMounted(()=>{
|
||||
})
|
||||
onUnmounted(()=>{
|
||||
})
|
||||
defineExpose({})
|
||||
const {} = toRefs(data);
|
||||
</script>
|
||||
<template>
|
||||
<div class="brand">
|
||||
<div class="header-img">
|
||||
<img src="@/assets/images/brand/brandBg.png" alt="">
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="merchant-info">
|
||||
<MerchantInfo></MerchantInfo>
|
||||
</div>
|
||||
<div class="commodity-list">
|
||||
<CommodityList @addShopping="addShopping"></CommodityList>
|
||||
</div>
|
||||
</div>
|
||||
<Footer></Footer>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="less" scoped>
|
||||
.brand{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
overflow-y: auto;
|
||||
.header-img{
|
||||
width: 100%;
|
||||
>img{
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
.content{
|
||||
display: flex;
|
||||
height: auto;
|
||||
align-items: flex-start;
|
||||
.merchant-info{
|
||||
width: 40rem;
|
||||
padding-left: 12.7rem;
|
||||
padding-right: 2.7rem;
|
||||
height: var(--app-view-height);
|
||||
overflow-y: auto;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
&::-webkit-scrollbar{
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
}
|
||||
.commodity-list{
|
||||
flex: 1;
|
||||
border-left: 0.5px solid #585858;
|
||||
border-right: 0.5px solid #585858;
|
||||
margin-right: 9rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
129
src/views/brand/merchant-info.vue
Normal file
@@ -0,0 +1,129 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
|
||||
//const props = defineProps({
|
||||
//})
|
||||
//const emit = defineEmits([
|
||||
//])
|
||||
let data = reactive({
|
||||
})
|
||||
onMounted(()=>{
|
||||
})
|
||||
onUnmounted(()=>{
|
||||
})
|
||||
defineExpose({})
|
||||
const {} = toRefs(data);
|
||||
</script>
|
||||
<template>
|
||||
<div class="merchantInfo">
|
||||
<div class="profile">
|
||||
<img src="@/assets/images/collectionStory/Rectangle.png" alt="">
|
||||
</div>
|
||||
<div class="info">
|
||||
<div class="detail">
|
||||
<div class="name">Lian Su</div>
|
||||
<div class="title">Roaming Clouds</div>
|
||||
</div>
|
||||
<div class="contact">
|
||||
<div class="title">Contact</div>
|
||||
<div class="email label">
|
||||
<div class="icon">
|
||||
<svg-icon name="brand-email" size="24" />
|
||||
</div>
|
||||
<div>lian.su@urieworweoo.com</div>
|
||||
</div>
|
||||
<div class="phone label">
|
||||
<div class="icon">
|
||||
<svg-icon name="brand-call" size="24" />
|
||||
</div>
|
||||
<div>+86 139 4829 7710</div>
|
||||
</div>
|
||||
<div class="address label">
|
||||
<div class="icon">
|
||||
<svg-icon name="brand-link" size="24" />
|
||||
</div>
|
||||
<div>746312432</div>
|
||||
</div>
|
||||
<div class="website label">
|
||||
<div class="icon">
|
||||
<svg-icon name="brand-link" size="24" />
|
||||
</div>
|
||||
<div>https://urieworweoo.com</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="about">
|
||||
<div class="title">About</div>
|
||||
<div class="content">Lian Su’s work weaves understated ethnic influences into contemporary minimalism. She explores materials and silhouettes that bridge heritage and modern sensibilities. Her designs reflect a quiet dialogue between cultural memory and forward-looking innovation.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="less" scoped>
|
||||
.merchantInfo{
|
||||
width: 100%;
|
||||
height: auto;
|
||||
position: relative;
|
||||
padding-top: 4rem;
|
||||
padding-bottom: 4rem;
|
||||
.profile{
|
||||
width: 20rem;
|
||||
height: 20rem;
|
||||
margin-left: 1.8rem;
|
||||
>img{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
.info{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6rem;
|
||||
margin-top: 4rem;
|
||||
.title{
|
||||
font-family: "KaiseiOpti-Bold";
|
||||
font-weight: 700;
|
||||
font-size: 3.4rem;
|
||||
line-height: 3.6rem;
|
||||
}
|
||||
> .detail{
|
||||
.name{
|
||||
margin-bottom: .8rem;
|
||||
font-weight: 500;
|
||||
font-size: 1.8rem;
|
||||
line-height: 100%;
|
||||
color: #232323;
|
||||
}
|
||||
}
|
||||
> .contact{
|
||||
.title{
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
.label{
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
margin-bottom: .6rem;
|
||||
font-family: "KaiseiOpti-Regular";
|
||||
font-weight: 400;
|
||||
font-size: 1.4rem;
|
||||
line-height: 100%;
|
||||
color: #585858;
|
||||
&:last-child{
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
> .about{
|
||||
.title{
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
.content{
|
||||
font-family: "KaiseiOpti-Regular";
|
||||
font-weight: 400;
|
||||
font-size: 1.6rem;
|
||||
line-height: 2.3rem;
|
||||
color: #585858;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
142
src/views/collectionStory/detail/coreConcept.vue
Normal file
@@ -0,0 +1,142 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
|
||||
import { Swiper, SwiperSlide } from 'swiper/vue';
|
||||
import 'swiper/css';
|
||||
import 'swiper/css/pagination';
|
||||
import 'swiper/css/navigation';
|
||||
import { Navigation, Pagination, Autoplay } from 'swiper/modules'
|
||||
import coreconcept_1 from "@/assets/images/collectionStory/coreconcept_1.png";
|
||||
import coreconcept_2 from "@/assets/images/collectionStory/coreconcept_2.png";
|
||||
import coreconcept_3 from "@/assets/images/collectionStory/coreconcept_3.png";
|
||||
import coreconcept_4 from "@/assets/images/collectionStory/coreconcept_4.png";
|
||||
import coreconcept_5 from "@/assets/images/collectionStory/coreconcept_5.png";
|
||||
|
||||
//const props = defineProps({
|
||||
//})
|
||||
//const emit = defineEmits([
|
||||
//])
|
||||
const modules = [Navigation, Pagination, Autoplay]
|
||||
let data = reactive({
|
||||
})
|
||||
let slides = [
|
||||
{ image: coreconcept_1, text: 'Layered Heritage, Coastal Stillness' },
|
||||
{ image: coreconcept_2, text: 'Nomadic Silhouettes, Golden Wandering' },
|
||||
{ image: coreconcept_3, text: 'Pastoral Communion, Woven Earth' },
|
||||
{ image: coreconcept_4, text: 'Layered winter silhouettes' },
|
||||
{ image: coreconcept_5, text: 'Insulated winterwear inspired by high-altitude traditions' },
|
||||
]
|
||||
onMounted(()=>{
|
||||
})
|
||||
onUnmounted(()=>{
|
||||
})
|
||||
defineExpose({})
|
||||
const {} = toRefs(data);
|
||||
</script>
|
||||
<template>
|
||||
<div class="core-concept">
|
||||
<div class="title">Core Concept</div>
|
||||
<div class="info">
|
||||
<div>
|
||||
We are spiritual nomads carrying what wind cannot take. This collection honors those who knew home is not a place, but what you wear. Earthy palettes of terracotta, ochre, and natural beige evoke pastoral landscapes, while layered textiles and handwoven fabrics embody portable heritage. Each garment balances burden and beauty—sustainable materials and artisanal craftsmanship create timeless pieces that travel with the wearer, transforming clothing into sanctuary and memory woven into fabric.
|
||||
</div>
|
||||
<br />
|
||||
<div>
|
||||
This winterwear series reinterprets high-altitude nomadic clothing through modern, refined construction. Layered silhouettes, tactile wools, and structured draping create warmth while preserving fluid movement. Textural contrasts and sculptural volumes evoke the surrounding snowy landscapes and monastic architecture. The collection balances cultural references with contemporary tailoring, presenting outerwear that feels protective, grounded, and quietly elegant within its harsh winter environment.
|
||||
</div>
|
||||
</div>
|
||||
<div class="swiper">
|
||||
<swiper
|
||||
:modules="modules"
|
||||
:slides-per-view="'auto'"
|
||||
space-between="24"
|
||||
:navigation="{
|
||||
prevEl: '.custom-prev',
|
||||
nextEl: '.custom-next'
|
||||
}"
|
||||
>
|
||||
<div class="custom-prev">
|
||||
<div style="transform: rotate(180deg);">
|
||||
<svg-icon name="collectionStory-swiperRight" size="24" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="custom-next">
|
||||
<div>
|
||||
<svg-icon name="collectionStory-swiperRight" size="24" />
|
||||
</div>
|
||||
</div>
|
||||
<swiper-slide v-for="item in slides" :key="item.id">
|
||||
<div class="slide-content">
|
||||
<img :src="item.image" :alt="item.text" />
|
||||
<div class="text">{{ item.text }}</div>
|
||||
</div>
|
||||
</swiper-slide>
|
||||
</swiper>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="less" scoped>
|
||||
.core-concept{
|
||||
width: 100%;
|
||||
height: auto;
|
||||
position: relative;
|
||||
padding: 6rem 0;
|
||||
> .title{
|
||||
padding: 0 2.4rem;
|
||||
font-family: "KaiseiOpti-Bold";
|
||||
font-weight: 700;
|
||||
font-size: 3rem;
|
||||
line-height: 100%;
|
||||
margin-bottom: 6rem;
|
||||
}
|
||||
> .info{
|
||||
width: 70.7rem;
|
||||
margin: 0 auto;
|
||||
margin-bottom: 4rem;
|
||||
> div{
|
||||
font-family: "KaiseiOpti-Regular";
|
||||
font-weight: 400;
|
||||
font-size: 1.6rem;
|
||||
line-height: 2.3rem;
|
||||
}
|
||||
}
|
||||
> .swiper{
|
||||
width: 100%;
|
||||
padding: 0 2.4rem;
|
||||
padding-top: 1rem;
|
||||
position: relative;
|
||||
.custom-prev,.custom-next{
|
||||
position: absolute;
|
||||
top: 16rem;
|
||||
z-index: 2;
|
||||
cursor: pointer;
|
||||
color: #fff;
|
||||
filter: drop-shadow(1px 1px 2px rgba(0, 0, 0, .5));
|
||||
}
|
||||
.custom-prev{
|
||||
left: 0;
|
||||
}
|
||||
.custom-next{
|
||||
left: auto;
|
||||
right: 0;
|
||||
}
|
||||
.slide-content{
|
||||
width: min-content;
|
||||
> img{
|
||||
height: 34.2rem;
|
||||
width: auto;
|
||||
}
|
||||
> .text{
|
||||
margin-top: 1rem;
|
||||
font-family: "KaiseiOpti-Regular";
|
||||
font-weight: 400;
|
||||
font-size: 1.4rem;
|
||||
line-height: 2.3rem;
|
||||
}
|
||||
}
|
||||
:deep(.swiper-slide){
|
||||
width: auto;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
85
src/views/collectionStory/detail/feelingWithAiDA.vue
Normal file
@@ -0,0 +1,85 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
|
||||
import feeling_1 from "@/assets/images/collectionStory/feeling_1.png";
|
||||
import feeling_2 from "@/assets/images/collectionStory/feeling_2.png";
|
||||
//const props = defineProps({
|
||||
//})
|
||||
//const emit = defineEmits([
|
||||
//])
|
||||
let data = reactive({
|
||||
})
|
||||
let slides = [
|
||||
{ image: feeling_1, text: 'Web interface in AiDA - the process of apparel edit' },
|
||||
{ image: feeling_2, text: 'Web interface in AiDA-Sketchboard' },
|
||||
]
|
||||
onMounted(()=>{
|
||||
})
|
||||
onUnmounted(()=>{
|
||||
})
|
||||
defineExpose({})
|
||||
const {} = toRefs(data);
|
||||
</script>
|
||||
<template>
|
||||
<div class="feeling-with-AiDA">
|
||||
<div class="title">Feeling with AiDA</div>
|
||||
<div class="info">
|
||||
<div>
|
||||
AiDA significantly enhances the creative latitude for cross-disciplinary creators like myself. Its functionalities, particularly the robust line-art extraction and the ability to edit the front and back garment panels, have been a major source of inspiration. This allows me to rapidly prototype by extracting traditional ethnic wear and conduct detailed style modifications directly on the canvas. As an integrated creation platform, AiDA enables a deeper focus on the design process. For seasoned designers, AI serves as a powerful source of inspiration, and AiDA is an exemplary manifestation of this principle.
|
||||
</div>
|
||||
</div>
|
||||
<div class="img-box">
|
||||
<div class="img-item" v-for="item in slides" :key="item.text">
|
||||
<img :src="item.image" alt="">
|
||||
<div class="text">{{ item.text }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="less" scoped>
|
||||
.feeling-with-AiDA{
|
||||
width: 100%;
|
||||
height: auto;
|
||||
position: relative;
|
||||
padding: 6rem 0;
|
||||
padding-bottom: 18rem;
|
||||
> .title{
|
||||
padding: 0 2.4rem;
|
||||
font-family: "KaiseiOpti-Bold";
|
||||
font-weight: 700;
|
||||
font-size: 3rem;
|
||||
line-height: 100%;
|
||||
margin-bottom: 6rem;
|
||||
}
|
||||
> .info{
|
||||
width: 70.7rem;
|
||||
margin: 0 auto;
|
||||
margin-bottom: 4rem;
|
||||
> div{
|
||||
font-family: "KaiseiOpti-Regular";
|
||||
font-weight: 400;
|
||||
font-size: 1.6rem;
|
||||
line-height: 2.3rem;
|
||||
}
|
||||
}
|
||||
> .img-box{
|
||||
width: 100%;
|
||||
margin-top: 4rem;
|
||||
display: flex;
|
||||
gap: 2.3rem;
|
||||
justify-content: center;
|
||||
> .img-item{
|
||||
width: min-content;
|
||||
> img{
|
||||
margin-bottom: .9rem;
|
||||
height: 34.3rem;
|
||||
}
|
||||
> .text{
|
||||
font-family: "KaiseiOpti-Regular";
|
||||
font-weight: 400;
|
||||
font-size: 1.4rem;
|
||||
line-height: 2.3rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
142
src/views/collectionStory/detail/index.vue
Normal file
@@ -0,0 +1,142 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
|
||||
import img from "@/assets/images/collectionStory/Rectangle.png";
|
||||
import coreConcept from "./coreConcept.vue";
|
||||
import inspiration from "./inspiration.vue";
|
||||
import feelingWithAiDA from "./feelingWithAiDA.vue";
|
||||
import CommodityItem from "@/components/CommodityItem.vue";
|
||||
//const props = defineProps({
|
||||
//})
|
||||
//const emit = defineEmits([
|
||||
//])
|
||||
let data = reactive({
|
||||
})
|
||||
const list = ref([
|
||||
{
|
||||
url: img,
|
||||
title: "Windswept Burden",
|
||||
price: "$100.00",
|
||||
},{
|
||||
url: img,
|
||||
title: "Windswept Burden",
|
||||
price: "$100.00",
|
||||
},{
|
||||
url: img,
|
||||
title: "Windswept Burden",
|
||||
price: "$100.00",
|
||||
},
|
||||
])
|
||||
const addShopping = (item) => {
|
||||
console.log(item);
|
||||
}
|
||||
onMounted(()=>{
|
||||
})
|
||||
onUnmounted(()=>{
|
||||
})
|
||||
defineExpose({})
|
||||
const {} = toRefs(data);
|
||||
</script>
|
||||
<template>
|
||||
<div class="detail">
|
||||
<div class="left">
|
||||
<div class="personal">
|
||||
<img :src="img" alt="">
|
||||
<div class="name">
|
||||
<span>Lian Su</span>
|
||||
<div class="icon">
|
||||
<SvgIcon name="share" size="24" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="center">
|
||||
<coreConcept ></coreConcept>
|
||||
<div class="line"></div>
|
||||
<inspiration ></inspiration>
|
||||
<div class="line"></div>
|
||||
<feelingWithAiDA ></feelingWithAiDA>
|
||||
</div>
|
||||
<div class="right">
|
||||
<div class="item" v-for="item in list" :key="item.url">
|
||||
<CommodityItem :url="item.url" :name="item.title" :price="item.price" @addShopping="addShopping(item)"></CommodityItem>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="less" scoped>
|
||||
.detail{
|
||||
width: 100%;
|
||||
height: auto;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
> div{
|
||||
// height: 100%;
|
||||
}
|
||||
> .left{
|
||||
width: 23rem;
|
||||
padding-top: 6.3rem;
|
||||
padding-left: 3rem;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
> .personal{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
> img{
|
||||
width: 10rem;
|
||||
height: 10rem;
|
||||
object-fit: cover;
|
||||
margin-bottom: 1.1rem;
|
||||
}
|
||||
> .name{
|
||||
display: flex;
|
||||
gap: .4rem;
|
||||
align-items: center;
|
||||
> span{
|
||||
font-family: 'KaiseiOpti-Bold';
|
||||
font-weight: 700;
|
||||
font-size: 1.8rem;
|
||||
line-height: 100%;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
> .center{
|
||||
flex: 1;
|
||||
border-left: 0.5px solid #585858;
|
||||
border-right: 0.5px solid #585858;
|
||||
// overflow-y: auto;
|
||||
overflow: hidden;
|
||||
// height: 100%;
|
||||
height: auto;
|
||||
.line{
|
||||
border: 0.5px solid #58585899;
|
||||
width: 100%;
|
||||
}
|
||||
&::-webkit-scrollbar{
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
> .right{
|
||||
width: 25.4rem;
|
||||
padding-top: 6rem;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
height: calc(100vh - var(--header-height));
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4rem;
|
||||
overflow-y: auto;
|
||||
&::-webkit-scrollbar{
|
||||
display: none;
|
||||
}
|
||||
> .item{
|
||||
margin-bottom: 2.3rem;
|
||||
width: 20rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
87
src/views/collectionStory/detail/inspiration.vue
Normal file
@@ -0,0 +1,87 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
|
||||
import inspiration_1 from "@/assets/images/collectionStory/inspiration_1.png";
|
||||
import inspiration_2 from "@/assets/images/collectionStory/inspiration_1.png";
|
||||
//const props = defineProps({
|
||||
//})
|
||||
//const emit = defineEmits([
|
||||
//])
|
||||
let data = reactive({
|
||||
})
|
||||
let slides = [
|
||||
{ image: inspiration_1, text: 'Moodboard 1 for this Collection' },
|
||||
{ image: inspiration_2, text: 'Moodboard 2 for this Collection' },
|
||||
]
|
||||
onMounted(()=>{
|
||||
})
|
||||
onUnmounted(()=>{
|
||||
})
|
||||
defineExpose({})
|
||||
const {} = toRefs(data);
|
||||
</script>
|
||||
<template>
|
||||
<div class="inspiration">
|
||||
<div class="title">Inspiration</div>
|
||||
<div class="info">
|
||||
<div>
|
||||
This project is inspired by the enduring traditions and unique material culture of high-altitude nomadic communities, particularly those found across Central Asia and the Himalayan plateau.
|
||||
</div>
|
||||
<div>
|
||||
The moodboard now balances rugged, natural fibers and dramatic, layered silhouettes with vibrant color and refined pattern work. Key visual elements include the strength of traditional nomadic crafts like falconry and the warmth of textured knitwear. The palette expands from earthy browns to striking accents of lime green, teal, and gold, often expressed through intricate geometric ornamental motifs. The silhouettes emphasize functional draping (scarves and outerwear) and structured, high-collar layering, translating ancient heritage into a bold, contemporary aesthetic.
|
||||
</div>
|
||||
</div>
|
||||
<div class="img-box">
|
||||
<div class="img-item" v-for="item in slides" :key="item.text">
|
||||
<img :src="item.image" alt="">
|
||||
<div class="text">{{ item.text }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="less" scoped>
|
||||
.inspiration{
|
||||
width: 100%;
|
||||
height: auto;
|
||||
position: relative;
|
||||
padding: 6rem 0;
|
||||
> .title{
|
||||
padding: 0 2.4rem;
|
||||
font-family: "KaiseiOpti-Bold";
|
||||
font-weight: 700;
|
||||
font-size: 3rem;
|
||||
line-height: 100%;
|
||||
margin-bottom: 6rem;
|
||||
}
|
||||
> .info{
|
||||
width: 70.7rem;
|
||||
margin: 0 auto;
|
||||
margin-bottom: 4rem;
|
||||
> div{
|
||||
font-family: "KaiseiOpti-Regular";
|
||||
font-weight: 400;
|
||||
font-size: 1.6rem;
|
||||
line-height: 2.3rem;
|
||||
}
|
||||
}
|
||||
> .img-box{
|
||||
width: 100%;
|
||||
margin-top: 4rem;
|
||||
display: flex;
|
||||
gap: 2.3rem;
|
||||
justify-content: center;
|
||||
> .img-item{
|
||||
width: min-content;
|
||||
> img{
|
||||
margin-bottom: .9rem;
|
||||
height: 34.3rem;
|
||||
}
|
||||
> .text{
|
||||
font-family: "KaiseiOpti-Regular";
|
||||
font-weight: 400;
|
||||
font-size: 1.4rem;
|
||||
line-height: 2.3rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,11 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
|
||||
import Detail from "./detail/index.vue";
|
||||
//const props = defineProps({
|
||||
//})
|
||||
//const emit = defineEmits([
|
||||
//])
|
||||
let data = reactive({
|
||||
})
|
||||
|
||||
onMounted(()=>{
|
||||
})
|
||||
onUnmounted(()=>{
|
||||
@@ -15,9 +17,39 @@ const {} = toRefs(data);
|
||||
</script>
|
||||
<template>
|
||||
<div class="collectionStory">
|
||||
<div class="title">
|
||||
collection Story
|
||||
<div class="first-screen">
|
||||
<img class="banner" src="@/assets/images/collectionStory/collection_story_banner.png" alt="">
|
||||
<div class="back">
|
||||
<SvgIcon name="collectionStory-back" size="20" />
|
||||
<div class="text">Back to Home</div>
|
||||
</div>
|
||||
<div class="title-content">
|
||||
<div class="title-box">
|
||||
<div class="left">
|
||||
<div class="title">
|
||||
Windswept Burden
|
||||
</div>
|
||||
<div class="info">
|
||||
Publish Date: 24th Nov 2025
|
||||
</div>
|
||||
</div>
|
||||
<div class="right">
|
||||
<div class="info">
|
||||
“We are spiritual nomads carrying what wind cannot take. <br />
|
||||
Inspired by those who knew home is not a place, but what you wear.”
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="scrolling-learn-more">
|
||||
<div>Scrolling Learn More</div>
|
||||
<SvgIcon name="collectionStory-scrollingLearnMore" size="48" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<Detail></Detail>
|
||||
</div>
|
||||
<Footer></Footer>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="less" scoped>
|
||||
@@ -25,5 +57,104 @@ const {} = toRefs(data);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
overflow-y: auto;
|
||||
.first-screen{
|
||||
position: relative;
|
||||
height: auto;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
> .back{
|
||||
position: absolute;
|
||||
top: 2.4rem;
|
||||
left: 2.4rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: .8rem;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
> .text{
|
||||
font-size: 2rem;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
> .title-content{
|
||||
width: 100%;
|
||||
height: 63.2rem;
|
||||
margin-top: 24.8rem;
|
||||
background: linear-gradient(180deg, rgba(0, 0, 0, 0) 38.37%, rgba(0, 0, 0, 0.192) 90.74%);
|
||||
padding: 0 4rem;
|
||||
> .title-box{
|
||||
margin-top: 36.7rem;
|
||||
color: #fff;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
> .left{
|
||||
font-family: 'KaiseiOpti-Bold';
|
||||
font-weight: 700;
|
||||
> .title{
|
||||
font-size: 6rem;
|
||||
line-height: 6rem;
|
||||
}
|
||||
> .info{
|
||||
margin-top: 1.7rem;
|
||||
font-size: 1.8rem;
|
||||
line-height: 100%;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
}
|
||||
> .right{
|
||||
> .info{
|
||||
font-weight: 500;
|
||||
font-size: 1.8rem;
|
||||
line-height: 100%;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.scrolling-learn-more{
|
||||
position: absolute;
|
||||
bottom: 2.1rem;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: #fff;
|
||||
animation: scroll 3s linear infinite;
|
||||
@keyframes scroll {
|
||||
0% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-20%);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0%);
|
||||
}
|
||||
}
|
||||
> div{
|
||||
font-family: 'KaiseiOpti-Regular';
|
||||
font-weight: 400;
|
||||
font-size: 1.4rem;
|
||||
line-height: 100%;
|
||||
text-align: center;
|
||||
margin-bottom: 1.5rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
.banner{
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
}
|
||||
}
|
||||
.content{
|
||||
width: 100%;
|
||||
// min-height: 100%;
|
||||
// height: 100%;
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
@@ -1,18 +1,22 @@
|
||||
<template>
|
||||
<div class="home-index">
|
||||
<section class="index">
|
||||
<img src="@/assets/images/home-bg.jpg" class="bg" />
|
||||
</section>
|
||||
<section class="designers"></section>
|
||||
<section class="design"></section>
|
||||
<section class="digital-items"></section>
|
||||
<section class="digital-items"></section>
|
||||
<section class="footer"></section>
|
||||
<section-index />
|
||||
<section-designers />
|
||||
<section-design />
|
||||
<section-digital-items1 />
|
||||
<section-digital-items2 />
|
||||
<section-footer />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import SectionIndex from './section-index.vue'
|
||||
import SectionDesigners from './section-designers.vue'
|
||||
import SectionDesign from './section-design.vue'
|
||||
import SectionDigitalItems1 from './section-digital-items1.vue'
|
||||
import SectionDigitalItems2 from './section-digital-items2.vue'
|
||||
import SectionFooter from './section-footer.vue'
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@@ -22,14 +26,18 @@
|
||||
overflow: hidden;
|
||||
overflow-y: auto;
|
||||
> section {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
> section.index {
|
||||
> section.bgw {
|
||||
position: relative;
|
||||
> .bg {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
}
|
||||
> .content {
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
49
src/views/home/section-design.vue
Normal file
@@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<section class="section-design bgw">
|
||||
<img src="@/assets/images/home/design-bg.jpg" class="bg" />
|
||||
<div class="content">
|
||||
<div class="aida-logo"><img src="@/assets/images/logos/aida.png" /></div>
|
||||
<div class="title">Design with AiDA</div>
|
||||
<div class="tip">
|
||||
Each garment on this platform is where designer vision blooms through AiDA. A tool that
|
||||
nurtures your creativity, never overshadows it. Let your ideas flourish.
|
||||
</div>
|
||||
<button custom>Try Now</button>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.section-design {
|
||||
> .content {
|
||||
width: 55rem;
|
||||
top: 12rem;
|
||||
left: 9rem;
|
||||
> .aida-logo {
|
||||
margin-bottom: 6rem;
|
||||
> img {
|
||||
width: auto;
|
||||
height: 6.9rem;
|
||||
}
|
||||
}
|
||||
> .title {
|
||||
font-family: KaiseiOpti-Bold;
|
||||
font-size: 5.6rem;
|
||||
line-height: 6.2rem;
|
||||
margin-bottom: 2rem;
|
||||
color: #fff;
|
||||
}
|
||||
> .tip {
|
||||
font-family: KaiseiOpti-Regular;
|
||||
font-size: 2rem;
|
||||
line-height: 2.8rem;
|
||||
color: #fff;
|
||||
margin-bottom: 10rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
130
src/views/home/section-designers.vue
Normal file
@@ -0,0 +1,130 @@
|
||||
<template>
|
||||
<section class="section-designers">
|
||||
<div class="title">Popular Designers</div>
|
||||
<div class="tip">
|
||||
Discover the designers shaping AiDA’s creative landscape,<br />as we present their most
|
||||
distinguished works each month.
|
||||
</div>
|
||||
<div class="content">
|
||||
<img src="@/assets/images/home/designers-left.jpg" />
|
||||
<div class="box">
|
||||
<div class="intro">
|
||||
<span>{{ list[index]?.intro || '' }}</span>
|
||||
<img src="@/assets/images/home/designers-right.jpg" />
|
||||
</div>
|
||||
<div
|
||||
class="name-item"
|
||||
v-for="(v, i) in list"
|
||||
:key="i"
|
||||
:class="{ active: i === index }"
|
||||
@click="index = i"
|
||||
>
|
||||
<span class="name">{{ v.name }}</span>
|
||||
<span class="icon">
|
||||
<svg-icon name="arrow_right" size="20" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
const index = ref(0)
|
||||
const list = ref([
|
||||
{
|
||||
name: 'Ji-Yeon Park',
|
||||
intro:
|
||||
'Through its fragile tulle layers over a grounded silhouette, the garment reflects the tension between vulnerability and strength within one’s journey.'
|
||||
},
|
||||
{
|
||||
name: 'Lian Su',
|
||||
intro: '阿巴阿巴~'
|
||||
},
|
||||
{
|
||||
name: 'Céline Moreau',
|
||||
intro: '这是Céline Moreau的设计~'
|
||||
}
|
||||
])
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.section-designers {
|
||||
padding: 9rem 8rem;
|
||||
> .title {
|
||||
font-family: KaiseiOpti-Bold;
|
||||
font-size: 5.6rem;
|
||||
line-height: 6.2rem;
|
||||
text-align: center;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
> .tip {
|
||||
font-family: KaiseiOpti-Regular;
|
||||
font-size: 2rem;
|
||||
line-height: 2.8rem;
|
||||
text-align: center;
|
||||
color: #979797;
|
||||
}
|
||||
> .content {
|
||||
margin-top: 5rem;
|
||||
border: 0.1rem solid #979797;
|
||||
border-left: none;
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
box-sizing: content-box;
|
||||
--height: 45rem;
|
||||
min-height: var(--height);
|
||||
display: flex;
|
||||
> img {
|
||||
width: var(--height);
|
||||
height: var(--height);
|
||||
margin: 2.4rem 2.4rem 2.4rem 0;
|
||||
}
|
||||
> .box {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
> .intro {
|
||||
display: flex;
|
||||
margin: 2rem 2rem 2rem 0;
|
||||
> span {
|
||||
flex: 1;
|
||||
font-family: KaiseiOpti-Regular;
|
||||
font-size: 2rem;
|
||||
line-height: 3rem;
|
||||
color: #232323;
|
||||
margin-right: 9rem;
|
||||
}
|
||||
> img {
|
||||
width: 25rem;
|
||||
height: 25rem;
|
||||
}
|
||||
}
|
||||
> .name-item {
|
||||
flex: 1;
|
||||
min-height: 5rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-top: 0.1rem solid #7b7b7b;
|
||||
padding: 0 2.1rem;
|
||||
font-family: KaiseiOpti-Regular;
|
||||
font-size: 2.4rem;
|
||||
color: #7b7b7b;
|
||||
> .icon {
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
&.active {
|
||||
font-family: KaiseiOpti-Bold;
|
||||
color: #232323;
|
||||
background: #f6f6f6;
|
||||
> .icon {
|
||||
transform: rotate(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
101
src/views/home/section-digital-items1.vue
Normal file
@@ -0,0 +1,101 @@
|
||||
<template>
|
||||
<section class="digital-items1 bgw">
|
||||
<img src="@/assets/images/home/digital-items-1.jpg" class="bg" />
|
||||
<div class="content">
|
||||
<div class="title">Digital Items</div>
|
||||
<div class="tip">
|
||||
AiDA captures your boldest thoughts and transforms them into vivid
|
||||
<br />
|
||||
digital visions—a virtual realm where creativity collides and evolves.
|
||||
</div>
|
||||
<button custom="black">Shop All</button>
|
||||
<div class="list">
|
||||
<div v-for="v in list" :key="v.url">
|
||||
<img :src="v.url" alt="" />
|
||||
<div class="title">{{ v.title }}</div>
|
||||
<div class="tip">{{ v.tip }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
const list = ref([
|
||||
{
|
||||
title: 'Women’s Itemk',
|
||||
tip: 'Blue Pleat Aria',
|
||||
url: 'http://118.31.39.42:3000/falls/digital-items-1.png'
|
||||
},
|
||||
{
|
||||
title: 'Girls’ Item',
|
||||
tip: 'Candy Riot',
|
||||
url: 'http://118.31.39.42:3000/falls/digital-items-2.jpg'
|
||||
},
|
||||
{
|
||||
title: 'Men’s Item',
|
||||
tip: 'Void Armour',
|
||||
url: 'http://118.31.39.42:3000/falls/digital-items-3.png'
|
||||
},
|
||||
{
|
||||
title: 'Boys’ Item',
|
||||
tip: 'Jester Edit',
|
||||
url: 'http://118.31.39.42:3000/falls/digital-items-4.png'
|
||||
}
|
||||
])
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.digital-items1 {
|
||||
> .content {
|
||||
top: 8rem;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
> .title {
|
||||
font-family: KaiseiOpti-Bold;
|
||||
font-size: 5.6rem;
|
||||
line-height: 6.2rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
> .tip {
|
||||
font-family: KaiseiOpti-Regular;
|
||||
font-size: 2rem;
|
||||
line-height: 2.8rem;
|
||||
margin-bottom: 2rem;
|
||||
color: #585858;
|
||||
text-align: center;
|
||||
}
|
||||
> .list {
|
||||
margin-top: 6rem;
|
||||
display: flex;
|
||||
gap: 2.4rem;
|
||||
> div {
|
||||
padding: 1rem;
|
||||
border: 0.1rem solid #979797;
|
||||
> img {
|
||||
width: 27.4rem;
|
||||
height: 34.6rem;
|
||||
margin-bottom: 1.2rem;
|
||||
}
|
||||
> .title {
|
||||
font-family: KaiseiOpti-Regular;
|
||||
font-size: 1.8rem;
|
||||
line-height: 2.2rem;
|
||||
color: #232323;
|
||||
margin-bottom: 0.6rem;
|
||||
}
|
||||
> .tip {
|
||||
font-family: KaiseiOpti-Regular;
|
||||
font-size: 1.2rem;
|
||||
line-height: 2.2rem;
|
||||
color: #979797;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
94
src/views/home/section-digital-items2.vue
Normal file
@@ -0,0 +1,94 @@
|
||||
<template>
|
||||
<section class="digital-items2 bgw">
|
||||
<img src="@/assets/images/home/digital-items-1.jpg" class="bg" />
|
||||
<div class="content">
|
||||
<div class="tip">
|
||||
AiDA accelerates style innovation, shaping daily pieces that keep
|
||||
<br />
|
||||
your wardrobe in sync with modern fashion’s rhythm.
|
||||
</div>
|
||||
<div class="list">
|
||||
<div v-for="v in list" :key="v.url">
|
||||
<img :src="v.url" alt="" />
|
||||
<div class="title">{{ v.title }}</div>
|
||||
<div class="tip">{{ v.tip }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
const list = ref([
|
||||
{
|
||||
title: 'Women’s Itemk',
|
||||
tip: 'Blue Pleat Aria',
|
||||
url: 'http://118.31.39.42:3000/falls/digital-items-1.png'
|
||||
},
|
||||
{
|
||||
title: 'Girls’ Item',
|
||||
tip: 'Candy Riot',
|
||||
url: 'http://118.31.39.42:3000/falls/digital-items-2.jpg'
|
||||
},
|
||||
{
|
||||
title: 'Men’s Item',
|
||||
tip: 'Void Armour',
|
||||
url: 'http://118.31.39.42:3000/falls/digital-items-3.png'
|
||||
},
|
||||
{
|
||||
title: 'Boys’ Item',
|
||||
tip: 'Jester Edit',
|
||||
url: 'http://118.31.39.42:3000/falls/digital-items-4.png'
|
||||
}
|
||||
])
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.digital-items2 {
|
||||
> .content {
|
||||
top: 8rem;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
> .tip {
|
||||
font-family: KaiseiOpti-Regular;
|
||||
font-size: 2rem;
|
||||
line-height: 2.8rem;
|
||||
margin-bottom: 2rem;
|
||||
color: #585858;
|
||||
text-align: center;
|
||||
}
|
||||
> .list {
|
||||
margin-top: 6rem;
|
||||
display: flex;
|
||||
gap: 2.4rem;
|
||||
> div {
|
||||
padding: 1rem;
|
||||
border: 0.1rem solid #979797;
|
||||
> img {
|
||||
width: 27.4rem;
|
||||
height: 34.6rem;
|
||||
margin-bottom: 1.2rem;
|
||||
}
|
||||
> .title {
|
||||
font-family: KaiseiOpti-Regular;
|
||||
font-size: 1.8rem;
|
||||
line-height: 2.2rem;
|
||||
color: #232323;
|
||||
margin-bottom: 0.6rem;
|
||||
}
|
||||
> .tip {
|
||||
font-family: KaiseiOpti-Regular;
|
||||
font-size: 1.2rem;
|
||||
line-height: 2.2rem;
|
||||
color: #979797;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
157
src/views/home/section-footer.vue
Normal file
@@ -0,0 +1,157 @@
|
||||
<template>
|
||||
<section class="section-footer">
|
||||
<div class="content">
|
||||
<div class="mate">
|
||||
<div class="logos">
|
||||
<img src="@/assets/images/logos/code-create-black.png" />
|
||||
<img src="@/assets/images/logos/stylish-arade-black.png" />
|
||||
<img src="@/assets/images/logos/aida-black.png" />
|
||||
</div>
|
||||
<div class="tip">
|
||||
Stylish Parade is a commerce platform for designers, serving as AiDA's commercial
|
||||
extension.
|
||||
</div>
|
||||
<div class="link">
|
||||
<span class="text">Bloom your Creativity with AiDA!</span>
|
||||
<span class="icon"><svg-icon name="arrow_right" size="12" /></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="help">
|
||||
<div class="title">HELP</div>
|
||||
<div class="item">FAQ</div>
|
||||
<div class="item">My Account</div>
|
||||
<div class="item">My Orders</div>
|
||||
<div class="item">Payment and Invoices</div>
|
||||
<div class="item">Copyright Licence</div>
|
||||
</div>
|
||||
<div class="polices">
|
||||
<div class="title">POLICES</div>
|
||||
<div class="item">Legal</div>
|
||||
<div class="item">Privacy Policy</div>
|
||||
<div class="item">Cookies Settings</div>
|
||||
<div class="item">Purchase Conditions</div>
|
||||
</div>
|
||||
<div class="company">
|
||||
<div class="title">COMPANY</div>
|
||||
<div class="item">About us</div>
|
||||
<div class="item">Offices</div>
|
||||
<div class="item">Join with us</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<div class="left">© Code-Create 2026</div>
|
||||
<div class="right">
|
||||
<img src="@/assets/images/icons/xiaohongshu.png" />
|
||||
<img src="@/assets/images/icons/linkedin.png" />
|
||||
<img src="@/assets/images/icons/instagram.png" />
|
||||
<img src="@/assets/images/icons/facebook.png" />
|
||||
<img src="@/assets/images/icons/douyin.png" />
|
||||
<img src="@/assets/images/icons/wechat.png" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.section-footer {
|
||||
background-color: #f6f6f6;
|
||||
border-top: 0.1rem solid #232323;
|
||||
> .content {
|
||||
display: flex;
|
||||
min-height: 37rem;
|
||||
padding: 0 8rem;
|
||||
border-bottom: 0.1rem solid #c4c4c4;
|
||||
> div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
padding: 3rem 2rem;
|
||||
flex: 1;
|
||||
border-right: 0.1rem solid #c4c4c4;
|
||||
&:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
}
|
||||
> .mate {
|
||||
> .logos {
|
||||
margin-bottom: 3rem;
|
||||
display: flex;
|
||||
gap: 2.9rem;
|
||||
> img {
|
||||
width: auto;
|
||||
height: 3rem;
|
||||
}
|
||||
}
|
||||
> .tip {
|
||||
font-family: KaiseiOpti-Regular;
|
||||
font-size: 1.4rem;
|
||||
line-height: 140%;
|
||||
color: #585858;
|
||||
margin-bottom: 4.4rem;
|
||||
}
|
||||
> .link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-bottom: 0.1rem solid #232323;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
> .text {
|
||||
font-size: 1.4rem;
|
||||
line-height: 120%;
|
||||
color: #232323;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
> .icon {
|
||||
width: 2.4rem;
|
||||
height: 2.4rem;
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
> .help,
|
||||
> .polices,
|
||||
> .company {
|
||||
> .title {
|
||||
font-family: KaiseiOpti-Bold;
|
||||
font-size: 1.8rem;
|
||||
color: #232323;
|
||||
margin-bottom: 2.3rem;
|
||||
}
|
||||
> .item {
|
||||
font-family: KaiseiOpti-Regular;
|
||||
font-size: 1.4rem;
|
||||
color: #585858;
|
||||
margin-bottom: 0.6rem;
|
||||
line-height: 2rem;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
> .footer {
|
||||
padding: 0 8rem;
|
||||
height: 7rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
> .left {
|
||||
font-family: KaiseiOpti-Regular;
|
||||
font-size: 1.2rem;
|
||||
color: #585858;
|
||||
}
|
||||
> .right {
|
||||
display: flex;
|
||||
> img {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
margin-left: 1.6rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
75
src/views/home/section-index.vue
Normal file
@@ -0,0 +1,75 @@
|
||||
<template>
|
||||
<section class="section-index bgw">
|
||||
<img src="@/assets/images/home/bg.jpg" class="bg" />
|
||||
<div class="shade-1"></div>
|
||||
<div class="shade-2"></div>
|
||||
<div class="content">
|
||||
<div class="title">Windswept Burden</div>
|
||||
<div class="tip">We are spiritual nomads carrying<br />what wind cannot take.</div>
|
||||
<button custom>View More</button>
|
||||
<div class="aida-logo"><img src="@/assets/images/logos/aida.png" /></div>
|
||||
<p class="tip">
|
||||
What you wear is how you present yourself to the world, especially today, when human
|
||||
contacts are so quick. Fashion is instant language
|
||||
</p>
|
||||
<p class="tip">I firmly believe that with the right footwear one can rule the world.</p>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.section-index {
|
||||
> .shade-1 {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 96rem;
|
||||
height: 100%;
|
||||
background: linear-gradient(to left, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.25) 100%);
|
||||
}
|
||||
> .shade-2 {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 30rem;
|
||||
background: linear-gradient(180deg, rgba(0, 0, 0, 0) 38.37%, rgba(0, 0, 0, 0.192) 90.74%);
|
||||
}
|
||||
> .content {
|
||||
top: 16rem;
|
||||
left: 9rem;
|
||||
color: #fff;
|
||||
> .title {
|
||||
font-family: KaiseiOpti-Bold;
|
||||
font-size: 6rem;
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
> div.tip {
|
||||
font-family: KaiseiOpti-Regular;
|
||||
font-size: 3.2rem;
|
||||
line-height: 4.3rem;
|
||||
}
|
||||
> button {
|
||||
margin-top: 11.6rem;
|
||||
margin-bottom: 13.9rem;
|
||||
}
|
||||
> .aida-logo {
|
||||
margin-bottom: 3rem;
|
||||
> img {
|
||||
width: auto;
|
||||
height: 5rem;
|
||||
}
|
||||
}
|
||||
> p.tip {
|
||||
font-family: KaiseiOpti-Regular;
|
||||
font-size: 1.2rem;
|
||||
line-height: 2rem;
|
||||
color: #ededed;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
130
src/views/login/css/style.css
Normal file
@@ -0,0 +1,130 @@
|
||||
.retrieve-password,
|
||||
.register,
|
||||
.login {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
.retrieve-password:deep(.el-form),
|
||||
.register:deep(.el-form),
|
||||
.login:deep(.el-form) {
|
||||
width: 100%;
|
||||
}
|
||||
.retrieve-password:deep(.el-form) .el-form-item,
|
||||
.register:deep(.el-form) .el-form-item,
|
||||
.login:deep(.el-form) .el-form-item {
|
||||
position: relative;
|
||||
margin-bottom: 1.6rem;
|
||||
}
|
||||
.retrieve-password:deep(.el-form) .el-form-item__content,
|
||||
.register:deep(.el-form) .el-form-item__content,
|
||||
.login:deep(.el-form) .el-form-item__content {
|
||||
position: relative;
|
||||
}
|
||||
.retrieve-password:deep(.el-form) .el-form-item__label,
|
||||
.register:deep(.el-form) .el-form-item__label,
|
||||
.login:deep(.el-form) .el-form-item__label {
|
||||
color: #232323;
|
||||
font-size: 1.2rem;
|
||||
margin-bottom: 0.8rem;
|
||||
line-height: 1.5rem;
|
||||
}
|
||||
.retrieve-password:deep(.el-form) .el-input,
|
||||
.register:deep(.el-form) .el-input,
|
||||
.login:deep(.el-form) .el-input {
|
||||
--el-input-height: 3.4rem;
|
||||
--el-input-border-radius: 0;
|
||||
--el-input-text-color: #232323;
|
||||
--el-border-color: #C4C4C4;
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
.retrieve-password:deep(.el-form) .el-input::placeholder,
|
||||
.register:deep(.el-form) .el-input::placeholder,
|
||||
.login:deep(.el-form) .el-input::placeholder {
|
||||
color: #9F9F9F;
|
||||
}
|
||||
.retrieve-password:deep(.el-form) .password-tip,
|
||||
.register:deep(.el-form) .password-tip,
|
||||
.login:deep(.el-form) .password-tip {
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
top: -1rem;
|
||||
right: 0;
|
||||
transform: translateY(-100%);
|
||||
}
|
||||
.retrieve-password:deep(.el-form) .password-warning,
|
||||
.register:deep(.el-form) .password-warning,
|
||||
.login:deep(.el-form) .password-warning {
|
||||
--el-checkbox-height: auto;
|
||||
margin-top: -0.6rem;
|
||||
margin-bottom: 1.6rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.retrieve-password:deep(.el-form) .password-warning > .icon,
|
||||
.register:deep(.el-form) .password-warning > .icon,
|
||||
.login:deep(.el-form) .password-warning > .icon {
|
||||
width: 1.4rem;
|
||||
height: 1.4rem;
|
||||
margin-right: 0.8rem;
|
||||
}
|
||||
.retrieve-password:deep(.el-form) .password-warning > .label,
|
||||
.register:deep(.el-form) .password-warning > .label,
|
||||
.login:deep(.el-form) .password-warning > .label {
|
||||
font-family: KaiseiOpti-Regular;
|
||||
font-size: 1rem;
|
||||
color: #9F9F9F;
|
||||
}
|
||||
.retrieve-password:deep(.el-form) .forgotPassword,
|
||||
.register:deep(.el-form) .forgotPassword,
|
||||
.login:deep(.el-form) .forgotPassword {
|
||||
margin-top: -0.8rem;
|
||||
margin-bottom: 5rem;
|
||||
font-size: 1.1rem;
|
||||
text-align: right;
|
||||
color: #666666;
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
font-family: KaiseiOpti-Regular;
|
||||
}
|
||||
.retrieve-password:deep(.el-form) .el-form-item__error,
|
||||
.register:deep(.el-form) .el-form-item__error,
|
||||
.login:deep(.el-form) .el-form-item__error {
|
||||
padding-top: 1px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
.retrieve-password:deep(.el-form) .submit,
|
||||
.register:deep(.el-form) .submit,
|
||||
.login:deep(.el-form) .submit {
|
||||
width: 100%;
|
||||
height: 4rem;
|
||||
font-size: 1.36rem;
|
||||
}
|
||||
.retrieve-password:deep(.el-form) .privacy,
|
||||
.register:deep(.el-form) .privacy,
|
||||
.login:deep(.el-form) .privacy {
|
||||
margin-top: -0.6rem;
|
||||
--el-checkbox-height: auto;
|
||||
}
|
||||
.retrieve-password:deep(.el-form) .privacy .el-checkbox__label,
|
||||
.register:deep(.el-form) .privacy .el-checkbox__label,
|
||||
.login:deep(.el-form) .privacy .el-checkbox__label {
|
||||
font-size: 1.1rem;
|
||||
color: #666666;
|
||||
font-family: KaiseiOpti-Regular;
|
||||
}
|
||||
.retrieve-password:deep(.el-form) .privacy .el-checkbox__label > div > span,
|
||||
.register:deep(.el-form) .privacy .el-checkbox__label > div > span,
|
||||
.login:deep(.el-form) .privacy .el-checkbox__label > div > span {
|
||||
font-family: KaiseiOpti-Bold;
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
color: #232323;
|
||||
}
|
||||
.retrieve-password > .other-login,
|
||||
.register > .other-login,
|
||||
.login > .other-login {
|
||||
margin-top: 4rem;
|
||||
}
|
||||
143
src/views/login/email-verify.vue
Normal file
@@ -0,0 +1,143 @@
|
||||
<template>
|
||||
<div class="visible-code">
|
||||
<div class="tip" v-html="$t('Login.verifyCodeHasSent', { email: props.email })"></div>
|
||||
<input-code @submit="onVerify" v-model="code" ref="inputCodeRef" />
|
||||
<p class="time" v-if="time > 0">{{ $t('Login.resendCodeIn', { time: timeStr }) }}</p>
|
||||
<p class="time" v-if="time === 0">
|
||||
<span @click="onResend">{{ $t('Login.resendCode') }}</span>
|
||||
</p>
|
||||
<button class="verify" custom="black" @click="onVerify">{{ $t('Login.verify') }}</button>
|
||||
<other-login />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import md5 from 'md5'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import OtherLogin from './other-login.vue'
|
||||
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
import { CountDown } from '@/utils/tools'
|
||||
import InputCode from '@/components/input-code.vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
const { t } = useI18n()
|
||||
const router = useRouter()
|
||||
const emit = defineEmits(['submit-email-code'])
|
||||
const props = defineProps({
|
||||
email: { type: String, required: true },
|
||||
type: {
|
||||
type: String as () => 'LOGIN' | 'REGISTER' | 'FORGOT_PWD',
|
||||
required: true
|
||||
},
|
||||
password: { type: String, default: '' }
|
||||
})
|
||||
const code = ref('')
|
||||
const time = ref(60)
|
||||
const timeStr = computed(() => CountDown(time.value))
|
||||
const timeout = ref(null)
|
||||
const setTime = (s = 120) => {
|
||||
clearTime()
|
||||
time.value = s
|
||||
timeout.value = setInterval(() => {
|
||||
time.value--
|
||||
if (time.value <= 0) {
|
||||
clearTime()
|
||||
time.value = 0
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
const clearTime = () => {
|
||||
time.value = -1
|
||||
clearTimeout(timeout.value)
|
||||
}
|
||||
onBeforeUnmount(() => {
|
||||
clearTime()
|
||||
})
|
||||
onMounted(() => {
|
||||
onSendCode()
|
||||
})
|
||||
const inputCodeRef = ref(null)
|
||||
const resetCode = () => {
|
||||
inputCodeRef.value?.resetCode?.()
|
||||
}
|
||||
const onSendCode = async () => {
|
||||
resetCode()
|
||||
const email = props.email
|
||||
if (!email) {
|
||||
console.warn('请输入邮箱')
|
||||
return Promise.reject('请输入邮箱')
|
||||
}
|
||||
// const data = {
|
||||
// email,
|
||||
// type: props.type
|
||||
// }
|
||||
// if (props.type === 'LOGIN') {
|
||||
// data['password'] = md5(props.password)
|
||||
// }
|
||||
// const res = await SendVerificationCode(data)
|
||||
// if (!res) {
|
||||
// ElMessage.error(t('Login.sendCodeError'))
|
||||
// return Promise.reject('发送验证码失败')
|
||||
// }
|
||||
setTime()
|
||||
return Promise.resolve()
|
||||
}
|
||||
const onResend = () => {
|
||||
if (time.value > 0) return
|
||||
onSendCode()
|
||||
}
|
||||
const onVerify = () => {
|
||||
if (code.value.length !== 6) return
|
||||
emit('submit-email-code', code.value)
|
||||
}
|
||||
defineExpose({
|
||||
onSendCode
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.visible-code {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
> .tip {
|
||||
text-align: center;
|
||||
margin-top: 4.6rem;
|
||||
line-height: 2.4rem;
|
||||
font-family: KaiseiOpti-Regular;
|
||||
font-size: 1.6rem;
|
||||
color: #666;
|
||||
&:deep(span) {
|
||||
color: #252727;
|
||||
font-family: KaiseiOpti-Medium;
|
||||
}
|
||||
}
|
||||
> .input-code {
|
||||
margin-top: 6rem;
|
||||
}
|
||||
> .verify {
|
||||
margin-top: 5rem;
|
||||
width: 100%;
|
||||
height: 4rem;
|
||||
--button-font-size: 1.4rem;
|
||||
}
|
||||
> .time {
|
||||
user-select: none;
|
||||
margin-top: 2.4rem;
|
||||
font-size: 1.6rem;
|
||||
color: #666;
|
||||
font-family: Regular;
|
||||
> span {
|
||||
color: #ff7a50;
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
font-family: Medium;
|
||||
}
|
||||
}
|
||||
> .other-login {
|
||||
margin-top: 6rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
117
src/views/login/index.vue
Normal file
@@ -0,0 +1,117 @@
|
||||
<template>
|
||||
<div class="index">
|
||||
<div class="header">
|
||||
<span class="tip">{{ $t('AlphaVersion') }}</span>
|
||||
<img src="@/assets/images/logo-1.png" class="logo" />
|
||||
<p class="split"></p>
|
||||
<button class="login" @click="onLogin">{{ $t('Login.login') }}</button>
|
||||
<button class="register" @click="onRegister">{{ $t('Login.register') }}</button>
|
||||
</div>
|
||||
<img src="@/assets/images/login/index-title.png" class="title" draggable="false" />
|
||||
<img src="@/assets/images/login/index-zhuangshi.png" class="zhuangshi" draggable="false" />
|
||||
<div class="tip">{{ $t('Login.indexTip') }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
const router = useRouter()
|
||||
const onLogin = () => {
|
||||
router.push({ name: 'login' })
|
||||
}
|
||||
const onRegister = () => {
|
||||
router.push({ name: 'register' })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.index {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
background-image: url('@/assets/images/login/index-bg.png');
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
> .header {
|
||||
position: absolute;
|
||||
top: 3rem;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
> * {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
> .tip {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
font-size: 3rem;
|
||||
text-align: center;
|
||||
font-family: Regular;
|
||||
color: #fff;
|
||||
z-index: 0;
|
||||
}
|
||||
> .logo {
|
||||
width: auto;
|
||||
height: 2.5rem;
|
||||
margin-left: 3.8rem;
|
||||
}
|
||||
> .split {
|
||||
margin: 0 auto;
|
||||
}
|
||||
> button {
|
||||
margin-right: 3rem;
|
||||
width: 20rem;
|
||||
height: 5.2rem;
|
||||
border-radius: 5rem;
|
||||
border: none;
|
||||
outline: none;
|
||||
font-size: 2.2rem;
|
||||
font-weight: 600;
|
||||
font-family: SemiBold;
|
||||
border: 0.2rem solid #fff;
|
||||
&:active {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
> .login {
|
||||
background-color: #fff;
|
||||
color: #713e1f;
|
||||
}
|
||||
> .register {
|
||||
background-color: transparent;
|
||||
color: #ffffff;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
}
|
||||
> .zhuangshi,
|
||||
> .title,
|
||||
> .tip {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
> .title {
|
||||
// width: 55%;
|
||||
width: 105.6rem;
|
||||
height: auto;
|
||||
top: 20.5rem;
|
||||
}
|
||||
> .zhuangshi {
|
||||
width: 21.5rem;
|
||||
height: auto;
|
||||
bottom: 13.4rem;
|
||||
}
|
||||
> .tip {
|
||||
font-size: 2.8rem;
|
||||
font-family: Regular;
|
||||
color: #fff;
|
||||
bottom: 8rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
120
src/views/login/less/style.less
Normal file
@@ -0,0 +1,120 @@
|
||||
.retrieve-password,
|
||||
.register,
|
||||
.login {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-top: 2rem;
|
||||
|
||||
&:deep(.el-form) {
|
||||
width: 100%;
|
||||
|
||||
.el-form-item {
|
||||
position: relative;
|
||||
margin-bottom: 1.6rem;
|
||||
}
|
||||
|
||||
.el-form-item__content {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
|
||||
.el-form-item__label {
|
||||
color: #232323;
|
||||
font-size: 1.2rem;
|
||||
margin-bottom: 0.8rem;
|
||||
line-height: 1.5rem;
|
||||
}
|
||||
|
||||
.el-input {
|
||||
--el-input-height: 3.4rem;
|
||||
--el-input-border-radius: 0;
|
||||
--el-input-text-color: #232323;
|
||||
--el-border-color: #C4C4C4;
|
||||
font-size: 1.4rem;
|
||||
|
||||
&::placeholder {
|
||||
color: #9F9F9F;
|
||||
}
|
||||
}
|
||||
|
||||
.password-tip {
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
top: -1rem;
|
||||
right: 0;
|
||||
transform: translateY(-100%);
|
||||
}
|
||||
|
||||
.password-warning {
|
||||
--el-checkbox-height: auto;
|
||||
margin-top: -0.6rem;
|
||||
margin-bottom: 1.6rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
>.icon {
|
||||
width: 1.4rem;
|
||||
height: 1.4rem;
|
||||
margin-right: 0.8rem;
|
||||
}
|
||||
|
||||
>.label {
|
||||
font-family: KaiseiOpti-Regular;
|
||||
font-size: 1rem;
|
||||
color: #9F9F9F;
|
||||
}
|
||||
}
|
||||
|
||||
.forgotPassword {
|
||||
margin-top: -0.8rem;
|
||||
margin-bottom: 5rem;
|
||||
font-size: 1.1rem;
|
||||
text-align: right;
|
||||
color: #666666;
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
font-family: KaiseiOpti-Regular;
|
||||
}
|
||||
|
||||
|
||||
.el-form-item__error {
|
||||
padding-top: 1px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
|
||||
.submit {
|
||||
width: 100%;
|
||||
height: 4rem;
|
||||
font-size: 1.36rem;
|
||||
}
|
||||
|
||||
.privacy {
|
||||
margin-top: -0.6rem;
|
||||
--el-checkbox-height: auto;
|
||||
|
||||
.el-checkbox__label {
|
||||
font-size: 1.1rem;
|
||||
color: #666666;
|
||||
font-family: KaiseiOpti-Regular;
|
||||
|
||||
>div {
|
||||
>span {
|
||||
font-family: KaiseiOpti-Bold;
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
color: #232323;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
>.other-login {
|
||||
margin-top: 4rem;
|
||||
}
|
||||
|
||||
}
|
||||
231
src/views/login/login-dialog.vue
Normal file
@@ -0,0 +1,231 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
class="login-dialog"
|
||||
v-model="show"
|
||||
header-class="login-dialog-header"
|
||||
align-center
|
||||
destroy-on-close
|
||||
:show-close="false"
|
||||
:close-on-press-escape="false"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<div class="login-dialog-content">
|
||||
<img class="bg" src="@/assets/images/login/bg.jpg" />
|
||||
<img class="logo" src="@/assets/images/logo.png" />
|
||||
<div class="close" @click="show = false"><svg-icon name="close" /></div>
|
||||
<div class="content" v-if="curentTabInfo">
|
||||
<div class="header">
|
||||
<div class="title" v-show="curentTabInfo.title">
|
||||
<div class="icon" @click="onBack"><svg-icon name="back" size="17" /></div>
|
||||
<div class="label">{{ curentTabInfo.title }}</div>
|
||||
</div>
|
||||
<div class="nav" v-show="!curentTabInfo.title">
|
||||
<div
|
||||
class="item"
|
||||
:class="{ active: currentTab === TabNames.sign_up }"
|
||||
@click="currentTab = TabNames.sign_up"
|
||||
>
|
||||
SIGN UP
|
||||
</div>
|
||||
<div
|
||||
class="item"
|
||||
:class="{ active: currentTab === TabNames.login }"
|
||||
@click="currentTab = TabNames.login"
|
||||
>
|
||||
LOG IN
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<component
|
||||
:is="curentTabInfo.component"
|
||||
@retrieve-password="currentTab = TabNames.retrieve_password"
|
||||
@login="onLogin"
|
||||
@register="onRegister"
|
||||
@submit-email-code="onSubmitEmailCode"
|
||||
:name="data.name"
|
||||
:email="data.email"
|
||||
:password="data.password"
|
||||
type="FORGOT_PWD"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, markRaw, watch, onBeforeUnmount } from 'vue'
|
||||
import md5 from 'md5'
|
||||
import login from './login.vue'
|
||||
import register from './register.vue'
|
||||
import emailVerify from './email-verify.vue'
|
||||
import myEvent from '@/utils/myEvent'
|
||||
const data = ref({
|
||||
name: '',
|
||||
email: '',
|
||||
password: '',
|
||||
type: ''
|
||||
})
|
||||
const show = ref(false)
|
||||
const TabNames = {
|
||||
login: 'login',
|
||||
sign_up: 'sign_up',
|
||||
email_verify: 'email_verify',
|
||||
retrieve_password: 'retrieve_password'
|
||||
}
|
||||
const tabList = markRaw([
|
||||
{
|
||||
name: TabNames.login,
|
||||
component: login
|
||||
},
|
||||
{
|
||||
name: TabNames.sign_up,
|
||||
component: register
|
||||
},
|
||||
{
|
||||
name: TabNames.email_verify,
|
||||
title: 'EMAIL VERIFICATION',
|
||||
component: emailVerify
|
||||
},
|
||||
{
|
||||
name: TabNames.retrieve_password,
|
||||
title: 'RETRIEVE PASSWORD',
|
||||
component: login
|
||||
}
|
||||
])
|
||||
const currentTab = ref(TabNames.login)
|
||||
const curentTabInfo = computed(() => tabList.find((v) => v.name === currentTab.value))
|
||||
const lastTab = ref('')
|
||||
watch(currentTab, (v, o) => (lastTab.value = o))
|
||||
const onBack = () => {
|
||||
if (lastTab.value) currentTab.value = lastTab.value
|
||||
}
|
||||
const open = (type?: string) => {
|
||||
currentTab.value = TabNames[type] || TabNames.login
|
||||
data.value.name = ''
|
||||
data.value.email = ''
|
||||
data.value.password = ''
|
||||
show.value = true
|
||||
}
|
||||
myEvent.add('openLoginDialog', open)
|
||||
onBeforeUnmount(() => {
|
||||
myEvent.remove('openLoginDialog', open)
|
||||
})
|
||||
|
||||
const onLogin = (res: any) => {
|
||||
data.value = res
|
||||
data.value.type = TabNames.login
|
||||
currentTab.value = TabNames.email_verify
|
||||
}
|
||||
const onRegister = (res: any) => {
|
||||
data.value = res
|
||||
data.value.type = TabNames.sign_up
|
||||
currentTab.value = TabNames.email_verify
|
||||
}
|
||||
const onSubmitEmailCode = (code: string) => {
|
||||
// data.value.code = code
|
||||
console.log(code)
|
||||
show.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.login-dialog-header {
|
||||
display: none;
|
||||
}
|
||||
.el-dialog.login-dialog {
|
||||
--el-dialog-border-radius: 0;
|
||||
--el-dialog-padding-primary: 0;
|
||||
width: 90rem;
|
||||
height: 62rem;
|
||||
.el-dialog__body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
.login-dialog-content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
> .bg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
> * {
|
||||
position: absolute;
|
||||
}
|
||||
> .logo {
|
||||
width: auto;
|
||||
height: 4rem;
|
||||
top: 4rem;
|
||||
left: 3rem;
|
||||
}
|
||||
> .close {
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
top: 2rem;
|
||||
right: 2rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
> .content {
|
||||
width: 34rem;
|
||||
top: 5rem;
|
||||
right: 6rem;
|
||||
height: calc(100% - 10rem);
|
||||
> .header {
|
||||
--padding-bottom: 1.2rem;
|
||||
padding-bottom: var(--padding-bottom);
|
||||
border-bottom: 0.1rem solid #9f9f9f;
|
||||
> div {
|
||||
display: flex;
|
||||
height: 3rem;
|
||||
}
|
||||
> .nav {
|
||||
> .item {
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
font-size: 2rem;
|
||||
font-family: KaiseiOpti-Regular;
|
||||
color: #9f9f9f;
|
||||
&.active {
|
||||
font-family: KaiseiOpti-Bold;
|
||||
color: #232323;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: calc(0rem - var(--padding-bottom) - 0.2rem);
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 0;
|
||||
border-bottom: 0.4rem solid #232323;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
> .title {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
> .icon {
|
||||
width: 2.4rem;
|
||||
height: 2.4rem;
|
||||
cursor: pointer;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
> .label {
|
||||
font-family: KaiseiOpti-Bold;
|
||||
font-size: 2rem;
|
||||
color: #232323;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
75
src/views/login/login.vue
Normal file
@@ -0,0 +1,75 @@
|
||||
<template>
|
||||
<div class="login">
|
||||
<el-form :model="formData" :rules="ruleForm" label-position="top" ref="formRef">
|
||||
<el-form-item :label="$t('Login.email')" prop="email">
|
||||
<el-input v-model="formData.email" :placeholder="$t('Login.enterEmail')" name="email" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('Login.password')" prop="password">
|
||||
<el-input
|
||||
v-model="formData.password"
|
||||
:placeholder="$t('Login.enterPassword')"
|
||||
type="password"
|
||||
show-password
|
||||
name="password"
|
||||
/>
|
||||
</el-form-item>
|
||||
<div class="forgotPassword">
|
||||
<span @click="onForgotPassword">{{ $t('Login.forgotPassword') }}</span>
|
||||
</div>
|
||||
<el-form-item>
|
||||
<button type="submit" class="submit" custom="black" @click.prevent="onSubmit">
|
||||
{{ $t('Login.login') }}
|
||||
</button>
|
||||
</el-form-item>
|
||||
<el-form-item prop="privacy" class="privacy">
|
||||
<el-checkbox v-model="formData.privacy">
|
||||
<div v-html="$t('Login.agreeTermsPolicy')"></div>
|
||||
</el-checkbox>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<other-login />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, reactive, ref } from 'vue'
|
||||
import { validateEmail, validatePassLength, validatePrivacy } from './tools'
|
||||
import OtherLogin from './other-login.vue'
|
||||
const emit = defineEmits(['retrieve-password', 'login'])
|
||||
const props = defineProps({
|
||||
name: { type: String, default: '' },
|
||||
email: { type: String, default: '' },
|
||||
password: { type: String, default: '' }
|
||||
})
|
||||
const ruleForm = reactive({
|
||||
email: [{ validator: validateEmail, trigger: 'change' }],
|
||||
password: [{ validator: validatePassLength, trigger: 'change' }],
|
||||
privacy: [{ validator: validatePrivacy, trigger: 'change' }]
|
||||
})
|
||||
const formData = reactive({
|
||||
email: props.email,
|
||||
password: props.password,
|
||||
privacy: false
|
||||
})
|
||||
const formRef = ref(null)
|
||||
|
||||
const onForgotPassword = () => {
|
||||
emit('retrieve-password')
|
||||
}
|
||||
const onSubmit = () => {
|
||||
formRef.value?.validate?.((valid) => {
|
||||
if (valid) {
|
||||
emit('login', {
|
||||
email: formData.email,
|
||||
password: formData.password
|
||||
})
|
||||
} else {
|
||||
console.warn('error submit!')
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import './less/style.less';
|
||||
</style>
|
||||
83
src/views/login/other-login.vue
Normal file
@@ -0,0 +1,83 @@
|
||||
<template>
|
||||
<div class="other-login">
|
||||
<div class="title">{{ $t('Login.orContinueWith') }}</div>
|
||||
<div class="btns">
|
||||
<button class="submit" custom @click="onGoogle">
|
||||
<img src="@/assets/images/login/google.png" />
|
||||
{{ $t('Login.googleLogin') }}
|
||||
</button>
|
||||
<button class="submit" custom @click="onWechat">
|
||||
<img src="@/assets/images/login/wechat.png" />
|
||||
{{ $t('Login.wechatLogin') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, reactive, ref } from 'vue'
|
||||
const onGoogle = () => {}
|
||||
const onWechat = () => {}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.other-login {
|
||||
width: 100%;
|
||||
> .title {
|
||||
width: 100%;
|
||||
color: #232323;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 1.1rem;
|
||||
font-family: KaiseiOpti-Regular;
|
||||
line-height: 2rem;
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
content: '';
|
||||
flex: 1;
|
||||
border-bottom: 0.05rem solid #232323;
|
||||
}
|
||||
|
||||
&::before {
|
||||
margin-right: 2rem;
|
||||
}
|
||||
|
||||
&::after {
|
||||
margin-left: 2rem;
|
||||
}
|
||||
}
|
||||
> .btns {
|
||||
margin-top: 2.4rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 2rem;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
> button {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
height: 3.4rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
|
||||
--button-bgcolor: transparent;
|
||||
--button-color: #666666;
|
||||
--button-font-size: 1rem;
|
||||
border: 0.1rem solid #c4c4c4;
|
||||
&:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
img {
|
||||
width: auto;
|
||||
height: 2rem;
|
||||
// margin-right: 2.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
60
src/views/login/password-tip.vue
Normal file
@@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<div class="password-tip">
|
||||
<div>
|
||||
<el-icon>
|
||||
<CloseBold v-if="validateLength(value)" />
|
||||
<Select v-else />
|
||||
</el-icon>
|
||||
<span>{{ $t('Login.passwordLengthError', { min: 6, max: 20 }) }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<el-icon>
|
||||
<CloseBold v-if="validateSpecial(value)" />
|
||||
<Select v-else />
|
||||
</el-icon>
|
||||
<span>{{ $t('Login.passwordSpecial') }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<el-icon>
|
||||
<CloseBold v-if="validateCase(value)" />
|
||||
<Select v-else />
|
||||
</el-icon>
|
||||
<span>{{ $t('Login.passwordCase') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, reactive, ref } from 'vue'
|
||||
import { Select, CloseBold } from '@element-plus/icons-vue'
|
||||
import { validateLength, validateSpecial, validateCase } from './tools'
|
||||
const props = defineProps({
|
||||
value: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.password-tip {
|
||||
background: #404040;
|
||||
color: #fff;
|
||||
font-size: 1.4rem;
|
||||
padding: 2rem;
|
||||
border-radius: 2rem;
|
||||
line-height: normal;
|
||||
|
||||
> div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 0.5rem;
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
> .el-icon {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
85
src/views/login/register.vue
Normal file
@@ -0,0 +1,85 @@
|
||||
<template>
|
||||
<div class="register">
|
||||
<el-form :model="formData" :rules="ruleForm" label-position="top" ref="formRef">
|
||||
<el-form-item :label="$t('Login.name')" prop="name">
|
||||
<el-input name="name" v-model="formData.name" :placeholder="$t('Login.enterName')" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('Login.password')" prop="password">
|
||||
<password-tip :value="formData.password" v-show="showPasswordTip" />
|
||||
<el-input
|
||||
name="password"
|
||||
v-model="formData.password"
|
||||
:placeholder="$t('Login.enterPassword')"
|
||||
type="password"
|
||||
show-password
|
||||
@blur="showPasswordTip = false"
|
||||
@focus="showPasswordTip = true"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<div class="password-warning">
|
||||
<span class="icon"><svg-icon name="warning" size="12" /></span>
|
||||
<span class="label">You must satisfy ALL password conditions to register.</span>
|
||||
</div>
|
||||
<el-form-item :label="$t('Login.email')" prop="email">
|
||||
<el-input name="email" v-model="formData.email" :placeholder="$t('Login.enterEmail')" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<button type="submit" class="submit" custom="black" @click.prevent="onSubmit">
|
||||
{{ $t('Login.register') }}
|
||||
</button>
|
||||
</el-form-item>
|
||||
<el-form-item prop="privacy" class="privacy">
|
||||
<el-checkbox v-model="formData.privacy">
|
||||
<div v-html="$t('Login.agreeTermsPolicy')"></div>
|
||||
</el-checkbox>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<other-login />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, reactive, ref } from 'vue'
|
||||
import { validateName, validateEmail, validatePass, validatePrivacy } from './tools'
|
||||
import OtherLogin from './other-login.vue'
|
||||
import PasswordTip from './password-tip.vue'
|
||||
const emit = defineEmits(['register'])
|
||||
const props = defineProps({
|
||||
name: { type: String, default: '' },
|
||||
email: { type: String, default: '' },
|
||||
password: { type: String, default: '' }
|
||||
})
|
||||
const ruleForm = reactive({
|
||||
name: [{ validator: validateName, trigger: 'change' }],
|
||||
email: [{ validator: validateEmail, trigger: 'change' }],
|
||||
password: [{ validator: validatePass, trigger: 'change' }],
|
||||
privacy: [{ validator: validatePrivacy, trigger: 'change' }]
|
||||
})
|
||||
const showPasswordTip = ref(false)
|
||||
const formData = reactive({
|
||||
name: props.name,
|
||||
email: props.email,
|
||||
password: props.password,
|
||||
privacy: false
|
||||
})
|
||||
const formRef = ref(null)
|
||||
const onSubmit = () => {
|
||||
formRef.value?.validate?.((valid) => {
|
||||
if (valid) {
|
||||
// console.log('submit!')
|
||||
emit('register', {
|
||||
name: formData.name,
|
||||
email: formData.email,
|
||||
password: formData.password
|
||||
})
|
||||
} else {
|
||||
console.warn('error submit!')
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import './less/style.less';
|
||||
</style>
|
||||
120
src/views/login/retrieve-password.vue
Normal file
@@ -0,0 +1,120 @@
|
||||
<template>
|
||||
<div class="retrieve-password">
|
||||
<div class="left">
|
||||
<img class="bg" src="@/assets/images/login/left-bg.png" />
|
||||
<img class="logo" src="@/assets/images/logo-1.png" />
|
||||
</div>
|
||||
<div class="right">
|
||||
<div class="top">
|
||||
<button class="back" @click="onBack">
|
||||
<svg-icon name="arrow-left" size="37" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="box">
|
||||
<img src="@/assets/images/login/elephant.png" />
|
||||
<template v-if="!isVisible">
|
||||
<div class="title">{{ $t('Login.retrievePassword') }}</div>
|
||||
<el-form :model="formData" :rules="ruleForm" label-position="top" ref="formRef">
|
||||
<el-form-item :label="$t('Login.email')" prop="email">
|
||||
<el-input
|
||||
v-model="formData.email"
|
||||
:placeholder="$t('Login.enterEmail')"
|
||||
name="email"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('Login.password')" prop="password">
|
||||
<password-tip :value="formData.password" v-show="showPasswordTip" />
|
||||
<el-input
|
||||
v-model="formData.password"
|
||||
:placeholder="$t('Login.enterPassword')"
|
||||
type="password"
|
||||
show-password
|
||||
name="password"
|
||||
@blur="showPasswordTip = false"
|
||||
@focus="showPasswordTip = true"
|
||||
/>
|
||||
</el-form-item>
|
||||
<br />
|
||||
<br />
|
||||
<el-form-item>
|
||||
<el-button class="submit" type="primary" @click="onSubmit">{{
|
||||
$t('submit')
|
||||
}}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
<!-- <visible-code
|
||||
v-show="isVisible"
|
||||
type="FORGOT_PWD"
|
||||
ref="visibleCodeRef"
|
||||
:email="formData.email"
|
||||
@submit="onVerifyCode"
|
||||
/> -->
|
||||
<other-login />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import md5 from 'md5'
|
||||
import { ForgotPassword } from '@/api/user'
|
||||
import { computed, reactive, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { validateEmail, validatePass } from './tools'
|
||||
import OtherLogin from './other-login.vue'
|
||||
import emailVerify from './email-verify.vue'
|
||||
import PasswordTip from './password-tip.vue'
|
||||
import { useUserInfoStore } from '@/stores'
|
||||
const userInfoStore = useUserInfoStore()
|
||||
const router = useRouter()
|
||||
const ruleForm = reactive({
|
||||
email: [{ validator: validateEmail, trigger: 'change' }],
|
||||
password: [{ validator: validatePass, trigger: 'change' }]
|
||||
})
|
||||
const isVisible = ref(false)
|
||||
const showPasswordTip = ref(false)
|
||||
const formData = reactive({
|
||||
email: '',
|
||||
password: ''
|
||||
})
|
||||
const formRef = ref(null)
|
||||
const onBack = () => {
|
||||
if (isVisible.value) {
|
||||
isVisible.value = false
|
||||
} else {
|
||||
router.back()
|
||||
}
|
||||
}
|
||||
const visibleCodeRef = ref(null)
|
||||
const onSubmit = () => {
|
||||
formRef.value?.validate?.((valid) => {
|
||||
if (valid) {
|
||||
// console.log('submit!')
|
||||
visibleCodeRef.value?.onSendCode().then(() => {
|
||||
isVisible.value = true
|
||||
})
|
||||
} else {
|
||||
console.warn('error submit!')
|
||||
}
|
||||
})
|
||||
}
|
||||
const onVerifyCode = (code: string) => {
|
||||
// console.log(code)
|
||||
ForgotPassword({
|
||||
email: formData.email,
|
||||
newPassword: md5(formData.password),
|
||||
verificationCode: code
|
||||
})
|
||||
.then((res) => {
|
||||
if (res) router.push({ name: 'login' })
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn(error)
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import './less/style.less';
|
||||
</style>
|
||||
54
src/views/login/tools.js
Normal file
@@ -0,0 +1,54 @@
|
||||
import i18n from '@/lang'
|
||||
const t = i18n.global.t
|
||||
export const validateName = (rule, value, callback) => {
|
||||
var str = ""
|
||||
if (!value) {
|
||||
str = t('Login.pleaseInputName')
|
||||
} else if (value.length < 2 || value.length > 20) {
|
||||
str = t('Login.nameLengthError', { min: 2, max: 20 })
|
||||
}
|
||||
callback(str ? new Error(str) : undefined)
|
||||
}
|
||||
export const validateEmail = (rule, value, callback) => {
|
||||
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(\.[a-zA-Z]{2,})?$/
|
||||
var str = ''
|
||||
if (!value) {
|
||||
str = t('Login.pleaseInputEmail')
|
||||
} else if (!emailRegex.test(value)) {
|
||||
str = t('Login.emailFormatError')
|
||||
}
|
||||
callback(str ? new Error(str) : undefined)
|
||||
}
|
||||
// 检查长度
|
||||
export const validateLength = (v, min = 6, max = 20) => (v.length < 6 || v.length > 20);
|
||||
//检查特殊字符
|
||||
export const validateSpecial = (v) => (!/[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(v));
|
||||
//检查大小写字母和数字
|
||||
export const validateCase = (v) => (!/[a-z]/.test(v) || !/[A-Z]/.test(v) || !/\d/.test(v));
|
||||
// 检查密码
|
||||
export const validatePass = (rule, value, callback) => {
|
||||
var str = ''
|
||||
if (validateLength(value)) {
|
||||
str = t('Login.passwordLengthError', { min: 6, max: 20 })
|
||||
} else if (validateSpecial(value)) {
|
||||
str = t('Login.passwordSpecial')
|
||||
} else if (validateCase(value)) {
|
||||
str = t('Login.passwordCase')
|
||||
}
|
||||
callback(str ? new Error(str) : undefined)
|
||||
}
|
||||
// 检查密码长度
|
||||
export const validatePassLength = (rule, value, callback) => {
|
||||
var str = ''
|
||||
if (validateLength(value)) {
|
||||
str = t('Login.passwordLengthError', { min: 6, max: 20 })
|
||||
}
|
||||
callback(str ? new Error(str) : undefined)
|
||||
}
|
||||
export const validatePrivacy = (rule, value, callback) => {
|
||||
if (!value) {
|
||||
callback(new Error(t('Login.pleaseTermsPolicy')))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
@@ -24,8 +24,44 @@
|
||||
>
|
||||
<svg-icon :name="activePath === v.path ? v.active_icon : v.icon" size="22" />
|
||||
</div>
|
||||
<div class="login">Login</div>
|
||||
<div class="profile"></div>
|
||||
<div class="login" @click="onLogin">Login</div>
|
||||
<el-popover
|
||||
ref="profilePopover"
|
||||
placement="bottom-end"
|
||||
trigger="click"
|
||||
:show-arrow="false"
|
||||
popper-style="width: 24rem; padding: 0; border-radius: 0; right: 2rem; top: 10rem;"
|
||||
>
|
||||
<template #reference><div class="profile"></div></template>
|
||||
<template #default>
|
||||
<div class="profile-content">
|
||||
<div class="info">
|
||||
<img src="@/assets/images/profile-content-bg.jpg" alt="" />
|
||||
<div class="content">
|
||||
<div class="profile"></div>
|
||||
<div class="name">Hi, Alexandra_chen</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nav-item" @click="onMyWardrobe">
|
||||
<div class="icon"><svg-icon name="my_wardrobe" size="18" /></div>
|
||||
<div class="label">My Wardrobe</div>
|
||||
</div>
|
||||
<div class="nav-item" @click="onNotifications">
|
||||
<div class="icon"><svg-icon name="notifications" size="14" /></div>
|
||||
<div class="label">Notifications</div>
|
||||
</div>
|
||||
<div class="nav-item" @click="onSettings">
|
||||
<div class="icon"><svg-icon name="settings" size="16" /></div>
|
||||
<div class="label">Settings</div>
|
||||
</div>
|
||||
<div class="hr"></div>
|
||||
<div class="nav-item logout" @click="onLogout">
|
||||
<div class="icon"><svg-icon name="logout" size="20" /></div>
|
||||
<div class="label">Log off</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-popover>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -33,6 +69,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import myEvent from '@/utils/myEvent'
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const activePath = computed(() => route.path)
|
||||
@@ -70,11 +107,34 @@
|
||||
if (path === activePath.value) return
|
||||
router.push(path)
|
||||
}
|
||||
const onLogin = () => {
|
||||
myEvent.emit('openLoginDialog')
|
||||
}
|
||||
const profilePopover = ref(null)
|
||||
const hideProfilePopover = () => {
|
||||
profilePopover.value?.hide()
|
||||
}
|
||||
const onMyWardrobe = () => {
|
||||
hideProfilePopover()
|
||||
console.log('my wardrobe')
|
||||
}
|
||||
const onNotifications = () => {
|
||||
hideProfilePopover()
|
||||
console.log('notifications')
|
||||
}
|
||||
const onSettings = () => {
|
||||
hideProfilePopover()
|
||||
console.log('settings')
|
||||
}
|
||||
const onLogout = () => {
|
||||
hideProfilePopover()
|
||||
console.log('logout')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
#main-header {
|
||||
height: 8rem;
|
||||
height: var(--header-height);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
@@ -145,4 +205,66 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.profile-content {
|
||||
width: 100%;
|
||||
> .info {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 2rem;
|
||||
> img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
> .content {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
> .profile {
|
||||
width: 5rem;
|
||||
height: 5rem;
|
||||
border-radius: 50%;
|
||||
background: #cfcfcf;
|
||||
}
|
||||
> .name {
|
||||
font-size: 1.4rem;
|
||||
color: #232323;
|
||||
}
|
||||
}
|
||||
}
|
||||
> .hr {
|
||||
margin: 1.2rem 1rem;
|
||||
border-top: 0.1rem solid #c4c4c4;
|
||||
}
|
||||
> .nav-item {
|
||||
margin-left: 2rem;
|
||||
margin-bottom: 1.2rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 2rem;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
> .icon {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
> .label {
|
||||
font-family: KaiseiOpti-Regular;
|
||||
font-size: 1.2rem;
|
||||
color: #585858;
|
||||
}
|
||||
}
|
||||
> .logout {
|
||||
> .label {
|
||||
font-family: KaiseiOpti-Medium;
|
||||
font-size: 1.4rem;
|
||||
color: #232323;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||