Compare commits
54 Commits
38094c4b2c
...
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 |
@@ -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"> -->
|
||||||
<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">
|
<link rel="stylesheet" href="/fonts/index.css">
|
||||||
<title>Lane Crawford</title>
|
<title>Stylish Parade</title>
|
||||||
<!-- Open Graph / WhatsApp share metadata -->
|
<!-- Open Graph / WhatsApp share metadata -->
|
||||||
<meta property="og:title" content="Lane Crawford" />
|
<meta property="og:title" content="Stylish Parade" />
|
||||||
<meta property="og:description" content="create and share looks from the Lane Crawford creation gallery." />
|
<meta property="og:description" content="create and share looks from the Stylish Parade creation gallery." />
|
||||||
<meta property="og:image" content="" />
|
<meta property="og:image" content="" />
|
||||||
<meta property="og:url" content="https://www.lc.aida.com.hk" />
|
<meta property="og:url" content="https://www.lc.aida.com.hk" />
|
||||||
<meta property="og:type" content="website" />
|
<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" />
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
<div class="view" ref="viewRef" :style="viewStyle"><RouteCache /></div>
|
<div class="view" ref="viewRef" :style="viewStyle"><RouteCache /></div>
|
||||||
<login-dialog />
|
<login-dialog />
|
||||||
<div id="loading" v-if="loading" v-loading="true"></div>
|
<div id="loading" v-if="loading" v-loading="true"></div>
|
||||||
|
<shopping-drawer />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@@ -11,6 +12,7 @@
|
|||||||
import MainHeader from '@/views/main-header.vue'
|
import MainHeader from '@/views/main-header.vue'
|
||||||
import LoginDialog from '@/views/login/login-dialog.vue'
|
import LoginDialog from '@/views/login/login-dialog.vue'
|
||||||
import { useGlobalStore } from '@/stores'
|
import { useGlobalStore } from '@/stores'
|
||||||
|
import ShoppingDrawer from '@/views/shopping-drawer.vue'
|
||||||
const globalStore = useGlobalStore()
|
const globalStore = useGlobalStore()
|
||||||
const loading = computed(() => globalStore.state.loading)
|
const loading = computed(() => globalStore.state.loading)
|
||||||
const viewRef = ref()
|
const viewRef = ref()
|
||||||
@@ -29,6 +31,12 @@
|
|||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
observer.disconnect()
|
observer.disconnect()
|
||||||
})
|
})
|
||||||
|
window['onClickPrivacy'] = () => {
|
||||||
|
const e = window.event || event
|
||||||
|
e.stopPropagation()
|
||||||
|
e.preventDefault()
|
||||||
|
console.log('点击了隐私政策')
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ body,
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
font-family: 'KaiseiOpti-Medium';
|
font-family: 'KaiseiOpti-Medium';
|
||||||
|
color: #232323;
|
||||||
}
|
}
|
||||||
@keyframes loading {
|
@keyframes loading {
|
||||||
0% {
|
0% {
|
||||||
@@ -96,8 +97,11 @@ body,
|
|||||||
--el-color-primary-dark-2: #565656;
|
--el-color-primary-dark-2: #565656;
|
||||||
/* 深灰色(加深20%) */
|
/* 深灰色(加深20%) */
|
||||||
}
|
}
|
||||||
|
.mini-scrollbar {
|
||||||
|
--mini-scrollbar-width: 0.4rem;
|
||||||
|
}
|
||||||
.mini-scrollbar::-webkit-scrollbar {
|
.mini-scrollbar::-webkit-scrollbar {
|
||||||
width: 0.4rem;
|
width: var(--mini-scrollbar-width);
|
||||||
}
|
}
|
||||||
.mini-scrollbar::-webkit-scrollbar-thumb {
|
.mini-scrollbar::-webkit-scrollbar-thumb {
|
||||||
border-radius: 0.4rem;
|
border-radius: 0.4rem;
|
||||||
@@ -107,62 +111,41 @@ body,
|
|||||||
--mosaic-bg-size: 1rem;
|
--mosaic-bg-size: 1rem;
|
||||||
--mosaic-bg-color1: #efefef;
|
--mosaic-bg-color1: #efefef;
|
||||||
--mosaic-bg-color2: #fff;
|
--mosaic-bg-color2: #fff;
|
||||||
background-image: repeating-conic-gradient(
|
background-image: repeating-conic-gradient(var(--mosaic-bg-color1) 0% 25%, var(--mosaic-bg-color2) 0% 50%);
|
||||||
var(--mosaic-bg-color1) 0% 25%,
|
|
||||||
var(--mosaic-bg-color2) 0% 50%
|
|
||||||
);
|
|
||||||
background-repeat: repeat;
|
background-repeat: repeat;
|
||||||
background-position: 50% 50%;
|
background-position: 50% 50%;
|
||||||
background-size: var(--mosaic-bg-size) var(--mosaic-bg-size);
|
background-size: var(--mosaic-bg-size) var(--mosaic-bg-size);
|
||||||
}
|
}
|
||||||
|
|
||||||
.flex {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
.flex-center {
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.flex-1 {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
.flex-col {
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
.align-center {
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.space-between {
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
.justify-center {
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
.relative {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
button[custom],
|
button[custom],
|
||||||
button[custom='white'] {
|
button[custom="white"] {
|
||||||
min-width: 19.4rem;
|
min-width: 19.4rem;
|
||||||
height: 5rem;
|
height: 5rem;
|
||||||
padding: 0 1rem;
|
padding: 0 1rem;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
border: none;
|
|
||||||
font-family: KaiseiOpti-Bold;
|
font-family: KaiseiOpti-Bold;
|
||||||
font-size: var(--button-font-size, 2rem);
|
font-size: var(--button-font-size, 2rem);
|
||||||
color: var(--button-color, #232323);
|
color: var(--button-color, #232323);
|
||||||
background: var(--button-bgcolor, #fff);
|
background: var(--button-bgcolor, #fff);
|
||||||
|
border: var(--button-border, none);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
button[custom]:active,
|
button[custom]:active,
|
||||||
button[custom='white']:active {
|
button[custom="white"]:active {
|
||||||
background: var(--button-click-bgcolor, #e4e4e4);
|
background: var(--button-click-bgcolor, #e4e4e4);
|
||||||
color: var(--button-click-color, #232323);
|
color: var(--button-click-color, #232323);
|
||||||
}
|
}
|
||||||
button[custom='black'] {
|
button[custom="black"] {
|
||||||
--button-bgcolor: #232323;
|
--button-bgcolor: #232323;
|
||||||
--button-color: #fff;
|
--button-color: #fff;
|
||||||
--button-click-bgcolor: #333;
|
--button-click-bgcolor: #333;
|
||||||
--button-click-color: #fff;
|
--button-click-color: #fff;
|
||||||
--button-font-size: 1.6rem;
|
--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%;
|
height: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
font-family: 'KaiseiOpti-Medium';
|
font-family: 'KaiseiOpti-Medium';
|
||||||
|
color: #232323;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes loading {
|
@keyframes loading {
|
||||||
@@ -118,8 +119,10 @@ body,
|
|||||||
|
|
||||||
// 迷你滚动条
|
// 迷你滚动条
|
||||||
.mini-scrollbar {
|
.mini-scrollbar {
|
||||||
|
--mini-scrollbar-width: 0.4rem;
|
||||||
|
|
||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
width: 0.4rem;
|
width: var(--mini-scrollbar-width);
|
||||||
}
|
}
|
||||||
|
|
||||||
&::-webkit-scrollbar-thumb {
|
&::-webkit-scrollbar-thumb {
|
||||||
@@ -146,11 +149,11 @@ button[custom="white"] {
|
|||||||
height: 5rem;
|
height: 5rem;
|
||||||
padding: 0 1rem;
|
padding: 0 1rem;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
border: none;
|
|
||||||
font-family: KaiseiOpti-Bold;
|
font-family: KaiseiOpti-Bold;
|
||||||
font-size: var(--button-font-size, 2rem);
|
font-size: var(--button-font-size, 2rem);
|
||||||
color: var(--button-color, #232323);
|
color: var(--button-color, #232323);
|
||||||
background: var(--button-bgcolor, #fff);
|
background: var(--button-bgcolor, #fff);
|
||||||
|
border: var(--button-border, none);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
@@ -165,4 +168,13 @@ button[custom="black"] {
|
|||||||
--button-click-bgcolor: #333;
|
--button-click-bgcolor: #333;
|
||||||
--button-click-color: #fff;
|
--button-click-color: #fff;
|
||||||
--button-font-size: 1.6rem;
|
--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/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 |
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 |
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 |
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 |
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 |
|
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 |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 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">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
|
import { ref, onMounted, onUnmounted, reactive, toRefs } from 'vue'
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
url: {
|
url: {
|
||||||
type: String,
|
type: String,
|
||||||
default: ''
|
default: ''
|
||||||
},
|
},
|
||||||
name: {
|
name: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'aaa'
|
default: 'aaa'
|
||||||
},
|
},
|
||||||
price: {
|
price: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '111'
|
default: '111'
|
||||||
}
|
},
|
||||||
})
|
download: {
|
||||||
const emit = defineEmits([
|
type: Boolean,
|
||||||
'addShopping'
|
default: false
|
||||||
])
|
},
|
||||||
let data = reactive({
|
showPrice: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
const emit = defineEmits(['addShopping', 'openDetail', 'download'])
|
||||||
|
let data = reactive({})
|
||||||
const addShopping = () => {
|
const addShopping = () => {
|
||||||
emit('addShopping')
|
if (props.download) {
|
||||||
|
emit('download')
|
||||||
|
} else {
|
||||||
|
emit('addShopping')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
const openDetail = () => {
|
||||||
onMounted(()=>{
|
emit('openDetail')
|
||||||
})
|
}
|
||||||
onUnmounted(()=>{
|
onMounted(() => {})
|
||||||
})
|
onUnmounted(() => {})
|
||||||
defineExpose({})
|
defineExpose({})
|
||||||
const {} = toRefs(data);
|
const {} = toRefs(data)
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="commodity-item">
|
<div class="commodity-item" :class="{ 'is-download': download }">
|
||||||
<img :src="props.url" alt="">
|
<img :src="props.url" alt="" @click="openDetail" />
|
||||||
<div class="detail">
|
<div class="detail">
|
||||||
<div calss="text">
|
<div class="text">
|
||||||
<div class="name">
|
<div class="name">
|
||||||
{{ props.name }}
|
{{ props.name }}
|
||||||
</div>
|
</div>
|
||||||
<div class="price">
|
<div class="price" :class="{ 'is-download': download }" v-if="props.showPrice">
|
||||||
{{ props.price }}
|
{{ props.price }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="btn" @click="addShopping">
|
<div class="btn" @click="addShopping">
|
||||||
<div class="text">
|
<div class="text">
|
||||||
<SvgIcon name="add" size="24"></SvgIcon>
|
<SvgIcon :name="download ? 'download' : 'add'" size="26" color="#232323"></SvgIcon>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.commodity-item{
|
.commodity-item {
|
||||||
width: var(--commodity-width,100%);
|
width: var(--commodity-width, 100%);
|
||||||
> img{
|
&.is-download{
|
||||||
width: 100%;
|
img{
|
||||||
height: var(--commodity-height,auto);
|
cursor: initial;
|
||||||
margin-bottom: var(--commodity-marginBottom,1rem);
|
}
|
||||||
}
|
}
|
||||||
> .detail{
|
> img {
|
||||||
display: flex;
|
width: 100%;
|
||||||
justify-content: space-between;
|
cursor: pointer;
|
||||||
align-items: center;
|
height: var(--commodity-height, auto);
|
||||||
.text{
|
margin-bottom: var(--commodity-marginBottom, 1rem);
|
||||||
color: #232323;
|
}
|
||||||
> .name{
|
> .detail {
|
||||||
font-family: "KaiseiOpti-Regular";
|
display: flex;
|
||||||
font-weight: 400;
|
justify-content: space-between;
|
||||||
font-size: var(--commodity-name-fontSize,1.6rem);
|
align-items: center;
|
||||||
line-height: var(--commodity-name-lineHeight,2.3rem);
|
> .text {
|
||||||
}
|
color: #232323;
|
||||||
> .price{
|
> .name {
|
||||||
font-family: "KaiseiOpti-Regular";
|
font-family: 'KaiseiOpti-Regular';
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-size: var(--commodity-price-fontSize,1.4rem);
|
font-size: var(--commodity-name-fontSize, 1.6rem);
|
||||||
line-height: var(--commodity-price-lineHeight,2.3rem);
|
line-height: var(--commodity-name-lineHeight, 2.3rem);
|
||||||
}
|
margin-bottom: var(--commodity-name-marginBottom, 0rem);
|
||||||
}
|
}
|
||||||
}
|
> .price {
|
||||||
}
|
font-family: 'KaiseiOpti-Regular';
|
||||||
</style>
|
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>
|
||||||
@@ -2,14 +2,15 @@ export default {
|
|||||||
Login: {
|
Login: {
|
||||||
login: 'Log in',
|
login: 'Log in',
|
||||||
register: 'Register',
|
register: 'Register',
|
||||||
loginTo: 'Log on to <span>FiDA</span>',
|
loginTip: 'Platform integrated with AiDA.<br />AiDA account login required.',
|
||||||
loginTitle: 'A multi-agent canvas for rapid, trend driven design iteration.',
|
|
||||||
name: 'Name',
|
name: 'Name',
|
||||||
email: 'Email',
|
email: 'Email',
|
||||||
password: 'Password',
|
password: 'Password',
|
||||||
|
passwordConfirmation: 'Password Confirmation',
|
||||||
enterName: 'Enter your name',
|
enterName: 'Enter your name',
|
||||||
enterEmail: 'Enter your email',
|
enterEmail: 'Enter your email',
|
||||||
enterPassword: 'Enter your password',
|
enterPassword: 'Enter your password',
|
||||||
|
enterPasswordAgain: 'Enter your password again',
|
||||||
forgotPassword: 'Forget password?',
|
forgotPassword: 'Forget password?',
|
||||||
pleaseInputName: 'Please input the name',
|
pleaseInputName: 'Please input the name',
|
||||||
nameLengthError: 'Name length must be between {min} and {max} characters',
|
nameLengthError: 'Name length must be between {min} and {max} characters',
|
||||||
@@ -28,7 +29,7 @@ export default {
|
|||||||
havenAccountToLogin: `Already have an account? <span onclick="onClickLogin()">Log in</span>`,
|
havenAccountToLogin: `Already have an account? <span onclick="onClickLogin()">Log in</span>`,
|
||||||
verifyEmail: 'Verify your email address',
|
verifyEmail: 'Verify your email address',
|
||||||
verifyCodeHasSent: 'A verification code has been sent to<br><span>{email}</span>',
|
verifyCodeHasSent: 'A verification code has been sent to<br><span>{email}</span>',
|
||||||
verify: 'Verify',
|
verify: 'VERIFY',
|
||||||
resendCode: 'Resend Code',
|
resendCode: 'Resend Code',
|
||||||
resendCodeIn: 'Resend Code in {time}',
|
resendCodeIn: 'Resend Code in {time}',
|
||||||
orContinueWith: 'or continue with',
|
orContinueWith: 'or continue with',
|
||||||
@@ -38,4 +39,93 @@ export default {
|
|||||||
sendCodeError: 'Send code error',
|
sendCodeError: 'Send code error',
|
||||||
retrievePassword: 'Retrieve password'
|
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,15 +2,15 @@ export default {
|
|||||||
Login: {
|
Login: {
|
||||||
login: '登录',
|
login: '登录',
|
||||||
register: '注册',
|
register: '注册',
|
||||||
signUp: '注册',
|
loginTip: '与 AiDA 集成的平台。<br />需要登录 AiDA 账户。',
|
||||||
loginTo: '登录到 <span>FiDA</span',
|
|
||||||
loginTitle: '一个多智能体画布,用于快速、趋势驱动的设计迭代。',
|
|
||||||
name: '姓名',
|
name: '姓名',
|
||||||
email: '邮箱',
|
email: '邮箱',
|
||||||
password: '密码',
|
password: '密码',
|
||||||
|
passwordConfirmation: '密码确认',
|
||||||
enterName: '请输入姓名',
|
enterName: '请输入姓名',
|
||||||
enterEmail: '请输入邮箱',
|
enterEmail: '请输入邮箱',
|
||||||
enterPassword: '请输入密码',
|
enterPassword: '请输入密码',
|
||||||
|
enterPasswordAgain: '请输入密码确认',
|
||||||
forgotPassword: '忘记密码?',
|
forgotPassword: '忘记密码?',
|
||||||
pleaseInputName: '请输入姓名',
|
pleaseInputName: '请输入姓名',
|
||||||
nameLengthError: '姓名长度必须在 {min} 到 {max} 个字符之间',
|
nameLengthError: '姓名长度必须在 {min} 到 {max} 个字符之间',
|
||||||
@@ -39,4 +39,93 @@ export default {
|
|||||||
sendCodeError: '发送验证码失败',
|
sendCodeError: '发送验证码失败',
|
||||||
retrievePassword: '找回密码'
|
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: '英国'
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,22 @@ const router = createRouter({
|
|||||||
path: '/brand',
|
path: '/brand',
|
||||||
name: 'brand',
|
name: 'brand',
|
||||||
component: () => import('../views/brand/index.vue')
|
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',
|
path: '/settings',
|
||||||
@@ -29,6 +45,26 @@ const router = createRouter({
|
|||||||
component: () => import('@/views/setting/index.vue'),
|
component: () => import('@/views/setting/index.vue'),
|
||||||
meta: { cache: true }
|
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(.*)',
|
path: '/:pathMatch(.*)',
|
||||||
name: '404',
|
name: '404',
|
||||||
|
|||||||
@@ -53,9 +53,9 @@ export function generateUUID(): string {
|
|||||||
if (typeof crypto !== 'undefined' && crypto.randomUUID) {
|
if (typeof crypto !== 'undefined' && crypto.randomUUID) {
|
||||||
return crypto.randomUUID()
|
return crypto.randomUUID()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 备用方案:手动生成UUID v4
|
// 备用方案:手动生成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 r = Math.random() * 16 | 0
|
||||||
const v = c === 'x' ? r : (r & 0x3 | 0x8)
|
const v = c === 'x' ? r : (r & 0x3 | 0x8)
|
||||||
return v.toString(16)
|
return v.toString(16)
|
||||||
@@ -70,39 +70,37 @@ export {
|
|||||||
|
|
||||||
/** 时间格式化-自定义格式
|
/** 时间格式化-自定义格式
|
||||||
* @param value 时间对象|时间戳|时间字符串
|
* @param value 时间对象|时间戳|时间字符串
|
||||||
* @param format 格式化字符串,默认值为 'yyyy-MM-dd HH:mm:ss'
|
* @param format 格式化字符串,默认值为 'YYYY-MM-DD HH:mm:ss'
|
||||||
* @returns 格式化后的时间字符串
|
* @returns 格式化后的时间字符串
|
||||||
*/
|
*/
|
||||||
export function FormatDate(value: Date | number | string, format: string = 'yyyy-MM-dd HH:mm:ss') {
|
export function FormatDate(value: Date | number | string, format: string = 'YYYY-MM-DD HH:mm:ss') {
|
||||||
const date = new Date(value);
|
const d = new Date(value);
|
||||||
const yyyy = String(date.getFullYear());
|
if (!d || isNaN(d.getTime())) return 'Invalid Date';
|
||||||
const yy = String(date.getFullYear()).slice(-2);
|
const pad = (n) => String(n).padStart(2, '0');
|
||||||
const MM = String(date.getMonth() + 1).padStart(2, '0');
|
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
||||||
const M = String(date.getMonth() + 1);
|
const tokens = {
|
||||||
const dd = String(date.getDate()).padStart(2, '0');
|
YYYY: d.getFullYear(),
|
||||||
const d = String(date.getDate());
|
YY: String(d.getFullYear()).slice(-2),
|
||||||
const HH = String(date.getHours()).padStart(2, '0');
|
MM: pad(d.getMonth() + 1),
|
||||||
const H = String(date.getHours());
|
M: d.getMonth() + 1,
|
||||||
const mm = String(date.getMinutes()).padStart(2, '0');
|
SM: months[d.getMonth()],
|
||||||
const m = String(date.getMinutes());
|
DD: pad(d.getDate()),
|
||||||
const ss = String(date.getSeconds()).padStart(2, '0');
|
D: d.getDate(),
|
||||||
const s = String(date.getSeconds());
|
HH: pad(d.getHours()),
|
||||||
const str = format.replaceAll('yyyy', yyyy)
|
H: d.getHours(),
|
||||||
.replaceAll('yy', yy)
|
hh: pad(d.getHours() % 12 || 12),
|
||||||
.replaceAll('MM', MM)
|
h: d.getHours() % 12 || 12,
|
||||||
.replaceAll('M', M)
|
mm: pad(d.getMinutes()),
|
||||||
.replaceAll('dd', dd)
|
m: d.getMinutes(),
|
||||||
.replaceAll('d', d)
|
ss: pad(d.getSeconds()),
|
||||||
.replaceAll('HH', HH)
|
s: d.getSeconds(),
|
||||||
.replaceAll('H', H)
|
A: d.getHours() < 12 ? 'AM' : 'PM',
|
||||||
.replaceAll('mm', mm)
|
a: d.getHours() < 12 ? 'am' : 'pm'
|
||||||
.replaceAll('m', m)
|
}
|
||||||
.replaceAll('ss', ss)
|
const reg = new RegExp(Object.keys(tokens).join('|'), 'g')
|
||||||
.replaceAll('s', s);
|
return format.replace(reg, match => tokens[match]);
|
||||||
return str;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 下载图片
|
* 下载图片
|
||||||
* @param list 图片列表
|
* @param list 图片列表
|
||||||
@@ -163,7 +161,7 @@ export function encryptPassword(password: string): string {
|
|||||||
* @param url 图片URL
|
* @param url 图片URL
|
||||||
* @returns 无
|
* @returns 无
|
||||||
*/
|
*/
|
||||||
export async function shareImageToWhatsapp (url: string){
|
export async function shareImageToWhatsapp(url: string) {
|
||||||
// 把图片 URL 转为 Blob
|
// 把图片 URL 转为 Blob
|
||||||
const blob = await fetch(url).then((res) => res.blob())
|
const blob = await fetch(url).then((res) => res.blob())
|
||||||
|
|
||||||
@@ -195,3 +193,21 @@ export function CountDown(time: number) {
|
|||||||
const ss = String(time % 60).padStart(2, '0');
|
const ss = String(time % 60).padStart(2, '0');
|
||||||
return `${mm}:${ss}`;
|
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">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
|
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
|
||||||
import CommodityList from "./commodity-list.vue";
|
import { useRouter } from "vue-router";
|
||||||
import MerchantInfo from "./merchant-info.vue";
|
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 props = defineProps({
|
||||||
//})
|
//})
|
||||||
//const emit = defineEmits([
|
//const emit = defineEmits([
|
||||||
//])
|
//])
|
||||||
|
const router = useRouter()
|
||||||
let data = reactive({
|
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(()=>{
|
onMounted(()=>{
|
||||||
})
|
})
|
||||||
onUnmounted(()=>{
|
onUnmounted(()=>{
|
||||||
@@ -18,15 +112,52 @@ const {} = toRefs(data);
|
|||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="brand">
|
<div class="brand">
|
||||||
<div class="header-img">
|
<div class="header-img" :class="{'active': searchBrand.length > 0}">
|
||||||
<img src="@/assets/images/brand/brandBg.png" alt="">
|
<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>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="merchant-info">
|
<div class="input">
|
||||||
<MerchantInfo></MerchantInfo>
|
<input type="text" v-model="searchBrand" @input="changeSearchBrand" placeholder="Search brand">
|
||||||
|
<div class="icon">
|
||||||
|
<SvgIcon name="brand-search" size="32" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="commodity-list">
|
<div class="merchantList" v-if="searchBrand.length > 0">
|
||||||
<CommodityList @addShopping="addShopping"></CommodityList>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
<Footer></Footer>
|
<Footer></Footer>
|
||||||
@@ -38,34 +169,151 @@ const {} = toRefs(data);
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
.header-img{
|
.header-img{
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
height: 34.4rem;
|
||||||
|
transition: all .3s;
|
||||||
|
&.active{
|
||||||
|
height: 14.7rem;
|
||||||
|
}
|
||||||
>img{
|
>img{
|
||||||
width: 100%;
|
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;
|
display: flex;
|
||||||
height: auto;
|
height: auto;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
.merchant-info{
|
flex-direction: column;
|
||||||
width: 40rem;
|
align-items: center;
|
||||||
padding-left: 12.7rem;
|
flex: 1;
|
||||||
padding-right: 2.7rem;
|
overflow: hidden;
|
||||||
height: var(--app-view-height);
|
> .input{
|
||||||
overflow-y: auto;
|
width: 66.6rem;
|
||||||
position: sticky;
|
display: flex;
|
||||||
top: 0;
|
border-bottom: 2px solid #232323;
|
||||||
&::-webkit-scrollbar{
|
padding: 1.4rem 1.4rem 1.4rem 2.4rem;
|
||||||
width: 0;
|
background-color: #f9f9f9;
|
||||||
height: 0;
|
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;
|
flex: 1;
|
||||||
border-left: 0.5px solid #585858;
|
overflow-y: auto;
|
||||||
border-right: 0.5px solid #585858;
|
gap: 3.2rem;
|
||||||
margin-right: 9rem;
|
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,
|
url: img,
|
||||||
title: "Windswept Burden",
|
title: "Windswept Burden",
|
||||||
price: "$100.00",
|
price: "$100.00",
|
||||||
},{
|
}
|
||||||
url: img,
|
|
||||||
title: "Windswept Burden",
|
|
||||||
price: "$100.00",
|
|
||||||
},
|
|
||||||
])
|
])
|
||||||
const type = ref('All')
|
const type = ref('All')
|
||||||
const addShopping = (item) => {
|
const addShopping = (item) => {
|
||||||
@@ -128,41 +124,26 @@ const {} = toRefs(data);
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.list{
|
.list{
|
||||||
border-top: 0.5px solid #585858;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
// display: grid;
|
||||||
|
// align-content: start;
|
||||||
|
// grid-template-columns: repeat(3, 1fr);
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
display: grid;
|
display: grid;
|
||||||
align-content: start;
|
align-content: start;
|
||||||
grid-template-columns: repeat(3, 1fr);
|
grid-template-columns: repeat(auto-fit, minmax(min(100%, 28rem), 1fr));
|
||||||
overflow-y: auto;
|
border-top: 0.5px solid #585858;
|
||||||
|
padding: .5px 0 0 .5px;
|
||||||
/* 垂直线(右边框) */
|
/* 垂直线(右边框) */
|
||||||
.item{
|
.item{
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 1.2rem;
|
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;
|
border-bottom: 0.5px solid #585858;
|
||||||
z-index: 1;
|
border-right: 0.5px solid #585858;
|
||||||
}
|
margin-right: -1px;
|
||||||
/* 移除最后一列的右边框 */
|
margin-bottom: -1px;
|
||||||
.item:nth-child(3n)::before {
|
|
||||||
display: none;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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 coreConcept from "./coreConcept.vue";
|
||||||
import inspiration from "./inspiration.vue";
|
import inspiration from "./inspiration.vue";
|
||||||
import feelingWithAiDA from "./feelingWithAiDA.vue";
|
import feelingWithAiDA from "./feelingWithAiDA.vue";
|
||||||
|
import joinUs from "./join-us.vue";
|
||||||
import CommodityItem from "@/components/CommodityItem.vue";
|
import CommodityItem from "@/components/CommodityItem.vue";
|
||||||
//const props = defineProps({
|
//const props = defineProps({
|
||||||
//})
|
//})
|
||||||
//const emit = defineEmits([
|
const emit = defineEmits([
|
||||||
//])
|
'addShopping'
|
||||||
|
])
|
||||||
let data = reactive({
|
let data = reactive({
|
||||||
})
|
})
|
||||||
const list = ref([
|
const list = ref([
|
||||||
@@ -27,8 +29,12 @@ const list = ref([
|
|||||||
},
|
},
|
||||||
])
|
])
|
||||||
const addShopping = (item) => {
|
const addShopping = (item) => {
|
||||||
console.log(item);
|
emit('addShopping', item)
|
||||||
}
|
}
|
||||||
|
const openCodeCreate = () => {
|
||||||
|
window.open('https://code-create.com.hk/', '_blank')
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(()=>{
|
onMounted(()=>{
|
||||||
})
|
})
|
||||||
onUnmounted(()=>{
|
onUnmounted(()=>{
|
||||||
@@ -40,21 +46,22 @@ const {} = toRefs(data);
|
|||||||
<div class="detail">
|
<div class="detail">
|
||||||
<div class="left">
|
<div class="left">
|
||||||
<div class="personal">
|
<div class="personal">
|
||||||
<img :src="img" alt="">
|
<img src="@/assets/images/collectionStory/code-create.png" alt="">
|
||||||
<div class="name">
|
<div class="name">
|
||||||
<span>Lian Su</span>
|
<span>Code-Create</span>
|
||||||
<div class="icon">
|
<div class="icon" @click="openCodeCreate">
|
||||||
<SvgIcon name="share" size="24" />
|
<SvgIcon name="share" size="24" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="center">
|
<div class="center">
|
||||||
<coreConcept ></coreConcept>
|
<joinUs></joinUs>
|
||||||
|
<!-- <coreConcept ></coreConcept>
|
||||||
<div class="line"></div>
|
<div class="line"></div>
|
||||||
<inspiration ></inspiration>
|
<inspiration ></inspiration>
|
||||||
<div class="line"></div>
|
<div class="line"></div>
|
||||||
<feelingWithAiDA ></feelingWithAiDA>
|
<feelingWithAiDA ></feelingWithAiDA> -->
|
||||||
</div>
|
</div>
|
||||||
<div class="right">
|
<div class="right">
|
||||||
<div class="item" v-for="item in list" :key="item.url">
|
<div class="item" v-for="item in list" :key="item.url">
|
||||||
@@ -66,10 +73,11 @@ const {} = toRefs(data);
|
|||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.detail{
|
.detail{
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: auto;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
height: calc(100vh - var(--header-height) - var(--footer-height));
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
|
overflow: hidden;
|
||||||
> div{
|
> div{
|
||||||
// height: 100%;
|
// height: 100%;
|
||||||
}
|
}
|
||||||
@@ -92,6 +100,9 @@ const {} = toRefs(data);
|
|||||||
display: flex;
|
display: flex;
|
||||||
gap: .4rem;
|
gap: .4rem;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
> .icon{
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
> span{
|
> span{
|
||||||
font-family: 'KaiseiOpti-Bold';
|
font-family: 'KaiseiOpti-Bold';
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
@@ -109,8 +120,9 @@ const {} = toRefs(data);
|
|||||||
border-right: 0.5px solid #585858;
|
border-right: 0.5px solid #585858;
|
||||||
// overflow-y: auto;
|
// overflow-y: auto;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
// height: 100%;
|
height: 100%;
|
||||||
height: auto;
|
// height: auto;
|
||||||
|
position: relative;
|
||||||
.line{
|
.line{
|
||||||
border: 0.5px solid #58585899;
|
border: 0.5px solid #58585899;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -122,14 +134,15 @@ const {} = toRefs(data);
|
|||||||
> .right{
|
> .right{
|
||||||
width: 25.4rem;
|
width: 25.4rem;
|
||||||
padding-top: 6rem;
|
padding-top: 6rem;
|
||||||
position: sticky;
|
// position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
height: calc(100vh - var(--header-height));
|
// height: calc(100vh - var(--header-height));
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 4rem;
|
gap: 4rem;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
height: 100%;
|
||||||
&::-webkit-scrollbar{
|
&::-webkit-scrollbar{
|
||||||
display: none;
|
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">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
|
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
|
||||||
import Detail from "./detail/index.vue";
|
import Detail from "./detail/index.vue";
|
||||||
|
import myEvent from '@/utils/myEvent'
|
||||||
//const props = defineProps({
|
//const props = defineProps({
|
||||||
//})
|
//})
|
||||||
//const emit = defineEmits([
|
//const emit = defineEmits([
|
||||||
//])
|
//])
|
||||||
let data = reactive({
|
let data = reactive({
|
||||||
})
|
})
|
||||||
|
const addShopping = (item) => {
|
||||||
|
myEvent.emit('addShopping', item)
|
||||||
|
}
|
||||||
onMounted(()=>{
|
onMounted(()=>{
|
||||||
})
|
})
|
||||||
onUnmounted(()=>{
|
onUnmounted(()=>{
|
||||||
@@ -25,29 +28,20 @@ const {} = toRefs(data);
|
|||||||
</div>
|
</div>
|
||||||
<div class="title-content">
|
<div class="title-content">
|
||||||
<div class="title-box">
|
<div class="title-box">
|
||||||
<div class="left">
|
<div class="title">
|
||||||
<div class="title">
|
We’re Seeking
|
||||||
Windswept Burden
|
|
||||||
</div>
|
|
||||||
<div class="info">
|
|
||||||
Publish Date: 24th Nov 2025
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="right">
|
<div class="info">
|
||||||
<div class="info">
|
Fashion Voice Worth Featuring.
|
||||||
“We are spiritual nomads carrying what wind cannot take. <br />
|
|
||||||
Inspired by those who knew home is not a place, but what you wear.”
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="scrolling-learn-more">
|
<div class="button">
|
||||||
<div>Scrolling Learn More</div>
|
<a href="mailto:info@code-create.com.hk">Contact Us if Interested</a>
|
||||||
<SvgIcon name="collectionStory-scrollingLearnMore" size="48" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<Detail></Detail>
|
<Detail @addShopping="addShopping"></Detail>
|
||||||
</div>
|
</div>
|
||||||
<Footer></Footer>
|
<Footer></Footer>
|
||||||
</div>
|
</div>
|
||||||
@@ -70,7 +64,7 @@ const {} = toRefs(data);
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: .8rem;
|
gap: .8rem;
|
||||||
color: #fff;
|
color: #000;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
> .text{
|
> .text{
|
||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
@@ -80,73 +74,46 @@ const {} = toRefs(data);
|
|||||||
}
|
}
|
||||||
> .title-content{
|
> .title-content{
|
||||||
width: 100%;
|
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;
|
position: absolute;
|
||||||
bottom: 2.1rem;
|
padding: 0 6.7rem;
|
||||||
left: 50%;
|
margin-top: 11.5rem;
|
||||||
transform: translateX(-50%);
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
color: #fff;
|
align-items: flex-start;
|
||||||
animation: scroll 3s linear infinite;
|
> .title-box{
|
||||||
@keyframes scroll {
|
display: flex;
|
||||||
0% {
|
flex-direction: column;
|
||||||
transform: translateY(0);
|
> .title{
|
||||||
|
font-size: 6.5rem;
|
||||||
|
line-height: 100%;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #585858;
|
||||||
}
|
}
|
||||||
50% {
|
> .info{
|
||||||
transform: translateY(-20%);
|
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{
|
.banner{
|
||||||
width: 100%;
|
width: 100%;
|
||||||
position: absolute;
|
|
||||||
z-index: -1;
|
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>
|
<template>
|
||||||
<div class="home-index">
|
<div class="home-index">
|
||||||
<section-index />
|
<section-index />
|
||||||
<section-designers />
|
<section-designer />
|
||||||
<section-design />
|
<section-design />
|
||||||
<section-digital-items1 />
|
<section-digital-items1 />
|
||||||
<section-digital-items2 />
|
<section-digital-items2 />
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import SectionIndex from './section-index.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 SectionDesign from './section-design.vue'
|
||||||
import SectionDigitalItems1 from './section-digital-items1.vue'
|
import SectionDigitalItems1 from './section-digital-items1.vue'
|
||||||
import SectionDigitalItems2 from './section-digital-items2.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="mate">
|
||||||
<div class="logos">
|
<div class="logos">
|
||||||
<img src="@/assets/images/logos/code-create-black.png" />
|
<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" />
|
<img src="@/assets/images/logos/aida-black.png" />
|
||||||
</div>
|
</div>
|
||||||
<div class="tip">
|
<div class="tip">
|
||||||
|
|||||||
@@ -1,74 +1,58 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="section-index bgw">
|
<section class="section-index bgw">
|
||||||
<img src="@/assets/images/home/bg.jpg" class="bg" />
|
<img src="@/assets/images/home/bg.png" class="bg" />
|
||||||
<div class="shade-1"></div>
|
|
||||||
<div class="shade-2"></div>
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="title">Windswept Burden</div>
|
<div class="title" v-html="title"></div>
|
||||||
<div class="tip">We are spiritual nomads carrying<br />what wind cannot take.</div>
|
<div class="tip">
|
||||||
<button custom>View More</button>
|
Discover collections through the stories behind their creation. A curated space connecting
|
||||||
<div class="aida-logo"><img src="@/assets/images/logos/aida.png" /></div>
|
designers, narratives, and fashion commerce.
|
||||||
<p class="tip">
|
</div>
|
||||||
What you wear is how you present yourself to the world, especially today, when human
|
<button custom="black-box" @click="handleClickArrow">
|
||||||
contacts are so quick. Fashion is instant language
|
<svg-icon name="arrow_right" size="34" />
|
||||||
</p>
|
</button>
|
||||||
<p class="tip">I firmly believe that with the right footwear one can rule the world.</p>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue'
|
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>
|
</script>
|
||||||
|
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
.section-index {
|
.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 {
|
> .content {
|
||||||
top: 16rem;
|
top: 13.7rem;
|
||||||
left: 9rem;
|
left: 9rem;
|
||||||
color: #fff;
|
color: #232323;
|
||||||
> .title {
|
> .title {
|
||||||
font-family: KaiseiOpti-Bold;
|
font-family: KaiseiOpti-Bold;
|
||||||
font-size: 6rem;
|
font-size: 7rem;
|
||||||
margin-bottom: 3rem;
|
margin-bottom: 3rem;
|
||||||
}
|
span {
|
||||||
> div.tip {
|
font-family: KaiseiOpti-Medium;
|
||||||
font-family: KaiseiOpti-Regular;
|
&.small {
|
||||||
font-size: 3.2rem;
|
font-size: 6rem;
|
||||||
line-height: 4.3rem;
|
}
|
||||||
}
|
|
||||||
> button {
|
|
||||||
margin-top: 11.6rem;
|
|
||||||
margin-bottom: 13.9rem;
|
|
||||||
}
|
|
||||||
> .aida-logo {
|
|
||||||
margin-bottom: 3rem;
|
|
||||||
> img {
|
|
||||||
width: auto;
|
|
||||||
height: 5rem;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
> p.tip {
|
> .tip {
|
||||||
font-family: KaiseiOpti-Regular;
|
width: 50rem;
|
||||||
font-size: 1.2rem;
|
font-size: 1.8rem;
|
||||||
line-height: 2rem;
|
line-height: 2.6rem;
|
||||||
color: #ededed;
|
color: #585858;
|
||||||
|
}
|
||||||
|
> button {
|
||||||
|
margin-top: 12rem;
|
||||||
|
min-width: 0;
|
||||||
|
width: 8rem;
|
||||||
|
height: 8rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,16 +35,22 @@
|
|||||||
.register:deep(.el-form) .el-input,
|
.register:deep(.el-form) .el-input,
|
||||||
.login:deep(.el-form) .el-input {
|
.login:deep(.el-form) .el-input {
|
||||||
--el-input-height: 3.4rem;
|
--el-input-height: 3.4rem;
|
||||||
|
--el-input-inner-height: auto;
|
||||||
--el-input-border-radius: 0;
|
--el-input-border-radius: 0;
|
||||||
--el-input-text-color: #232323;
|
--el-input-text-color: #232323;
|
||||||
--el-border-color: #C4C4C4;
|
--el-border-color: #C4C4C4;
|
||||||
font-size: 1.4rem;
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
.retrieve-password:deep(.el-form) .el-input::placeholder,
|
.retrieve-password:deep(.el-form) .el-input::placeholder,
|
||||||
.register:deep(.el-form) .el-input::placeholder,
|
.register:deep(.el-form) .el-input::placeholder,
|
||||||
.login:deep(.el-form) .el-input::placeholder {
|
.login:deep(.el-form) .el-input::placeholder {
|
||||||
color: #9F9F9F;
|
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,
|
.retrieve-password:deep(.el-form) .password-tip,
|
||||||
.register:deep(.el-form) .password-tip,
|
.register:deep(.el-form) .password-tip,
|
||||||
.login:deep(.el-form) .password-tip {
|
.login:deep(.el-form) .password-tip {
|
||||||
@@ -108,16 +114,47 @@
|
|||||||
margin-top: -0.6rem;
|
margin-top: -0.6rem;
|
||||||
--el-checkbox-height: auto;
|
--el-checkbox-height: auto;
|
||||||
}
|
}
|
||||||
.retrieve-password:deep(.el-form) .privacy .el-checkbox__label,
|
.retrieve-password:deep(.el-form) .privacy .el-checkbox,
|
||||||
.register:deep(.el-form) .privacy .el-checkbox__label,
|
.register:deep(.el-form) .privacy .el-checkbox,
|
||||||
.login:deep(.el-form) .privacy .el-checkbox__label {
|
.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;
|
font-size: 1.1rem;
|
||||||
color: #666666;
|
color: #666666;
|
||||||
font-family: KaiseiOpti-Regular;
|
font-family: KaiseiOpti-Regular;
|
||||||
}
|
}
|
||||||
.retrieve-password:deep(.el-form) .privacy .el-checkbox__label > div > span,
|
.retrieve-password:deep(.el-form) .privacy .el-checkbox .el-checkbox__label > div > span,
|
||||||
.register:deep(.el-form) .privacy .el-checkbox__label > div > span,
|
.register:deep(.el-form) .privacy .el-checkbox .el-checkbox__label > div > span,
|
||||||
.login:deep(.el-form) .privacy .el-checkbox__label > div > span {
|
.login:deep(.el-form) .privacy .el-checkbox .el-checkbox__label > div > span {
|
||||||
font-family: KaiseiOpti-Bold;
|
font-family: KaiseiOpti-Bold;
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="visible-code">
|
<div class="email-verify">
|
||||||
<div class="tip" v-html="$t('Login.verifyCodeHasSent', { email: props.email })"></div>
|
<div class="tip" v-html="$t('Login.verifyCodeHasSent', { email: props.email })"></div>
|
||||||
<input-code @submit="onVerify" v-model="code" ref="inputCodeRef" />
|
<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">{{ $t('Login.resendCodeIn', { time: timeStr }) }}</p>
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
<span @click="onResend">{{ $t('Login.resendCode') }}</span>
|
<span @click="onResend">{{ $t('Login.resendCode') }}</span>
|
||||||
</p>
|
</p>
|
||||||
<button class="verify" custom="black" @click="onVerify">{{ $t('Login.verify') }}</button>
|
<button class="verify" custom="black" @click="onVerify">{{ $t('Login.verify') }}</button>
|
||||||
<other-login />
|
<other-login v-if="isShowOtherLogin" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -29,7 +29,8 @@
|
|||||||
type: String as () => 'LOGIN' | 'REGISTER' | 'FORGOT_PWD',
|
type: String as () => 'LOGIN' | 'REGISTER' | 'FORGOT_PWD',
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
password: { type: String, default: '' }
|
password: { type: String, default: '' },
|
||||||
|
isShowOtherLogin: { type: Boolean, default: true }
|
||||||
})
|
})
|
||||||
const code = ref('')
|
const code = ref('')
|
||||||
const time = ref(60)
|
const time = ref(60)
|
||||||
@@ -96,7 +97,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.visible-code {
|
.email-verify {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -123,17 +124,15 @@
|
|||||||
--button-font-size: 1.4rem;
|
--button-font-size: 1.4rem;
|
||||||
}
|
}
|
||||||
> .time {
|
> .time {
|
||||||
|
font-family: KaiseiOpti-Regular;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
margin-top: 2.4rem;
|
margin-top: 2.4rem;
|
||||||
font-size: 1.6rem;
|
font-size: 1.2rem;
|
||||||
color: #666;
|
color: #666;
|
||||||
font-family: Regular;
|
|
||||||
> span {
|
> span {
|
||||||
color: #ff7a50;
|
color: #232323;
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-weight: 500;
|
|
||||||
font-family: Medium;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
> .other-login {
|
> .other-login {
|
||||||
|
|||||||
@@ -1,117 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="index">
|
|
||||||
<div class="header">
|
|
||||||
<span class="tip">{{ $t('AlphaVersion') }}</span>
|
|
||||||
<img src="@/assets/images/logo-1.png" class="logo" />
|
|
||||||
<p class="split"></p>
|
|
||||||
<button class="login" @click="onLogin">{{ $t('Login.login') }}</button>
|
|
||||||
<button class="register" @click="onRegister">{{ $t('Login.register') }}</button>
|
|
||||||
</div>
|
|
||||||
<img src="@/assets/images/login/index-title.png" class="title" draggable="false" />
|
|
||||||
<img src="@/assets/images/login/index-zhuangshi.png" class="zhuangshi" draggable="false" />
|
|
||||||
<div class="tip">{{ $t('Login.indexTip') }}</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { computed } from 'vue'
|
|
||||||
import { useRouter } from 'vue-router'
|
|
||||||
const router = useRouter()
|
|
||||||
const onLogin = () => {
|
|
||||||
router.push({ name: 'login' })
|
|
||||||
}
|
|
||||||
const onRegister = () => {
|
|
||||||
router.push({ name: 'register' })
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="less" scoped>
|
|
||||||
.index {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
position: relative;
|
|
||||||
background-image: url('@/assets/images/login/index-bg.png');
|
|
||||||
background-size: cover;
|
|
||||||
background-position: center;
|
|
||||||
> .header {
|
|
||||||
position: absolute;
|
|
||||||
top: 3rem;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
> * {
|
|
||||||
position: relative;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
> .tip {
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
font-size: 3rem;
|
|
||||||
text-align: center;
|
|
||||||
font-family: Regular;
|
|
||||||
color: #fff;
|
|
||||||
z-index: 0;
|
|
||||||
}
|
|
||||||
> .logo {
|
|
||||||
width: auto;
|
|
||||||
height: 2.5rem;
|
|
||||||
margin-left: 3.8rem;
|
|
||||||
}
|
|
||||||
> .split {
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
> button {
|
|
||||||
margin-right: 3rem;
|
|
||||||
width: 20rem;
|
|
||||||
height: 5.2rem;
|
|
||||||
border-radius: 5rem;
|
|
||||||
border: none;
|
|
||||||
outline: none;
|
|
||||||
font-size: 2.2rem;
|
|
||||||
font-weight: 600;
|
|
||||||
font-family: SemiBold;
|
|
||||||
border: 0.2rem solid #fff;
|
|
||||||
&:active {
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
> .login {
|
|
||||||
background-color: #fff;
|
|
||||||
color: #713e1f;
|
|
||||||
}
|
|
||||||
> .register {
|
|
||||||
background-color: transparent;
|
|
||||||
color: #ffffff;
|
|
||||||
backdrop-filter: blur(10px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
> .zhuangshi,
|
|
||||||
> .title,
|
|
||||||
> .tip {
|
|
||||||
position: absolute;
|
|
||||||
left: 50%;
|
|
||||||
transform: translateX(-50%);
|
|
||||||
}
|
|
||||||
> .title {
|
|
||||||
// width: 55%;
|
|
||||||
width: 105.6rem;
|
|
||||||
height: auto;
|
|
||||||
top: 20.5rem;
|
|
||||||
}
|
|
||||||
> .zhuangshi {
|
|
||||||
width: 21.5rem;
|
|
||||||
height: auto;
|
|
||||||
bottom: 13.4rem;
|
|
||||||
}
|
|
||||||
> .tip {
|
|
||||||
font-size: 2.8rem;
|
|
||||||
font-family: Regular;
|
|
||||||
color: #fff;
|
|
||||||
bottom: 8rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -29,14 +29,19 @@
|
|||||||
|
|
||||||
.el-input {
|
.el-input {
|
||||||
--el-input-height: 3.4rem;
|
--el-input-height: 3.4rem;
|
||||||
|
--el-input-inner-height: auto;
|
||||||
--el-input-border-radius: 0;
|
--el-input-border-radius: 0;
|
||||||
--el-input-text-color: #232323;
|
--el-input-text-color: #232323;
|
||||||
--el-border-color: #C4C4C4;
|
--el-border-color: #C4C4C4;
|
||||||
font-size: 1.4rem;
|
font-size: 1rem;
|
||||||
|
|
||||||
&::placeholder {
|
&::placeholder {
|
||||||
color: #9F9F9F;
|
color: #9F9F9F;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.el-input__wrapper {
|
||||||
|
padding: 0.1rem 1rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.password-tip {
|
.password-tip {
|
||||||
@@ -95,22 +100,53 @@
|
|||||||
margin-top: -0.6rem;
|
margin-top: -0.6rem;
|
||||||
--el-checkbox-height: auto;
|
--el-checkbox-height: auto;
|
||||||
|
|
||||||
.el-checkbox__label {
|
|
||||||
font-size: 1.1rem;
|
|
||||||
color: #666666;
|
|
||||||
font-family: KaiseiOpti-Regular;
|
|
||||||
|
|
||||||
>div {
|
|
||||||
>span {
|
.el-checkbox {
|
||||||
font-family: KaiseiOpti-Bold;
|
margin-right: 0;
|
||||||
text-decoration: underline;
|
height: auto;
|
||||||
cursor: pointer;
|
--el-checkbox-font-size: 1.4rem;
|
||||||
color: #232323;
|
--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 {
|
>.other-login {
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
:close-on-click-modal="false"
|
:close-on-click-modal="false"
|
||||||
>
|
>
|
||||||
<div class="login-dialog-content">
|
<div class="login-dialog-content">
|
||||||
<img class="bg" src="@/assets/images/login/bg.jpg" />
|
|
||||||
<img class="logo" src="@/assets/images/logo.png" />
|
<img class="logo" src="@/assets/images/logo.png" />
|
||||||
<div class="close" @click="show = false"><svg-icon name="close" /></div>
|
<div class="close" @click="show = false"><svg-icon name="close" /></div>
|
||||||
<div class="content" v-if="curentTabInfo">
|
<div class="content" v-if="curentTabInfo">
|
||||||
@@ -22,8 +21,10 @@
|
|||||||
<div class="nav" v-show="!curentTabInfo.title">
|
<div class="nav" v-show="!curentTabInfo.title">
|
||||||
<div
|
<div
|
||||||
class="item"
|
class="item"
|
||||||
:class="{ active: currentTab === TabNames.sign_up }"
|
:class="{
|
||||||
@click="currentTab = TabNames.sign_up"
|
active: [TabNames.register, TabNames.register_success].includes(currentTab)
|
||||||
|
}"
|
||||||
|
@click="currentTab = TabNames.register"
|
||||||
>
|
>
|
||||||
SIGN UP
|
SIGN UP
|
||||||
</div>
|
</div>
|
||||||
@@ -42,6 +43,7 @@
|
|||||||
@login="onLogin"
|
@login="onLogin"
|
||||||
@register="onRegister"
|
@register="onRegister"
|
||||||
@submit-email-code="onSubmitEmailCode"
|
@submit-email-code="onSubmitEmailCode"
|
||||||
|
@back="onBack"
|
||||||
:name="data.name"
|
:name="data.name"
|
||||||
:email="data.email"
|
:email="data.email"
|
||||||
:password="data.password"
|
:password="data.password"
|
||||||
@@ -58,6 +60,8 @@
|
|||||||
import login from './login.vue'
|
import login from './login.vue'
|
||||||
import register from './register.vue'
|
import register from './register.vue'
|
||||||
import emailVerify from './email-verify.vue'
|
import emailVerify from './email-verify.vue'
|
||||||
|
import registerSuccess from './register-success.vue'
|
||||||
|
import retrievePassword from './retrieve-password.vue'
|
||||||
import myEvent from '@/utils/myEvent'
|
import myEvent from '@/utils/myEvent'
|
||||||
const data = ref({
|
const data = ref({
|
||||||
name: '',
|
name: '',
|
||||||
@@ -65,11 +69,11 @@
|
|||||||
password: '',
|
password: '',
|
||||||
type: ''
|
type: ''
|
||||||
})
|
})
|
||||||
const show = ref(false)
|
|
||||||
const TabNames = {
|
const TabNames = {
|
||||||
login: 'login',
|
login: 'login',
|
||||||
sign_up: 'sign_up',
|
register: 'register',
|
||||||
email_verify: 'email_verify',
|
email_verify: 'email_verify',
|
||||||
|
register_success: 'register_success',
|
||||||
retrieve_password: 'retrieve_password'
|
retrieve_password: 'retrieve_password'
|
||||||
}
|
}
|
||||||
const tabList = markRaw([
|
const tabList = markRaw([
|
||||||
@@ -78,7 +82,7 @@
|
|||||||
component: login
|
component: login
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: TabNames.sign_up,
|
name: TabNames.register,
|
||||||
component: register
|
component: register
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -89,15 +93,20 @@
|
|||||||
{
|
{
|
||||||
name: TabNames.retrieve_password,
|
name: TabNames.retrieve_password,
|
||||||
title: 'RETRIEVE PASSWORD',
|
title: 'RETRIEVE PASSWORD',
|
||||||
component: login
|
component: retrievePassword
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: TabNames.register_success,
|
||||||
|
component: registerSuccess
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
const show = ref(false)
|
||||||
const currentTab = ref(TabNames.login)
|
const currentTab = ref(TabNames.login)
|
||||||
const curentTabInfo = computed(() => tabList.find((v) => v.name === currentTab.value))
|
const curentTabInfo = computed(() => tabList.find((v) => v.name === currentTab.value))
|
||||||
const lastTab = ref('')
|
const lastTab = ref('')
|
||||||
watch(currentTab, (v, o) => (lastTab.value = o))
|
watch(currentTab, (v, o) => (lastTab.value = o))
|
||||||
const onBack = () => {
|
const onBack = () => {
|
||||||
if (lastTab.value) currentTab.value = lastTab.value
|
currentTab.value = lastTab.value || TabNames.login
|
||||||
}
|
}
|
||||||
const open = (type?: string) => {
|
const open = (type?: string) => {
|
||||||
currentTab.value = TabNames[type] || TabNames.login
|
currentTab.value = TabNames[type] || TabNames.login
|
||||||
@@ -118,13 +127,17 @@
|
|||||||
}
|
}
|
||||||
const onRegister = (res: any) => {
|
const onRegister = (res: any) => {
|
||||||
data.value = res
|
data.value = res
|
||||||
data.value.type = TabNames.sign_up
|
data.value.type = TabNames.register
|
||||||
currentTab.value = TabNames.email_verify
|
currentTab.value = TabNames.email_verify
|
||||||
}
|
}
|
||||||
const onSubmitEmailCode = (code: string) => {
|
const onSubmitEmailCode = (code: string) => {
|
||||||
// data.value.code = code
|
if (data.value.type === TabNames.login) {
|
||||||
console.log(code)
|
console.log('登录', code)
|
||||||
show.value = false
|
show.value = false
|
||||||
|
} else {
|
||||||
|
console.log('注册', code)
|
||||||
|
currentTab.value = TabNames.register_success
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -147,14 +160,16 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
background: url('@/assets/images/login/bg.jpg') no-repeat center center / 100% 100%;
|
||||||
|
> *:not(.content) {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
> .bg {
|
> .bg {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
> * {
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
> .logo {
|
> .logo {
|
||||||
width: auto;
|
width: auto;
|
||||||
height: 4rem;
|
height: 4rem;
|
||||||
@@ -170,9 +185,10 @@
|
|||||||
}
|
}
|
||||||
> .content {
|
> .content {
|
||||||
width: 34rem;
|
width: 34rem;
|
||||||
top: 5rem;
|
|
||||||
right: 6rem;
|
|
||||||
height: calc(100% - 10rem);
|
height: calc(100% - 10rem);
|
||||||
|
margin: 5rem 6rem auto auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
> .header {
|
> .header {
|
||||||
--padding-bottom: 1.2rem;
|
--padding-bottom: 1.2rem;
|
||||||
padding-bottom: var(--padding-bottom);
|
padding-bottom: var(--padding-bottom);
|
||||||
|
|||||||
@@ -40,15 +40,15 @@
|
|||||||
.password-tip {
|
.password-tip {
|
||||||
background: #404040;
|
background: #404040;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-size: 1.4rem;
|
font-size: 1.1rem;
|
||||||
padding: 2rem;
|
padding: 1.5rem;
|
||||||
border-radius: 2rem;
|
border-radius: 1.5rem;
|
||||||
line-height: normal;
|
line-height: normal;
|
||||||
|
|
||||||
> div {
|
> div {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 1rem;
|
||||||
&:last-child {
|
&:last-child {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|||||||
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>
|
||||||
@@ -1,120 +1,183 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="retrieve-password">
|
<div class="retrieve-password">
|
||||||
<div class="left">
|
<el-form
|
||||||
<img class="bg" src="@/assets/images/login/left-bg.png" />
|
:model="formData"
|
||||||
<img class="logo" src="@/assets/images/logo-1.png" />
|
:rules="ruleForm"
|
||||||
</div>
|
label-position="top"
|
||||||
<div class="right">
|
ref="form1Ref"
|
||||||
<div class="top">
|
v-show="index === 0"
|
||||||
<button class="back" @click="onBack">
|
>
|
||||||
<svg-icon name="arrow-left" size="37" />
|
<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>
|
</button>
|
||||||
</div>
|
</el-form-item>
|
||||||
<div class="box">
|
</el-form>
|
||||||
<img src="@/assets/images/login/elephant.png" />
|
<div class="verify-box" v-if="index === 1">
|
||||||
<template v-if="!isVisible">
|
<email-verify
|
||||||
<div class="title">{{ $t('Login.retrievePassword') }}</div>
|
type="FORGOT_PWD"
|
||||||
<el-form :model="formData" :rules="ruleForm" label-position="top" ref="formRef">
|
:email="formData.email"
|
||||||
<el-form-item :label="$t('Login.email')" prop="email">
|
@submit-email-code="onVerifyCode"
|
||||||
<el-input
|
:is-show-other-login="false"
|
||||||
v-model="formData.email"
|
/>
|
||||||
:placeholder="$t('Login.enterEmail')"
|
|
||||||
name="email"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item :label="$t('Login.password')" prop="password">
|
|
||||||
<password-tip :value="formData.password" v-show="showPasswordTip" />
|
|
||||||
<el-input
|
|
||||||
v-model="formData.password"
|
|
||||||
:placeholder="$t('Login.enterPassword')"
|
|
||||||
type="password"
|
|
||||||
show-password
|
|
||||||
name="password"
|
|
||||||
@blur="showPasswordTip = false"
|
|
||||||
@focus="showPasswordTip = true"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<el-form-item>
|
|
||||||
<el-button class="submit" type="primary" @click="onSubmit">{{
|
|
||||||
$t('submit')
|
|
||||||
}}</el-button>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
</template>
|
|
||||||
<!-- <visible-code
|
|
||||||
v-show="isVisible"
|
|
||||||
type="FORGOT_PWD"
|
|
||||||
ref="visibleCodeRef"
|
|
||||||
:email="formData.email"
|
|
||||||
@submit="onVerifyCode"
|
|
||||||
/> -->
|
|
||||||
<other-login />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import md5 from 'md5'
|
import md5 from 'md5'
|
||||||
import { ForgotPassword } from '@/api/user'
|
|
||||||
import { computed, reactive, ref } from 'vue'
|
import { computed, reactive, ref } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
|
||||||
import { validateEmail, validatePass } from './tools'
|
import { validateEmail, validatePass } from './tools'
|
||||||
import OtherLogin from './other-login.vue'
|
|
||||||
import emailVerify from './email-verify.vue'
|
|
||||||
import PasswordTip from './password-tip.vue'
|
import PasswordTip from './password-tip.vue'
|
||||||
import { useUserInfoStore } from '@/stores'
|
import EmailVerify from './email-verify.vue'
|
||||||
const userInfoStore = useUserInfoStore()
|
const emit = defineEmits(['back'])
|
||||||
const router = useRouter()
|
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({
|
const ruleForm = reactive({
|
||||||
email: [{ validator: validateEmail, trigger: 'change' }],
|
email: [{ validator: validateEmail, trigger: 'change' }],
|
||||||
password: [{ validator: validatePass, trigger: 'change' }]
|
password: [{ validator: validatePass, trigger: 'change' }],
|
||||||
|
confirmPassword: [{ validator: validateConfirmPassword, trigger: 'change' }]
|
||||||
})
|
})
|
||||||
const isVisible = ref(false)
|
|
||||||
const showPasswordTip = ref(false)
|
const showPasswordTip = ref(false)
|
||||||
const formData = reactive({
|
const formData = reactive({
|
||||||
email: '',
|
email: '',
|
||||||
password: ''
|
code: '',
|
||||||
|
password: '',
|
||||||
|
confirmPassword: ''
|
||||||
})
|
})
|
||||||
const formRef = ref(null)
|
const form1Ref = ref(null)
|
||||||
const onBack = () => {
|
|
||||||
if (isVisible.value) {
|
|
||||||
isVisible.value = false
|
|
||||||
} else {
|
|
||||||
router.back()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const visibleCodeRef = ref(null)
|
const visibleCodeRef = ref(null)
|
||||||
const onSubmit = () => {
|
const form2Ref = ref(null)
|
||||||
formRef.value?.validate?.((valid) => {
|
const onSubmit1 = () => {
|
||||||
|
form1Ref.value?.validate?.((valid) => {
|
||||||
if (valid) {
|
if (valid) {
|
||||||
// console.log('submit!')
|
index.value = 1
|
||||||
visibleCodeRef.value?.onSendCode().then(() => {
|
} else {
|
||||||
isVisible.value = true
|
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 {
|
} else {
|
||||||
console.warn('error submit!')
|
console.warn('error submit!')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const onVerifyCode = (code: string) => {
|
const onVerifyCode = (code: string) => {
|
||||||
// console.log(code)
|
if (!code) return
|
||||||
ForgotPassword({
|
formData.code = code
|
||||||
email: formData.email,
|
index.value = 2
|
||||||
newPassword: md5(formData.password),
|
|
||||||
verificationCode: code
|
|
||||||
})
|
|
||||||
.then((res) => {
|
|
||||||
if (res) router.push({ name: 'login' })
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.warn(error)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
@import './less/style.less';
|
@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>
|
</style>
|
||||||
|
|||||||
@@ -8,7 +8,9 @@
|
|||||||
v-for="v in navList1"
|
v-for="v in navList1"
|
||||||
:key="v.path"
|
:key="v.path"
|
||||||
class="nav-item"
|
class="nav-item"
|
||||||
:class="{ active: activePath === v.path }"
|
:class="{
|
||||||
|
active: v.path === '/' ? activePath === v.path : new RegExp(`^${v.path}`).test(activePath)
|
||||||
|
}"
|
||||||
@click="onNavItemClick(v.path)"
|
@click="onNavItemClick(v.path)"
|
||||||
>
|
>
|
||||||
<span>{{ v.name }}</span>
|
<span>{{ v.name }}</span>
|
||||||
@@ -19,7 +21,7 @@
|
|||||||
class="icon"
|
class="icon"
|
||||||
v-for="v in navList2"
|
v-for="v in navList2"
|
||||||
:key="v.path"
|
:key="v.path"
|
||||||
:class="{ active: activePath === v.path }"
|
:class="{ active: new RegExp(`^${v.path}`).test(activePath) }"
|
||||||
@click="onNavItemClick(v.path)"
|
@click="onNavItemClick(v.path)"
|
||||||
>
|
>
|
||||||
<svg-icon :name="activePath === v.path ? v.active_icon : v.icon" size="22" />
|
<svg-icon :name="activePath === v.path ? v.active_icon : v.icon" size="22" />
|
||||||
@@ -62,6 +64,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-popover>
|
</el-popover>
|
||||||
|
<div class="language" @click="onLanguageClick">
|
||||||
|
<span :class="{ active: locale === 'CHINESE_SIMPLIFIED' }">中</span> /
|
||||||
|
<span :class="{ active: locale === 'ENGLISH' }">ENG</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -70,6 +76,8 @@
|
|||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import { useRouter, useRoute } from 'vue-router'
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
import myEvent from '@/utils/myEvent'
|
import myEvent from '@/utils/myEvent'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
const { t, locale } = useI18n()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const activePath = computed(() => route.path)
|
const activePath = computed(() => route.path)
|
||||||
@@ -95,12 +103,12 @@
|
|||||||
{
|
{
|
||||||
icon: 'cart_0',
|
icon: 'cart_0',
|
||||||
active_icon: 'cart_1',
|
active_icon: 'cart_1',
|
||||||
path: '/cart'
|
path: '/shoppingCart'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: 'user_0',
|
icon: 'user_0',
|
||||||
active_icon: 'user_1',
|
active_icon: 'user_1',
|
||||||
path: '/user'
|
path: '/account'
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
const onNavItemClick = (path: string) => {
|
const onNavItemClick = (path: string) => {
|
||||||
@@ -117,22 +125,27 @@
|
|||||||
const onMyWardrobe = () => {
|
const onMyWardrobe = () => {
|
||||||
hideProfilePopover()
|
hideProfilePopover()
|
||||||
console.log('my wardrobe')
|
console.log('my wardrobe')
|
||||||
|
router.push('/wardrobe')
|
||||||
}
|
}
|
||||||
const onNotifications = () => {
|
const onNotifications = () => {
|
||||||
hideProfilePopover()
|
hideProfilePopover()
|
||||||
console.log('notifications')
|
router.push('/notifications')
|
||||||
}
|
}
|
||||||
const onSettings = () => {
|
const onSettings = () => {
|
||||||
hideProfilePopover()
|
hideProfilePopover()
|
||||||
console.log('settings')
|
router.push('/settings')
|
||||||
}
|
}
|
||||||
const onLogout = () => {
|
const onLogout = () => {
|
||||||
hideProfilePopover()
|
hideProfilePopover()
|
||||||
console.log('logout')
|
console.log('logout')
|
||||||
}
|
}
|
||||||
|
const onLanguageClick = () => {
|
||||||
|
locale.value = locale.value === 'ENGLISH' ? 'CHINESE_SIMPLIFIED' : 'ENGLISH'
|
||||||
|
localStorage.setItem('language', locale.value)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less">
|
<style lang="less" scoped>
|
||||||
#main-header {
|
#main-header {
|
||||||
height: var(--header-height);
|
height: var(--header-height);
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -158,7 +171,6 @@
|
|||||||
height: 2.4rem;
|
height: 2.4rem;
|
||||||
}
|
}
|
||||||
> .login {
|
> .login {
|
||||||
font-family: Kaisei Opti;
|
|
||||||
font-size: 1.6rem;
|
font-size: 1.6rem;
|
||||||
}
|
}
|
||||||
> .profile {
|
> .profile {
|
||||||
@@ -167,6 +179,15 @@
|
|||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: #f5f5f5;
|
background: #f5f5f5;
|
||||||
}
|
}
|
||||||
|
> .language {
|
||||||
|
font-family: KaiseiOpti-Regular;
|
||||||
|
font-size: 1.6rem;
|
||||||
|
color: #c2c2c2;
|
||||||
|
> .active {
|
||||||
|
color: #232323;
|
||||||
|
font-family: KaiseiOpti-Medium;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
> .center,
|
> .center,
|
||||||
> .right {
|
> .right {
|
||||||
@@ -194,7 +215,6 @@
|
|||||||
font-size: 1.6rem;
|
font-size: 1.6rem;
|
||||||
color: #232323;
|
color: #232323;
|
||||||
border-bottom: 0.1rem solid transparent;
|
border-bottom: 0.1rem solid transparent;
|
||||||
font-family: Kaisei Opti;
|
|
||||||
}
|
}
|
||||||
&.active {
|
&.active {
|
||||||
> span {
|
> span {
|
||||||
|
|||||||
136
src/views/notifications/components/NotificationListItem.vue
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
<template>
|
||||||
|
<article class="notification-item" :class="{ expanded: item.isExpanded, unread: item.isUnread }">
|
||||||
|
<button type="button" class="notification-trigger" @click="handleToggle">
|
||||||
|
<span class="status-dot" :class="{ visible: item.isUnread }" />
|
||||||
|
|
||||||
|
<div class="notification-main">
|
||||||
|
<h3 class="notification-title">{{ item.title }}</h3>
|
||||||
|
<p class="notification-date">{{ item.date }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span class="notification-arrow" :class="arrowClasses" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div v-if="item.isExpanded" class="notification-content">
|
||||||
|
{{ item.content }}
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import type { NotificationRecord } from '../types'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
item: NotificationRecord
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
toggle: [id: NotificationRecord['id']]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const arrowClasses = computed(() => ({
|
||||||
|
expanded: props.item.isExpanded
|
||||||
|
}))
|
||||||
|
|
||||||
|
const handleToggle = () => {
|
||||||
|
emit('toggle', props.item.id)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.notification-item {
|
||||||
|
border-bottom: 0.05rem solid #ded8d2;
|
||||||
|
|
||||||
|
&.expanded {
|
||||||
|
.notification-title {
|
||||||
|
color: #585858;
|
||||||
|
font-family: 'KaiseiOpti-Medium';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-trigger {
|
||||||
|
width: 100%;
|
||||||
|
padding: 2rem 0;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 0.8rem minmax(0, 1fr) 2.4rem;
|
||||||
|
align-items: center;
|
||||||
|
gap: 2.4rem;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-dot {
|
||||||
|
width: 0.8rem;
|
||||||
|
height: 0.8rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: transparent;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
|
||||||
|
&.visible {
|
||||||
|
background: #232323;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-main {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-title {
|
||||||
|
margin: 0;
|
||||||
|
color: #232323;
|
||||||
|
font-size: 1.6rem;
|
||||||
|
line-height: 1.4;
|
||||||
|
font-family: 'KaiseiOpti-Bold';
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-date {
|
||||||
|
margin: 0.8rem 0 0;
|
||||||
|
color: #9f9f9f;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
line-height: 1.4;
|
||||||
|
font-family: 'KaiseiOpti-Regular';
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-arrow {
|
||||||
|
width: 0.9rem;
|
||||||
|
height: 0.9rem;
|
||||||
|
justify-self: end;
|
||||||
|
border-right: 0.1rem solid #8f8f8f;
|
||||||
|
border-bottom: 0.1rem solid #8f8f8f;
|
||||||
|
transform: rotate(-45deg);
|
||||||
|
transition: transform 0.2s ease, border-color 0.2s ease;
|
||||||
|
|
||||||
|
&.expanded {
|
||||||
|
transform: rotate(45deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-content {
|
||||||
|
padding: 0 5.6rem 2.4rem 3.2rem;
|
||||||
|
color: #585858;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
line-height: 2;
|
||||||
|
font-family: 'KaiseiOpti-Regular';
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.notification-trigger {
|
||||||
|
gap: 1.6rem;
|
||||||
|
padding: 1.8rem 0;
|
||||||
|
grid-template-columns: 0.8rem minmax(0, 1fr) 1.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-title {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-content {
|
||||||
|
padding: 0 3rem 2rem 2.4rem;
|
||||||
|
font-size: 1.3rem;
|
||||||
|
line-height: 1.8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
111
src/views/notifications/components/NotificationsList.vue
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
<template>
|
||||||
|
<section class="notifications-panel">
|
||||||
|
<div class="notifications-toolbar">
|
||||||
|
<div class="unread-summary">
|
||||||
|
<span class="unread-label">UNREAD</span>
|
||||||
|
<span class="unread-count">{{ unreadCount }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="mark-all-button"
|
||||||
|
:disabled="unreadCount === 0"
|
||||||
|
@click="emit('markAllAsRead')"
|
||||||
|
>
|
||||||
|
Mark all as read
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="notifications-items">
|
||||||
|
<NotificationListItem
|
||||||
|
v-for="item in items"
|
||||||
|
:key="item.id"
|
||||||
|
:item="item"
|
||||||
|
@toggle="emit('toggleItem', $event)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import NotificationListItem from './NotificationListItem.vue'
|
||||||
|
import type { NotificationRecord } from '../types'
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
items: NotificationRecord[]
|
||||||
|
unreadCount: number
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
toggleItem: [id: NotificationRecord['id']]
|
||||||
|
markAllAsRead: []
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.notifications-panel {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notifications-toolbar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 2rem;
|
||||||
|
margin-bottom: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unread-summary {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unread-label {
|
||||||
|
color: #979797;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
line-height: 1.4;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
font-family: 'KaiseiOpti-Regular';
|
||||||
|
}
|
||||||
|
|
||||||
|
.unread-count {
|
||||||
|
min-width: 3.1rem;
|
||||||
|
height: 2.4rem;
|
||||||
|
padding: 0 0.8rem;
|
||||||
|
border-radius: 2rem;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: #232323;
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
line-height: 1;
|
||||||
|
font-family: 'KaiseiOpti-Bold';
|
||||||
|
}
|
||||||
|
|
||||||
|
.mark-all-button {
|
||||||
|
padding: 0;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
color: #979797;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
line-height: 1.4;
|
||||||
|
text-decoration: underline;
|
||||||
|
text-underline-offset: 0.2rem;
|
||||||
|
font-family: 'KaiseiOpti-Regular';
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
cursor: default;
|
||||||
|
opacity: 0.55;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.notifications-toolbar {
|
||||||
|
align-items: flex-start;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-bottom: 2.8rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
166
src/views/notifications/index.vue
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
<template>
|
||||||
|
<div class="notifications-view mini-scrollbar">
|
||||||
|
<section class="notifications-hero">
|
||||||
|
<div class="notifications-hero__content">
|
||||||
|
<h1 class="notifications-hero__title">Notifications</h1>
|
||||||
|
<p class="notifications-hero__subtitle">System announcements and updates</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="notifications-content">
|
||||||
|
<NotificationsList
|
||||||
|
:items="notifications"
|
||||||
|
:unread-count="unreadCount"
|
||||||
|
@toggle-item="handleToggleItem"
|
||||||
|
@mark-all-as-read="handleMarkAllAsRead"
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, ref } from 'vue'
|
||||||
|
import NotificationsList from './components/NotificationsList.vue'
|
||||||
|
import type { NotificationRecord } from './types'
|
||||||
|
|
||||||
|
const notifications = ref<NotificationRecord[]>([
|
||||||
|
{
|
||||||
|
id: 'maintenance-mar-10-primary',
|
||||||
|
title: 'Platform Maintenance Notice - Mar 10, 2026',
|
||||||
|
date: 'Mar 6, 2026',
|
||||||
|
content:
|
||||||
|
'We will perform scheduled platform maintenance on Mar 10, 2026 to improve checkout stability and notification delivery. During this window, a few account features may respond more slowly than usual.',
|
||||||
|
isUnread: true,
|
||||||
|
isExpanded: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'maintenance-mar-10-reminder',
|
||||||
|
title: 'Platform Maintenance Notice - Mar 10, 2026',
|
||||||
|
date: 'Feb 28, 2026',
|
||||||
|
content:
|
||||||
|
'This is an early reminder for the Mar 10 maintenance window. Please avoid making urgent profile or order updates right before the scheduled service period.',
|
||||||
|
isUnread: true,
|
||||||
|
isExpanded: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'terms-mar-1',
|
||||||
|
title: 'Updated Terms of Service - Effective Mar 1, 2026',
|
||||||
|
date: 'Feb 20, 2026',
|
||||||
|
content:
|
||||||
|
'We updated our Terms of Service to clarify digital item ownership, payment processing responsibilities, and account conduct expectations. Please review the new terms before your next purchase.',
|
||||||
|
isUnread: true,
|
||||||
|
isExpanded: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'welcome',
|
||||||
|
title: 'Welcome to Stylish Parade',
|
||||||
|
date: 'Jan 4, 2026',
|
||||||
|
content:
|
||||||
|
'Thanks for joining Stylish Parade. Explore brand stories, save your favorite pieces, and keep your profile updated so we can recommend the right collections for you.',
|
||||||
|
isUnread: false,
|
||||||
|
isExpanded: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'holiday-support',
|
||||||
|
title: 'Platform Maintenance Notice - Mar 10, 2026',
|
||||||
|
date: 'Dec 20, 2025',
|
||||||
|
content:
|
||||||
|
'Our customer support team will have limited availability during the holiday season from Dec 24 to Jan 2. Response times may be longer than usual. The platform will remain fully operational throughout this period. We wish you a wonderful holiday season.',
|
||||||
|
isUnread: false,
|
||||||
|
isExpanded: true
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
const unreadCount = computed(() => notifications.value.filter((item) => item.isUnread).length)
|
||||||
|
|
||||||
|
const handleToggleItem = (id: NotificationRecord['id']) => {
|
||||||
|
const targetItem = notifications.value.find((item) => item.id === id)
|
||||||
|
|
||||||
|
if (!targetItem) return
|
||||||
|
|
||||||
|
const nextExpanded = !targetItem.isExpanded
|
||||||
|
|
||||||
|
notifications.value = notifications.value.map((item) => ({
|
||||||
|
...item,
|
||||||
|
isUnread: item.id === id ? false : item.isUnread,
|
||||||
|
isExpanded: item.id === id ? nextExpanded : false
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleMarkAllAsRead = () => {
|
||||||
|
notifications.value = notifications.value.map((item) => ({
|
||||||
|
...item,
|
||||||
|
isUnread: false
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.notifications-view {
|
||||||
|
height: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
background: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notifications-hero {
|
||||||
|
min-height: 14.8rem;
|
||||||
|
padding: 3.2rem 2rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: linear-gradient(180deg, rgba(245, 243, 240, 0.92) 0%, rgba(250, 249, 246, 0.95) 100%),
|
||||||
|
linear-gradient(
|
||||||
|
90deg,
|
||||||
|
rgba(227, 221, 212, 0.28) 0%,
|
||||||
|
rgba(255, 255, 255, 0.24) 50%,
|
||||||
|
rgba(227, 221, 212, 0.28) 100%
|
||||||
|
);
|
||||||
|
border-bottom: 0.05rem solid #dfd8d1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notifications-hero__content {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notifications-hero__title {
|
||||||
|
margin: 0;
|
||||||
|
color: #232323;
|
||||||
|
font-size: 4rem;
|
||||||
|
line-height: 1;
|
||||||
|
font-family: 'KaiseiOpti-Bold';
|
||||||
|
}
|
||||||
|
|
||||||
|
.notifications-hero__subtitle {
|
||||||
|
margin: 1.2rem 0 0;
|
||||||
|
color: #585858;
|
||||||
|
font-size: 1.6rem;
|
||||||
|
line-height: 1.4;
|
||||||
|
font-family: 'KaiseiOpti-Regular';
|
||||||
|
}
|
||||||
|
|
||||||
|
.notifications-content {
|
||||||
|
width: min(108rem, calc(100% - 4rem));
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 4rem 0 8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.notifications-hero {
|
||||||
|
min-height: 12rem;
|
||||||
|
padding: 2.8rem 1.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notifications-hero__title {
|
||||||
|
font-size: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notifications-hero__subtitle {
|
||||||
|
margin-top: 0.8rem;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notifications-content {
|
||||||
|
width: calc(100% - 3.2rem);
|
||||||
|
padding: 3.2rem 0 4.8rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
8
src/views/notifications/types.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export interface NotificationRecord {
|
||||||
|
id: string
|
||||||
|
title: string
|
||||||
|
date: string
|
||||||
|
content: string
|
||||||
|
isUnread: boolean
|
||||||
|
isExpanded: boolean
|
||||||
|
}
|
||||||
263
src/views/setting/components/EmailVerificationDialog.vue
Normal file
@@ -0,0 +1,263 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="visible" class="verification-dialog" @click.self="handleClose">
|
||||||
|
<div class="verification-dialog__panel">
|
||||||
|
<button type="button" class="verification-dialog__close" @click="handleClose">
|
||||||
|
<SvgIcon name="close" size="24" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="verification-dialog__title">{{ t('Settings.dialog.title') }}</div>
|
||||||
|
<div class="verification-dialog__subtitle">{{ t('Settings.dialog.subtitle') }}</div>
|
||||||
|
<div class="verification-dialog__email">{{ email }}</div>
|
||||||
|
|
||||||
|
<InputCode
|
||||||
|
ref="verificationCodeRef"
|
||||||
|
class="verification-code"
|
||||||
|
@update:model-value="handleVerificationCodeChange"
|
||||||
|
@submit="handleVerificationCodeChange"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="verification-dialog__submit"
|
||||||
|
:disabled="saving || verificationCode.length !== 6"
|
||||||
|
@click="handleSubmit"
|
||||||
|
>
|
||||||
|
{{ t('Settings.dialog.submit') }}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="verification-dialog__resend"
|
||||||
|
:disabled="resendCountdown > 0"
|
||||||
|
@click="handleResend"
|
||||||
|
>
|
||||||
|
{{ resendButtonText }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import InputCode from '@/components/input-code.vue'
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
visible: boolean
|
||||||
|
email: string
|
||||||
|
saving?: boolean
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
saving: false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
close: []
|
||||||
|
resend: []
|
||||||
|
submit: [code: string]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const RESEND_COUNTDOWN_SECONDS = 60
|
||||||
|
|
||||||
|
const verificationCode = ref('')
|
||||||
|
const resendCountdown = ref(RESEND_COUNTDOWN_SECONDS)
|
||||||
|
const verificationCodeRef = ref<{ resetCode: (size?: number) => void } | null>(null)
|
||||||
|
let resendCountdownTimer: number | null = null
|
||||||
|
|
||||||
|
const formattedResendCountdown = computed(
|
||||||
|
() => `00:${String(resendCountdown.value).padStart(2, '0')}`
|
||||||
|
)
|
||||||
|
|
||||||
|
const resendButtonText = computed(() =>
|
||||||
|
resendCountdown.value > 0
|
||||||
|
? t('Settings.dialog.resendCodeIn', { time: formattedResendCountdown.value })
|
||||||
|
: t('Settings.dialog.resendCode')
|
||||||
|
)
|
||||||
|
|
||||||
|
const clearResendCountdownTimer = () => {
|
||||||
|
if (resendCountdownTimer) {
|
||||||
|
window.clearInterval(resendCountdownTimer)
|
||||||
|
resendCountdownTimer = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const startResendCountdown = () => {
|
||||||
|
clearResendCountdownTimer()
|
||||||
|
resendCountdown.value = RESEND_COUNTDOWN_SECONDS
|
||||||
|
resendCountdownTimer = window.setInterval(() => {
|
||||||
|
if (resendCountdown.value <= 1) {
|
||||||
|
resendCountdown.value = 0
|
||||||
|
clearResendCountdownTimer()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resendCountdown.value -= 1
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetVerificationCodeInput = async () => {
|
||||||
|
verificationCode.value = ''
|
||||||
|
await nextTick()
|
||||||
|
verificationCodeRef.value?.resetCode?.()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
emit('close')
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleVerificationCodeChange = (value: string) => {
|
||||||
|
verificationCode.value = value
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = () => {
|
||||||
|
if (verificationCode.value.length !== 6) return
|
||||||
|
|
||||||
|
emit('submit', verificationCode.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleResend = async () => {
|
||||||
|
if (resendCountdown.value > 0) return
|
||||||
|
|
||||||
|
emit('resend')
|
||||||
|
startResendCountdown()
|
||||||
|
await resetVerificationCodeInput()
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.visible,
|
||||||
|
async (visible) => {
|
||||||
|
if (!visible) {
|
||||||
|
clearResendCountdownTimer()
|
||||||
|
verificationCode.value = ''
|
||||||
|
resendCountdown.value = RESEND_COUNTDOWN_SECONDS
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await resetVerificationCodeInput()
|
||||||
|
startResendCountdown()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.email,
|
||||||
|
async () => {
|
||||||
|
if (!props.visible) return
|
||||||
|
|
||||||
|
await resetVerificationCodeInput()
|
||||||
|
startResendCountdown()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
clearResendCountdownTimer()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.verification-dialog {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 2000;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 2rem;
|
||||||
|
background: rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.verification-dialog__panel {
|
||||||
|
width: 60rem;
|
||||||
|
padding: 4.8rem 7.2rem 5rem;
|
||||||
|
position: relative;
|
||||||
|
background: #efefef;
|
||||||
|
box-shadow: 0 2rem 6rem rgba(35, 35, 35, 0.14);
|
||||||
|
}
|
||||||
|
|
||||||
|
.verification-dialog__close {
|
||||||
|
position: absolute;
|
||||||
|
top: 2rem;
|
||||||
|
right: 2rem;
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: transparent;
|
||||||
|
color: #585858;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.verification-dialog__title {
|
||||||
|
color: #232323;
|
||||||
|
text-align: center;
|
||||||
|
font-family: 'KaiseiOpti-Bold';
|
||||||
|
font-size: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.verification-dialog__subtitle {
|
||||||
|
margin-top: 2rem;
|
||||||
|
color: #585858;
|
||||||
|
text-align: center;
|
||||||
|
font-family: 'KaiseiOpti-Regular';
|
||||||
|
font-size: 1.6rem;
|
||||||
|
line-height: 2.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.verification-dialog__email {
|
||||||
|
margin-top: 0.8rem;
|
||||||
|
color: #232323;
|
||||||
|
text-align: center;
|
||||||
|
font-family: 'KaiseiOpti-Bold';
|
||||||
|
font-size: 1.6rem;
|
||||||
|
line-height: 2.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.verification-code {
|
||||||
|
margin-top: 4.8rem;
|
||||||
|
--input-code-justify-content: center;
|
||||||
|
--input-code-input-gap: 1.4rem;
|
||||||
|
--input-code-input-width: 6rem;
|
||||||
|
--input-code-input-height: 6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.verification-dialog__submit {
|
||||||
|
width: 100%;
|
||||||
|
height: 4.8rem;
|
||||||
|
margin-top: 5rem;
|
||||||
|
border: 0.1rem solid #232323;
|
||||||
|
background: #232323;
|
||||||
|
color: #ffffff;
|
||||||
|
font-family: 'KaiseiOpti-Bold';
|
||||||
|
font-size: 1.6rem;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.verification-dialog__resend {
|
||||||
|
margin: 2rem auto 0;
|
||||||
|
display: block;
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
background: transparent;
|
||||||
|
color: #7c7c7c;
|
||||||
|
font-family: 'KaiseiOpti-Regular';
|
||||||
|
font-size: 1.6rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
text-decoration: underline;
|
||||||
|
text-underline-offset: 0.2rem;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
cursor: default;
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
235
src/views/setting/components/ProfileSection.vue
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
<template>
|
||||||
|
<SettingsSection
|
||||||
|
:title="t('Settings.profile.title')"
|
||||||
|
:description="t('Settings.profile.description')"
|
||||||
|
>
|
||||||
|
<div class="profile-header">
|
||||||
|
<div class="avatar relative">
|
||||||
|
<SvgIcon name="user_0" size="46" class="avatar-icon" />
|
||||||
|
<img src="@/assets/images/edit.png" class="avatar-edit-icon" />
|
||||||
|
</div>
|
||||||
|
<div class="profile-summary">
|
||||||
|
<div class="profile-name">{{ fullName }}</div>
|
||||||
|
<div class="profile-email">{{ displayData.email }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="read-section">
|
||||||
|
<div class="read-row two-column">
|
||||||
|
<div class="read-item">
|
||||||
|
<div class="read-label">{{ t('Settings.profile.firstName') }}</div>
|
||||||
|
<div v-show="!isEditing" class="read-box">{{ displayData.firstName }}</div>
|
||||||
|
<div v-show="isEditing" class="form-item-value name">
|
||||||
|
<el-input
|
||||||
|
:model-value="firstName"
|
||||||
|
:placeholder="t('Settings.profile.firstNamePlaceholder')"
|
||||||
|
@update:model-value="emit('update:firstName', String($event))"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="read-item">
|
||||||
|
<div class="read-label">{{ t('Settings.profile.lastName') }}</div>
|
||||||
|
<div v-show="!isEditing" class="read-box">{{ displayData.lastName }}</div>
|
||||||
|
<div v-show="isEditing" class="form-item-value name">
|
||||||
|
<el-input
|
||||||
|
:model-value="lastName"
|
||||||
|
:placeholder="t('Settings.profile.lastNamePlaceholder')"
|
||||||
|
@update:model-value="emit('update:lastName', String($event))"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="read-row">
|
||||||
|
<div class="read-label">{{ t('Settings.profile.username') }}</div>
|
||||||
|
<div v-show="!isEditing" class="read-box">{{ displayData.username }}</div>
|
||||||
|
<div v-show="isEditing" class="form-item-value">
|
||||||
|
<el-input
|
||||||
|
:model-value="username"
|
||||||
|
:placeholder="t('Settings.profile.usernamePlaceholder')"
|
||||||
|
@update:model-value="emit('update:username', String($event))"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="read-tip">{{ t('Settings.profile.usernameTip') }}</div>
|
||||||
|
|
||||||
|
<div class="read-row role-row">
|
||||||
|
<div class="read-label">{{ t('Settings.profile.role') }}</div>
|
||||||
|
<div :class="{ 'readonly-radio-group': !isEditing }">
|
||||||
|
<Radio multiple :max="2" v-model="roleSelection" :options="roleOptions" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="read-tip">{{ t('Settings.profile.roleTip') }}</div>
|
||||||
|
</div>
|
||||||
|
</SettingsSection>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import SettingsSection from './SettingsSection.vue'
|
||||||
|
import Radio from './Radio.vue'
|
||||||
|
import type { RoleOption, RoleValue, SettingsData } from '../types'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
displayData: SettingsData
|
||||||
|
firstName: string
|
||||||
|
lastName: string
|
||||||
|
username: string
|
||||||
|
fullName: string
|
||||||
|
isEditing: boolean
|
||||||
|
roleModel: RoleValue[]
|
||||||
|
roleOptions: RoleOption[]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(event: 'update:firstName', value: string): void
|
||||||
|
(event: 'update:lastName', value: string): void
|
||||||
|
(event: 'update:username', value: string): void
|
||||||
|
(event: 'update:roleModel', value: RoleValue[]): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const roleSelection = computed<RoleValue[]>({
|
||||||
|
get: () => props.roleModel,
|
||||||
|
set: (value) => {
|
||||||
|
emit('update:roleModel', value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.field-text() {
|
||||||
|
font-family: 'KaiseiOpti-Regular';
|
||||||
|
font-size: 1.6rem;
|
||||||
|
line-height: 2.4rem;
|
||||||
|
color: #232323;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-frame() {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 4rem;
|
||||||
|
border: 0.1rem solid #979797;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-wrapper() {
|
||||||
|
box-shadow: none;
|
||||||
|
border-radius: 0;
|
||||||
|
padding: 0 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
column-gap: 2.6rem;
|
||||||
|
margin-bottom: 3.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
width: 8rem;
|
||||||
|
height: 8rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 0.1rem solid #d8d0c7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-edit-icon {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 3rem;
|
||||||
|
height: 3rem;
|
||||||
|
border: 0.1rem solid #fff;
|
||||||
|
border-radius: 50%;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-summary {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
row-gap: 0.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-name {
|
||||||
|
font-family: 'KaiseiOpti-Medium';
|
||||||
|
font-size: 2.4rem;
|
||||||
|
line-height: 3.6rem;
|
||||||
|
color: #232323;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-email {
|
||||||
|
font-family: 'KaiseiOpti-Regular';
|
||||||
|
font-size: 1.8rem;
|
||||||
|
line-height: 2.4rem;
|
||||||
|
color: #979797;
|
||||||
|
}
|
||||||
|
|
||||||
|
.read-section {
|
||||||
|
width: 58rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.read-label {
|
||||||
|
margin-bottom: 0.8rem;
|
||||||
|
font-family: 'KaiseiOpti-Medium';
|
||||||
|
font-size: 1.4rem;
|
||||||
|
line-height: 2.4rem;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
color: #585858;
|
||||||
|
}
|
||||||
|
|
||||||
|
.read-tip {
|
||||||
|
margin-top: 0.8rem;
|
||||||
|
font-family: 'KaiseiOpti-Regular';
|
||||||
|
font-size: 1.2rem;
|
||||||
|
line-height: 1.6rem;
|
||||||
|
color: #9f9f9f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.read-row + .read-row {
|
||||||
|
margin-top: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.read-row.two-column {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 28.4rem 28.4rem;
|
||||||
|
column-gap: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.read-box {
|
||||||
|
.field-frame();
|
||||||
|
.field-text();
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.8rem 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item-value {
|
||||||
|
.field-frame();
|
||||||
|
|
||||||
|
&.name {
|
||||||
|
width: 28.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-input) {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-input__wrapper) {
|
||||||
|
.control-wrapper();
|
||||||
|
min-height: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-input__inner) {
|
||||||
|
.field-text();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.readonly-radio-group {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.role-row {
|
||||||
|
margin-top: 3rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
<div class="radio-button-group">
|
<div class="radio-button-group">
|
||||||
<button
|
<button
|
||||||
v-for="item in options"
|
v-for="item in options"
|
||||||
:key="item.value"
|
:key="String(item.value)"
|
||||||
type="button"
|
type="button"
|
||||||
:class="[
|
:class="[
|
||||||
'radio-button',
|
'radio-button',
|
||||||
@@ -29,6 +29,7 @@ const props = defineProps<{
|
|||||||
modelValue: string | number | boolean | Array<string | number | boolean> | null
|
modelValue: string | number | boolean | Array<string | number | boolean> | null
|
||||||
options: Option[] // 按钮选项数组
|
options: Option[] // 按钮选项数组
|
||||||
multiple?: boolean // 是否支持多选,默认为 false
|
multiple?: boolean // 是否支持多选,默认为 false
|
||||||
|
max?: number // 多选时最多可选数量,不传则不限制
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
@@ -54,6 +55,9 @@ const selectOption = (value: any) => {
|
|||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
current.splice(index, 1)
|
current.splice(index, 1)
|
||||||
} else {
|
} else {
|
||||||
|
if (typeof props.max === 'number' && props.max > 0 && current.length >= props.max) {
|
||||||
|
current.shift()
|
||||||
|
}
|
||||||
current.push(value)
|
current.push(value)
|
||||||
}
|
}
|
||||||
emit('update:modelValue', current)
|
emit('update:modelValue', current)
|
||||||
|
|||||||
128
src/views/setting/components/RegionSection.vue
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
<template>
|
||||||
|
<SettingsSection
|
||||||
|
:title="t('Settings.region.title')"
|
||||||
|
:description="t('Settings.region.description')"
|
||||||
|
>
|
||||||
|
<div class="region-row">
|
||||||
|
<div class="security-label">{{ t('Settings.region.displayLanguage') }}</div>
|
||||||
|
<div v-show="!isEditing" class="security-static field-box">
|
||||||
|
{{ displayLanguageLabel }}
|
||||||
|
</div>
|
||||||
|
<div v-show="isEditing" class="outlined-field select-field">
|
||||||
|
<el-select
|
||||||
|
:model-value="language"
|
||||||
|
:placeholder="t('Settings.region.selectLanguage')"
|
||||||
|
@update:model-value="emit('update:language', $event as LanguageValue)"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in languageOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="region-row">
|
||||||
|
<div class="security-label">{{ t('Settings.region.region') }}</div>
|
||||||
|
<div v-show="!isEditing" class="security-static field-box">
|
||||||
|
{{ displayRegionLabel }}
|
||||||
|
</div>
|
||||||
|
<div v-show="isEditing" class="outlined-field select-field">
|
||||||
|
<el-select
|
||||||
|
:model-value="region"
|
||||||
|
:placeholder="t('Settings.region.selectRegion')"
|
||||||
|
@update:model-value="emit('update:region', $event as RegionValue)"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in regionOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</SettingsSection>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import SettingsSection from './SettingsSection.vue'
|
||||||
|
import type { LanguageValue, RegionValue, SettingOption } from '../types'
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
language: LanguageValue
|
||||||
|
region: RegionValue
|
||||||
|
displayLanguageLabel: string
|
||||||
|
displayRegionLabel: string
|
||||||
|
isEditing: boolean
|
||||||
|
languageOptions: SettingOption<LanguageValue>[]
|
||||||
|
regionOptions: SettingOption<RegionValue>[]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(event: 'update:language', value: LanguageValue): void
|
||||||
|
(event: 'update:region', value: RegionValue): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.field-text() {
|
||||||
|
font-family: 'KaiseiOpti-Regular';
|
||||||
|
font-size: 1.6rem;
|
||||||
|
line-height: 2.4rem;
|
||||||
|
color: #232323;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-frame() {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 4rem;
|
||||||
|
border: 0.1rem solid #979797;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-wrapper() {
|
||||||
|
box-shadow: none;
|
||||||
|
border-radius: 0;
|
||||||
|
padding: 0 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.region-row + .region-row {
|
||||||
|
margin-top: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.security-label {
|
||||||
|
margin-bottom: 0.8rem;
|
||||||
|
font-family: 'KaiseiOpti-Medium';
|
||||||
|
font-size: 1.4rem;
|
||||||
|
line-height: 2.4rem;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
color: #585858;
|
||||||
|
}
|
||||||
|
|
||||||
|
.security-static,
|
||||||
|
.field-box {
|
||||||
|
.field-frame();
|
||||||
|
.field-text();
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.8rem 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.outlined-field {
|
||||||
|
.field-frame();
|
||||||
|
|
||||||
|
:deep(.el-select) {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-select__wrapper) {
|
||||||
|
.control-wrapper();
|
||||||
|
min-height: 4rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
242
src/views/setting/components/SecuritySection.vue
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
<template>
|
||||||
|
<SettingsSection
|
||||||
|
:title="t('Settings.security.title')"
|
||||||
|
:description="t('Settings.security.description')"
|
||||||
|
content-class="security-container"
|
||||||
|
>
|
||||||
|
<div class="inner-divider" />
|
||||||
|
<div class="security-row">
|
||||||
|
<div class="security-inline-row">
|
||||||
|
<div class="security-label inline">{{ t('Settings.security.email') }}</div>
|
||||||
|
<div class="security-static">{{ email }}</div>
|
||||||
|
<button v-show="isEditing" type="button" class="small-btn" @click="emit('reset-email')">
|
||||||
|
{{ t('Settings.buttons.cancel') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-show="isEditing" class="security-row">
|
||||||
|
<div class="security-label">{{ t('Settings.security.newEmail') }}</div>
|
||||||
|
<div class="outlined-field verify-field">
|
||||||
|
<el-input
|
||||||
|
:model-value="newEmail"
|
||||||
|
:placeholder="t('Settings.security.newEmailPlaceholder')"
|
||||||
|
@update:model-value="emit('update:newEmail', String($event))"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="verify-btn"
|
||||||
|
:class="{ verified: isEmailVerified }"
|
||||||
|
@click="emit('verify-email')"
|
||||||
|
>
|
||||||
|
{{ isEmailVerified ? t('Settings.security.verified') : t('Settings.security.verify') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div v-if="isEmailVerified" class="security-tip verified-tip">
|
||||||
|
{{ t('Settings.security.verifiedTip') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="inner-divider" />
|
||||||
|
|
||||||
|
<div class="security-row">
|
||||||
|
<div class="security-inline-row">
|
||||||
|
<div class="security-label inline">{{ t('Settings.security.password') }}</div>
|
||||||
|
<div class="security-static password-mask">.........</div>
|
||||||
|
<button v-show="isEditing" type="button" class="small-btn" @click="emit('reset-password')">
|
||||||
|
{{ t('Settings.buttons.cancel') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-show="isEditing" class="security-row">
|
||||||
|
<div class="security-label">{{ t('Settings.security.newPassword') }}</div>
|
||||||
|
<div class="outlined-field">
|
||||||
|
<el-input
|
||||||
|
:model-value="newPassword"
|
||||||
|
type="password"
|
||||||
|
show-password
|
||||||
|
:placeholder="t('Settings.security.newPasswordPlaceholder')"
|
||||||
|
@update:model-value="emit('update:newPassword', String($event))"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="security-tip">{{ t('Settings.security.passwordTip') }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-show="isEditing" class="security-row">
|
||||||
|
<div class="security-label">{{ t('Settings.security.currentPassword') }}</div>
|
||||||
|
<div class="outlined-field">
|
||||||
|
<el-input
|
||||||
|
:model-value="currentPassword"
|
||||||
|
type="password"
|
||||||
|
show-password
|
||||||
|
:placeholder="t('Settings.security.currentPasswordPlaceholder')"
|
||||||
|
@update:model-value="emit('update:currentPassword', String($event))"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="inner-divider" />
|
||||||
|
</SettingsSection>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import SettingsSection from './SettingsSection.vue'
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
email: string
|
||||||
|
newEmail: string
|
||||||
|
newPassword: string
|
||||||
|
currentPassword: string
|
||||||
|
isEditing: boolean
|
||||||
|
isEmailVerified: boolean
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(event: 'update:newEmail', value: string): void
|
||||||
|
(event: 'update:newPassword', value: string): void
|
||||||
|
(event: 'update:currentPassword', value: string): void
|
||||||
|
(event: 'reset-email'): void
|
||||||
|
(event: 'reset-password'): void
|
||||||
|
(event: 'verify-email'): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.field-text() {
|
||||||
|
font-family: 'KaiseiOpti-Regular';
|
||||||
|
font-size: 1.6rem;
|
||||||
|
line-height: 2.4rem;
|
||||||
|
color: #232323;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-frame() {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 4rem;
|
||||||
|
border: 0.1rem solid #979797;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-wrapper() {
|
||||||
|
box-shadow: none;
|
||||||
|
border-radius: 0;
|
||||||
|
padding: 0 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.security-row + .security-row {
|
||||||
|
margin-top: 2.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.security-label {
|
||||||
|
margin: 0 0 0.8rem;
|
||||||
|
font-family: 'KaiseiOpti-Medium';
|
||||||
|
font-size: 1.4rem;
|
||||||
|
line-height: 2.4rem;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
color: #585858;
|
||||||
|
|
||||||
|
&.inline {
|
||||||
|
width: 10.8rem;
|
||||||
|
margin-bottom: 0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.security-static {
|
||||||
|
.field-text();
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex: 1;
|
||||||
|
min-height: 2.4rem;
|
||||||
|
padding: 0.1rem 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.security-inline-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 2.8rem;
|
||||||
|
min-height: 3.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.security-tip {
|
||||||
|
margin-top: 0.6rem;
|
||||||
|
font-family: 'KaiseiOpti-Regular';
|
||||||
|
font-size: 1.2rem;
|
||||||
|
line-height: 1.6rem;
|
||||||
|
color: #9f9f9f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.outlined-field {
|
||||||
|
.field-frame();
|
||||||
|
|
||||||
|
:deep(.el-input) {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-input__wrapper) {
|
||||||
|
.control-wrapper();
|
||||||
|
min-height: 4rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.verify-field {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 0.8rem;
|
||||||
|
|
||||||
|
:deep(.el-input) {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.verify-btn {
|
||||||
|
border: none;
|
||||||
|
min-width: 11rem;
|
||||||
|
height: 2.8rem;
|
||||||
|
line-height: 2.8rem;
|
||||||
|
border-left: 0.1rem solid #979797;
|
||||||
|
background: #ffffff;
|
||||||
|
font-family: 'KaiseiOpti-Medium';
|
||||||
|
font-size: 1.4rem;
|
||||||
|
color: #232323;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0 2rem;
|
||||||
|
|
||||||
|
&.verified {
|
||||||
|
color: #ffffff;
|
||||||
|
background: #232323;
|
||||||
|
border-left-color: #232323;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.password-mask {
|
||||||
|
font-family: 'KaiseiOpti-Bold';
|
||||||
|
letter-spacing: 0.08rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inner-divider {
|
||||||
|
height: 1px;
|
||||||
|
margin: 2rem 0;
|
||||||
|
background-color: #c4c4c4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.small-btn {
|
||||||
|
width: 10rem;
|
||||||
|
height: 3.2rem;
|
||||||
|
align-self: flex-start;
|
||||||
|
border: 0.1rem solid #c4c4c4;
|
||||||
|
background: #f6f6f6;
|
||||||
|
font-family: 'KaiseiOpti-Bold';
|
||||||
|
font-size: 1.2rem;
|
||||||
|
line-height: 2.6rem;
|
||||||
|
letter-spacing: -0.03em;
|
||||||
|
color: #232323;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.verified-tip {
|
||||||
|
color: #6f7f68;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
73
src/views/setting/components/SettingsActions.vue
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
<template>
|
||||||
|
<div class="action-container">
|
||||||
|
<template v-if="isEditing">
|
||||||
|
<button type="button" class="primary-btn" :disabled="saving" @click="emit('save')">
|
||||||
|
{{ saving ? t('Settings.buttons.saving') : t('Settings.buttons.saveChange') }}
|
||||||
|
</button>
|
||||||
|
<button type="button" class="secondary-btn" :disabled="saving" @click="emit('discard')">
|
||||||
|
{{ t('Settings.buttons.discard') }}
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<button type="button" class="primary-btn edit-btn" @click="emit('edit')">
|
||||||
|
{{ t('Settings.buttons.edit') }}
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
isEditing: boolean
|
||||||
|
saving: boolean
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(event: 'edit'): void
|
||||||
|
(event: 'save'): void
|
||||||
|
(event: 'discard'): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.action-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
column-gap: 1.2rem;
|
||||||
|
margin-top: 2.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.primary-btn,
|
||||||
|
.secondary-btn {
|
||||||
|
height: 4.4rem;
|
||||||
|
border: 0.1rem solid #c4c4c4;
|
||||||
|
background: #f6f6f6;
|
||||||
|
font-family: 'KaiseiOpti-Bold';
|
||||||
|
font-size: 1.6rem;
|
||||||
|
line-height: 2.6rem;
|
||||||
|
color: #232323;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.primary-btn {
|
||||||
|
width: 23.6rem;
|
||||||
|
padding: 0 2.4rem;
|
||||||
|
border-color: #232323;
|
||||||
|
background: #232323;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.secondary-btn {
|
||||||
|
width: 12rem;
|
||||||
|
background: #ffffff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
52
src/views/setting/components/SettingsSection.vue
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
<template>
|
||||||
|
<section class="setting-section">
|
||||||
|
<div class="label-container">
|
||||||
|
<div class="label">{{ title }}</div>
|
||||||
|
<div class="label-desc">{{ description }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div :class="['context-container', contentClass]">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
defineProps<{
|
||||||
|
title: string
|
||||||
|
description: string
|
||||||
|
contentClass?: string
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.setting-section {
|
||||||
|
display: flex;
|
||||||
|
column-gap: 21.4rem;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label-container {
|
||||||
|
width: 27.6rem;
|
||||||
|
font-family: 'KaiseiOpti-Medium';
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
font-size: 2.8rem;
|
||||||
|
line-height: 3.6rem;
|
||||||
|
color: #232323;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label-desc {
|
||||||
|
width: 24rem;
|
||||||
|
margin-top: 2rem;
|
||||||
|
font-size: 1.6rem;
|
||||||
|
line-height: 2.2rem;
|
||||||
|
color: #666666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.context-container {
|
||||||
|
width: 58rem;
|
||||||
|
padding-top: 0.2rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
46
src/views/setting/types.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
export const roleValues = [
|
||||||
|
'fashionEnthusiast',
|
||||||
|
'contentCreator',
|
||||||
|
'student',
|
||||||
|
'retailBuyer',
|
||||||
|
'fashionDesigner',
|
||||||
|
'brandBusiness',
|
||||||
|
'prCommunications',
|
||||||
|
'stylist',
|
||||||
|
'graphicDesigner',
|
||||||
|
'artist3d',
|
||||||
|
'other'
|
||||||
|
] as const
|
||||||
|
|
||||||
|
export const languageValues = ['english', 'chinese'] as const
|
||||||
|
export const regionValues = ['hongKongSar', 'mainlandChina', 'singapore', 'unitedKingdom'] as const
|
||||||
|
|
||||||
|
export type RoleValue = (typeof roleValues)[number]
|
||||||
|
export type LanguageValue = (typeof languageValues)[number]
|
||||||
|
export type RegionValue = (typeof regionValues)[number]
|
||||||
|
|
||||||
|
export interface SettingsData {
|
||||||
|
firstName: string
|
||||||
|
lastName: string
|
||||||
|
email: string
|
||||||
|
username: string
|
||||||
|
role: RoleValue[]
|
||||||
|
language: LanguageValue
|
||||||
|
region: RegionValue
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SecurityDraft {
|
||||||
|
newEmail: string
|
||||||
|
newPassword: string
|
||||||
|
currentPassword: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RoleOption {
|
||||||
|
name: string
|
||||||
|
value: RoleValue
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SettingOption<T extends string> {
|
||||||
|
label: string
|
||||||
|
value: T
|
||||||
|
}
|
||||||
288
src/views/setting/useSettingsForm.ts
Normal file
@@ -0,0 +1,288 @@
|
|||||||
|
import { computed, ref, shallowRef, watch, type Ref } from 'vue'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import {
|
||||||
|
languageValues,
|
||||||
|
regionValues,
|
||||||
|
roleValues,
|
||||||
|
type LanguageValue,
|
||||||
|
type RegionValue,
|
||||||
|
type RoleValue,
|
||||||
|
type SecurityDraft,
|
||||||
|
type SettingsData
|
||||||
|
} from './types'
|
||||||
|
|
||||||
|
type Translate = (key: string, ...args: unknown[]) => string
|
||||||
|
|
||||||
|
interface UseSettingsFormOptions {
|
||||||
|
t: Translate
|
||||||
|
locale: Ref<string>
|
||||||
|
}
|
||||||
|
|
||||||
|
const languageLocaleMap: Record<LanguageValue, 'ENGLISH' | 'CHINESE_SIMPLIFIED'> = {
|
||||||
|
english: 'ENGLISH',
|
||||||
|
chinese: 'CHINESE_SIMPLIFIED'
|
||||||
|
}
|
||||||
|
|
||||||
|
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
||||||
|
|
||||||
|
const createDefaultData = (): SettingsData => ({
|
||||||
|
firstName: 'Alexandra',
|
||||||
|
lastName: 'Chen',
|
||||||
|
email: 'alex.chen@gmail.com',
|
||||||
|
username: '@alexandra_chen',
|
||||||
|
role: ['student', 'graphicDesigner'],
|
||||||
|
language: 'english',
|
||||||
|
region: 'hongKongSar'
|
||||||
|
})
|
||||||
|
|
||||||
|
const cloneSettingsData = (data: SettingsData): SettingsData => ({
|
||||||
|
firstName: data.firstName,
|
||||||
|
lastName: data.lastName,
|
||||||
|
email: data.email,
|
||||||
|
username: data.username,
|
||||||
|
role: [...data.role],
|
||||||
|
language: data.language,
|
||||||
|
region: data.region
|
||||||
|
})
|
||||||
|
|
||||||
|
const createEmptySecurityDraft = (): SecurityDraft => ({
|
||||||
|
newEmail: '',
|
||||||
|
newPassword: '',
|
||||||
|
currentPassword: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
export function useSettingsForm({ t, locale }: UseSettingsFormOptions) {
|
||||||
|
const sourceData = ref<SettingsData>(createDefaultData())
|
||||||
|
const draftData = ref<SettingsData>(cloneSettingsData(sourceData.value))
|
||||||
|
const securityDraft = ref<SecurityDraft>(createEmptySecurityDraft())
|
||||||
|
const isEditing = shallowRef(false)
|
||||||
|
const saving = shallowRef(false)
|
||||||
|
const isVerificationDialogVisible = shallowRef(false)
|
||||||
|
const verificationTargetEmail = shallowRef('')
|
||||||
|
const verifiedEmail = shallowRef('')
|
||||||
|
|
||||||
|
const roleList = computed(() =>
|
||||||
|
roleValues.map((value) => ({
|
||||||
|
name: t(`Settings.roles.${value}`),
|
||||||
|
value
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
|
||||||
|
const languageList = computed(() =>
|
||||||
|
languageValues.map((value) => ({
|
||||||
|
label: t(`Settings.languages.${value}`),
|
||||||
|
value
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
|
||||||
|
const regionList = computed(() =>
|
||||||
|
regionValues.map((value) => ({
|
||||||
|
label: t(`Settings.regions.${value}`),
|
||||||
|
value
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
|
||||||
|
const displayData = computed(() => (isEditing.value ? draftData.value : sourceData.value))
|
||||||
|
const normalizedNewEmail = computed(() => securityDraft.value.newEmail.trim())
|
||||||
|
const hasNewEmailChange = computed(
|
||||||
|
() => normalizedNewEmail.value.length > 0 && normalizedNewEmail.value !== sourceData.value.email
|
||||||
|
)
|
||||||
|
const isEmailVerified = computed(
|
||||||
|
() => hasNewEmailChange.value && verifiedEmail.value === normalizedNewEmail.value
|
||||||
|
)
|
||||||
|
const displayLanguageLabel = computed(() => t(`Settings.languages.${displayData.value.language}`))
|
||||||
|
const displayRegionLabel = computed(() => t(`Settings.regions.${displayData.value.region}`))
|
||||||
|
|
||||||
|
const fullName = computed(() => {
|
||||||
|
const data = displayData.value
|
||||||
|
return `${data.firstName} ${data.lastName}`.trim()
|
||||||
|
})
|
||||||
|
|
||||||
|
const roleModel = computed<RoleValue[]>({
|
||||||
|
get: () => displayData.value.role,
|
||||||
|
set: (value) => {
|
||||||
|
if (isEditing.value) {
|
||||||
|
draftData.value.role = value
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceData.value.role = value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const resetEmailVerificationState = () => {
|
||||||
|
isVerificationDialogVisible.value = false
|
||||||
|
verificationTargetEmail.value = ''
|
||||||
|
verifiedEmail.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const syncAppLanguage = (language: LanguageValue) => {
|
||||||
|
const nextLocale = languageLocaleMap[language]
|
||||||
|
locale.value = nextLocale
|
||||||
|
localStorage.setItem('language', nextLocale)
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetDraftState = () => {
|
||||||
|
draftData.value = cloneSettingsData(sourceData.value)
|
||||||
|
securityDraft.value = createEmptySecurityDraft()
|
||||||
|
resetEmailVerificationState()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleEdit = () => {
|
||||||
|
resetDraftState()
|
||||||
|
isEditing.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetSecurityEmail = () => {
|
||||||
|
securityDraft.value.newEmail = ''
|
||||||
|
resetEmailVerificationState()
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetSecurityPassword = () => {
|
||||||
|
securityDraft.value.newPassword = ''
|
||||||
|
securityDraft.value.currentPassword = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDiscard = () => {
|
||||||
|
resetDraftState()
|
||||||
|
isEditing.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeVerificationDialog = () => {
|
||||||
|
isVerificationDialogVisible.value = false
|
||||||
|
verificationTargetEmail.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleVerifyEmail = () => {
|
||||||
|
const nextEmail = normalizedNewEmail.value
|
||||||
|
|
||||||
|
if (!nextEmail) {
|
||||||
|
ElMessage.warning(t('Settings.messages.enterNewEmailFirst'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!emailPattern.test(nextEmail)) {
|
||||||
|
ElMessage.warning(t('Settings.messages.invalidEmail'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextEmail === sourceData.value.email) {
|
||||||
|
ElMessage.warning(t('Settings.messages.sameEmail'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (verifiedEmail.value === nextEmail) {
|
||||||
|
ElMessage.success(t('Settings.messages.alreadyVerified'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
verificationTargetEmail.value = nextEmail
|
||||||
|
handleSendVerifyCode()
|
||||||
|
isVerificationDialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSendVerifyCode = () => {
|
||||||
|
ElMessage.success(t('Settings.messages.verificationCodeSent'))
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleVerificationSubmit = (code: string) => {
|
||||||
|
if (code.length !== 6) {
|
||||||
|
ElMessage.warning(t('Settings.messages.enterVerificationCode'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
verifiedEmail.value = verificationTargetEmail.value
|
||||||
|
closeVerificationDialog()
|
||||||
|
ElMessage.success(t('Settings.messages.verificationCompleted'))
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildNextData = (): SettingsData => {
|
||||||
|
const nextEmail = securityDraft.value.newEmail.trim() || draftData.value.email
|
||||||
|
|
||||||
|
return {
|
||||||
|
firstName: draftData.value.firstName.trim(),
|
||||||
|
lastName: draftData.value.lastName.trim(),
|
||||||
|
username: draftData.value.username.trim(),
|
||||||
|
email: nextEmail,
|
||||||
|
role: [...draftData.value.role],
|
||||||
|
language: draftData.value.language,
|
||||||
|
region: draftData.value.region
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSave = async () => {
|
||||||
|
if (hasNewEmailChange.value && !isEmailVerified.value) {
|
||||||
|
ElMessage.warning(t('Settings.messages.verifyEmailBeforeSave'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextData = buildNextData()
|
||||||
|
const previousLanguage = sourceData.value.language
|
||||||
|
saving.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
sourceData.value = cloneSettingsData(nextData)
|
||||||
|
|
||||||
|
if (nextData.language !== previousLanguage) {
|
||||||
|
syncAppLanguage(nextData.language)
|
||||||
|
}
|
||||||
|
|
||||||
|
draftData.value = cloneSettingsData(sourceData.value)
|
||||||
|
securityDraft.value = createEmptySecurityDraft()
|
||||||
|
resetEmailVerificationState()
|
||||||
|
isEditing.value = false
|
||||||
|
ElMessage.success(t('Settings.messages.settingsUpdated'))
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(error)
|
||||||
|
} finally {
|
||||||
|
saving.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => securityDraft.value.newEmail,
|
||||||
|
(value) => {
|
||||||
|
const trimmedValue = value.trim()
|
||||||
|
|
||||||
|
if (verifiedEmail.value && trimmedValue !== verifiedEmail.value) {
|
||||||
|
verifiedEmail.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
isVerificationDialogVisible.value &&
|
||||||
|
verificationTargetEmail.value &&
|
||||||
|
trimmedValue !== verificationTargetEmail.value
|
||||||
|
) {
|
||||||
|
closeVerificationDialog()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
sourceData,
|
||||||
|
draftData,
|
||||||
|
securityDraft,
|
||||||
|
isEditing,
|
||||||
|
saving,
|
||||||
|
isVerificationDialogVisible,
|
||||||
|
verificationTargetEmail,
|
||||||
|
roleList,
|
||||||
|
languageList,
|
||||||
|
regionList,
|
||||||
|
displayData,
|
||||||
|
isEmailVerified,
|
||||||
|
displayLanguageLabel,
|
||||||
|
displayRegionLabel,
|
||||||
|
fullName,
|
||||||
|
roleModel,
|
||||||
|
handleEdit,
|
||||||
|
handleDiscard,
|
||||||
|
handleSave,
|
||||||
|
resetSecurityEmail,
|
||||||
|
resetSecurityPassword,
|
||||||
|
handleVerifyEmail,
|
||||||
|
handleSendVerifyCode,
|
||||||
|
handleVerificationSubmit,
|
||||||
|
closeVerificationDialog
|
||||||
|
}
|
||||||
|
}
|
||||||
216
src/views/shopping-drawer.vue
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
|
||||||
|
import myEvent from '@/utils/myEvent'
|
||||||
|
// import scList from '@/views/shoppingCart/sc-list.vue'
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
import img from '@/assets/images/brand-null.png'
|
||||||
|
|
||||||
|
//const props = defineProps({
|
||||||
|
//})
|
||||||
|
//const emit = defineEmits([
|
||||||
|
//])
|
||||||
|
let data = reactive({
|
||||||
|
})
|
||||||
|
const router = useRouter()
|
||||||
|
const isShoppingShow = ref(true)
|
||||||
|
const shoppingClose = () => {
|
||||||
|
isShoppingShow.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const goShopping = () => {
|
||||||
|
router.push({path: '/shoppingCart'})
|
||||||
|
isShoppingShow.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(()=>{
|
||||||
|
myEvent.add('addShopping', (item) => {
|
||||||
|
isShoppingShow.value = true
|
||||||
|
console.log(item)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
onUnmounted(()=>{
|
||||||
|
myEvent.remove('addShopping')
|
||||||
|
})
|
||||||
|
defineExpose({})
|
||||||
|
const {} = toRefs(data);
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<el-drawer v-model="isShoppingShow" width="50rem" class="addShoppingDrawer" :close-on-click-modal="false" title="I am the title" :with-header="false">
|
||||||
|
<div class="addShoppingInfo">
|
||||||
|
<div class="header">
|
||||||
|
<div class="title">Added to your Shopping Cart</div>
|
||||||
|
<span class="close" @click="shoppingClose"
|
||||||
|
><svg-icon name="close" size="13"
|
||||||
|
/></span>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<div class="img-list">
|
||||||
|
<div class="img-box">
|
||||||
|
<img :src="img" alt="">
|
||||||
|
</div>
|
||||||
|
<div class="img-box">
|
||||||
|
<img :src="img" alt="">
|
||||||
|
</div>
|
||||||
|
<div class="img-box">
|
||||||
|
<img :src="img" alt="">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="inf-box">
|
||||||
|
<div class="name">North Outfit Set</div>
|
||||||
|
<div class="shopping-name">
|
||||||
|
<div class="icon">
|
||||||
|
<SvgIcon name="shop" size="24" />
|
||||||
|
</div>
|
||||||
|
Roaming Clouds
|
||||||
|
</div>
|
||||||
|
<div class="price">$15 <span class="currency">HKD</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="statement">
|
||||||
|
<div class="icon">
|
||||||
|
<SvgIcon name="statement" size="16.6" />
|
||||||
|
</div>
|
||||||
|
Digital Assets Only. No physical product included.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="button" @click="goShopping">
|
||||||
|
SeE Shopping Cart
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- <sc-list is-mini style="flex: 0.6;" @close="shoppingClose"/> -->
|
||||||
|
</el-drawer>
|
||||||
|
</template>
|
||||||
|
<style lang="less">
|
||||||
|
.el-drawer.addShoppingDrawer{
|
||||||
|
--el-drawer-padding-primary: 2.4rem 3.4rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<style lang="less" scoped>
|
||||||
|
|
||||||
|
.addShoppingInfo{
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
> .header{
|
||||||
|
border-bottom: 0.1rem solid #c4c4c4;
|
||||||
|
display: flex;
|
||||||
|
padding-bottom: 2.4rem;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
> .title{
|
||||||
|
font-family: KaiseiOpti-Bold;
|
||||||
|
font-size: 2rem;
|
||||||
|
line-height: 120%;
|
||||||
|
color: #121212;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
> .close{
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
> .content{
|
||||||
|
flex: 1;
|
||||||
|
padding-top: 9.6rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
> .img-list{
|
||||||
|
height: 37.5rem;
|
||||||
|
width: 33.7rem;
|
||||||
|
position: relative;
|
||||||
|
> .img-box{
|
||||||
|
position: absolute;
|
||||||
|
overflow: hidden;
|
||||||
|
width: 26.9rem;
|
||||||
|
height: 34.1rem;
|
||||||
|
border: 1px solid #EFEFEF;
|
||||||
|
top: 1.7rem;
|
||||||
|
right: 0;
|
||||||
|
left: auto;
|
||||||
|
transform-origin: bottom;
|
||||||
|
box-shadow: 1rem .8rem 2.4rem 0px #4D4D4D0A;
|
||||||
|
> img{
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
&:nth-child(1){
|
||||||
|
transform: rotate(-8deg);
|
||||||
|
background-color: #eaeaea;
|
||||||
|
right: 2rem;
|
||||||
|
}
|
||||||
|
&:nth-child(2){
|
||||||
|
transform: rotate(-4deg);
|
||||||
|
background-color: #eeeeee;
|
||||||
|
right: 1rem;
|
||||||
|
}
|
||||||
|
&:nth-child(3){
|
||||||
|
transform: rotate(0);
|
||||||
|
background-color: #fafafa;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
> .inf-box{
|
||||||
|
margin-top: 5.18rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
> .name{
|
||||||
|
font-family: KaiseiOpti-Bold;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 2.4rem;
|
||||||
|
line-height: 140%;
|
||||||
|
}
|
||||||
|
> .shopping-name{
|
||||||
|
margin-top: 1.3rem;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 1.6rem;
|
||||||
|
line-height: 140%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
> .icon{
|
||||||
|
margin-right: 0.8rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
> .price{
|
||||||
|
margin-top: 1.2rem;
|
||||||
|
font-family: KaiseiOpti-Bold;
|
||||||
|
font-weight: 700;
|
||||||
|
font-style: Bold;
|
||||||
|
font-size: 1.8rem;
|
||||||
|
line-height: 140%;
|
||||||
|
> .currency{
|
||||||
|
font-size: 1.2rem;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 140%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
> .statement{
|
||||||
|
margin-top: 5rem;
|
||||||
|
font-family: KaiseiOpti-Regular;
|
||||||
|
color: #979797;
|
||||||
|
display: flex;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
line-height: 140%;
|
||||||
|
> .icon{
|
||||||
|
margin-right: 0.8rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
> .button{
|
||||||
|
font-family: KaiseiOpti-Regular;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 4.6rem;
|
||||||
|
letter-spacing: 3%;
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
border: 1px solid #232323;
|
||||||
|
margin-bottom: calc(6rem - 2.4rem);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
48
src/views/shoppingCart/index.vue
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
<template>
|
||||||
|
<div class="shopping-cart">
|
||||||
|
<div class="content">
|
||||||
|
<sc-list @selected-change="(v) => (selectedList = v)" />
|
||||||
|
<order-summary :list="selectedList" />
|
||||||
|
<!-- <sc-list is-mini style="height: 70rem" /> -->
|
||||||
|
<!-- <sc-list is-mini is-view title="Order Summary" style="height: 70rem" /> -->
|
||||||
|
</div>
|
||||||
|
<my-footer />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, onMounted, ref } from 'vue'
|
||||||
|
import orderSummary from './order-summary.vue'
|
||||||
|
import scList from './sc-list.vue'
|
||||||
|
import myFooter from '@/components/Footer.vue'
|
||||||
|
const selectedList = ref([])
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.shopping-cart {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
--content-top: 4.8rem;
|
||||||
|
> .content {
|
||||||
|
max-width: 126rem;
|
||||||
|
padding-top: var(--content-top);
|
||||||
|
margin: 0 auto;
|
||||||
|
min-height: var(--app-view-height);
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
> .sc-list {
|
||||||
|
flex: 1;
|
||||||
|
margin-right: 7.5rem;
|
||||||
|
margin-bottom: 8rem;
|
||||||
|
--sc-list-header-top: var(--content-top);
|
||||||
|
}
|
||||||
|
> .order-summary {
|
||||||
|
position: sticky;
|
||||||
|
top: var(--content-top);
|
||||||
|
max-height: var(--app-view-height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||