Merge branch 'main' of ssh://18.167.251.121:10002/aidlab/Aida_Purchaser_Front
@@ -3,6 +3,7 @@
|
||||
<div class="view" ref="viewRef" :style="viewStyle"><RouteCache /></div>
|
||||
<login-dialog />
|
||||
<div id="loading" v-if="loading" v-loading="true"></div>
|
||||
<shopping-drawer />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -11,6 +12,7 @@
|
||||
import MainHeader from '@/views/main-header.vue'
|
||||
import LoginDialog from '@/views/login/login-dialog.vue'
|
||||
import { useGlobalStore } from '@/stores'
|
||||
import ShoppingDrawer from '@/views/shopping-drawer.vue'
|
||||
const globalStore = useGlobalStore()
|
||||
const loading = computed(() => globalStore.state.loading)
|
||||
const viewRef = ref()
|
||||
@@ -25,10 +27,17 @@
|
||||
})
|
||||
onMounted(() => {
|
||||
observer.observe(viewRef.value)
|
||||
console.log('onMounted')
|
||||
})
|
||||
onBeforeUnmount(() => {
|
||||
observer.disconnect()
|
||||
})
|
||||
window['onClickPrivacy'] = () => {
|
||||
const e = window.event || event
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
console.log('点击了隐私政策')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
|
||||
@@ -20,6 +20,7 @@ body,
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
font-family: 'KaiseiOpti-Medium';
|
||||
color: #232323;
|
||||
}
|
||||
@keyframes loading {
|
||||
0% {
|
||||
@@ -96,8 +97,11 @@ body,
|
||||
--el-color-primary-dark-2: #565656;
|
||||
/* 深灰色(加深20%) */
|
||||
}
|
||||
.mini-scrollbar {
|
||||
--mini-scrollbar-width: 0.4rem;
|
||||
}
|
||||
.mini-scrollbar::-webkit-scrollbar {
|
||||
width: 0.4rem;
|
||||
width: var(--mini-scrollbar-width);
|
||||
}
|
||||
.mini-scrollbar::-webkit-scrollbar-thumb {
|
||||
border-radius: 0.4rem;
|
||||
@@ -107,42 +111,13 @@ body,
|
||||
--mosaic-bg-size: 1rem;
|
||||
--mosaic-bg-color1: #efefef;
|
||||
--mosaic-bg-color2: #fff;
|
||||
background-image: repeating-conic-gradient(
|
||||
var(--mosaic-bg-color1) 0% 25%,
|
||||
var(--mosaic-bg-color2) 0% 50%
|
||||
);
|
||||
background-image: repeating-conic-gradient(var(--mosaic-bg-color1) 0% 25%, var(--mosaic-bg-color2) 0% 50%);
|
||||
background-repeat: repeat;
|
||||
background-position: 50% 50%;
|
||||
background-size: var(--mosaic-bg-size) var(--mosaic-bg-size);
|
||||
}
|
||||
|
||||
.flex {
|
||||
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='white'] {
|
||||
button[custom="white"] {
|
||||
min-width: 19.4rem;
|
||||
height: 5rem;
|
||||
padding: 0 1rem;
|
||||
@@ -155,11 +130,11 @@ button[custom='white'] {
|
||||
cursor: pointer;
|
||||
}
|
||||
button[custom]:active,
|
||||
button[custom='white']:active {
|
||||
button[custom="white"]:active {
|
||||
background: var(--button-click-bgcolor, #e4e4e4);
|
||||
color: var(--button-click-color, #232323);
|
||||
}
|
||||
button[custom='black'] {
|
||||
button[custom="black"] {
|
||||
--button-bgcolor: #232323;
|
||||
--button-color: #fff;
|
||||
--button-click-bgcolor: #333;
|
||||
|
||||
@@ -22,6 +22,7 @@ body,
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
font-family: 'KaiseiOpti-Medium';
|
||||
color: #232323;
|
||||
}
|
||||
|
||||
@keyframes loading {
|
||||
@@ -118,8 +119,10 @@ body,
|
||||
|
||||
// 迷你滚动条
|
||||
.mini-scrollbar {
|
||||
--mini-scrollbar-width: 0.4rem;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 0.4rem;
|
||||
width: var(--mini-scrollbar-width);
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
|
||||
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/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 |
BIN
src/assets/images/shopping-cart-null.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
@@ -15,14 +15,17 @@ const props = defineProps({
|
||||
}
|
||||
})
|
||||
const emit = defineEmits([
|
||||
'addShopping'
|
||||
'addShopping',
|
||||
'openDetail'
|
||||
])
|
||||
let data = reactive({
|
||||
})
|
||||
const addShopping = () => {
|
||||
emit('addShopping')
|
||||
}
|
||||
|
||||
const openDetail = () => {
|
||||
emit('openDetail')
|
||||
}
|
||||
onMounted(()=>{
|
||||
})
|
||||
onUnmounted(()=>{
|
||||
@@ -32,7 +35,7 @@ const {} = toRefs(data);
|
||||
</script>
|
||||
<template>
|
||||
<div class="commodity-item">
|
||||
<img :src="props.url" alt="">
|
||||
<img :src="props.url" alt="" @click="openDetail">
|
||||
<div class="detail">
|
||||
<div class="text">
|
||||
<div class="name">
|
||||
@@ -55,6 +58,7 @@ const {} = toRefs(data);
|
||||
width: var(--commodity-width,100%);
|
||||
> img{
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
height: var(--commodity-height,auto);
|
||||
margin-bottom: var(--commodity-marginBottom,1rem);
|
||||
}
|
||||
@@ -79,5 +83,8 @@ const {} = toRefs(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
.btn{
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -23,17 +23,28 @@ const router = createRouter({
|
||||
name: 'brand',
|
||||
component: () => import('../views/brand/index.vue')
|
||||
},
|
||||
{
|
||||
path: '/digitalItem',
|
||||
name: 'digitalItem',
|
||||
component: () => import('../views/digitalItem/index.vue')
|
||||
},
|
||||
{
|
||||
path: '/digitalItem',
|
||||
name: 'digitalItem',
|
||||
component: () => import('../views/digitalItem/index.vue'),
|
||||
meta: { cache: true }
|
||||
},
|
||||
{
|
||||
path: '/digitalItem/:id',
|
||||
name: 'digitalItemDetail',
|
||||
component: () => import('../views/digitalDetail/index.vue'),
|
||||
},
|
||||
{
|
||||
path: '/settings',
|
||||
name: 'settings',
|
||||
component: () => import('@/views/setting/index.vue'),
|
||||
meta: { cache: true }
|
||||
},
|
||||
{
|
||||
path: '/shoppingCart',// 购物车
|
||||
name: 'shoppingCart',
|
||||
component: () => import('@/views/shoppingCart/index.vue')
|
||||
},
|
||||
{
|
||||
path: '/notifications',
|
||||
name: 'notifications',
|
||||
|
||||
@@ -53,9 +53,9 @@ export function generateUUID(): string {
|
||||
if (typeof crypto !== 'undefined' && crypto.randomUUID) {
|
||||
return crypto.randomUUID()
|
||||
}
|
||||
|
||||
|
||||
// 备用方案:手动生成UUID v4
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
||||
const r = Math.random() * 16 | 0
|
||||
const v = c === 'x' ? r : (r & 0x3 | 0x8)
|
||||
return v.toString(16)
|
||||
@@ -70,39 +70,37 @@ export {
|
||||
|
||||
/** 时间格式化-自定义格式
|
||||
* @param value 时间对象|时间戳|时间字符串
|
||||
* @param format 格式化字符串,默认值为 'yyyy-MM-dd HH:mm:ss'
|
||||
* @param format 格式化字符串,默认值为 'YYYY-MM-DD HH:mm:ss'
|
||||
* @returns 格式化后的时间字符串
|
||||
*/
|
||||
export function FormatDate(value: Date | number | string, format: string = 'yyyy-MM-dd HH:mm:ss') {
|
||||
const date = new Date(value);
|
||||
const yyyy = String(date.getFullYear());
|
||||
const yy = String(date.getFullYear()).slice(-2);
|
||||
const MM = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const M = String(date.getMonth() + 1);
|
||||
const dd = String(date.getDate()).padStart(2, '0');
|
||||
const d = String(date.getDate());
|
||||
const HH = String(date.getHours()).padStart(2, '0');
|
||||
const H = String(date.getHours());
|
||||
const mm = String(date.getMinutes()).padStart(2, '0');
|
||||
const m = String(date.getMinutes());
|
||||
const ss = String(date.getSeconds()).padStart(2, '0');
|
||||
const s = String(date.getSeconds());
|
||||
const str = format.replaceAll('yyyy', yyyy)
|
||||
.replaceAll('yy', yy)
|
||||
.replaceAll('MM', MM)
|
||||
.replaceAll('M', M)
|
||||
.replaceAll('dd', dd)
|
||||
.replaceAll('d', d)
|
||||
.replaceAll('HH', HH)
|
||||
.replaceAll('H', H)
|
||||
.replaceAll('mm', mm)
|
||||
.replaceAll('m', m)
|
||||
.replaceAll('ss', ss)
|
||||
.replaceAll('s', s);
|
||||
return str;
|
||||
export function FormatDate(value: Date | number | string, format: string = 'YYYY-MM-DD HH:mm:ss') {
|
||||
const d = new Date(value);
|
||||
if (!d || isNaN(d.getTime())) return 'Invalid Date';
|
||||
const pad = (n) => String(n).padStart(2, '0');
|
||||
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
||||
const tokens = {
|
||||
YYYY: d.getFullYear(),
|
||||
YY: String(d.getFullYear()).slice(-2),
|
||||
MM: pad(d.getMonth() + 1),
|
||||
M: d.getMonth() + 1,
|
||||
SM: months[d.getMonth()],
|
||||
DD: pad(d.getDate()),
|
||||
D: d.getDate(),
|
||||
HH: pad(d.getHours()),
|
||||
H: d.getHours(),
|
||||
hh: pad(d.getHours() % 12 || 12),
|
||||
h: d.getHours() % 12 || 12,
|
||||
mm: pad(d.getMinutes()),
|
||||
m: d.getMinutes(),
|
||||
ss: pad(d.getSeconds()),
|
||||
s: d.getSeconds(),
|
||||
A: d.getHours() < 12 ? 'AM' : 'PM',
|
||||
a: d.getHours() < 12 ? 'am' : 'pm'
|
||||
}
|
||||
const reg = new RegExp(Object.keys(tokens).join('|'), 'g')
|
||||
return format.replace(reg, match => tokens[match]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 下载图片
|
||||
* @param list 图片列表
|
||||
@@ -163,7 +161,7 @@ export function encryptPassword(password: string): string {
|
||||
* @param url 图片URL
|
||||
* @returns 无
|
||||
*/
|
||||
export async function shareImageToWhatsapp (url: string){
|
||||
export async function shareImageToWhatsapp(url: string) {
|
||||
// 把图片 URL 转为 Blob
|
||||
const blob = await fetch(url).then((res) => res.blob())
|
||||
|
||||
@@ -195,3 +193,19 @@ export function CountDown(time: number) {
|
||||
const ss = String(time % 60).padStart(2, '0');
|
||||
return `${mm}:${ss}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 字节转换为可读格式
|
||||
* @param {number} bytes - 字节数
|
||||
* @param {number} decimals - 保留小数位数,默认2位
|
||||
* @returns {string} 格式化后的字符串
|
||||
*/
|
||||
export function FormatBytes(bytes, decimals = 2) {
|
||||
if (bytes === 0) return '0 B';
|
||||
if (!bytes || isNaN(bytes)) return '0 B';
|
||||
const k = 1024;
|
||||
const sizes = ['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]}`;
|
||||
}
|
||||
|
||||
@@ -2,13 +2,21 @@
|
||||
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) => {}
|
||||
const addShopping = (item) => {
|
||||
myEvent.emit('addShopping', item)
|
||||
}
|
||||
const openDetail = (item) => {
|
||||
router.push({name: 'digitalDetail', params: {id: item.id}})
|
||||
}
|
||||
onMounted(()=>{
|
||||
})
|
||||
onUnmounted(()=>{
|
||||
@@ -26,7 +34,7 @@ const {} = toRefs(data);
|
||||
<MerchantInfo></MerchantInfo>
|
||||
</div>
|
||||
<div class="commodity-list">
|
||||
<CommodityList @addShopping="addShopping"></CommodityList>
|
||||
<CommodityList @addShopping="addShopping" @openDetail="openDetail"></CommodityList>
|
||||
</div>
|
||||
</div>
|
||||
<Footer></Footer>
|
||||
|
||||
@@ -7,8 +7,9 @@ import feelingWithAiDA from "./feelingWithAiDA.vue";
|
||||
import CommodityItem from "@/components/CommodityItem.vue";
|
||||
//const props = defineProps({
|
||||
//})
|
||||
//const emit = defineEmits([
|
||||
//])
|
||||
const emit = defineEmits([
|
||||
'addShopping'
|
||||
])
|
||||
let data = reactive({
|
||||
})
|
||||
const list = ref([
|
||||
@@ -27,7 +28,7 @@ const list = ref([
|
||||
},
|
||||
])
|
||||
const addShopping = (item) => {
|
||||
console.log(item);
|
||||
emit('addShopping', item)
|
||||
}
|
||||
onMounted(()=>{
|
||||
})
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
|
||||
import Detail from "./detail/index.vue";
|
||||
import myEvent from '@/utils/myEvent'
|
||||
//const props = defineProps({
|
||||
//})
|
||||
//const emit = defineEmits([
|
||||
//])
|
||||
let data = reactive({
|
||||
})
|
||||
|
||||
const addShopping = (item) => {
|
||||
myEvent.emit('addShopping', item)
|
||||
}
|
||||
onMounted(()=>{
|
||||
})
|
||||
onUnmounted(()=>{
|
||||
@@ -47,7 +50,7 @@ const {} = toRefs(data);
|
||||
</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<Detail></Detail>
|
||||
<Detail @addShopping="addShopping"></Detail>
|
||||
</div>
|
||||
<Footer></Footer>
|
||||
</div>
|
||||
|
||||
315
src/views/digitalDetail/index.vue
Normal file
@@ -0,0 +1,315 @@
|
||||
<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">
|
||||
<div class="icon">
|
||||
<svg-icon name="digital-document" size="24"></svg-icon>
|
||||
</div>
|
||||
<span>12mb | 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;
|
||||
> span{
|
||||
margin-left: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.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>
|
||||
@@ -4,7 +4,8 @@ import img from "@/assets/images/collectionStory/Rectangle.png";
|
||||
//const props = defineProps({
|
||||
//})
|
||||
const emit = defineEmits([
|
||||
'addShopping'
|
||||
'addShopping',
|
||||
'openDetail'
|
||||
])
|
||||
let data = reactive({
|
||||
})
|
||||
@@ -47,6 +48,9 @@ const type = ref('All')
|
||||
const addShopping = (item) => {
|
||||
emit('addShopping', item)
|
||||
}
|
||||
const openDetail = (item) => {
|
||||
emit('openDetail', item)
|
||||
}
|
||||
onMounted(()=>{
|
||||
})
|
||||
onUnmounted(()=>{
|
||||
@@ -58,7 +62,7 @@ const {} = toRefs(data);
|
||||
<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)"></CommodityItem>
|
||||
<CommodityItem :url="item.url" :name="item.title" :price="item.price" @addShopping="addShopping(item)" @openDetail="openDetail(item)"></CommodityItem>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,14 +1,31 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
|
||||
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";
|
||||
// 定义组件名称
|
||||
defineOptions({
|
||||
name: 'digitalItem'
|
||||
})
|
||||
//const props = defineProps({
|
||||
//})
|
||||
//const emit = defineEmits([
|
||||
//])
|
||||
const digitalItemRef = ref(null)
|
||||
const scrollTop = ref(0)
|
||||
const router = useRouter()
|
||||
let data = reactive({
|
||||
})
|
||||
const addShopping = (item) => {}
|
||||
const openDetail = (item) => {
|
||||
scrollTop.value = digitalItemRef.value.scrollTop
|
||||
router.push({
|
||||
path: '/digitalItem/' + 123,
|
||||
})
|
||||
}
|
||||
onActivated(()=>{
|
||||
digitalItemRef.value.scrollTop = scrollTop.value
|
||||
})
|
||||
onMounted(()=>{
|
||||
})
|
||||
onUnmounted(()=>{
|
||||
@@ -17,7 +34,7 @@ defineExpose({})
|
||||
const {} = toRefs(data);
|
||||
</script>
|
||||
<template>
|
||||
<div class="digitalItem">
|
||||
<div class="digitalItem" ref="digitalItemRef">
|
||||
<div class="header-img">
|
||||
<img src="@/assets/images/digitalItem/digital_item_banner.png" alt="">
|
||||
<div class="text">
|
||||
@@ -30,7 +47,7 @@ const {} = toRefs(data);
|
||||
<MerchantInfo></MerchantInfo>
|
||||
</div>
|
||||
<div class="commodity-list">
|
||||
<CommodityList @addShopping="addShopping"></CommodityList>
|
||||
<CommodityList @addShopping="addShopping" @openDetail="openDetail"></CommodityList>
|
||||
</div>
|
||||
</div>
|
||||
<Footer></Footer>
|
||||
|
||||
@@ -95,7 +95,7 @@
|
||||
{
|
||||
icon: 'cart_0',
|
||||
active_icon: 'cart_1',
|
||||
path: '/cart'
|
||||
path: '/shoppingCart'
|
||||
},
|
||||
{
|
||||
icon: 'user_0',
|
||||
@@ -133,7 +133,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
<style lang="less" scoped>
|
||||
#main-header {
|
||||
height: var(--header-height);
|
||||
display: flex;
|
||||
|
||||
40
src/views/shopping-drawer.vue
Normal file
@@ -0,0 +1,40 @@
|
||||
<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'
|
||||
|
||||
//const props = defineProps({
|
||||
//})
|
||||
//const emit = defineEmits([
|
||||
//])
|
||||
let data = reactive({
|
||||
})
|
||||
const isShoppingShow = ref(false)
|
||||
const shoppingClose = () => {
|
||||
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" :close-on-click-modal="false" title="I am the title" :with-header="false">
|
||||
<sc-list is-mini style="flex: 0.6;" @close="shoppingClose"/>
|
||||
</el-drawer>
|
||||
</template>
|
||||
<style lang="less" scoped>
|
||||
.homeNavBox{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
</style>
|
||||
46
src/views/shoppingCart/index.vue
Normal file
@@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<div class="shopping-cart">
|
||||
<div class="content">
|
||||
<sc-list @selected-change="(v) => (selectedList = v)" />
|
||||
<!-- <sc-list is-mini is-view title="Order Summary" /> -->
|
||||
<order-summary :list="selectedList" />
|
||||
</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 {
|
||||
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>
|
||||
184
src/views/shoppingCart/order-summary.vue
Normal file
@@ -0,0 +1,184 @@
|
||||
<template>
|
||||
<div class="order-summary">
|
||||
<div class="title">Order Summary</div>
|
||||
<div class="count">
|
||||
<span class="label">Selected</span>
|
||||
<span class="value">{{ brandsList.length }}</span>
|
||||
</div>
|
||||
<div class="hr"></div>
|
||||
<div class="brands-header">
|
||||
<span class="icon"><svg-icon name="order-shop" size="18" /></span>
|
||||
<span class="text">Brands</span>
|
||||
</div>
|
||||
<div class="brands-item" v-for="v in brandsList" :key="v.brand">
|
||||
<span class="label">{{ v.brand }}</span>
|
||||
<span class="value"
|
||||
><span>{{ v.children.length }}</span
|
||||
>item</span
|
||||
>
|
||||
</div>
|
||||
<br />
|
||||
<div class="total-file-size">
|
||||
<span class="label">
|
||||
<span class="icon"><svg-icon name="order-file" size="18" /></span>
|
||||
<span class="text">Total File Size</span>
|
||||
</span>
|
||||
<span class="value"
|
||||
>{{ totalSize.size }} <span>{{ totalSize.unit }}</span></span
|
||||
>
|
||||
</div>
|
||||
<div class="hr"></div>
|
||||
<br />
|
||||
<div class="total">
|
||||
<span class="label">Total</span>
|
||||
<span class="value"
|
||||
><span>${{ totalAmount }}</span> HKD</span
|
||||
>
|
||||
</div>
|
||||
<div class="hr"></div>
|
||||
<button class="checkout-btn" custom="black" @click="handleCheckout">CHECKOUT SELECTED</button>
|
||||
<div class="tip">Digital assets. Creator retains copyright.</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { FormatBytes, FormatDate } from '@/utils/tools'
|
||||
const props = defineProps({
|
||||
list: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
})
|
||||
const brandsList = computed(() => {
|
||||
const arr = []
|
||||
props.list.forEach((v) => {
|
||||
const index = arr.findIndex((v_) => v_.brand === v.brand)
|
||||
if (index === -1) {
|
||||
arr.push({
|
||||
brand: v.brand,
|
||||
children: [v]
|
||||
})
|
||||
} else {
|
||||
arr[index].children.push(v)
|
||||
}
|
||||
})
|
||||
return arr
|
||||
})
|
||||
const totalSize = computed(() => {
|
||||
const total = props.list.reduce((pre, cur) => pre + cur.fileSize, 0)
|
||||
const str = FormatBytes(total)
|
||||
return {
|
||||
size: str.split(' ')[0],
|
||||
unit: str.split(' ')[1]
|
||||
}
|
||||
})
|
||||
const totalAmount = computed(() => props.list.reduce((pre, cur) => pre + cur.amount, 0).toFixed(2))
|
||||
const handleCheckout = () => {
|
||||
console.log('购买:', props.list)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.order-summary {
|
||||
width: 39.8rem;
|
||||
padding: 3rem 2rem 3rem 2.4rem;
|
||||
height: auto;
|
||||
background-color: #f6f6f6;
|
||||
> .title {
|
||||
font-family: KaiseiOpti-Bold;
|
||||
font-size: 2.4rem;
|
||||
line-height: 2.3rem;
|
||||
color: #232323;
|
||||
margin-bottom: 1.8rem;
|
||||
}
|
||||
> div {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
> .count {
|
||||
color: #232323;
|
||||
> .label {
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
> .value {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
}
|
||||
> .hr {
|
||||
margin: 1.2rem 0;
|
||||
width: 100%;
|
||||
height: 0;
|
||||
border-top: 0.1rem solid #c4c4c4;
|
||||
}
|
||||
> .brands-header {
|
||||
justify-content: flex-start;
|
||||
|
||||
margin-bottom: 1rem;
|
||||
> .icon {
|
||||
width: 2.4rem;
|
||||
height: 2.4rem;
|
||||
margin-right: 0.4rem;
|
||||
}
|
||||
> .text {
|
||||
font-size: 1.4rem;
|
||||
color: #232323;
|
||||
}
|
||||
}
|
||||
> .brands-item {
|
||||
margin-bottom: 0.8rem;
|
||||
padding-left: 1rem;
|
||||
font-size: 1.2rem;
|
||||
> .label {
|
||||
text-decoration: underline;
|
||||
color: #585858;
|
||||
}
|
||||
> .value {
|
||||
color: #808080;
|
||||
&:deep(span) {
|
||||
font-size: 1.4rem;
|
||||
color: #585858;
|
||||
margin-right: 0.8rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
> .total-file-size {
|
||||
> .label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
> .icon {
|
||||
width: 2.4rem;
|
||||
height: 2.4rem;
|
||||
margin-right: 0.4rem;
|
||||
}
|
||||
}
|
||||
> .value {
|
||||
> span {
|
||||
color: #808080;
|
||||
}
|
||||
}
|
||||
}
|
||||
> .total {
|
||||
> .value {
|
||||
color: #585858;
|
||||
> span {
|
||||
font-size: 2.2rem;
|
||||
color: #232323;
|
||||
}
|
||||
}
|
||||
}
|
||||
> .checkout-btn {
|
||||
width: 100%;
|
||||
margin-top: 3rem;
|
||||
}
|
||||
> .tip {
|
||||
margin-top: 1rem;
|
||||
font-family: KaiseiOpti-Regular;
|
||||
font-size: 1.2rem;
|
||||
justify-content: center;
|
||||
color: #808080;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
614
src/views/shoppingCart/sc-list.vue
Normal file
@@ -0,0 +1,614 @@
|
||||
<template>
|
||||
<!-- 购物车列表 -->
|
||||
<div class="sc-list" :class="{ mini: isMini, view: isView }">
|
||||
<div class="header">
|
||||
<div class="title">
|
||||
<span class="text">{{ title || 'Shopping Cart' }}</span>
|
||||
<span class="close" v-if="isMini && !isView" @click="onClose"
|
||||
><svg-icon name="close" size="13"
|
||||
/></span>
|
||||
</div>
|
||||
<div class="options" v-if="!isMini">
|
||||
<div class="left">
|
||||
<el-checkbox
|
||||
:model-value="allSelected"
|
||||
:indeterminate="selectedCount === 0 ? false : selectedCount < list.length"
|
||||
@click="handleAllAllClick"
|
||||
/>
|
||||
<span class="count">{{ selectedCount }} Selected</span>
|
||||
<div class="hr"></div>
|
||||
<div class="btn" @click="handleAllAllClick(true)">Select All</div>
|
||||
<div class="btn" @click="handleAllAllClick(false)">Deselect All</div>
|
||||
</div>
|
||||
<div class="right">
|
||||
<el-select v-model="sortBy" 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 sortByOptions"
|
||||
:key="item.label"
|
||||
:value="item.value"
|
||||
:label="item.label"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list">
|
||||
<div class="list-null" v-show="list.length === 0">
|
||||
<img src="@/assets/images/shopping-cart-null.png" alt="" />
|
||||
<div class="title">Your Cart is empty</div>
|
||||
<div class="tip">Discover new fashion assets and add them to your cart.</div>
|
||||
<button custom v-if="!isMini" @click="handleExploreClick">EXPLORE DIGITAL ITEMS</button>
|
||||
</div>
|
||||
<div class="item" v-for="v in list" :key="v.id">
|
||||
<el-checkbox v-model="v.checked" v-if="!isMini" @change="handleSelectedChange" />
|
||||
<img :src="v.url" />
|
||||
<div class="content">
|
||||
<div class="title">{{ v.title }}</div>
|
||||
<div class="brand">
|
||||
<span class="icon"><svg-icon name="order-shop" size="24" /></span>
|
||||
<span class="text">{{ v.brand }}</span>
|
||||
</div>
|
||||
<div class="tags" v-if="!isMini || isView">
|
||||
<span v-for="tag in v.tags" :key="tag" class="tag">{{ tag }}</span>
|
||||
</div>
|
||||
<div class="size" v-if="!isView">
|
||||
<div class="icon"><svg-icon name="order-file" size="18" /></div>
|
||||
<div class="text">
|
||||
<span>{{ FormatBytes(v.fileSize) }}</span>
|
||||
<span v-if="!isMini"
|
||||
> | {{
|
||||
FormatDate(v.date, 'SM D, YYYY, h:mm A')
|
||||
}}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right">
|
||||
<div class="amount">${{ v.amount }}<span> HKD</span></div>
|
||||
<div class="remove" v-if="!isView">
|
||||
<span class="icon"><svg-icon name="order-delete" size="18" /></span>
|
||||
<span class="text">Remove</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer" v-if="isMini">
|
||||
<div class="total size" v-show="list.length > 0">
|
||||
<span class="label">Total File Size</span>
|
||||
<span class="value"
|
||||
>{{ allTotalSize.size }}<span> {{ allTotalSize.unit }}</span></span
|
||||
>
|
||||
</div>
|
||||
<div class="total" v-show="list.length > 0">
|
||||
<span class="label">Total</span>
|
||||
<span class="value">${{ allAmount }}<span> HKD</span></span>
|
||||
</div>
|
||||
|
||||
<div class="tip" v-if="isView">Digital assets. Creator retains copyright.</div>
|
||||
<button custom="black" v-if="!isView">CHECKOUT</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, onMounted } from 'vue'
|
||||
import { FormatBytes, FormatDate } from '@/utils/tools'
|
||||
const emit = defineEmits(['close', 'selected-change'])
|
||||
const props = defineProps({
|
||||
title: { type: String, default: '' },
|
||||
isMini: { type: Boolean, default: false },
|
||||
isView: { type: Boolean, default: false }
|
||||
})
|
||||
const allTotalSize = computed(() => {
|
||||
const total = list.value.reduce((pre, cur) => pre + cur.fileSize, 0)
|
||||
const str = FormatBytes(total)
|
||||
return {
|
||||
size: str.split(' ')[0],
|
||||
unit: str.split(' ')[1]
|
||||
}
|
||||
})
|
||||
const allAmount = computed(() => list.value.reduce((pre, cur) => pre + cur.amount, 0).toFixed(2))
|
||||
const selectedCount = computed(() => list.value.filter((v) => v.checked).length)
|
||||
const allSelected = computed(() => list.value.every((v) => v.checked))
|
||||
const sortBy = ref('')
|
||||
const sortByOptions = ref([
|
||||
{
|
||||
label: 'Default',
|
||||
value: 'Default'
|
||||
},
|
||||
{
|
||||
label: 'Selected First',
|
||||
value: 'Selected First'
|
||||
},
|
||||
{
|
||||
label: 'Date Added',
|
||||
value: 'Date Added'
|
||||
}
|
||||
])
|
||||
|
||||
const list = ref([
|
||||
{
|
||||
id: 1,
|
||||
url: 'http://118.31.39.42:3000/falls/shopping-cart-1.png',
|
||||
title: 'North Outfit Set',
|
||||
brand: 'Roaming Clouds',
|
||||
fileSize: 1024, // kb
|
||||
date: '2026-5-20 5:20',
|
||||
amount: 49.99,
|
||||
tags: ['female', 'skirt', 'blouse', 'outwear'],
|
||||
checked: true
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
url: 'http://118.31.39.42:3000/falls/shopping-cart-2.png',
|
||||
title: 'Weekend Drift Co-ord',
|
||||
brand: 'Urban Line Edit',
|
||||
fileSize: 1225, // kb
|
||||
date: '2026-5-21 13:14',
|
||||
amount: 9.99,
|
||||
tags: ['female', 'skirt', 'blouse', 'outwear'],
|
||||
checked: false
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
url: 'http://118.31.39.42:3000/falls/shopping-cart-3.png',
|
||||
title: 'Static Street Suit',
|
||||
brand: 'Off Grid Apparel',
|
||||
fileSize: 1024 * 18, // kb
|
||||
date: '2026-5-21 13:14',
|
||||
amount: 12,
|
||||
tags: ['female', 'skirt', 'blouse', 'outwear'],
|
||||
checked: true
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
url: 'http://118.31.39.42:3000/falls/shopping-cart-4.png',
|
||||
title: 'Maison Contour Suit',
|
||||
brand: 'Ivory Muse Studio',
|
||||
fileSize: 100, // kb
|
||||
date: '2026-5-21 13:14',
|
||||
amount: 18,
|
||||
tags: ['female', 'skirt', 'blouse', 'outwear'],
|
||||
checked: true
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
url: 'http://118.31.39.42:3000/falls/shopping-cart-5.png',
|
||||
title: 'Prime Atelier Set',
|
||||
brand: 'Ivory Muse Studio',
|
||||
fileSize: 1024 * 24, // kb
|
||||
date: '2026-5-21 13:14',
|
||||
amount: 20,
|
||||
tags: ['female', 'skirt', 'blouse', 'outwear'],
|
||||
checked: false
|
||||
}
|
||||
])
|
||||
const handleAllAllClick = (checked?: boolean) => {
|
||||
const checked_ = typeof checked === 'boolean' ? checked : !allSelected.value
|
||||
list.value.forEach((v) => (v.checked = checked_))
|
||||
handleSelectedChange()
|
||||
}
|
||||
const handleSelectedChange = () => {
|
||||
const arr = list.value.filter((v) => v.checked)
|
||||
emit('selected-change', arr)
|
||||
}
|
||||
const onClose = () => {
|
||||
emit('close')
|
||||
}
|
||||
onMounted(() => {
|
||||
handleSelectedChange()
|
||||
})
|
||||
const handleExploreClick = () => {
|
||||
console.log('探索')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.sc-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
&.mini {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
--sc-list-title-font-size: 2rem;
|
||||
--sc-list-title-padding-bottom: 3rem;
|
||||
--sc-list-list-item-padding: 2rem 0;
|
||||
--sc-list-list-item-img-width: 10.4rem;
|
||||
--sc-list-list-item-img-height: 13.2rem;
|
||||
--sc-list-list-item-content-margin: 0 2rem;
|
||||
--sc-list-list-item-margin-bottom: 0.8rem;
|
||||
--sc-list-list-item-title-font-size: 1.6rem;
|
||||
--sc-list-list-item-brand-font-size: 1.4rem;
|
||||
--sc-list-list-item-amount-font-size: 1.8rem;
|
||||
--sc-list-list-item-currency-font-size: 1.6rem;
|
||||
> .list {
|
||||
flex: 1;
|
||||
min-height: 30rem;
|
||||
overflow-y: auto;
|
||||
> .item {
|
||||
> .content {
|
||||
align-self: baseline;
|
||||
}
|
||||
> .right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
height: var(--sc-list-list-item-img-height);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
&.mini.view {
|
||||
--sc-list-title-padding-bottom: 2.4rem;
|
||||
--sc-list-list-item-img-width: 9.6rem;
|
||||
--sc-list-list-item-img-height: 12.2rem;
|
||||
--sc-list-list-item-padding: 1.6rem;
|
||||
--sc-list-list-item-margin-bottom: 0.8rem;
|
||||
--sc-list-list-item-title-font-size: 2rem;
|
||||
> .header {
|
||||
border: none;
|
||||
}
|
||||
> .list {
|
||||
> .item {
|
||||
> .content {
|
||||
margin-top: 1.2rem;
|
||||
> .tags {
|
||||
> .tag {
|
||||
min-width: 0;
|
||||
height: 2rem;
|
||||
line-height: 2rem;
|
||||
font-size: 1.2rem;
|
||||
padding: 0 0.8rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
> .right {
|
||||
margin-top: 1.2rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
> .footer {
|
||||
margin-top: 4.1rem;
|
||||
> .total {
|
||||
margin-bottom: 1.7rem;
|
||||
> .label {
|
||||
font-family: KaiseiOpti-Medium;
|
||||
font-size: 2rem;
|
||||
color: #232323;
|
||||
}
|
||||
> .value {
|
||||
font-family: KaiseiOpti-Medium;
|
||||
font-size: 2.2rem;
|
||||
> span {
|
||||
font-family: KaiseiOpti-Regular;
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
> .tip {
|
||||
font-family: KaiseiOpti-Regular;
|
||||
font-size: 1.2rem;
|
||||
color: #808080;
|
||||
margin-top: 2.4rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
&:not(.mini) {
|
||||
> .header {
|
||||
position: sticky;
|
||||
top: var(--sc-list-header-top, 0);
|
||||
background-color: #fff;
|
||||
z-index: 999;
|
||||
&:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: calc(var(--sc-list-header-top, 0) + 2px);
|
||||
transform: translateY(-100%);
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
> .list {
|
||||
> .list-null {
|
||||
margin-top: 10rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
> .header {
|
||||
border-bottom: 0.1rem solid #c4c4c4;
|
||||
padding-bottom: var(--sc-list-title-padding-bottom, 3rem);
|
||||
> .title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
> .text {
|
||||
font-family: KaiseiOpti-Bold;
|
||||
font-size: var(--sc-list-title-font-size, 4rem);
|
||||
color: #121212;
|
||||
}
|
||||
> .close {
|
||||
width: 2.4rem;
|
||||
height: 2.4rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
> .options {
|
||||
margin-top: 1rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
> .left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
> .count {
|
||||
font-family: KaiseiOpti-Regular;
|
||||
font-size: 1.8rem;
|
||||
color: #232323;
|
||||
}
|
||||
> .hr {
|
||||
width: 0;
|
||||
height: 100%;
|
||||
border-left: 0.1rem solid #c4c4c4;
|
||||
margin: 0 2rem;
|
||||
}
|
||||
> .btn {
|
||||
margin-right: 1.2rem;
|
||||
font-family: KaiseiOpti-Regular;
|
||||
font-size: 1.8rem;
|
||||
color: #979797;
|
||||
text-decoration: underline;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
> .list {
|
||||
// height: 1000px;
|
||||
> .list-null {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
> img {
|
||||
width: 14rem;
|
||||
height: auto;
|
||||
margin-bottom: 2.4rem;
|
||||
}
|
||||
> .title {
|
||||
font-family: KaiseiOpti-Bold;
|
||||
font-size: 1.6rem;
|
||||
color: #979797;
|
||||
margin-bottom: 0.8rem;
|
||||
}
|
||||
> .tip {
|
||||
width: 50%;
|
||||
font-family: KaiseiOpti-Regular;
|
||||
font-size: 1.4rem;
|
||||
color: #979797;
|
||||
}
|
||||
> button {
|
||||
min-width: 30rem;
|
||||
height: 4.4rem;
|
||||
border: 0.1rem solid #c4c4c4;
|
||||
font-family: KaiseiOpti-Medium;
|
||||
font-size: 1.6rem;
|
||||
color: #979797;
|
||||
margin-top: 4.2rem;
|
||||
}
|
||||
}
|
||||
> .item {
|
||||
border-bottom: 0.1rem solid #c4c4c4;
|
||||
padding: var(--sc-list-list-item-padding, 2.4rem 0);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
> img {
|
||||
width: var(--sc-list-list-item-img-width, 14.8rem);
|
||||
height: var(--sc-list-list-item-img-height, 18.8rem);
|
||||
object-fit: contain;
|
||||
background-color: #f6f6f6;
|
||||
}
|
||||
> .content {
|
||||
flex: 1;
|
||||
margin: var(--sc-list-list-item-content-margin, 0 4rem);
|
||||
> * {
|
||||
margin-bottom: var(--sc-list-list-item-margin-bottom, 1.6rem);
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
> .title {
|
||||
font-family: KaiseiOpti-Bold;
|
||||
font-size: var(--sc-list-list-item-title-font-size, 2.4rem);
|
||||
color: #232323;
|
||||
}
|
||||
> .brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
> .icon {
|
||||
width: 2.4rem;
|
||||
height: 2.4rem;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
> .text {
|
||||
font-size: var(--sc-list-list-item-brand-font-size, 1.6rem);
|
||||
text-decoration: underline;
|
||||
color: #232323;
|
||||
}
|
||||
}
|
||||
> .tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.8rem;
|
||||
> .tag {
|
||||
min-width: 8.8rem;
|
||||
height: 2.4rem;
|
||||
line-height: 2.4rem;
|
||||
border-radius: 2.4rem;
|
||||
font-size: 1.4rem;
|
||||
padding: 0 1rem;
|
||||
text-align: center;
|
||||
color: #8f8f8f;
|
||||
background-color: #eee;
|
||||
}
|
||||
}
|
||||
> .size {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
> .icon {
|
||||
width: 2.4rem;
|
||||
height: 2.4rem;
|
||||
margin-right: 1rem;
|
||||
color: #808080;
|
||||
}
|
||||
|
||||
> .text {
|
||||
font-family: KaiseiOpti-Regular;
|
||||
font-size: 1.4rem;
|
||||
color: #808080;
|
||||
}
|
||||
}
|
||||
}
|
||||
> .right {
|
||||
align-self: end;
|
||||
> .amount {
|
||||
font-family: KaiseiOpti-Bold;
|
||||
font-size: var(--sc-list-list-item-amount-font-size, 2.2rem);
|
||||
color: #232323;
|
||||
> span {
|
||||
font-size: var(--sc-list-list-item-currency-font-size, 1.4rem);
|
||||
color: #585858;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
}
|
||||
> .remove {
|
||||
margin-top: var(--sc-list-list-item-remove-margin-top, 9rem);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
> .icon {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
margin-right: 0.4rem;
|
||||
}
|
||||
> .text {
|
||||
font-family: KaiseiOpti-Regular;
|
||||
font-size: 1.4rem;
|
||||
color: #808080;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
> .footer {
|
||||
margin-top: 3rem;
|
||||
> .total {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
> .label {
|
||||
font-family: KaiseiOpti-Bold;
|
||||
font-size: 1.6rem;
|
||||
color: #121212;
|
||||
}
|
||||
> .value {
|
||||
font-family: KaiseiOpti-Bold;
|
||||
font-size: 2rem;
|
||||
color: #232323;
|
||||
> span {
|
||||
font-family: KaiseiOpti-Medium;
|
||||
font-size: 1.6rem;
|
||||
color: #585858;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
}
|
||||
}
|
||||
> button {
|
||||
width: 100%;
|
||||
height: 4.6rem;
|
||||
--button-font-size: 1.4rem;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
.sc-list:deep(.el-checkbox) {
|
||||
margin-right: 3rem;
|
||||
height: auto;
|
||||
--el-checkbox-font-size: 1.4rem;
|
||||
--el-checkbox-input-width: 2.4rem;
|
||||
--el-checkbox-input-height: 2.4rem;
|
||||
--el-checkbox-checked-bg-color: #000;
|
||||
--el-checkbox-checked-input-border-color: #000;
|
||||
--el-checkbox-input-border: 0.1rem solid #232323;
|
||||
--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.6rem;
|
||||
height: 1.3rem;
|
||||
border-width: 0.2rem;
|
||||
}
|
||||
}
|
||||
.sc-list: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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||