Compare commits
63 Commits
282a5b2252
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8c8ec7846d | ||
|
|
8d441766c5 | ||
|
|
bf907a1378 | ||
|
|
de6295f2af | ||
|
|
3e0a7b8928 | ||
|
|
09909552bc | ||
|
|
baf57515c0 | ||
|
|
b8c844363c | ||
|
|
87b071c319 | ||
|
|
bdc824e1f6 | ||
|
|
1c7b2d32a6 | ||
|
|
cffd554365 | ||
|
|
33043eedf1 | ||
| ce35f788ca | |||
| 5bbdeb6236 | |||
| 93813f7b56 | |||
|
|
51e6933f9f | ||
|
|
c0b4742966 | ||
| f76c9ed828 | |||
| d50e3781db | |||
| e48e369ef1 | |||
| bb8344b27a | |||
| b538853800 | |||
| f772e3d250 | |||
| f5a7ad51f0 | |||
| 8df8618f40 | |||
|
|
f355835819 | ||
|
|
42c2817c2f | ||
| 9bf0211e78 | |||
| 281272c9f9 | |||
|
|
71cfef996d | ||
|
|
09125378b6 | ||
|
|
e8da956543 | ||
|
|
ad81feaf81 | ||
| 254f61e524 | |||
| 3512d9ae87 | |||
|
|
98e2548f01 | ||
|
|
a6484782a5 | ||
|
|
017d052cd8 | ||
|
|
bd35b3f89f | ||
|
|
66b019eb2a | ||
|
|
4c2edb53e4 | ||
|
|
829f164833 | ||
|
|
45298e5f23 | ||
|
|
7ca69021c4 | ||
|
|
b0ee5a0783 | ||
| 5c931317f0 | |||
| 751cd1aa1f | |||
|
|
80eaa803dd | ||
|
|
7b5dcdacd7 | ||
| cce310c2dc | |||
| 5d240edb84 | |||
|
|
32a801b24f | ||
|
|
d65590304f | ||
| 38094c4b2c | |||
| 7b4acaa001 | |||
| 8a5a0ad3f5 | |||
|
|
fd86f5fa74 | ||
|
|
5da88beaf1 | ||
|
|
1be347cadf | ||
|
|
e16c551f4e | ||
|
|
4dc0a54b2e | ||
|
|
9cd63c90c9 |
@@ -7,14 +7,14 @@
|
||||
<!-- <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"> -->
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0" />
|
||||
<link rel="stylesheet" href="/fonts/index.css">
|
||||
<title>Lane Crawford</title>
|
||||
<title>Stylish Parade</title>
|
||||
<!-- Open Graph / WhatsApp share metadata -->
|
||||
<meta property="og:title" content="Lane Crawford" />
|
||||
<meta property="og:description" content="create and share looks from the Lane Crawford creation gallery." />
|
||||
<meta property="og:title" content="Stylish Parade" />
|
||||
<meta property="og:description" content="create and share looks from the Stylish Parade creation gallery." />
|
||||
<meta property="og:image" content="" />
|
||||
<meta property="og:url" content="https://www.lc.aida.com.hk" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:site_name" content="Lane Crawford" />
|
||||
<meta property="og:site_name" content="Stylish Parade" />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
30
src/App.vue
@@ -1,16 +1,42 @@
|
||||
<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>
|
||||
<shopping-drawer />
|
||||
</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'
|
||||
import ShoppingDrawer from '@/views/shopping-drawer.vue'
|
||||
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()
|
||||
})
|
||||
window['onClickPrivacy'] = () => {
|
||||
const e = window.event || event
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
console.log('点击了隐私政策')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
|
||||
@@ -20,6 +20,7 @@ body,
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
font-family: 'KaiseiOpti-Medium';
|
||||
color: #232323;
|
||||
}
|
||||
@keyframes loading {
|
||||
0% {
|
||||
@@ -96,8 +97,11 @@ body,
|
||||
--el-color-primary-dark-2: #565656;
|
||||
/* 深灰色(加深20%) */
|
||||
}
|
||||
.mini-scrollbar {
|
||||
--mini-scrollbar-width: 0.4rem;
|
||||
}
|
||||
.mini-scrollbar::-webkit-scrollbar {
|
||||
width: 0.4rem;
|
||||
width: var(--mini-scrollbar-width);
|
||||
}
|
||||
.mini-scrollbar::-webkit-scrollbar-thumb {
|
||||
border-radius: 0.4rem;
|
||||
@@ -118,11 +122,11 @@ button[custom="white"] {
|
||||
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);
|
||||
border: var(--button-border, none);
|
||||
cursor: pointer;
|
||||
}
|
||||
button[custom]:active,
|
||||
@@ -137,3 +141,11 @@ button[custom="black"] {
|
||||
--button-click-color: #fff;
|
||||
--button-font-size: 1.6rem;
|
||||
}
|
||||
button[custom="black-box"] {
|
||||
--button-bgcolor: transparent;
|
||||
--button-color: #232323;
|
||||
--button-border: 0.2rem solid #979797;
|
||||
--button-click-bgcolor: #979797;
|
||||
--button-click-color: #fff;
|
||||
--button-font-size: 1.6rem;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ body,
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
font-family: 'KaiseiOpti-Medium';
|
||||
color: #232323;
|
||||
}
|
||||
|
||||
@keyframes loading {
|
||||
@@ -118,8 +119,10 @@ body,
|
||||
|
||||
// 迷你滚动条
|
||||
.mini-scrollbar {
|
||||
--mini-scrollbar-width: 0.4rem;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 0.4rem;
|
||||
width: var(--mini-scrollbar-width);
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
@@ -146,11 +149,11 @@ button[custom="white"] {
|
||||
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);
|
||||
border: var(--button-border, none);
|
||||
cursor: pointer;
|
||||
|
||||
&:active {
|
||||
@@ -165,4 +168,13 @@ button[custom="black"] {
|
||||
--button-click-bgcolor: #333;
|
||||
--button-click-color: #fff;
|
||||
--button-font-size: 1.6rem;
|
||||
}
|
||||
|
||||
button[custom="black-box"] {
|
||||
--button-bgcolor: transparent;
|
||||
--button-color: #232323;
|
||||
--button-border: 0.2rem solid #979797;
|
||||
--button-click-bgcolor: #979797;
|
||||
--button-click-color: #fff;
|
||||
--button-font-size: 1.6rem;
|
||||
}
|
||||
6
src/assets/icons/Invoice.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13.75 5.41675H6.25V6.25008H13.75V5.41675Z" fill="currentColor"/>
|
||||
<path d="M13.75 8.75H6.25V9.58333H13.75V8.75Z" fill="currentColor"/>
|
||||
<path d="M10.4167 12.0833H6.25V12.9166H10.4167V12.0833Z" fill="currentColor"/>
|
||||
<path d="M2.5 18.3334L6.25 16.6667L10 18.3334L13.75 16.6667L17.5 18.3334V1.66675H2.5V18.3334ZM3.33333 2.50008H16.6667V17.0509L14.0883 15.9051L13.75 15.7547L13.4117 15.9051L10 17.4213L6.58833 15.9051L6.25 15.7547L5.91167 15.9051L3.33333 17.0509V2.50008Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 602 B |
5
src/assets/icons/ThumbEdit.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="15" cy="15" r="14.5" fill="#232323" stroke="white"/>
|
||||
<path d="M8.18359 19.5693V22.3285H11.8137C12.056 22.3285 12.2879 22.2298 12.4559 22.055L22.7247 11.3755C23.061 11.0257 23.0556 10.4711 22.7125 10.128L20.4083 7.82384C20.0557 7.47124 19.4824 7.47665 19.1365 7.83584L8.43278 18.9513C8.27291 19.1173 8.18359 19.3388 8.18359 19.5693Z" stroke="white" stroke-width="0.89098" stroke-linecap="round"/>
|
||||
<path d="M17.9883 8.96387L21.5522 12.5278" stroke="white" stroke-width="0.89098" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 641 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 |
5
src/assets/icons/brand/delete.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14.1113 5.26938H12.1387V3.63221C12.1387 2.87065 11.518 2.25 10.7565 2.25H6.73062C5.96906 2.25 5.34841 2.87065 5.34841 3.63221V5.26938H3.37575C3.16774 5.26938 3 5.43713 3 5.64513C3 5.85313 3.16774 6.02087 3.37575 6.02087H4.00646V14.3678C4.00646 15.1293 4.62711 15.75 5.38867 15.75H12.0984C12.86 15.75 13.4806 15.1293 13.4806 14.3678V6.02423H14.1113C14.3193 6.02423 14.4871 5.85649 14.4871 5.64848C14.4871 5.44048 14.3193 5.27274 14.1113 5.27274V5.26938ZM6.73062 3.00485H10.7565C11.102 3.00485 11.3872 3.28666 11.3872 3.63556V5.12848C11.3872 5.209 11.3234 5.27274 11.2429 5.27274H6.24751C6.167 5.27274 6.10325 5.209 6.10325 5.12848V3.63556C6.10325 3.29001 6.38506 3.00485 6.73397 3.00485H6.73062ZM12.0984 15.0019H5.38867C5.04312 15.0019 4.75795 14.7201 4.75795 14.3711V6.02423H12.7258V14.3711C12.7258 14.7167 12.444 15.0019 12.0951 15.0019H12.0984Z" fill="#979797"/>
|
||||
<path d="M7.40098 7.95319C7.15943 7.95319 6.96484 8.14777 6.96484 8.38932V12.2977C6.96484 12.5393 7.15943 12.7339 7.40098 12.7339C7.64253 12.7339 7.83711 12.5393 7.83711 12.2977V8.38932C7.83711 8.14777 7.64253 7.95319 7.40098 7.95319Z" fill="#979797"/>
|
||||
<path d="M10.0846 7.95319C9.84302 7.95319 9.64844 8.14777 9.64844 8.38932V12.2977C9.64844 12.5393 9.84302 12.7339 10.0846 12.7339C10.3261 12.7339 10.5207 12.5393 10.5207 12.2977V8.38932C10.5207 8.14777 10.3261 7.95319 10.0846 7.95319Z" fill="#979797"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
5
src/assets/icons/brand/more.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.5 12.5C9.5 13.8805 10.6195 15 12 15C13.3805 15 14.5 13.8805 14.5 12.5C14.5 11.1195 13.3805 10 12 10C10.6195 10 9.5 11.1195 9.5 12.5ZM13.5 12.5C13.5 13.327 12.827 14 12 14C11.173 14 10.5 13.327 10.5 12.5C10.5 11.673 11.173 11 12 11C12.827 11 13.5 11.673 13.5 12.5Z" fill="#7B7B7B"/>
|
||||
<path d="M3 12.5C3 13.8805 4.1195 15 5.5 15C6.8805 15 8 13.8805 8 12.5C8 11.1195 6.8805 10 5.5 10C4.1195 10 3 11.1195 3 12.5ZM7 12.5C7 13.327 6.327 14 5.5 14C4.673 14 4 13.327 4 12.5C4 11.673 4.673 11 5.5 11C6.327 11 7 11.673 7 12.5Z" fill="#7B7B7B"/>
|
||||
<path d="M16 12.5C16 13.8805 17.1195 15 18.5 15C19.8805 15 21 13.8805 21 12.5C21 11.1195 19.8805 10 18.5 10C17.1195 10 16 11.1195 16 12.5ZM20 12.5C20 13.327 19.327 14 18.5 14C17.673 14 17 13.327 17 12.5C17 11.673 17.673 11 18.5 11C19.327 11 20 11.673 20 12.5Z" fill="#7B7B7B"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 927 B |
4
src/assets/icons/brand/search.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="13.8654" cy="13.8665" r="7.86667" stroke="#232323" stroke-width="1.33333"/>
|
||||
<path d="M19.5586 19.5552L26.6697 26.6663" stroke="#232323" stroke-width="1.33333" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 299 B |
3
src/assets/icons/brand/time.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10 20C4.48583 20 0 15.5142 0 10C0 4.48583 4.48583 0 10 0C15.5142 0 20 4.48583 20 10C20 15.5142 15.5142 20 10 20ZM10 0.833333C4.94583 0.833333 0.833333 4.94583 0.833333 10C0.833333 15.0542 4.94583 19.1667 10 19.1667C15.0542 19.1667 19.1667 15.0542 19.1667 10C19.1667 4.94583 15.0542 0.833333 10 0.833333ZM12.3333 14.0833C12.5175 13.945 12.555 13.6842 12.4175 13.5L10.0008 10.2775V4.58333C10.0008 4.35333 9.81417 4.16667 9.58417 4.16667C9.35417 4.16667 9.1675 4.35333 9.1675 4.58333V10.4167C9.1675 10.5067 9.19667 10.5942 9.25083 10.6667L11.7508 14C11.8333 14.1092 11.9583 14.1667 12.0842 14.1667C12.1708 14.1667 12.2583 14.1392 12.3333 14.0833Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 773 B |
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 |
5
src/assets/icons/digital/Info.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.995 2C6.48087 2 2 6.48584 2 12C2 17.5142 6.48584 22 11.995 22C17.5042 22 21.9901 17.5142 21.9901 12C21.9901 6.48584 17.5092 2 11.995 2ZM11.995 20.8823C7.09687 20.8823 3.11277 16.8982 3.11277 12C3.11277 7.10184 7.09687 3.11773 11.995 3.11773C16.8932 3.11773 20.8773 7.10184 20.8773 12C20.8773 16.8982 16.8932 20.8823 11.995 20.8823Z" fill="#585858"/>
|
||||
<path d="M12.6169 11.0065C12.6169 10.6636 12.3389 10.3856 11.996 10.3856C11.653 10.3856 11.375 10.6636 11.375 11.0065V16.9678C11.375 17.3107 11.653 17.5887 11.996 17.5887C12.3389 17.5887 12.6169 17.3107 12.6169 16.9678V11.0065Z" fill="#585858"/>
|
||||
<path d="M11.9935 8.02584C12.54 8.02584 12.9871 7.57875 12.9871 7.0323C12.9871 6.48585 12.54 6.03876 11.9935 6.03876C11.4471 6.03876 11 6.48585 11 7.0323C11 7.57875 11.4471 8.02584 11.9935 8.02584Z" fill="#585858"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 928 B |
4
src/assets/icons/digital/back.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="0.583333" y="0.583333" width="26.8333" height="26.8333" stroke="#232323" stroke-width="1.16667"/>
|
||||
<path d="M17.2441 19.3639C17.4438 19.5645 17.4438 19.8889 17.2441 20.0895C17.0442 20.2903 16.7205 20.2903 16.5205 20.0895L10.709 14.2506L10.707 14.2477C10.5752 14.1088 10.5718 13.8877 10.709 13.7496L16.5205 7.91077C16.7205 7.70981 17.0441 7.70981 17.2441 7.91077C17.4438 8.11136 17.4438 8.43576 17.2441 8.63635L12.0029 13.902V13.9078L11.9639 13.9489C11.9326 13.982 11.9358 14.0298 11.9619 14.0563L17.2441 19.3639Z" fill="#232323" stroke="#232323" stroke-width="0.28"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 679 B |
8
src/assets/icons/digital/document.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14 3.5C17.0376 3.5 19.5 5.96243 19.5 9V20.5H4.5V3.5H14Z" stroke="#585858" stroke-linejoin="round"/>
|
||||
<path d="M14.5 3.5C14.5 8 14.7 8 19.5 8" stroke="#585858"/>
|
||||
<path d="M8 11H16" stroke="#585858" stroke-linecap="round"/>
|
||||
<path d="M8 8H12" stroke="#585858" stroke-linecap="round"/>
|
||||
<path d="M8 14H16" stroke="#585858" stroke-linecap="round"/>
|
||||
<path d="M8 17H16" stroke="#585858" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 516 B |
5
src/assets/icons/digital/pullIcon.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g opacity="0.5">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.82684 8.7465L7.13386 6.43947L9.44089 8.7465C9.67278 8.97839 10.0474 8.97839 10.2793 8.7465C10.5112 8.51461 10.5112 8.14001 10.2793 7.90812L7.55008 5.17893C7.31819 4.94704 6.94359 4.94704 6.7117 5.17893L3.98251 7.90812C3.75062 8.14001 3.75062 8.51461 3.98251 8.7465C4.2144 8.97244 4.59495 8.97839 4.82684 8.7465Z" fill="black"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 506 B |
3
src/assets/icons/download.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="26" height="20" viewBox="0 0 26 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 18.6667H25.3333V20H0V18.6667ZM18 8.39067L13.3333 13.0573V0H12V13.0573L7.33333 8.39067L6.39067 9.33333L12.6667 15.6093L18.9427 9.33333L18 8.39067Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 284 B |
4
src/assets/icons/downloadBtn.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="M6.32243 7.42574L9.409 10.5123C9.43299 10.5363 9.46897 10.5443 9.50096 10.5323C9.53294 10.5203 9.55293 10.4883 9.55293 10.4563V2.94781C9.55293 2.69992 9.75284 2.50002 10.0007 2.50002C10.2486 2.50002 10.4485 2.69992 10.4485 2.94781V10.4563C10.4485 10.4923 10.4685 10.5203 10.5005 10.5323C10.5325 10.5443 10.5685 10.5403 10.5925 10.5123L13.679 7.42574C13.851 7.25782 14.1468 7.25782 14.3147 7.42574C14.3987 7.5097 14.4467 7.62565 14.4467 7.74559C14.4467 7.86554 14.3987 7.97749 14.3147 8.06545L10.1287 12.2515C10.0607 12.3195 9.93676 12.3195 9.86879 12.2515L5.68272 8.06545C5.59876 7.98148 5.55078 7.86554 5.55078 7.74559C5.55078 7.62565 5.59876 7.5137 5.68272 7.42574C5.85064 7.25782 6.1505 7.25782 6.31843 7.42574H6.32243Z" fill="currentColor"/>
|
||||
<path d="M17.1966 12.6062C16.9487 12.6062 16.7488 12.8061 16.7488 13.054V16.2525C16.7488 16.4444 16.5929 16.6044 16.397 16.6044H3.60289C3.41098 16.6044 3.25106 16.4484 3.25106 16.2525V13.054C3.25106 12.8061 3.05115 12.6062 2.80326 12.6062C2.55538 12.6062 2.35547 12.8061 2.35547 13.054V16.2525C2.35547 16.9402 2.91521 17.4999 3.60289 17.4999H16.397C17.0847 17.4999 17.6444 16.9402 17.6444 16.2525V13.054C17.6444 12.8061 17.4445 12.6062 17.1966 12.6062Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
3
src/assets/icons/dui.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="20" height="14" viewBox="0 0 20 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M19.7269 0.156104C19.5188 -0.0520347 19.1025 -0.0520347 18.8944 0.156104L7.13454 11.9159C6.97843 12.0721 6.66622 12.0721 6.51012 11.9159L0.994441 6.40027C0.786302 6.19213 0.370025 6.19213 0.161886 6.40027C-0.0462532 6.60841 0.00578167 6.66044 0.00578167 6.81655C0.00578167 6.97265 0.0578162 7.12876 0.161886 7.23283L6.40605 13.477C6.51012 13.5811 6.66622 13.6331 6.82233 13.6331C6.97843 13.6331 7.13454 13.5811 7.23861 13.477L19.7269 0.98866C19.831 0.88459 19.883 0.728486 19.883 0.572382C19.883 0.416278 19.831 0.260174 19.7269 0.156104Z" fill="#232323"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 669 B |
5
src/assets/icons/eye.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="19" height="13" viewBox="0 0 19 13" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0.5 6.49998C3.33333 1.97224 10.9 -4.36659 18.5 6.49998" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M18.5 6.49998C15.6667 11.0277 8.1 17.3666 0.5 6.49998" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<circle cx="9.5" cy="6.49998" r="2.5" stroke="#232323"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 432 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 |
5
src/assets/icons/order/delete.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M15.678 5.85487H13.4861V4.03579C13.4861 3.18961 12.7965 2.5 11.9503 2.5H7.47716C6.63099 2.5 5.94138 3.18961 5.94138 4.03579V5.85487H3.74953C3.51841 5.85487 3.33203 6.04125 3.33203 6.27237C3.33203 6.50348 3.51841 6.68986 3.74953 6.68986H4.45032V15.9642C4.45032 16.8104 5.13993 17.5 5.98611 17.5H13.4414C14.2875 17.5 14.9772 16.8104 14.9772 15.9642V6.69359H15.678C15.9091 6.69359 16.0954 6.50721 16.0954 6.27609C16.0954 6.04498 15.9091 5.8586 15.678 5.8586V5.85487ZM7.47716 3.33872H11.9503C12.3343 3.33872 12.6511 3.65184 12.6511 4.03951V5.69831C12.6511 5.78777 12.5803 5.8586 12.4908 5.8586H6.94038C6.85092 5.8586 6.78009 5.78777 6.78009 5.69831V4.03951C6.78009 3.65557 7.09321 3.33872 7.48089 3.33872H7.47716ZM13.4414 16.6687H5.98611C5.60216 16.6687 5.28531 16.3556 5.28531 15.9679V6.69359H14.1384V15.9679C14.1384 16.3519 13.8253 16.6687 13.4376 16.6687H13.4414Z" fill="#808080"/>
|
||||
<path d="M8.22287 8.83685C7.95448 8.83685 7.73828 9.05306 7.73828 9.32145V13.6641C7.73828 13.9325 7.95448 14.1487 8.22287 14.1487C8.49126 14.1487 8.70747 13.9325 8.70747 13.6641V9.32145C8.70747 9.05306 8.49126 8.83685 8.22287 8.83685Z" fill="#808080"/>
|
||||
<path d="M11.2072 8.83685C10.9389 8.83685 10.7227 9.05306 10.7227 9.32145V13.6641C10.7227 13.9325 10.9389 14.1487 11.2072 14.1487C11.4756 14.1487 11.6918 13.9325 11.6918 13.6641V9.32145C11.6918 9.05306 11.4756 8.83685 11.2072 8.83685Z" fill="#808080"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
8
src/assets/icons/order/file.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<svg width="16" height="18" viewBox="0 0 16 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10 0.5C13.0376 0.5 15.5 2.96243 15.5 6V17.5H0.5V0.5H10Z" stroke="#232323" stroke-linejoin="round"/>
|
||||
<path d="M10.5 0.5C10.5 5 10.7 5 15.5 5" stroke="#232323"/>
|
||||
<path d="M4 8H12" stroke="#232323" stroke-linecap="round"/>
|
||||
<path d="M4 5H8" stroke="#232323" stroke-linecap="round"/>
|
||||
<path d="M4 11H12" stroke="#232323" stroke-linecap="round"/>
|
||||
<path d="M4 14H12" stroke="#232323" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 514 B |
5
src/assets/icons/order/shop.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M20 6.4C20 5.3 19.1 4 18 4H7.5C7.4 4 7.2 4 7.1 4C5.9 4.2 5 5.2 5 6.4L4 9C4 10.4 5.1 11.5 6.5 11.5C7.9 11.5 8.1 11.1 8.5 10.5C8.9 11.1 9.7 11.5 10.5 11.5C11.3 11.5 12.1 11.1 12.5 10.5C12.9 11.1 13.7 11.5 14.5 11.5C15.3 11.5 16.1 11.1 16.5 10.5C16.9 11.1 17.7 11.5 18.5 11.5C19.9 11.5 21 10.4 21 9M5.9 6.4C5.9 5.6 6.5 5 7.2 4.9C7.2 4.9 7.3 4.9 7.4 4.9H17.9C18.5 4.9 19 5.8 19 6.4C19 7 20 9.1 20 9.1C20 10 19.3 10.7 18.4 10.7C17.5 10.7 16.8 10 16.8 9.1V7.6H15.9V9.1C15.9 10 15.2 10.7 14.3 10.7C13.4 10.7 12.7 10 12.7 9.1V7.6H11.8V9.1C11.8 10 11.1 10.7 10.2 10.7C9.3 10.7 8.6 10 8.6 9.1V7.6H7.7V9.1C7.7 10 7.50938 10.7 6.60938 10.7C5.48438 10.7 4.9 9.9 4.9 9L5.9 6.4Z" fill="#232323"/>
|
||||
<path d="M6 11.2V19.3C6 19.9 6.4 20.3 7 20.3H18C18.6 20.3 19 19.9 19 19.3V13.5" stroke="#232323" stroke-linecap="round"/>
|
||||
<path d="M17 15.5V13.5C17 13.2239 16.7761 13 16.5 13C16.2239 13 16 13.2239 16 13.5V15.5C16 15.7761 16.2239 16 16.5 16C16.7761 16 17 15.7761 17 15.5Z" fill="#232323"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 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 |
5
src/assets/icons/shop.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M20 6.4C20 5.3 19.1 4 18 4H7.5C7.4 4 7.2 4 7.1 4C5.9 4.2 5 5.2 5 6.4L4 9C4 10.4 5.1 11.5 6.5 11.5C7.9 11.5 8.1 11.1 8.5 10.5C8.9 11.1 9.7 11.5 10.5 11.5C11.3 11.5 12.1 11.1 12.5 10.5C12.9 11.1 13.7 11.5 14.5 11.5C15.3 11.5 16.1 11.1 16.5 10.5C16.9 11.1 17.7 11.5 18.5 11.5C19.9 11.5 21 10.4 21 9M5.9 6.4C5.9 5.6 6.5 5 7.2 4.9C7.2 4.9 7.3 4.9 7.4 4.9H17.9C18.5 4.9 19 5.8 19 6.4C19 7 20 9.1 20 9.1C20 10 19.3 10.7 18.4 10.7C17.5 10.7 16.8 10 16.8 9.1V7.6H15.9V9.1C15.9 10 15.2 10.7 14.3 10.7C13.4 10.7 12.7 10 12.7 9.1V7.6H11.8V9.1C11.8 10 11.1 10.7 10.2 10.7C9.3 10.7 8.6 10 8.6 9.1V7.6H7.7V9.1C7.7 10 7.50938 10.7 6.60938 10.7C5.48438 10.7 4.9 9.9 4.9 9L5.9 6.4Z" fill="#232323"/>
|
||||
<path d="M6 11.2V19.3C6 19.9 6.4 20.3 7 20.3H18C18.6 20.3 19 19.9 19 19.3V13.5" stroke="#232323" stroke-width="0.75" stroke-linecap="round"/>
|
||||
<path d="M17 15.5V13.5C17 13.2239 16.7761 13 16.5 13C16.2239 13 16 13.2239 16 13.5V15.5C16 15.7761 16.2239 16 16.5 16C16.7761 16 17 15.7761 17 15.5Z" fill="#232323"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
4
src/assets/icons/statement.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.32919 0C3.73406 0 0 3.7382 0 8.33333C0 12.9285 3.7382 16.6667 8.32919 16.6667C12.9202 16.6667 16.6584 12.9285 16.6584 8.33333C16.6584 3.7382 12.9243 0 8.32919 0ZM8.32919 15.7352C4.24739 15.7352 0.927306 12.4151 0.927306 8.33333C0.927306 4.25153 4.24739 0.931445 8.32919 0.931445C12.411 0.931445 15.7311 4.25153 15.7311 8.33333C15.7311 12.4151 12.411 15.7352 8.32919 15.7352Z" fill="#979797"/>
|
||||
<path d="M8.84744 7.50551C8.84744 7.21972 8.61576 6.98804 8.32997 6.98804C8.04418 6.98804 7.8125 7.21972 7.8125 7.50551V12.4732C7.8125 12.759 8.04418 12.9907 8.32997 12.9907C8.61576 12.9907 8.84744 12.759 8.84744 12.4732V7.50551Z" fill="#979797"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 756 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/account/account-bg.png
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
BIN
src/assets/images/account/designer-lian-su.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
src/assets/images/account/item-01.png
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
BIN
src/assets/images/account/item-02.png
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
BIN
src/assets/images/account/item-03.png
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
BIN
src/assets/images/account/item-04.png
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
BIN
src/assets/images/account/item-05.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
src/assets/images/account/item-06.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
src/assets/images/account/item-07.png
Normal file
|
After Width: | Height: | Size: 1.5 MiB |
BIN
src/assets/images/account/item-08.png
Normal file
|
After Width: | Height: | Size: 1.5 MiB |
BIN
src/assets/images/account/item-09.png
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
BIN
src/assets/images/background.png
Normal file
|
After Width: | Height: | Size: 2.4 MiB |
BIN
src/assets/images/brand-null.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 713 KiB After Width: | Height: | Size: 597 KiB |
BIN
src/assets/images/brand/brandDetailBg.png
Normal file
|
After Width: | Height: | Size: 713 KiB |
BIN
src/assets/images/brand/brandLoading.gif
Normal file
|
After Width: | Height: | Size: 133 KiB |
BIN
src/assets/images/collectionStory/code-create.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 808 KiB |
BIN
src/assets/images/digitalItem/digital_item_banner.png
Normal file
|
After Width: | Height: | Size: 615 KiB |
BIN
src/assets/images/edit.png
Normal file
|
After Width: | Height: | Size: 928 B |
|
Before Width: | Height: | Size: 851 KiB |
BIN
src/assets/images/home/bg.png
Normal file
|
After Width: | Height: | Size: 652 KiB |
BIN
src/assets/images/home/designer-bg.png
Normal file
|
After Width: | Height: | Size: 688 KiB |
|
Before Width: | Height: | Size: 569 KiB |
|
Before Width: | Height: | Size: 151 KiB |
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 |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
BIN
src/assets/images/profile-content-bg.jpg
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
src/assets/images/shopping-cart-null.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
src/assets/images/wardrobe/checked.png
Normal file
|
After Width: | Height: | Size: 317 B |
BIN
src/assets/images/wardrobe/empty-wardrobe.png
Normal file
|
After Width: | Height: | Size: 8.8 KiB |
BIN
src/assets/images/wardrobe/select.png
Normal file
|
After Width: | Height: | Size: 208 B |
@@ -1,82 +1,104 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
|
||||
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({
|
||||
url: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: 'aaa'
|
||||
},
|
||||
price: {
|
||||
type: String,
|
||||
default: '111'
|
||||
},
|
||||
download: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
showPrice: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
})
|
||||
const emit = defineEmits(['addShopping', 'openDetail', 'download'])
|
||||
let data = reactive({})
|
||||
const addShopping = () => {
|
||||
emit('addShopping')
|
||||
if (props.download) {
|
||||
emit('download')
|
||||
} else {
|
||||
emit('addShopping')
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(()=>{
|
||||
})
|
||||
onUnmounted(()=>{
|
||||
})
|
||||
const openDetail = () => {
|
||||
emit('openDetail')
|
||||
}
|
||||
onMounted(() => {})
|
||||
onUnmounted(() => {})
|
||||
defineExpose({})
|
||||
const {} = toRefs(data);
|
||||
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>
|
||||
<div class="commodity-item" :class="{ 'is-download': download }">
|
||||
<img :src="props.url" alt="" @click="openDetail" />
|
||||
<div class="detail">
|
||||
<div class="text">
|
||||
<div class="name">
|
||||
{{ props.name }}
|
||||
</div>
|
||||
<div class="price" :class="{ 'is-download': download }" v-if="props.showPrice">
|
||||
{{ props.price }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn" @click="addShopping">
|
||||
<div class="text">
|
||||
<SvgIcon :name="download ? 'download' : 'add'" size="26" color="#232323"></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>
|
||||
.commodity-item {
|
||||
width: var(--commodity-width, 100%);
|
||||
&.is-download{
|
||||
img{
|
||||
cursor: initial;
|
||||
}
|
||||
}
|
||||
> img {
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
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);
|
||||
margin-bottom: var(--commodity-name-marginBottom, 0rem);
|
||||
}
|
||||
> .price {
|
||||
font-family: 'KaiseiOpti-Regular';
|
||||
font-weight: 400;
|
||||
font-size: var(--commodity-price-fontSize, 1.4rem);
|
||||
line-height: var(--commodity-price-lineHeight, 2.3rem);
|
||||
&.is-download {
|
||||
color: #979797;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.btn {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
87
src/components/checked.vue
Normal file
@@ -0,0 +1,87 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, reactive, toRefs, computed } from "vue";
|
||||
const props = defineProps({
|
||||
list:{
|
||||
type:Array,
|
||||
default:()=>[]
|
||||
},
|
||||
selected:{
|
||||
type:String,
|
||||
default:()=>''
|
||||
}
|
||||
})
|
||||
const emit = defineEmits([
|
||||
'update:selected'
|
||||
])
|
||||
const checkList = computed(()=>{
|
||||
if(props.selected[0] === ''){
|
||||
return props.list.map(item => item.value)
|
||||
}else{
|
||||
return [...props.selected]
|
||||
}
|
||||
})
|
||||
const handleChange = (val) => {
|
||||
emit('update:selected', val)
|
||||
}
|
||||
const checkAll = computed(()=>{
|
||||
return checkList.value.length === props.list.length
|
||||
})
|
||||
const handleCheckAllChange = (val) => {
|
||||
if(val){
|
||||
emit('update:selected', props.list.map(item => item.value))
|
||||
}else{
|
||||
emit('update:selected', [])
|
||||
}
|
||||
}
|
||||
let data = reactive({
|
||||
})
|
||||
onMounted(()=>{
|
||||
})
|
||||
onUnmounted(()=>{
|
||||
})
|
||||
defineExpose({})
|
||||
const {} = toRefs(data);
|
||||
</script>
|
||||
<template>
|
||||
<div class="all">
|
||||
<el-checkbox
|
||||
v-model="checkAll"
|
||||
@change="handleCheckAllChange"
|
||||
>
|
||||
All
|
||||
</el-checkbox>
|
||||
</div>
|
||||
<el-checkbox-group v-model="checkList" @change="handleChange">
|
||||
<el-checkbox
|
||||
v-for="item in props.list"
|
||||
:key="item.value"
|
||||
:value="item.value"
|
||||
>
|
||||
{{ item.label }}
|
||||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</template>
|
||||
<style lang="less" scoped>
|
||||
.all{
|
||||
margin-bottom: 1.2rem;
|
||||
}
|
||||
.el-checkbox-group{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.2rem;
|
||||
}
|
||||
label{
|
||||
--el-checkbox-font-size: 1.6rem;
|
||||
--el-checkbox-checked-text-color: #232323;
|
||||
--el-checkbox-font-weight: 400;
|
||||
--el-checkbox-height: 2rem;
|
||||
--el-checkbox-checked-bg-color: #232323;
|
||||
--el-checkbox-checked-input-border-color: #232323;
|
||||
--el-checkbox-input-border: 1px solid #232323;
|
||||
font-family: "KaiseiOpti-Regular";
|
||||
line-height: 2rem;
|
||||
.el-checkbox__label{
|
||||
padding-left: 1.4rem;
|
||||
}
|
||||
}
|
||||
</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>
|
||||
125
src/lang/en.ts
@@ -2,5 +2,130 @@ export default {
|
||||
Login: {
|
||||
login: 'Log in',
|
||||
register: 'Register',
|
||||
loginTip: 'Platform integrated with AiDA.<br />AiDA account login required.',
|
||||
name: 'Name',
|
||||
email: 'Email',
|
||||
password: 'Password',
|
||||
passwordConfirmation: 'Password Confirmation',
|
||||
enterName: 'Enter your name',
|
||||
enterEmail: 'Enter your email',
|
||||
enterPassword: 'Enter your password',
|
||||
enterPasswordAgain: 'Enter your password again',
|
||||
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'
|
||||
},
|
||||
Settings: {
|
||||
title: 'Settings',
|
||||
slogan: 'Manage your account settings and preferences',
|
||||
profile: {
|
||||
title: 'Profile',
|
||||
description: 'Update your display name, avatar, social links and account security.',
|
||||
firstName: 'FIRST NAME',
|
||||
lastName: 'LAST NAME',
|
||||
firstNamePlaceholder: 'First Name',
|
||||
lastNamePlaceholder: 'Last Name',
|
||||
username: 'USERNAME',
|
||||
usernamePlaceholder: 'Username',
|
||||
usernameTip: 'Your public username on Stylish Parade.',
|
||||
role: 'ROLE',
|
||||
roleTip: 'Select up to 2 labels that suit you.',
|
||||
},
|
||||
security: {
|
||||
title: 'Security',
|
||||
description: 'Manage your login email and password.',
|
||||
email: 'EMAIL',
|
||||
newEmail: 'NEW EMAIL ADDRESS',
|
||||
newEmailPlaceholder: 'Enter new email',
|
||||
verify: 'Verify',
|
||||
verified: 'Verified',
|
||||
verifiedTip: 'Your new email has been verified and is ready to save.',
|
||||
password: 'PASSWORD',
|
||||
newPassword: 'NEW PASSWORD',
|
||||
newPasswordPlaceholder: 'Enter new password',
|
||||
passwordTip: 'You must satisfy ALL password conditions to register.',
|
||||
currentPassword: 'CURRENT PASSWORD',
|
||||
currentPasswordPlaceholder: 'Confirm with your password'
|
||||
},
|
||||
region: {
|
||||
title: 'Language & Region',
|
||||
description: 'Set your preferred language, region and currency display.',
|
||||
displayLanguage: 'DISPLAY LANGUAGE',
|
||||
selectLanguage: 'Select language',
|
||||
region: 'REGION',
|
||||
selectRegion: 'Select region'
|
||||
},
|
||||
buttons: {
|
||||
cancel: 'CANCEL',
|
||||
discard: 'DISCARD',
|
||||
edit: 'EDIT',
|
||||
saveChange: 'SAVE CHANGE',
|
||||
saving: 'SAVING...'
|
||||
},
|
||||
dialog: {
|
||||
title: 'Check your new email',
|
||||
subtitle: 'Enter the 6-digit code sent to',
|
||||
submit: 'Submit',
|
||||
resendCode: 'Resend Code',
|
||||
resendCodeIn: 'Resend Code in {time}'
|
||||
},
|
||||
messages: {
|
||||
enterNewEmailFirst: 'Please enter your new email address first',
|
||||
invalidEmail: 'Please enter a valid email address',
|
||||
sameEmail: 'Please enter a different email address',
|
||||
alreadyVerified: 'This email has already been verified',
|
||||
verificationCodeSent: 'Verification code sent',
|
||||
enterVerificationCode: 'Please enter the 6-digit verification code',
|
||||
verificationCompleted: 'Email verification completed',
|
||||
verifyEmailBeforeSave: 'Please verify your new email before saving',
|
||||
settingsUpdated: 'Settings updated'
|
||||
},
|
||||
roles: {
|
||||
fashionEnthusiast: 'Fashion Enthusiast',
|
||||
contentCreator: 'Content Creator',
|
||||
student: 'Student',
|
||||
retailBuyer: 'Retail / Buyer',
|
||||
fashionDesigner: 'Fashion Designer',
|
||||
brandBusiness: 'Brand / Business',
|
||||
prCommunications: 'PR & Communications',
|
||||
stylist: 'Stylist',
|
||||
graphicDesigner: 'Graphic Designer',
|
||||
artist3d: '3D Artist',
|
||||
other: 'Other'
|
||||
},
|
||||
languages: {
|
||||
english: 'English',
|
||||
chinese: 'Chinese',
|
||||
},
|
||||
regions: {
|
||||
hongKongSar: 'Hong Kong SAR',
|
||||
mainlandChina: 'Mainland China',
|
||||
singapore: 'Singapore',
|
||||
unitedKingdom: 'United Kingdom'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,5 +2,130 @@ export default {
|
||||
Login: {
|
||||
login: '登录',
|
||||
register: '注册',
|
||||
loginTip: '与 AiDA 集成的平台。<br />需要登录 AiDA 账户。',
|
||||
name: '姓名',
|
||||
email: '邮箱',
|
||||
password: '密码',
|
||||
passwordConfirmation: '密码确认',
|
||||
enterName: '请输入姓名',
|
||||
enterEmail: '请输入邮箱',
|
||||
enterPassword: '请输入密码',
|
||||
enterPasswordAgain: '请输入密码确认',
|
||||
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: '找回密码'
|
||||
},
|
||||
Settings: {
|
||||
title: '设置',
|
||||
slogan: '管理你的账户设置和偏好',
|
||||
profile: {
|
||||
title: '个人资料',
|
||||
description: '更新你的显示名称、头像、社交链接和账户安全信息。',
|
||||
firstName: '名字',
|
||||
lastName: '姓氏',
|
||||
firstNamePlaceholder: '请输入名字',
|
||||
lastNamePlaceholder: '请输入姓氏',
|
||||
username: '用户名',
|
||||
usernamePlaceholder: '请输入用户名',
|
||||
usernameTip: '这是你在 Stylish Parade 上公开显示的用户名。',
|
||||
role: '身份标签',
|
||||
roleTip: '最多选择 2 个符合你的标签。',
|
||||
},
|
||||
security: {
|
||||
title: '安全',
|
||||
description: '管理你的登录邮箱和密码。',
|
||||
email: '邮箱',
|
||||
newEmail: '新邮箱地址',
|
||||
newEmailPlaceholder: '请输入新邮箱',
|
||||
verify: '验证',
|
||||
verified: '已验证',
|
||||
verifiedTip: '你的新邮箱已验证成功,可以保存。',
|
||||
password: '密码',
|
||||
newPassword: '新密码',
|
||||
newPasswordPlaceholder: '请输入新密码',
|
||||
passwordTip: '你必须满足所有密码条件才能注册。',
|
||||
currentPassword: '当前密码',
|
||||
currentPasswordPlaceholder: '请输入当前密码确认'
|
||||
},
|
||||
region: {
|
||||
title: '语言与地区',
|
||||
description: '设置你偏好的语言、地区和货币显示方式。',
|
||||
displayLanguage: '显示语言',
|
||||
selectLanguage: '请选择语言',
|
||||
region: '地区',
|
||||
selectRegion: '请选择地区'
|
||||
},
|
||||
buttons: {
|
||||
cancel: '取消',
|
||||
discard: '放弃',
|
||||
edit: '编辑',
|
||||
saveChange: '保存更改',
|
||||
saving: '保存中...'
|
||||
},
|
||||
dialog: {
|
||||
title: '检查你的新邮箱',
|
||||
subtitle: '请输入发送到以下邮箱的 6 位验证码',
|
||||
submit: '提交',
|
||||
resendCode: '重新发送验证码',
|
||||
resendCodeIn: '{time} 后可重新发送验证码'
|
||||
},
|
||||
messages: {
|
||||
enterNewEmailFirst: '请先输入新的邮箱地址',
|
||||
invalidEmail: '请输入有效的邮箱地址',
|
||||
sameEmail: '请输入不同的邮箱地址',
|
||||
alreadyVerified: '该邮箱已完成验证',
|
||||
verificationCodeSent: '验证码已发送',
|
||||
enterVerificationCode: '请输入 6 位验证码',
|
||||
verificationCompleted: '邮箱验证完成',
|
||||
verifyEmailBeforeSave: '请先完成新邮箱验证再保存',
|
||||
settingsUpdated: '设置已更新'
|
||||
},
|
||||
roles: {
|
||||
fashionEnthusiast: '时尚爱好者',
|
||||
contentCreator: '内容创作者',
|
||||
student: '学生',
|
||||
retailBuyer: '零售 / 买手',
|
||||
fashionDesigner: '服装设计师',
|
||||
brandBusiness: '品牌 / 商业',
|
||||
prCommunications: '公关与传播',
|
||||
stylist: '造型师',
|
||||
graphicDesigner: '平面设计师',
|
||||
artist3d: '3D 艺术家',
|
||||
other: '其他'
|
||||
},
|
||||
languages: {
|
||||
english: '英文',
|
||||
chinese: '中文',
|
||||
},
|
||||
regions: {
|
||||
hongKongSar: '中国香港特别行政区',
|
||||
mainlandChina: '中国大陆',
|
||||
singapore: '新加坡',
|
||||
unitedKingdom: '英国'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,37 +7,77 @@ import { createRouter, createWebHistory } from 'vue-router'
|
||||
* 3. 路由的name默认是文件名,如果文件名与name不一致,通过defineOptions({ name: 'componentName' })来设置
|
||||
*/
|
||||
const router = createRouter({
|
||||
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'),
|
||||
},
|
||||
]
|
||||
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: '/brand/:id',
|
||||
name: 'brandDetail',
|
||||
component: () => import('../views/brandDetail/index.vue')
|
||||
},
|
||||
{
|
||||
path: '/digitalItem',
|
||||
name: 'digitalItem',
|
||||
component: () => import('../views/digitalItem/index.vue'),
|
||||
meta: { cache: true }
|
||||
},
|
||||
{
|
||||
path: '/digitalItem/:id',
|
||||
name: 'digitalItemDetail',
|
||||
component: () => import('../views/digitalDetail/index.vue')
|
||||
},
|
||||
{
|
||||
path: '/settings',
|
||||
name: 'settings',
|
||||
component: () => import('@/views/setting/index.vue'),
|
||||
meta: { cache: true }
|
||||
},
|
||||
{
|
||||
path: '/shoppingCart', // 购物车
|
||||
name: 'shoppingCart',
|
||||
component: () => import('@/views/shoppingCart/index.vue')
|
||||
},
|
||||
{
|
||||
path: '/notifications',
|
||||
name: 'notifications',
|
||||
component: () => import('@/views/notifications/index.vue')
|
||||
},
|
||||
{
|
||||
path: '/wardrobe',
|
||||
name: 'wardrobe',
|
||||
component: () => import('@/views/wardrobe/index.vue')
|
||||
},
|
||||
{
|
||||
path:'/account',
|
||||
name:'account',
|
||||
component:()=>import('@/views/account/index.vue')
|
||||
},
|
||||
{
|
||||
path: '/:pathMatch(.*)',
|
||||
name: '404',
|
||||
component: () => import('../views/404.vue')
|
||||
}
|
||||
],
|
||||
history: createWebHistory('/')
|
||||
})
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
next()
|
||||
next()
|
||||
})
|
||||
|
||||
router.afterEach(() => {
|
||||
})
|
||||
router.afterEach(() => {})
|
||||
|
||||
export default router
|
||||
|
||||
@@ -53,9 +53,9 @@ export function generateUUID(): string {
|
||||
if (typeof crypto !== 'undefined' && crypto.randomUUID) {
|
||||
return crypto.randomUUID()
|
||||
}
|
||||
|
||||
|
||||
// 备用方案:手动生成UUID v4
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
||||
const r = Math.random() * 16 | 0
|
||||
const v = c === 'x' ? r : (r & 0x3 | 0x8)
|
||||
return v.toString(16)
|
||||
@@ -70,39 +70,37 @@ export {
|
||||
|
||||
/** 时间格式化-自定义格式
|
||||
* @param value 时间对象|时间戳|时间字符串
|
||||
* @param format 格式化字符串,默认值为 'yyyy-MM-dd HH:mm:ss'
|
||||
* @param format 格式化字符串,默认值为 'YYYY-MM-DD HH:mm:ss'
|
||||
* @returns 格式化后的时间字符串
|
||||
*/
|
||||
export function FormatDate(value: Date | number | string, format: string = 'yyyy-MM-dd HH:mm:ss') {
|
||||
const date = new Date(value);
|
||||
const yyyy = String(date.getFullYear());
|
||||
const yy = String(date.getFullYear()).slice(-2);
|
||||
const MM = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const M = String(date.getMonth() + 1);
|
||||
const dd = String(date.getDate()).padStart(2, '0');
|
||||
const d = String(date.getDate());
|
||||
const HH = String(date.getHours()).padStart(2, '0');
|
||||
const H = String(date.getHours());
|
||||
const mm = String(date.getMinutes()).padStart(2, '0');
|
||||
const m = String(date.getMinutes());
|
||||
const ss = String(date.getSeconds()).padStart(2, '0');
|
||||
const s = String(date.getSeconds());
|
||||
const str = format.replaceAll('yyyy', yyyy)
|
||||
.replaceAll('yy', yy)
|
||||
.replaceAll('MM', MM)
|
||||
.replaceAll('M', M)
|
||||
.replaceAll('dd', dd)
|
||||
.replaceAll('d', d)
|
||||
.replaceAll('HH', HH)
|
||||
.replaceAll('H', H)
|
||||
.replaceAll('mm', mm)
|
||||
.replaceAll('m', m)
|
||||
.replaceAll('ss', ss)
|
||||
.replaceAll('s', s);
|
||||
return str;
|
||||
export function FormatDate(value: Date | number | string, format: string = 'YYYY-MM-DD HH:mm:ss') {
|
||||
const d = new Date(value);
|
||||
if (!d || isNaN(d.getTime())) return 'Invalid Date';
|
||||
const pad = (n) => String(n).padStart(2, '0');
|
||||
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
||||
const tokens = {
|
||||
YYYY: d.getFullYear(),
|
||||
YY: String(d.getFullYear()).slice(-2),
|
||||
MM: pad(d.getMonth() + 1),
|
||||
M: d.getMonth() + 1,
|
||||
SM: months[d.getMonth()],
|
||||
DD: pad(d.getDate()),
|
||||
D: d.getDate(),
|
||||
HH: pad(d.getHours()),
|
||||
H: d.getHours(),
|
||||
hh: pad(d.getHours() % 12 || 12),
|
||||
h: d.getHours() % 12 || 12,
|
||||
mm: pad(d.getMinutes()),
|
||||
m: d.getMinutes(),
|
||||
ss: pad(d.getSeconds()),
|
||||
s: d.getSeconds(),
|
||||
A: d.getHours() < 12 ? 'AM' : 'PM',
|
||||
a: d.getHours() < 12 ? 'am' : 'pm'
|
||||
}
|
||||
const reg = new RegExp(Object.keys(tokens).join('|'), 'g')
|
||||
return format.replace(reg, match => tokens[match]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 下载图片
|
||||
* @param list 图片列表
|
||||
@@ -163,7 +161,7 @@ export function encryptPassword(password: string): string {
|
||||
* @param url 图片URL
|
||||
* @returns 无
|
||||
*/
|
||||
export async function shareImageToWhatsapp (url: string){
|
||||
export async function shareImageToWhatsapp(url: string) {
|
||||
// 把图片 URL 转为 Blob
|
||||
const blob = await fetch(url).then((res) => res.blob())
|
||||
|
||||
@@ -183,4 +181,33 @@ 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}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 字节转换为可读格式
|
||||
* @param {number} bytes - 字节数
|
||||
* @param {number} options - 选项对象
|
||||
* @param {number} options.decimals - 保留小数位数,默认2位
|
||||
* @param {boolean} options.unitBig - 是否使用大写单位,默认false
|
||||
* @returns {string} 格式化后的字符串
|
||||
*/
|
||||
export function FormatBytes(bytes, options: { decimals?: number, unitBig?: boolean } = {}) {
|
||||
const { decimals = 2, unitBig = false } = options;
|
||||
if (!bytes || isNaN(bytes)) return unitBig ? '0 B' : '0 b';
|
||||
const k = 1024;
|
||||
const sizes = unitBig ? ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] : ['b', 'kb', 'mb', 'gb', 'tb', 'pb', 'eb', 'zb', 'yb'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
const value = bytes / Math.pow(k, i);
|
||||
return `${Number(value.toFixed(decimals))} ${sizes[i]}`;
|
||||
}
|
||||
505
src/views/account/index.vue
Normal file
@@ -0,0 +1,505 @@
|
||||
<template>
|
||||
<div class="account-container">
|
||||
<img src="@/assets/images/account/account-bg.png" alt="" class="banner" />
|
||||
|
||||
<div class="account-main">
|
||||
<aside class="designer-panel">
|
||||
<div class="designer-avatar">
|
||||
<img :src="designerPortrait" alt="Lian Su" />
|
||||
</div>
|
||||
|
||||
<div class="designer-content">
|
||||
<section class="designer-heading">
|
||||
<div class="designer-name">Lian Su</div>
|
||||
<h1 class="designer-title">Roaming Clouds</h1>
|
||||
</section>
|
||||
|
||||
<section class="designer-section">
|
||||
<h2 class="section-title">Contact</h2>
|
||||
<div class="contact-list">
|
||||
<div class="contact-item">
|
||||
<SvgIcon name="brand-email" size="24" />
|
||||
<span>lian.su.studio@mail.com</span>
|
||||
</div>
|
||||
<div class="contact-item">
|
||||
<SvgIcon name="brand-call" size="24" />
|
||||
<span>+86 139 4829 7710</span>
|
||||
</div>
|
||||
<div class="contact-item">
|
||||
<SvgIcon name="brand-link" size="24" />
|
||||
<span>746312432</span>
|
||||
</div>
|
||||
<div class="contact-item">
|
||||
<SvgIcon name="brand-link" size="24" />
|
||||
<span>https://urieworweoo.com</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="designer-section">
|
||||
<h2 class="section-title">About</h2>
|
||||
<p class="designer-about">
|
||||
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.
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<section class="items-section">
|
||||
<header class="items-header">
|
||||
<h2 class="items-title">Items</h2>
|
||||
<div class="items-tabs" role="tablist" aria-label="Item gender filters">
|
||||
<button
|
||||
v-for="tab in tabs"
|
||||
:key="tab"
|
||||
type="button"
|
||||
class="items-tab"
|
||||
:class="{ active: activeTab === tab }"
|
||||
:aria-selected="activeTab === tab"
|
||||
role="tab"
|
||||
@click="activeTab = tab"
|
||||
>
|
||||
{{ tab }}
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="items-grid">
|
||||
<article v-for="item in visibleItems" :key="item.id" class="product-card">
|
||||
<div class="product-image">
|
||||
<img
|
||||
:src="item.url"
|
||||
:alt="item.title"
|
||||
:style="{ transform: `translateY(${item.imageOffset})` }"
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
<div class="product-info">
|
||||
<div class="product-copy">
|
||||
<h3 class="product-name">{{ item.title }}</h3>
|
||||
<div class="product-price">{{ item.price }}</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="add-button"
|
||||
:aria-label="`Add ${item.title} to cart`"
|
||||
@click="addShopping(item)"
|
||||
>
|
||||
<SvgIcon name="add" size="24" color="#232323" />
|
||||
</button>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, shallowRef } from 'vue'
|
||||
import myEvent from '@/utils/myEvent'
|
||||
import designerPortrait from '@/assets/images/account/designer-lian-su.png'
|
||||
import item01 from '@/assets/images/account/item-01.png'
|
||||
import item02 from '@/assets/images/account/item-02.png'
|
||||
import item03 from '@/assets/images/account/item-03.png'
|
||||
import item04 from '@/assets/images/account/item-04.png'
|
||||
import item05 from '@/assets/images/account/item-05.png'
|
||||
import item06 from '@/assets/images/account/item-06.png'
|
||||
import item07 from '@/assets/images/account/item-07.png'
|
||||
import item08 from '@/assets/images/account/item-08.png'
|
||||
import item09 from '@/assets/images/account/item-09.png'
|
||||
|
||||
type Gender = 'Male' | 'Female'
|
||||
type Tab = 'All' | Gender
|
||||
|
||||
interface AccountItem {
|
||||
id: number
|
||||
title: string
|
||||
price: string
|
||||
url: string
|
||||
gender: Gender
|
||||
imageOffset: string
|
||||
}
|
||||
|
||||
const tabs: Tab[] = ['All', 'Male', 'Female']
|
||||
const activeTab = shallowRef<Tab>('All')
|
||||
|
||||
const items: AccountItem[] = [
|
||||
{ id: 1, title: 'Item Name', price: '$430', url: item01, gender: 'Female', imageOffset: '0rem' },
|
||||
{ id: 2, title: 'Item Name', price: '$392', url: item02, gender: 'Female', imageOffset: '0rem' },
|
||||
{ id: 3, title: 'Item Name', price: '$211', url: item03, gender: 'Male', imageOffset: '0rem' },
|
||||
{ id: 4, title: 'Item Name', price: '$187', url: item04, gender: 'Female', imageOffset: '0rem' },
|
||||
{ id: 5, title: 'Item Name', price: '$325', url: item05, gender: 'Male', imageOffset: '-1.4rem' },
|
||||
{ id: 6, title: 'Item Name', price: '$458', url: item06, gender: 'Female', imageOffset: '-0.4rem' },
|
||||
{ id: 7, title: 'Item Name', price: '$192', url: item07, gender: 'Male', imageOffset: '-3.6rem' },
|
||||
{ id: 8, title: 'Item Name', price: '$93', url: item08, gender: 'Female', imageOffset: '0rem' },
|
||||
{ id: 9, title: 'Item Name', price: '$198', url: item09, gender: 'Male', imageOffset: '-3.8rem' }
|
||||
]
|
||||
|
||||
const visibleItems = computed(() => {
|
||||
if (activeTab.value === 'All') return items
|
||||
return items.filter((item) => item.gender === activeTab.value)
|
||||
})
|
||||
|
||||
const addShopping = (item: AccountItem) => {
|
||||
myEvent.emit('addShopping', item)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.account-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
overflow-y: auto;
|
||||
background-color: #ffffff;
|
||||
|
||||
.banner {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 27.6rem;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.account-main {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin: 0 9rem;
|
||||
min-height: 170.3rem;
|
||||
border-top: 0.5px solid #585858;
|
||||
}
|
||||
|
||||
.designer-panel {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
width: 29.7rem;
|
||||
height: var(--app-view-height);
|
||||
padding-top: 4rem;
|
||||
overflow-y: auto;
|
||||
flex: 0 0 29.7rem;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.designer-avatar {
|
||||
width: 20rem;
|
||||
height: 20rem;
|
||||
margin-left: 5.5rem;
|
||||
border: 0.1rem solid #d2d2d7;
|
||||
|
||||
> img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.designer-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6rem;
|
||||
width: 24.6rem;
|
||||
margin-top: 4rem;
|
||||
margin-left: 3.7rem;
|
||||
}
|
||||
|
||||
.designer-heading,
|
||||
.designer-section {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.designer-name {
|
||||
margin-bottom: 0.8rem;
|
||||
font-family: KaiseiOpti-Medium;
|
||||
font-size: 1.8rem;
|
||||
font-weight: 500;
|
||||
line-height: 1;
|
||||
color: #232323;
|
||||
}
|
||||
|
||||
.designer-title,
|
||||
.section-title {
|
||||
margin: 0;
|
||||
font-family: KaiseiOpti-Bold;
|
||||
font-weight: 700;
|
||||
color: #121212;
|
||||
}
|
||||
|
||||
.designer-title {
|
||||
width: 23.1rem;
|
||||
font-size: 3.4rem;
|
||||
line-height: 3.6rem;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
margin-bottom: 2rem;
|
||||
font-size: 3.4rem;
|
||||
line-height: 3.1rem;
|
||||
}
|
||||
|
||||
.contact-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.6rem;
|
||||
}
|
||||
|
||||
.contact-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2rem;
|
||||
min-width: 0;
|
||||
font-family: KaiseiOpti-Regular;
|
||||
font-size: 1.4rem;
|
||||
font-weight: 400;
|
||||
line-height: 2.4rem;
|
||||
color: #585858;
|
||||
|
||||
> :first-child {
|
||||
width: 2.4rem;
|
||||
height: 2.4rem;
|
||||
flex: 0 0 2.4rem;
|
||||
}
|
||||
|
||||
> span {
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
}
|
||||
|
||||
.designer-about {
|
||||
width: 22rem;
|
||||
margin: 0;
|
||||
font-family: KaiseiOpti-Regular;
|
||||
font-size: 1.6rem;
|
||||
font-weight: 400;
|
||||
line-height: 2.3rem;
|
||||
color: #585858;
|
||||
}
|
||||
|
||||
.items-section {
|
||||
flex: 1;
|
||||
min-height: 170.3rem;
|
||||
border-left: 0.5px solid #585858;
|
||||
border-right: 0.5px solid #585858;
|
||||
}
|
||||
|
||||
.items-header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 2;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.items-title {
|
||||
margin: 0;
|
||||
padding: 4rem 0 3.6rem 1.2rem;
|
||||
font-family: KaiseiOpti-Bold;
|
||||
font-size: 3.6rem;
|
||||
font-weight: 700;
|
||||
line-height: 6rem;
|
||||
color: #121212;
|
||||
}
|
||||
|
||||
.items-tabs {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
padding: 0 1.2rem;
|
||||
margin-bottom: 5.9rem;
|
||||
}
|
||||
|
||||
.items-tab {
|
||||
position: relative;
|
||||
min-width: 6rem;
|
||||
padding: 0;
|
||||
border: none;
|
||||
background: transparent;
|
||||
font-family: KaiseiOpti-Regular;
|
||||
font-size: 1.988rem;
|
||||
font-weight: 400;
|
||||
line-height: 2.6rem;
|
||||
text-align: center;
|
||||
color: #7b7b7b;
|
||||
cursor: pointer;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: -0.1rem;
|
||||
display: none;
|
||||
width: 100%;
|
||||
border-bottom: 0.1rem solid #232323;
|
||||
}
|
||||
|
||||
&.active {
|
||||
font-family: KaiseiOpti-Bold;
|
||||
font-weight: 700;
|
||||
color: #232323;
|
||||
|
||||
&::after {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.items-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
align-content: start;
|
||||
border-top: 0.5px solid #585858;
|
||||
}
|
||||
|
||||
.product-card {
|
||||
position: relative;
|
||||
min-height: 44.7rem;
|
||||
padding: 1.2rem;
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&::before {
|
||||
top: 0;
|
||||
right: 0;
|
||||
height: 100%;
|
||||
border-right: 0.5px solid #585858;
|
||||
}
|
||||
|
||||
&::after {
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
border-bottom: 0.5px solid #585858;
|
||||
}
|
||||
|
||||
&:nth-child(3n)::before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.product-image {
|
||||
height: 37.5rem;
|
||||
margin-bottom: 0.8rem;
|
||||
overflow: hidden;
|
||||
background-color: #f7f7f7;
|
||||
|
||||
> img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
will-change: transform;
|
||||
}
|
||||
}
|
||||
|
||||
.product-info {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 1.2rem;
|
||||
}
|
||||
|
||||
.product-copy {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.product-name {
|
||||
margin: 0;
|
||||
font-family: KaiseiOpti-Regular;
|
||||
font-size: 1.6rem;
|
||||
font-weight: 400;
|
||||
line-height: 2.3rem;
|
||||
color: #232323;
|
||||
}
|
||||
|
||||
.product-price {
|
||||
font-family: KaiseiOpti-Regular;
|
||||
font-size: 1.4rem;
|
||||
font-weight: 400;
|
||||
line-height: 2.3rem;
|
||||
color: #585858;
|
||||
}
|
||||
|
||||
.add-button {
|
||||
width: 2.4rem;
|
||||
height: 2.4rem;
|
||||
padding: 0;
|
||||
margin-top: 1rem;
|
||||
border: none;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
flex: 0 0 2.4rem;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.account-main {
|
||||
display: block;
|
||||
margin: 0 2.4rem;
|
||||
min-height: auto;
|
||||
}
|
||||
|
||||
.designer-panel {
|
||||
position: static;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
padding: 4rem 0;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.designer-avatar {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.designer-content {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.items-section {
|
||||
min-height: auto;
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.items-header {
|
||||
position: static;
|
||||
}
|
||||
|
||||
.items-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.product-card:nth-child(3n)::before {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.product-card:nth-child(2n)::before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.account-main {
|
||||
margin: 0 1.6rem;
|
||||
}
|
||||
|
||||
.items-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.product-card::before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
127
src/views/brand/brand-item.vue
Normal file
@@ -0,0 +1,127 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
|
||||
const props = defineProps({
|
||||
item:{
|
||||
type:Object,
|
||||
default:()=>{},
|
||||
},
|
||||
})
|
||||
const emit = defineEmits([
|
||||
'viewProfile',
|
||||
])
|
||||
const viewProfile = (item) => {
|
||||
emit('viewProfile', item)
|
||||
}
|
||||
let data = reactive({
|
||||
})
|
||||
onMounted(()=>{
|
||||
})
|
||||
onUnmounted(()=>{
|
||||
})
|
||||
defineExpose({})
|
||||
const {} = toRefs(data);
|
||||
</script>
|
||||
<template>
|
||||
<div class="item">
|
||||
<div class="left">
|
||||
<div class="portrait">
|
||||
<img :src="item.portrait" alt="">
|
||||
</div>
|
||||
<div class="info">
|
||||
<div class="name">{{ item.name }}</div>
|
||||
<div class="collection">
|
||||
{{ item.collectionsName }} |
|
||||
{{ item?.collections?.length || 0 }} Collections
|
||||
</div>
|
||||
<div class="view-profile" @click="viewProfile(item)">View Profile</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right">
|
||||
<div class="img-list">
|
||||
<div class="img-item" v-for="itemImg in item?.collections?.slice(0,5)" :key="item.id">
|
||||
<img :src="itemImg" alt="">
|
||||
</div>
|
||||
</div>
|
||||
<div class="more">
|
||||
<div class="icon" v-show="item?.collections?.length > 5">
|
||||
<svgIcon name="brand-more" size="24" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="less" scoped>
|
||||
.item{
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 14rem;
|
||||
justify-content: space-between;
|
||||
border-bottom: 0.5px solid #C4C4C4;
|
||||
> .left{
|
||||
display: flex;
|
||||
.portrait{
|
||||
width: 8rem;
|
||||
height: 8rem;
|
||||
margin-right: 2.4rem;
|
||||
> img{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
.info{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
> .name{
|
||||
font-family: "KaiseiOpti-Bold";
|
||||
font-weight: 700;
|
||||
font-size: 2rem;
|
||||
line-height: 100%;
|
||||
}
|
||||
> .collection{
|
||||
color: #7B7B7B;
|
||||
font-family: "KaiseiOpti-Regular";
|
||||
font-weight: 400;
|
||||
font-size: 1.2rem;
|
||||
line-height: 2.3rem;
|
||||
margin-top: .4rem;
|
||||
}
|
||||
> .view-profile{
|
||||
margin-top: 2.4rem;
|
||||
border-bottom: 2px solid #585858;
|
||||
background: #F9F9F9;
|
||||
font-family: "KaiseiOpti-Regular";
|
||||
font-weight: 400;
|
||||
font-size: 1.4rem;
|
||||
letter-spacing: -0.4px;
|
||||
line-height: 3.4rem;
|
||||
padding: 0 2.55rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
> .right{
|
||||
display: flex;
|
||||
> .img-list{
|
||||
display: flex;
|
||||
gap: 3.2rem;
|
||||
> .img-item{
|
||||
width: 9.2rem;
|
||||
height: 12rem;
|
||||
img{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
}
|
||||
> .more{
|
||||
width: 8rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,14 +1,108 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
|
||||
import CommodityList from "./commodity-list.vue";
|
||||
import MerchantInfo from "./merchant-info.vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import myEvent from '@/utils/myEvent'
|
||||
import scListNull from '@/views/shoppingCart/sc-list-null.vue'
|
||||
import brandItem from '@/views/brand/brand-item.vue'
|
||||
|
||||
import img from '@/assets/images/collectionStory/Rectangle.png'
|
||||
//const props = defineProps({
|
||||
//})
|
||||
//const emit = defineEmits([
|
||||
//])
|
||||
const router = useRouter()
|
||||
let data = reactive({
|
||||
})
|
||||
const addShopping = (item) => {}
|
||||
|
||||
const searchBrand = ref('')
|
||||
const merchantList = ref([
|
||||
])
|
||||
const getMerchantData = reactive({
|
||||
pageSize: 10,
|
||||
pageNum: 1,
|
||||
isShowMark:false,
|
||||
isNoData:false,
|
||||
})
|
||||
const list = ref([
|
||||
' 1',
|
||||
'Brand 2',
|
||||
'Brand 3',
|
||||
'1213123 4',
|
||||
'Brand 4',
|
||||
'2222 4',
|
||||
'B23rand 4',
|
||||
'Bran112222d 4',
|
||||
' 4',
|
||||
])
|
||||
|
||||
let changeSearchBrandTime = null
|
||||
const changeSearchBrand = () => {
|
||||
clearTimeout(changeSearchBrandTime)
|
||||
changeSearchBrandTime = setTimeout(()=>{
|
||||
getMerchantData.pageNum = 1
|
||||
merchantList.value = []
|
||||
getMerchantData.isShowMark = false
|
||||
getMerchantData.isNoData = false
|
||||
},300)
|
||||
}
|
||||
|
||||
const getBrandList = async () => {
|
||||
if(getMerchantData.isShowMark && !getMerchantData.isNoData)return
|
||||
getMerchantData.isShowMark = true
|
||||
let value = {
|
||||
pageSize: getMerchantData.pageSize,
|
||||
pageNum: getMerchantData.pageNum,
|
||||
status: 1,
|
||||
}
|
||||
setTimeout(()=>{
|
||||
if(merchantList.value.length >= 5){
|
||||
getMerchantData.isNoData = true
|
||||
merchantList.value = []
|
||||
return
|
||||
}
|
||||
getMerchantData.pageNum += 1
|
||||
merchantList.value.push({
|
||||
name:'Roaming Clouds',
|
||||
portrait: img,
|
||||
collectionsName:'by Lian Su ',
|
||||
collections:[
|
||||
img,img,img,
|
||||
],
|
||||
})
|
||||
getMerchantData.isShowMark = false
|
||||
},1000)
|
||||
// await getPublishList(value).then((res)=>{
|
||||
// if(res.content.length == 0)getMerchantData.isNoData = true
|
||||
// getMerchantData.pageNum += 1
|
||||
// list.value.push(...res.content)
|
||||
// })
|
||||
}
|
||||
const vObserve = {
|
||||
mounted (el,binding) {
|
||||
getMerchantData.isShowMark = false
|
||||
getMerchantData.isNoData = false
|
||||
new IntersectionObserver(
|
||||
(entries, observer) => {
|
||||
// 如果不是相交,则直接返回
|
||||
// console.log(entries[0]);
|
||||
if (!entries[0].intersectionRatio) return;
|
||||
getMerchantData.pageNum += 1
|
||||
binding.value()
|
||||
},
|
||||
// { root:worksPage }
|
||||
).observe(el);
|
||||
}
|
||||
}
|
||||
|
||||
const deleteHistory = (item) => {
|
||||
list.value = list.value.filter((i) => i != item)
|
||||
}
|
||||
const viewProfile = (item) => {
|
||||
router.push({
|
||||
path:'/brand/1',
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(()=>{
|
||||
})
|
||||
onUnmounted(()=>{
|
||||
@@ -18,15 +112,52 @@ const {} = toRefs(data);
|
||||
</script>
|
||||
<template>
|
||||
<div class="brand">
|
||||
<div class="header-img">
|
||||
<div class="header-img" :class="{'active': searchBrand.length > 0}">
|
||||
<img src="@/assets/images/brand/brandBg.png" alt="">
|
||||
<div class="text-box">
|
||||
<div class="title">Brand</div>
|
||||
<span>Every brand, every story — discover who's behind the collections.</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="merchant-info">
|
||||
<MerchantInfo></MerchantInfo>
|
||||
<div class="input">
|
||||
<input type="text" v-model="searchBrand" @input="changeSearchBrand" placeholder="Search brand">
|
||||
<div class="icon">
|
||||
<SvgIcon name="brand-search" size="32" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="commodity-list">
|
||||
<CommodityList @addShopping="addShopping"></CommodityList>
|
||||
<div class="merchantList" v-if="searchBrand.length > 0">
|
||||
<brand-item v-for="item in merchantList" :key="item.name" :item="item" @viewProfile="viewProfile"></brand-item>
|
||||
<div class="end" v-show="!getMerchantData.isNoData && !getMerchantData.isShowMark">- The End-</div>
|
||||
<div v-show="!getMerchantData.isNoData" class="material_content_list_loding">
|
||||
<span class="page_loading" v-show="!getMerchantData.isShowMark" v-observe="getBrandList"></span>
|
||||
<img v-if="getMerchantData.isShowMark" src="@/assets/images/brand/brandLoading.gif" alt="">
|
||||
</div>
|
||||
|
||||
<div class="merchantListNull" v-if="getMerchantData.isNoData && searchBrand.length > 0">
|
||||
<sc-list-null
|
||||
nullImage="brand"
|
||||
:showButton="false"
|
||||
title="Brand No Found"
|
||||
tip="Try using another keywords."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="null-input" v-if="searchBrand.length == 0">
|
||||
<div class="title">
|
||||
<div class="icon">
|
||||
<SvgIcon name="brand-time" size="20" />
|
||||
</div>
|
||||
<span>Searching History</span>
|
||||
</div>
|
||||
<div class="history">
|
||||
<div v-for="item in list" :key="item" @click.stop="searchBrand = item" class="item">
|
||||
<span>{{item}}</span>
|
||||
<div class="icon" @click.stop="deleteHistory(item)">
|
||||
<SvgIcon name="brand-delete" size="18" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Footer></Footer>
|
||||
@@ -38,26 +169,151 @@ const {} = toRefs(data);
|
||||
height: 100%;
|
||||
position: relative;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.header-img{
|
||||
width: 100%;
|
||||
position: relative;
|
||||
height: 34.4rem;
|
||||
transition: all .3s;
|
||||
&.active{
|
||||
height: 14.7rem;
|
||||
}
|
||||
>img{
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
}
|
||||
> .text-box{
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
text-align: center;
|
||||
> .title{
|
||||
font-family: 'KaiseiOpti-Bold';
|
||||
font-weight: 700;
|
||||
font-size: 4rem;
|
||||
line-height: 2.3rem;
|
||||
letter-spacing: 0%;
|
||||
color: #000;
|
||||
}
|
||||
> span{
|
||||
display: block;
|
||||
margin-top: 2.4rem;
|
||||
font-family: 'KaiseiOpti-Regular';
|
||||
font-weight: 400;
|
||||
font-size: 1.6rem;
|
||||
line-height: 140%;
|
||||
}
|
||||
}
|
||||
}
|
||||
.content{
|
||||
>.content{
|
||||
display: flex;
|
||||
height: auto;
|
||||
align-items: flex-start;
|
||||
.merchant-info{
|
||||
width: 40rem;
|
||||
padding-left: 12.7rem;
|
||||
padding-right: 2.7rem;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
> .input{
|
||||
width: 66.6rem;
|
||||
display: flex;
|
||||
border-bottom: 2px solid #232323;
|
||||
padding: 1.4rem 1.4rem 1.4rem 2.4rem;
|
||||
background-color: #f9f9f9;
|
||||
display: flex;
|
||||
margin-top: 8rem;
|
||||
> input{
|
||||
width: 57.2rem;
|
||||
line-height: 3.2rem;
|
||||
padding: 0;
|
||||
border: none;
|
||||
font-family: 'KaiseiOpti-Regular';
|
||||
font-weight: 400;
|
||||
font-size: 1.6rem;
|
||||
line-height: 100%;
|
||||
margin-right: 2.4rem;
|
||||
background-color: transparent;
|
||||
outline: none;
|
||||
}
|
||||
> .icon{
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.commodity-list{
|
||||
> .null-input{
|
||||
margin-top: 8rem;
|
||||
.title{
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
.icon{
|
||||
margin-right: 2rem;
|
||||
}
|
||||
}
|
||||
.history{
|
||||
margin-top: 4rem;
|
||||
width: 59rem;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
column-gap: 3.2rem;
|
||||
row-gap: 1.2rem;
|
||||
.item{
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
> span{
|
||||
font-family: 'KaiseiOpti-Regular';
|
||||
font-weight: 400;
|
||||
font-size: 1.4rem;
|
||||
line-height: 100%;
|
||||
margin-right: .4rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
> .merchantList{
|
||||
width: 121.8rem;
|
||||
margin-top: 6rem;
|
||||
flex: 1;
|
||||
border-left: 0.5px solid #585858;
|
||||
border-right: 0.5px solid #585858;
|
||||
margin-right: 9rem;
|
||||
overflow-y: auto;
|
||||
gap: 3.2rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
::-webkit-scrollbar{
|
||||
display: none;
|
||||
}
|
||||
.end{
|
||||
font-family: 'KaiseiOpti-Regular';
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
line-height: 140%;
|
||||
height: 7rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
> .material_content_list_loding{
|
||||
width: 100%;
|
||||
height: 5rem;
|
||||
aspect-ratio: 1/1;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
justify-content: center;
|
||||
> .page_loading{
|
||||
width: 5rem;
|
||||
height: 5rem;
|
||||
}
|
||||
> img{
|
||||
width: 5rem;
|
||||
height: 5rem;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
> .merchantListNull{
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,11 +37,7 @@ const list = ref([
|
||||
url: img,
|
||||
title: "Windswept Burden",
|
||||
price: "$100.00",
|
||||
},{
|
||||
url: img,
|
||||
title: "Windswept Burden",
|
||||
price: "$100.00",
|
||||
},
|
||||
}
|
||||
])
|
||||
const type = ref('All')
|
||||
const addShopping = (item) => {
|
||||
@@ -128,41 +124,26 @@ const {} = toRefs(data);
|
||||
}
|
||||
}
|
||||
.list{
|
||||
border-top: 0.5px solid #585858;
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
// display: grid;
|
||||
// align-content: start;
|
||||
// grid-template-columns: repeat(3, 1fr);
|
||||
overflow: hidden;
|
||||
|
||||
display: grid;
|
||||
align-content: start;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
overflow-y: auto;
|
||||
grid-template-columns: repeat(auto-fit, minmax(min(100%, 28rem), 1fr));
|
||||
border-top: 0.5px solid #585858;
|
||||
padding: .5px 0 0 .5px;
|
||||
/* 垂直线(右边框) */
|
||||
.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;
|
||||
border-right: 0.5px solid #585858;
|
||||
margin-right: -1px;
|
||||
margin-bottom: -1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
80
src/views/brandDetail/index.vue
Normal file
@@ -0,0 +1,80 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
|
||||
import CommodityList from "./commodity-list.vue";
|
||||
import MerchantInfo from "./merchant-info.vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import myEvent from '@/utils/myEvent'
|
||||
//const props = defineProps({
|
||||
//})
|
||||
//const emit = defineEmits([
|
||||
//])
|
||||
const router = useRouter()
|
||||
let data = reactive({
|
||||
})
|
||||
const addShopping = (item) => {
|
||||
myEvent.emit('addShopping', item)
|
||||
}
|
||||
const openDetail = (item) => {
|
||||
router.push({name: 'digitalDetail', params: {id: item.id}})
|
||||
}
|
||||
onMounted(()=>{
|
||||
})
|
||||
onUnmounted(()=>{
|
||||
})
|
||||
defineExpose({})
|
||||
const {} = toRefs(data);
|
||||
</script>
|
||||
<template>
|
||||
<div class="brand">
|
||||
<div class="header-img">
|
||||
<img src="@/assets/images/brand/brandDetailBg.png" alt="">
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="merchant-info">
|
||||
<MerchantInfo></MerchantInfo>
|
||||
</div>
|
||||
<div class="commodity-list">
|
||||
<CommodityList @addShopping="addShopping" @openDetail="openDetail"></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>
|
||||
@@ -4,11 +4,13 @@ import img from "@/assets/images/collectionStory/Rectangle.png";
|
||||
import coreConcept from "./coreConcept.vue";
|
||||
import inspiration from "./inspiration.vue";
|
||||
import feelingWithAiDA from "./feelingWithAiDA.vue";
|
||||
import joinUs from "./join-us.vue";
|
||||
import CommodityItem from "@/components/CommodityItem.vue";
|
||||
//const props = defineProps({
|
||||
//})
|
||||
//const emit = defineEmits([
|
||||
//])
|
||||
const emit = defineEmits([
|
||||
'addShopping'
|
||||
])
|
||||
let data = reactive({
|
||||
})
|
||||
const list = ref([
|
||||
@@ -27,8 +29,12 @@ const list = ref([
|
||||
},
|
||||
])
|
||||
const addShopping = (item) => {
|
||||
console.log(item);
|
||||
emit('addShopping', item)
|
||||
}
|
||||
const openCodeCreate = () => {
|
||||
window.open('https://code-create.com.hk/', '_blank')
|
||||
}
|
||||
|
||||
onMounted(()=>{
|
||||
})
|
||||
onUnmounted(()=>{
|
||||
@@ -40,21 +46,22 @@ const {} = toRefs(data);
|
||||
<div class="detail">
|
||||
<div class="left">
|
||||
<div class="personal">
|
||||
<img :src="img" alt="">
|
||||
<img src="@/assets/images/collectionStory/code-create.png" alt="">
|
||||
<div class="name">
|
||||
<span>Lian Su</span>
|
||||
<div class="icon">
|
||||
<span>Code-Create</span>
|
||||
<div class="icon" @click="openCodeCreate">
|
||||
<SvgIcon name="share" size="24" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="center">
|
||||
<coreConcept ></coreConcept>
|
||||
<joinUs></joinUs>
|
||||
<!-- <coreConcept ></coreConcept>
|
||||
<div class="line"></div>
|
||||
<inspiration ></inspiration>
|
||||
<div class="line"></div>
|
||||
<feelingWithAiDA ></feelingWithAiDA>
|
||||
<feelingWithAiDA ></feelingWithAiDA> -->
|
||||
</div>
|
||||
<div class="right">
|
||||
<div class="item" v-for="item in list" :key="item.url">
|
||||
@@ -66,10 +73,11 @@ const {} = toRefs(data);
|
||||
<style lang="less" scoped>
|
||||
.detail{
|
||||
width: 100%;
|
||||
height: auto;
|
||||
position: relative;
|
||||
display: flex;
|
||||
height: calc(100vh - var(--header-height) - var(--footer-height));
|
||||
align-items: flex-start;
|
||||
overflow: hidden;
|
||||
> div{
|
||||
// height: 100%;
|
||||
}
|
||||
@@ -92,6 +100,9 @@ const {} = toRefs(data);
|
||||
display: flex;
|
||||
gap: .4rem;
|
||||
align-items: center;
|
||||
> .icon{
|
||||
cursor: pointer;
|
||||
}
|
||||
> span{
|
||||
font-family: 'KaiseiOpti-Bold';
|
||||
font-weight: 700;
|
||||
@@ -109,8 +120,9 @@ const {} = toRefs(data);
|
||||
border-right: 0.5px solid #585858;
|
||||
// overflow-y: auto;
|
||||
overflow: hidden;
|
||||
// height: 100%;
|
||||
height: auto;
|
||||
height: 100%;
|
||||
// height: auto;
|
||||
position: relative;
|
||||
.line{
|
||||
border: 0.5px solid #58585899;
|
||||
width: 100%;
|
||||
@@ -122,14 +134,15 @@ const {} = toRefs(data);
|
||||
> .right{
|
||||
width: 25.4rem;
|
||||
padding-top: 6rem;
|
||||
position: sticky;
|
||||
// position: sticky;
|
||||
top: 0;
|
||||
height: calc(100vh - var(--header-height));
|
||||
// height: calc(100vh - var(--header-height));
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4rem;
|
||||
overflow-y: auto;
|
||||
height: 100%;
|
||||
&::-webkit-scrollbar{
|
||||
display: none;
|
||||
}
|
||||
|
||||
59
src/views/collectionStory/detail/join-us.vue
Normal file
@@ -0,0 +1,59 @@
|
||||
<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="joinUs">
|
||||
<div class="title">Join Our Designer Community</div>
|
||||
<div class="info">
|
||||
<div>
|
||||
Join our community of visionaries and publish your collection story.
|
||||
</div>
|
||||
<div>
|
||||
We are currently seeking collections that deeply integrate the AiDA creative workflow, specifically those that resonate through powerful core concepts and evocative inspiration.
|
||||
</div>
|
||||
<br />
|
||||
<div>
|
||||
This architecture is designed to elevate your exposure through profound "propositional expression," ensuring that soulful, story-driven designs achieve higher market premiums and superior sales conversion.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="less" scoped>
|
||||
.joinUs{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,13 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
|
||||
import Detail from "./detail/index.vue";
|
||||
import myEvent from '@/utils/myEvent'
|
||||
//const props = defineProps({
|
||||
//})
|
||||
//const emit = defineEmits([
|
||||
//])
|
||||
let data = reactive({
|
||||
})
|
||||
|
||||
const addShopping = (item) => {
|
||||
myEvent.emit('addShopping', item)
|
||||
}
|
||||
onMounted(()=>{
|
||||
})
|
||||
onUnmounted(()=>{
|
||||
@@ -25,29 +28,20 @@ const {} = toRefs(data);
|
||||
</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 class="title">
|
||||
We’re Seeking
|
||||
</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 class="info">
|
||||
Fashion Voice Worth Featuring.
|
||||
</div>
|
||||
</div>
|
||||
<div class="scrolling-learn-more">
|
||||
<div>Scrolling Learn More</div>
|
||||
<SvgIcon name="collectionStory-scrollingLearnMore" size="48" />
|
||||
<div class="button">
|
||||
<a href="mailto:info@code-create.com.hk">Contact Us if Interested</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<Detail></Detail>
|
||||
<Detail @addShopping="addShopping"></Detail>
|
||||
</div>
|
||||
<Footer></Footer>
|
||||
</div>
|
||||
@@ -70,7 +64,7 @@ const {} = toRefs(data);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: .8rem;
|
||||
color: #fff;
|
||||
color: #000;
|
||||
cursor: pointer;
|
||||
> .text{
|
||||
font-size: 2rem;
|
||||
@@ -80,73 +74,46 @@ const {} = toRefs(data);
|
||||
}
|
||||
> .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%);
|
||||
padding: 0 6.7rem;
|
||||
margin-top: 11.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: #fff;
|
||||
animation: scroll 3s linear infinite;
|
||||
@keyframes scroll {
|
||||
0% {
|
||||
transform: translateY(0);
|
||||
align-items: flex-start;
|
||||
> .title-box{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
> .title{
|
||||
font-size: 6.5rem;
|
||||
line-height: 100%;
|
||||
font-weight: 500;
|
||||
color: #585858;
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-20%);
|
||||
> .info{
|
||||
font-size: 3rem;
|
||||
font-weight: 500;
|
||||
line-height: 100%;
|
||||
color: #585858;
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0%);
|
||||
}
|
||||
> .button{
|
||||
padding: 0 4.5rem;
|
||||
line-height: 5.1rem;
|
||||
background-color: #1B1B1B;
|
||||
color: #fff;
|
||||
margin-top: 4rem;
|
||||
font-weight: 700;
|
||||
font-size: 2rem;
|
||||
letter-spacing: -0.4px;
|
||||
cursor: pointer;
|
||||
> a{
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
> 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;
|
||||
}
|
||||
}
|
||||
|
||||
310
src/views/digitalDetail/index.vue
Normal file
@@ -0,0 +1,310 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import img from "@/assets/images/collectionStory/Rectangle.png";
|
||||
import myEvent from '@/utils/myEvent'
|
||||
|
||||
|
||||
//const props = defineProps({
|
||||
//})
|
||||
//const emit = defineEmits([
|
||||
//])
|
||||
const router = useRouter()
|
||||
let data = reactive({
|
||||
})
|
||||
const addShopping = (item) => {
|
||||
myEvent.emit('addShopping', item)
|
||||
}
|
||||
onMounted(()=>{
|
||||
})
|
||||
onUnmounted(()=>{
|
||||
})
|
||||
defineExpose({})
|
||||
const {} = toRefs(data);
|
||||
</script>
|
||||
<template>
|
||||
<div class="digitalItemDetail">
|
||||
<div class="center">
|
||||
<div class="img-list">
|
||||
<div class="title">
|
||||
<div>Sketch</div>
|
||||
<div>Illustration</div>
|
||||
<div>Product</div>
|
||||
</div>
|
||||
<div class="img">
|
||||
<div class="sketch">
|
||||
<img :src="img" v-for="item in 4" :key="item" alt="">
|
||||
</div>
|
||||
<div class="illustration">
|
||||
<img :src="img" v-for="item in 4" :key="item" alt="">
|
||||
</div>
|
||||
<div class="product">
|
||||
<img :src="img" v-for="item in 4" :key="item" alt="">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="img-detail">
|
||||
<div class="back" @click="router.back()">
|
||||
<div class="icon">
|
||||
<svg-icon name="digital-back" size="28"></svg-icon>
|
||||
</div>
|
||||
<span>Back</span>
|
||||
</div>
|
||||
<div class="img-info">
|
||||
<div class="img-type">FEMALE / skirt, blouse, Outwear</div>
|
||||
<div class="img-name">Heritage Layered Set</div>
|
||||
<div class="img-price">$100 <span class="mini-scrollbar">HKD</span></div>
|
||||
</div>
|
||||
<div class="commodity">
|
||||
<div class="info">
|
||||
<img class="profile" :src="img" alt="">
|
||||
<div class="detail">
|
||||
<div class="name">Roaming Clouds</div>
|
||||
<div class="release-time">
|
||||
<span>Release in Feb 26, 2026</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="introduce">
|
||||
This ensemble artfully merges traditional folk heritage with contemporary tailoring, creating a timeless silhouette that honors ancestral craftsmanship while embracing modern sophistication.
|
||||
</div>
|
||||
</div>
|
||||
<div class="notice">
|
||||
<div class="title">Copyright & License Notice</div>
|
||||
<div class="conter">
|
||||
<div class="contet-title">
|
||||
<div class="icon">
|
||||
<svg-icon name="digital-Info" size="24"></svg-icon>
|
||||
</div>
|
||||
<span>License Included in Asset</span>
|
||||
</div>
|
||||
<div class="info">
|
||||
All products on this platform are digital assets, not physical goods. Purchase grants a usage license only; copyright and intellectual property rights remain with the original creator, unless otherwise stated.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="button">
|
||||
<div class="buy-now">Buy Now</div>
|
||||
<div class="add-cart" @click="addShopping(item)">
|
||||
<div class="icon">
|
||||
<svg-icon name="cart_0" size="24"></svg-icon>
|
||||
</div>
|
||||
Add to Cart
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Footer></Footer>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="less" scoped>
|
||||
:deep(.el-drawer__body){
|
||||
--el-drawer-padding-primary: 2.4rem 3.4rem 2.4rem 6rem;
|
||||
}
|
||||
.digitalItemDetail{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.center{
|
||||
flex: 1;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
.img-list{
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
--row-width: 33.333%;
|
||||
// --row-width: 29.3rem;
|
||||
overflow-y: auto;
|
||||
&::-webkit-scrollbar{
|
||||
display: none;
|
||||
}
|
||||
> .title{
|
||||
display: flex;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
> div{
|
||||
width: var(--row-width);
|
||||
line-height: 8.6rem;
|
||||
padding-left: 2.4rem;
|
||||
border-right: 0.5px solid #C4C4C4;
|
||||
font-weight: 500;
|
||||
font-size: 1.6rem;
|
||||
background-color: #f5f5f5;
|
||||
&:last-child{
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
> .img{
|
||||
display: flex;
|
||||
|
||||
> div{
|
||||
display: flex;
|
||||
width: var(--row-width);
|
||||
border-right: 0.5px solid #C4C4C4;
|
||||
flex-direction: column;
|
||||
&:last-child{
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.img-detail{
|
||||
border-left: 0.5px solid #585858;
|
||||
padding-left: 3.2rem;
|
||||
width: 57rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-y: auto;
|
||||
&::-webkit-scrollbar{
|
||||
display: none;
|
||||
}
|
||||
.back{
|
||||
display: flex;
|
||||
margin-top: 2.8rem;
|
||||
gap: 1.4rem;
|
||||
align-items: center;
|
||||
width: min-content;
|
||||
cursor: pointer;
|
||||
> span{
|
||||
font-weight: 500;
|
||||
font-size: 1.6rem;
|
||||
line-height: 120%;
|
||||
}
|
||||
}
|
||||
.img-info{
|
||||
margin-top: 2.8rem;
|
||||
.img-type{
|
||||
font-weight: 500;
|
||||
font-size: 1.2rem;
|
||||
line-height: 120%;
|
||||
color: #808080;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.img-name{
|
||||
font-family: KaiseiOpti-Bold;
|
||||
font-weight: 700;
|
||||
font-size: 3rem;
|
||||
line-height: 120%;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.img-price{
|
||||
font-family: KaiseiOpti-Bold;
|
||||
font-weight: 700;
|
||||
font-size: 3rem;
|
||||
line-height: 120%;
|
||||
> span{
|
||||
font-weight: 500;
|
||||
font-size: 2rem;
|
||||
line-height: 120%;
|
||||
color: #585858;
|
||||
}
|
||||
}
|
||||
}
|
||||
.commodity{
|
||||
margin-top: 4rem;
|
||||
.info{
|
||||
display: flex;
|
||||
gap: 1.4rem;
|
||||
margin-bottom: 1.4rem;
|
||||
> .profile{
|
||||
width: 5.4rem;
|
||||
height: 5.4rem;
|
||||
object-fit: cover;
|
||||
}
|
||||
> .detail{
|
||||
.name{
|
||||
text-decoration: underline;
|
||||
font-weight: 500;
|
||||
font-size: 1.8rem;
|
||||
line-height: 100%;
|
||||
margin-bottom: .8rem;
|
||||
}
|
||||
.release-time{
|
||||
font-family: KaiseiOpti-Regular;
|
||||
font-weight: 400;
|
||||
font-size: 1.4rem;
|
||||
line-height: 140%;
|
||||
color: #585858;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
.introduce{
|
||||
font-family: KaiseiOpti-Regular;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 140%;
|
||||
color: #585858;
|
||||
width: 50.8rem;
|
||||
}
|
||||
}
|
||||
.notice{
|
||||
margin-top: 6rem;
|
||||
.title{
|
||||
margin-bottom: 1rem;
|
||||
font-weight: 500;
|
||||
font-size: 1.8rem;
|
||||
line-height: 140%;
|
||||
}
|
||||
.conter{
|
||||
width: 50.8rem;
|
||||
background-color: #f6f6f6;
|
||||
padding: 2rem;
|
||||
.contet-title{
|
||||
margin-bottom: 1.2rem;
|
||||
display: flex;
|
||||
gap: .8rem;
|
||||
align-items: center;
|
||||
> span{
|
||||
font-family: KaiseiOpti-Regular;
|
||||
font-weight: 400;
|
||||
font-size: 1.4rem;
|
||||
line-height: 140%;
|
||||
}
|
||||
}
|
||||
.info{
|
||||
font-family: KaiseiOpti-Regular;
|
||||
font-weight: 400;
|
||||
font-size: 1.2rem;
|
||||
line-height: 140%;
|
||||
color: #585858;
|
||||
}
|
||||
}
|
||||
}
|
||||
.button{
|
||||
width: 50.8rem;
|
||||
margin-top: auto;
|
||||
> div{
|
||||
width: 100%;
|
||||
font-weight: 500;
|
||||
font-size: 1.6rem;
|
||||
line-height: 120%;
|
||||
letter-spacing: 3%;
|
||||
border: 1px solid #232323;
|
||||
line-height: 4.8rem;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
.buy-now{
|
||||
background-color: #232323;
|
||||
color: #fff;
|
||||
}
|
||||
.add-cart{
|
||||
background-color: #fff;
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
101
src/views/digitalItem/commodity-list.vue
Normal file
@@ -0,0 +1,101 @@
|
||||
<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',
|
||||
'openDetail'
|
||||
])
|
||||
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)
|
||||
}
|
||||
const openDetail = (item) => {
|
||||
emit('openDetail', item)
|
||||
}
|
||||
onMounted(()=>{
|
||||
})
|
||||
onUnmounted(()=>{
|
||||
})
|
||||
defineExpose({})
|
||||
const {} = toRefs(data);
|
||||
</script>
|
||||
<template>
|
||||
<div class="commodityList">
|
||||
<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)" @openDetail="openDetail(item)"></CommodityItem>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="less" scoped>
|
||||
.commodityList{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.list{
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
// display: grid;
|
||||
// align-content: start;
|
||||
// grid-template-columns: repeat(3, 1fr);
|
||||
overflow: hidden;
|
||||
|
||||
display: grid;
|
||||
align-content: start;
|
||||
grid-template-columns: repeat(auto-fit, minmax(min(100%, 28rem), 1fr));
|
||||
border-top: 0.5px solid #585858;
|
||||
padding: .5px 0 0 .5px;
|
||||
/* 垂直线(右边框) */
|
||||
.item{
|
||||
position: relative;
|
||||
padding: 1.2rem;
|
||||
border-bottom: 0.5px solid #585858;
|
||||
border-right: 0.5px solid #585858;
|
||||
margin-right: -1px;
|
||||
margin-bottom: -1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
217
src/views/digitalItem/index.vue
Normal file
@@ -0,0 +1,217 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, reactive, toRefs, onActivated } from "vue";
|
||||
import CommodityList from "./commodity-list.vue";
|
||||
import MerchantInfo from "./merchant-info.vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import scListNull from '@/views/shoppingCart/sc-list-null.vue'
|
||||
|
||||
// 定义组件名称
|
||||
defineOptions({
|
||||
name: 'digitalItem'
|
||||
})
|
||||
//const props = defineProps({
|
||||
//})
|
||||
//const emit = defineEmits([
|
||||
//])
|
||||
const digitalItemRef = ref(null)
|
||||
const scrollTop = ref(0)
|
||||
const router = useRouter()
|
||||
let data = reactive({
|
||||
})
|
||||
|
||||
const categoriesList = ref([
|
||||
{
|
||||
value:'Best Selling',
|
||||
label:'Best Selling'
|
||||
},{
|
||||
value:'Price: Low to High',
|
||||
label:'Price: Low to High'
|
||||
},{
|
||||
value:'Newest First',
|
||||
label:'Newest First'
|
||||
},
|
||||
])
|
||||
const categories = ref('Newest First')
|
||||
const addShopping = (item) => {}
|
||||
const openDetail = (item) => {
|
||||
scrollTop.value = digitalItemRef.value.scrollTop
|
||||
router.push({
|
||||
path: '/digitalItem/' + 123,
|
||||
})
|
||||
}
|
||||
onActivated(()=>{
|
||||
digitalItemRef.value.scrollTop = scrollTop.value
|
||||
})
|
||||
onMounted(()=>{
|
||||
})
|
||||
onUnmounted(()=>{
|
||||
})
|
||||
defineExpose({})
|
||||
const {} = toRefs(data);
|
||||
</script>
|
||||
<template>
|
||||
<div class="digitalItem" ref="digitalItemRef">
|
||||
<div class="header-img">
|
||||
<img src="@/assets/images/digitalItem/digital_item_banner.png" alt="">
|
||||
<div class="text">
|
||||
<div class="title">Digital Item</div>
|
||||
<p class="info">Virtual fashion creations collected in your personal archive</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="filters">
|
||||
<div class="filter-item">
|
||||
<el-select v-model="categories" placeholder="Sort By" :teleported="false">
|
||||
<template #label="{ label }">
|
||||
<span class="header-label">Sort By</span>
|
||||
<span class="header-value">{{ label }}</span>
|
||||
</template>
|
||||
<el-option
|
||||
v-for="item in categoriesList"
|
||||
:key="item.label"
|
||||
:value="item.value"
|
||||
:label="item.label"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="merchant-info">
|
||||
<MerchantInfo></MerchantInfo>
|
||||
</div>
|
||||
<div class="commodity-list">
|
||||
<CommodityList v-if="true" @addShopping="addShopping" @openDetail="openDetail"></CommodityList>
|
||||
<div v-else class="null">
|
||||
<sc-list-null
|
||||
nullImage="shopping-cart"
|
||||
:showButton="false"
|
||||
title="Nothing in Digital Item"
|
||||
tip="Try adjusting your filters or refreshing the page."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Footer></Footer>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="less" scoped>
|
||||
.digitalItem{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
overflow-y: auto;
|
||||
.header-img{
|
||||
width: 100%;
|
||||
position: relative;
|
||||
>img{
|
||||
width: 100%;
|
||||
}
|
||||
> .text{
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
> .title{
|
||||
font-family: KaiseiOpti-Bold;
|
||||
color: #232323;
|
||||
font-weight: 700;
|
||||
font-size: 4rem;
|
||||
line-height: 2.3rem;
|
||||
letter-spacing: 0%;
|
||||
text-align: center;
|
||||
}
|
||||
> .info{
|
||||
font-family: KaiseiOpti-Regular;
|
||||
color: #585858;
|
||||
font-size: 1.6rem;
|
||||
line-height: 140%;
|
||||
margin-top: 1.2rem;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
> .filters{
|
||||
width: 100%;
|
||||
height: 6rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 9rem;
|
||||
justify-content: flex-end;
|
||||
> .filter-item{
|
||||
:deep(.el-select) {
|
||||
width: 15rem;
|
||||
--el-border-radius-base: 0;
|
||||
--el-select-input-color: rgba(0, 0, 0, 0.5);
|
||||
--el-select-input-font-size: 1rem;
|
||||
.el-select__wrapper {
|
||||
font-size: 1.07rem;
|
||||
padding: 0 0.7rem;
|
||||
line-height: 1;
|
||||
min-height: 0;
|
||||
height: 2.2rem;
|
||||
|
||||
.header-label {
|
||||
font-family: KaiseiOpti-Regular;
|
||||
color: rgba(0, 0, 0, 0.5);
|
||||
margin-right: 0.6rem;
|
||||
}
|
||||
.header-value {
|
||||
font-family: KaiseiOpti-Bold;
|
||||
color: #232323;
|
||||
}
|
||||
}
|
||||
.el-select__popper {
|
||||
--el-popper-border-radius: 0;
|
||||
border: 0.1rem solid #d0d0d0;
|
||||
.el-select-dropdown__list {
|
||||
padding: 0;
|
||||
> .el-select-dropdown__item {
|
||||
margin-bottom: 0.89rem;
|
||||
color: #232323;
|
||||
font-size: 1.069rem;
|
||||
height: 2.68rem;
|
||||
line-height: 2.68rem;
|
||||
padding: 0 1.4rem;
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
&.is-selected {
|
||||
font-family: KaiseiOpti-Bold;
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
> .content{
|
||||
display: flex;
|
||||
height: auto;
|
||||
// align-items: flex-start;
|
||||
border-top: 0.5px solid #585858;
|
||||
.merchant-info{
|
||||
width: 38.5rem;
|
||||
padding-left: 10.2rem;
|
||||
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;
|
||||
display: flex;
|
||||
.null{
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
127
src/views/digitalItem/merchant-info.vue
Normal file
@@ -0,0 +1,127 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
|
||||
//const props = defineProps({
|
||||
//})
|
||||
//const emit = defineEmits([
|
||||
//])
|
||||
let data = reactive({
|
||||
})
|
||||
const categoriesList = ref([
|
||||
{
|
||||
label: 'Outwear',
|
||||
value: 'Outwear'
|
||||
},
|
||||
{
|
||||
label: 'Dress',
|
||||
value: 'Dress'
|
||||
},
|
||||
{
|
||||
label: 'Trousers',
|
||||
value: 'Trousers'
|
||||
},
|
||||
{
|
||||
label: 'Blouse',
|
||||
value: 'Blouse'
|
||||
},
|
||||
{
|
||||
label: 'Skirt',
|
||||
value: 'Skirt'
|
||||
},
|
||||
{
|
||||
label: 'Accessories',
|
||||
value: 'Accessories'
|
||||
},
|
||||
]);
|
||||
const genderList = ref([
|
||||
{
|
||||
label: 'Male',
|
||||
value: 'Male'
|
||||
},
|
||||
{
|
||||
label: 'Female',
|
||||
value: 'Female'
|
||||
},
|
||||
])
|
||||
const categories = ref([''])
|
||||
const gender = ref([''])
|
||||
|
||||
const clearFilters = () => {
|
||||
categories.value = ['']
|
||||
gender.value = ['']
|
||||
}
|
||||
onMounted(()=>{
|
||||
})
|
||||
onUnmounted(()=>{
|
||||
})
|
||||
defineExpose({})
|
||||
const {} = toRefs(data);
|
||||
</script>
|
||||
<template>
|
||||
<div class="filters">
|
||||
<div class="title">
|
||||
<div class="left">Filters</div>
|
||||
<div class="right" @click="clearFilters">Clear</div>
|
||||
</div>
|
||||
<div class="categories">Categories</div>
|
||||
<div class="line"></div>
|
||||
<div class="multiple">
|
||||
<checked :list="categoriesList" v-model:selected="categories" />
|
||||
</div>
|
||||
<div class="categories">Gender</div>
|
||||
<div class="line"></div>
|
||||
<div class="multiple">
|
||||
<checked :list="genderList" v-model:selected="gender" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="less" scoped>
|
||||
.filters{
|
||||
width: 100%;
|
||||
height: auto;
|
||||
position: relative;
|
||||
padding-top: 4rem;
|
||||
padding-bottom: 4rem;
|
||||
.title{
|
||||
margin-bottom: 3rem;
|
||||
display: flex;
|
||||
padding: 0 1.2rem;
|
||||
.left{
|
||||
margin-right: 12.2rem;
|
||||
font-family: "KaiseiOpti-Bold";
|
||||
font-weight: 700;
|
||||
font-size: 2.4rem;
|
||||
line-height: 3.5rem;
|
||||
color: #232323;
|
||||
}
|
||||
.right{
|
||||
text-decoration: underline;
|
||||
font-family: "KaiseiOpti-Regular";
|
||||
font-weight: 400;
|
||||
font-size: 1.6rem;
|
||||
line-height: 2.4rem;
|
||||
letter-spacing: -0.48px;
|
||||
text-align: right;
|
||||
color: #979797;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.categories{
|
||||
font-family: "KaiseiOpti-Bold";
|
||||
font-weight: 700;
|
||||
font-size: 1.8rem;
|
||||
line-height: 2.3rem;
|
||||
color: #585858;
|
||||
margin-bottom: 1.1rem;
|
||||
padding: 0 1.2rem;
|
||||
}
|
||||
.line{
|
||||
border-top: 0.5px solid #C4C4C4;
|
||||
width: 27.1rem;
|
||||
margin-bottom: 2.2rem;
|
||||
}
|
||||
.multiple{
|
||||
padding: 0 2.3rem;
|
||||
margin-bottom: 2.9rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="home-index">
|
||||
<section-index />
|
||||
<section-designers />
|
||||
<section-designer />
|
||||
<section-design />
|
||||
<section-digital-items1 />
|
||||
<section-digital-items2 />
|
||||
@@ -12,7 +12,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import SectionIndex from './section-index.vue'
|
||||
import SectionDesigners from './section-designers.vue'
|
||||
import SectionDesigner from './section-designer.vue'
|
||||
import SectionDesign from './section-design.vue'
|
||||
import SectionDigitalItems1 from './section-digital-items1.vue'
|
||||
import SectionDigitalItems2 from './section-digital-items2.vue'
|
||||
|
||||
51
src/views/home/section-designer.vue
Normal file
@@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<section class="section-designer">
|
||||
<div class="title">Designer Community</div>
|
||||
<div class="tip">
|
||||
Discover the designers shaping AiDA’s creative landscape. <br />
|
||||
Each month, we will showcase a curated selection of their most distinguished works.
|
||||
</div>
|
||||
<button custom="black" @click="onSearchBrand">Search Brands</button>
|
||||
<img src="@/assets/images/home/designer-bg.png" />
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
const router = useRouter()
|
||||
const onSearchBrand = () => {
|
||||
router.push({ name: 'brand' })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.section-designer {
|
||||
padding: 9rem 8rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
> .title {
|
||||
font-family: KaiseiOpti-Bold;
|
||||
font-size: 5.6rem;
|
||||
line-height: 6.2rem;
|
||||
text-align: center;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
> .tip {
|
||||
font-family: KaiseiOpti-Regular;
|
||||
font-size: 2rem;
|
||||
line-height: 2.8rem;
|
||||
text-align: center;
|
||||
color: #979797;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
> button {
|
||||
margin-bottom: 4.6rem;
|
||||
}
|
||||
> img {
|
||||
width: 73%;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,130 +0,0 @@
|
||||
<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>
|
||||
@@ -4,7 +4,7 @@
|
||||
<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/stylish-parade-black.png" />
|
||||
<img src="@/assets/images/logos/aida-black.png" />
|
||||
</div>
|
||||
<div class="tip">
|
||||
|
||||
@@ -1,74 +1,58 @@
|
||||
<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>
|
||||
<img src="@/assets/images/home/bg.png" class="bg" />
|
||||
<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 class="title" v-html="title"></div>
|
||||
<div class="tip">
|
||||
Discover collections through the stories behind their creation. A curated space connecting
|
||||
designers, narratives, and fashion commerce.
|
||||
</div>
|
||||
<button custom="black-box" @click="handleClickArrow">
|
||||
<svg-icon name="arrow_right" size="34" />
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
const router = useRouter()
|
||||
const title =
|
||||
'We’re Seeking<br /><span>Fashion Voice</span><br /><span class="small">Worth Featuring.</span>'
|
||||
const handleClickArrow = () => {
|
||||
router.push({ name: 'collectionStory' })
|
||||
}
|
||||
</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;
|
||||
top: 13.7rem;
|
||||
left: 9rem;
|
||||
color: #fff;
|
||||
color: #232323;
|
||||
> .title {
|
||||
font-family: KaiseiOpti-Bold;
|
||||
font-size: 6rem;
|
||||
font-size: 7rem;
|
||||
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;
|
||||
span {
|
||||
font-family: KaiseiOpti-Medium;
|
||||
&.small {
|
||||
font-size: 6rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
> p.tip {
|
||||
font-family: KaiseiOpti-Regular;
|
||||
font-size: 1.2rem;
|
||||
line-height: 2rem;
|
||||
color: #ededed;
|
||||
> .tip {
|
||||
width: 50rem;
|
||||
font-size: 1.8rem;
|
||||
line-height: 2.6rem;
|
||||
color: #585858;
|
||||
}
|
||||
> button {
|
||||
margin-top: 12rem;
|
||||
min-width: 0;
|
||||
width: 8rem;
|
||||
height: 8rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
167
src/views/login/css/style.css
Normal file
@@ -0,0 +1,167 @@
|
||||
.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-inner-height: auto;
|
||||
--el-input-border-radius: 0;
|
||||
--el-input-text-color: #232323;
|
||||
--el-border-color: #C4C4C4;
|
||||
font-size: 1rem;
|
||||
}
|
||||
.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) .el-input .el-input__wrapper,
|
||||
.register:deep(.el-form) .el-input .el-input__wrapper,
|
||||
.login:deep(.el-form) .el-input .el-input__wrapper {
|
||||
padding: 0.1rem 1rem;
|
||||
}
|
||||
.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,
|
||||
.register:deep(.el-form) .privacy .el-checkbox,
|
||||
.login:deep(.el-form) .privacy .el-checkbox {
|
||||
margin-right: 0;
|
||||
height: auto;
|
||||
--el-checkbox-font-size: 1.4rem;
|
||||
--el-checkbox-input-width: 1.4rem;
|
||||
--el-checkbox-input-height: 1.4rem;
|
||||
--el-checkbox-checked-bg-color: #000;
|
||||
--el-checkbox-checked-input-border-color: #000;
|
||||
--el-checkbox-input-border: 0.1rem solid #c4c4c4;
|
||||
--el-checkbox-bg-color: #fff;
|
||||
--el-checkbox-border-radius: 0;
|
||||
}
|
||||
.retrieve-password:deep(.el-form) .privacy .el-checkbox .el-checkbox__input.is-indeterminate .el-checkbox__inner:before,
|
||||
.register:deep(.el-form) .privacy .el-checkbox .el-checkbox__input.is-indeterminate .el-checkbox__inner:before,
|
||||
.login:deep(.el-form) .privacy .el-checkbox .el-checkbox__input.is-indeterminate .el-checkbox__inner:before {
|
||||
height: 0.2rem;
|
||||
width: 65%;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: scale(1) translate(-50%, -50%);
|
||||
border-radius: 0.1rem;
|
||||
}
|
||||
.retrieve-password:deep(.el-form) .privacy .el-checkbox .el-checkbox__inner:after,
|
||||
.register:deep(.el-form) .privacy .el-checkbox .el-checkbox__inner:after,
|
||||
.login:deep(.el-form) .privacy .el-checkbox .el-checkbox__inner:after {
|
||||
width: 0.4rem;
|
||||
height: 0.8rem;
|
||||
border-width: 0.1rem;
|
||||
}
|
||||
.retrieve-password:deep(.el-form) .privacy .el-checkbox .el-checkbox__label,
|
||||
.register:deep(.el-form) .privacy .el-checkbox .el-checkbox__label,
|
||||
.login:deep(.el-form) .privacy .el-checkbox .el-checkbox__label {
|
||||
font-size: 1.1rem;
|
||||
color: #666666;
|
||||
font-family: KaiseiOpti-Regular;
|
||||
}
|
||||
.retrieve-password:deep(.el-form) .privacy .el-checkbox .el-checkbox__label > div > span,
|
||||
.register:deep(.el-form) .privacy .el-checkbox .el-checkbox__label > div > span,
|
||||
.login:deep(.el-form) .privacy .el-checkbox .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;
|
||||
}
|
||||
142
src/views/login/email-verify.vue
Normal file
@@ -0,0 +1,142 @@
|
||||
<template>
|
||||
<div class="email-verify">
|
||||
<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 v-if="isShowOtherLogin" />
|
||||
</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: '' },
|
||||
isShowOtherLogin: { type: Boolean, default: true }
|
||||
})
|
||||
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>
|
||||
.email-verify {
|
||||
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 {
|
||||
font-family: KaiseiOpti-Regular;
|
||||
user-select: none;
|
||||
margin-top: 2.4rem;
|
||||
font-size: 1.2rem;
|
||||
color: #666;
|
||||
> span {
|
||||
color: #232323;
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
> .other-login {
|
||||
margin-top: 6rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
156
src/views/login/less/style.less
Normal file
@@ -0,0 +1,156 @@
|
||||
.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-inner-height: auto;
|
||||
--el-input-border-radius: 0;
|
||||
--el-input-text-color: #232323;
|
||||
--el-border-color: #C4C4C4;
|
||||
font-size: 1rem;
|
||||
|
||||
&::placeholder {
|
||||
color: #9F9F9F;
|
||||
}
|
||||
|
||||
.el-input__wrapper {
|
||||
padding: 0.1rem 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.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 {
|
||||
margin-right: 0;
|
||||
height: auto;
|
||||
--el-checkbox-font-size: 1.4rem;
|
||||
--el-checkbox-input-width: 1.4rem;
|
||||
--el-checkbox-input-height: 1.4rem;
|
||||
--el-checkbox-checked-bg-color: #000;
|
||||
--el-checkbox-checked-input-border-color: #000;
|
||||
--el-checkbox-input-border: 0.1rem solid #c4c4c4;
|
||||
--el-checkbox-bg-color: #fff;
|
||||
--el-checkbox-border-radius: 0;
|
||||
|
||||
.el-checkbox__input.is-indeterminate .el-checkbox__inner:before {
|
||||
height: 0.2rem;
|
||||
width: 65%;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: scale(1) translate(-50%, -50%);
|
||||
border-radius: 0.1rem;
|
||||
}
|
||||
|
||||
.el-checkbox__inner:after {
|
||||
width: 0.4rem;
|
||||
height: 0.8rem;
|
||||
border-width: 0.1rem;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
}
|
||||
247
src/views/login/login-dialog.vue
Normal file
@@ -0,0 +1,247 @@
|
||||
<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="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: [TabNames.register, TabNames.register_success].includes(currentTab)
|
||||
}"
|
||||
@click="currentTab = TabNames.register"
|
||||
>
|
||||
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"
|
||||
@back="onBack"
|
||||
: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 registerSuccess from './register-success.vue'
|
||||
import retrievePassword from './retrieve-password.vue'
|
||||
import myEvent from '@/utils/myEvent'
|
||||
const data = ref({
|
||||
name: '',
|
||||
email: '',
|
||||
password: '',
|
||||
type: ''
|
||||
})
|
||||
const TabNames = {
|
||||
login: 'login',
|
||||
register: 'register',
|
||||
email_verify: 'email_verify',
|
||||
register_success: 'register_success',
|
||||
retrieve_password: 'retrieve_password'
|
||||
}
|
||||
const tabList = markRaw([
|
||||
{
|
||||
name: TabNames.login,
|
||||
component: login
|
||||
},
|
||||
{
|
||||
name: TabNames.register,
|
||||
component: register
|
||||
},
|
||||
{
|
||||
name: TabNames.email_verify,
|
||||
title: 'EMAIL VERIFICATION',
|
||||
component: emailVerify
|
||||
},
|
||||
{
|
||||
name: TabNames.retrieve_password,
|
||||
title: 'RETRIEVE PASSWORD',
|
||||
component: retrievePassword
|
||||
},
|
||||
{
|
||||
name: TabNames.register_success,
|
||||
component: registerSuccess
|
||||
}
|
||||
])
|
||||
const show = ref(false)
|
||||
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 = () => {
|
||||
currentTab.value = lastTab.value || TabNames.login
|
||||
}
|
||||
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.register
|
||||
currentTab.value = TabNames.email_verify
|
||||
}
|
||||
const onSubmitEmailCode = (code: string) => {
|
||||
if (data.value.type === TabNames.login) {
|
||||
console.log('登录', code)
|
||||
show.value = false
|
||||
} else {
|
||||
console.log('注册', code)
|
||||
currentTab.value = TabNames.register_success
|
||||
}
|
||||
}
|
||||
</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;
|
||||
background: url('@/assets/images/login/bg.jpg') no-repeat center center / 100% 100%;
|
||||
> *:not(.content) {
|
||||
position: absolute;
|
||||
}
|
||||
> .bg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
> .logo {
|
||||
width: auto;
|
||||
height: 4rem;
|
||||
top: 4rem;
|
||||
left: 3rem;
|
||||
}
|
||||
> .close {
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
top: 2rem;
|
||||
right: 2rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
> .content {
|
||||
width: 34rem;
|
||||
height: calc(100% - 10rem);
|
||||
margin: 5rem 6rem auto auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
> .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.1rem;
|
||||
padding: 1.5rem;
|
||||
border-radius: 1.5rem;
|
||||
line-height: normal;
|
||||
|
||||
> div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 1rem;
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
> .el-icon {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
113
src/views/login/register-success.vue
Normal file
@@ -0,0 +1,113 @@
|
||||
<template>
|
||||
<div class="register-success">
|
||||
<div class="icon"><svg-icon name="dui" size="20" /></div>
|
||||
<div class="title">Welcome to Stylish Parade!</div>
|
||||
<div class="title">Please switch to the Login tab to log in.</div>
|
||||
<div class="footer">
|
||||
<div class="title">
|
||||
<span class="text">What awaits you in Stylish Parade</span>
|
||||
<span class="icon"><svg-icon name="arrow_right" size="11" /></span>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div>
|
||||
<div class="title">Behind the design</div>
|
||||
<div class="tip">
|
||||
Discover how designers bring ideas to life with AiDA — from first sketch to final look.
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="title">Creative digital works</div>
|
||||
<div class="tip">
|
||||
Unlock a growing library of inspiring digital works to refresh your creative mind.
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="title">A fashion community</div>
|
||||
<div class="tip">
|
||||
Join a space where fashion speaks — exchange ideas and connect with creators worldwide.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
const { t } = useI18n()
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.register-success {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-top: 7.7rem;
|
||||
> .icon {
|
||||
width: 4rem;
|
||||
height: 4rem;
|
||||
border-radius: 50%;
|
||||
background-color: #fff;
|
||||
border: 0.1rem solid #e8e8e8;
|
||||
margin-bottom: 1.8rem;
|
||||
}
|
||||
> .title {
|
||||
font-size: 1.6rem;
|
||||
line-height: 2.4rem;
|
||||
text-align: center;
|
||||
color: #232323;
|
||||
}
|
||||
> .footer {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
left: 0;
|
||||
bottom: 7rem;
|
||||
padding: 0 6rem;
|
||||
> .title {
|
||||
font-family: KaiseiOpti-Regular;
|
||||
font-size: 1.4rem;
|
||||
line-height: 2.4rem;
|
||||
color: #666;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
margin-bottom: 1.2rem;
|
||||
> .icon {
|
||||
margin-left: 1.2rem;
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
> .content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 1.8rem;
|
||||
> div {
|
||||
padding: 2.4rem 1.5rem 0;
|
||||
height: 14.8rem;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
box-shadow: 3px 4px 8px 0px rgba(0, 0, 0, 0.11);
|
||||
flex: 1;
|
||||
&:first-child {
|
||||
flex: 0.84;
|
||||
}
|
||||
> .title {
|
||||
font-family: KaiseiOpti-Bold;
|
||||
font-size: 1.6rem;
|
||||
line-height: 2.4rem;
|
||||
margin-bottom: 1.2rem;
|
||||
color: #232323;
|
||||
}
|
||||
> .tip {
|
||||
font-family: KaiseiOpti-Regular;
|
||||
font-size: 1.2rem;
|
||||
line-height: 1.7rem;
|
||||
color: #585858;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</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>
|
||||
183
src/views/login/retrieve-password.vue
Normal file
@@ -0,0 +1,183 @@
|
||||
<template>
|
||||
<div class="retrieve-password">
|
||||
<el-form
|
||||
:model="formData"
|
||||
:rules="ruleForm"
|
||||
label-position="top"
|
||||
ref="form1Ref"
|
||||
v-show="index === 0"
|
||||
>
|
||||
<div class="title">Please enter your email address below to verify your identity.</div>
|
||||
<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 class="submit-item">
|
||||
<button class="submit" type="submit" custom="black" @click.prevent="onSubmit1">
|
||||
SUBMIT
|
||||
</button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="verify-box" v-if="index === 1">
|
||||
<email-verify
|
||||
type="FORGOT_PWD"
|
||||
:email="formData.email"
|
||||
@submit-email-code="onVerifyCode"
|
||||
:is-show-other-login="false"
|
||||
/>
|
||||
</div>
|
||||
<el-form
|
||||
:model="formData"
|
||||
:rules="ruleForm"
|
||||
label-position="top"
|
||||
ref="form2Ref"
|
||||
v-show="index === 2"
|
||||
>
|
||||
<div class="title">
|
||||
Enter a new password for <br />
|
||||
<span>{{ formData.email }}</span>
|
||||
</div>
|
||||
<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>
|
||||
<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.passwordConfirmation')" prop="confirmPassword">
|
||||
<el-input
|
||||
v-model="formData.confirmPassword"
|
||||
:placeholder="$t('Login.enterPasswordAgain')"
|
||||
type="password"
|
||||
show-password
|
||||
name="password"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item class="submit-item">
|
||||
<button class="submit" type="submit" custom="black" @click.prevent="onSubmit2">
|
||||
SUBMIT
|
||||
</button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import md5 from 'md5'
|
||||
import { computed, reactive, ref } from 'vue'
|
||||
import { validateEmail, validatePass } from './tools'
|
||||
import PasswordTip from './password-tip.vue'
|
||||
import EmailVerify from './email-verify.vue'
|
||||
const emit = defineEmits(['back'])
|
||||
const validateConfirmPassword = (rule: any, value: string, callback: any) => {
|
||||
if (value !== formData.password) {
|
||||
callback(new Error('Passwords do not match'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
const index = ref(0)
|
||||
const ruleForm = reactive({
|
||||
email: [{ validator: validateEmail, trigger: 'change' }],
|
||||
password: [{ validator: validatePass, trigger: 'change' }],
|
||||
confirmPassword: [{ validator: validateConfirmPassword, trigger: 'change' }]
|
||||
})
|
||||
const showPasswordTip = ref(false)
|
||||
const formData = reactive({
|
||||
email: '',
|
||||
code: '',
|
||||
password: '',
|
||||
confirmPassword: ''
|
||||
})
|
||||
const form1Ref = ref(null)
|
||||
const visibleCodeRef = ref(null)
|
||||
const form2Ref = ref(null)
|
||||
const onSubmit1 = () => {
|
||||
form1Ref.value?.validate?.((valid) => {
|
||||
if (valid) {
|
||||
index.value = 1
|
||||
} else {
|
||||
console.warn('error submit!')
|
||||
}
|
||||
})
|
||||
}
|
||||
const onSubmit2 = () => {
|
||||
form2Ref.value?.validate?.((valid) => {
|
||||
if (valid) {
|
||||
const data = {
|
||||
email: formData.email,
|
||||
code: formData.code,
|
||||
password: md5(formData.password)
|
||||
}
|
||||
console.log(data)
|
||||
emit('back')
|
||||
} else {
|
||||
console.warn('error submit!')
|
||||
}
|
||||
})
|
||||
}
|
||||
const onVerifyCode = (code: string) => {
|
||||
if (!code) return
|
||||
formData.code = code
|
||||
index.value = 2
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@import './less/style.less';
|
||||
|
||||
.retrieve-password {
|
||||
flex: 1;
|
||||
&:deep(.el-form) {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.el-form-item.submit-item {
|
||||
margin-top: auto;
|
||||
}
|
||||
.el-input {
|
||||
--el-input-height: 4.8rem;
|
||||
}
|
||||
.el-form-item:nth-last-child(2) {
|
||||
margin-bottom: 10rem;
|
||||
}
|
||||
> .title {
|
||||
font-family: KaiseiOpti-Regular;
|
||||
font-size: 1.6rem;
|
||||
line-height: 2.4rem;
|
||||
text-align: center;
|
||||
color: #585858;
|
||||
margin-top: auto;
|
||||
margin-bottom: 3rem;
|
||||
> span {
|
||||
font-family: KaiseiOpti-Medium;
|
||||
color: #252727;
|
||||
}
|
||||
}
|
||||
}
|
||||
> .verify-box {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
&:deep(.email-verify) {
|
||||
> .tip {
|
||||
margin-top: 8rem;
|
||||
}
|
||||
> .input-code {
|
||||
margin-top: 3rem;
|
||||
}
|
||||
> .verify {
|
||||
margin-top: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</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()
|
||||
}
|
||||
}
|
||||