购物车

This commit is contained in:
李志鹏
2026-04-23 11:48:22 +08:00
parent 32a801b24f
commit b0ee5a0783
11 changed files with 689 additions and 37 deletions

View File

@@ -25,10 +25,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">

View File

@@ -96,8 +96,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 +110,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 +129,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;

View File

@@ -118,8 +118,10 @@ body,
// 迷你滚动条
.mini-scrollbar {
--mini-scrollbar-width: 0.4rem;
&::-webkit-scrollbar {
width: 0.4rem;
width: var(--mini-scrollbar-width);
}
&::-webkit-scrollbar-thumb {

View 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

View 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

View 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

View File

@@ -29,6 +29,11 @@ const router = createRouter({
component: () => import('@/views/setting/index.vue'),
meta: { cache: true }
},
{
path: '/shoppingCart',// 购物车
name: 'shoppingCart',
component: () => import('@/views/shoppingCart/index.vue')
},
{
path: '/:pathMatch(.*)',
name: '404',

View File

@@ -95,7 +95,7 @@
{
icon: 'cart_0',
active_icon: 'cart_1',
path: '/cart'
path: '/shoppingCart'
},
{
icon: 'user_0',
@@ -132,7 +132,7 @@
}
</script>
<style lang="less">
<style lang="less" scoped>
#main-header {
height: var(--header-height);
display: flex;

View File

@@ -0,0 +1,45 @@
<template>
<div class="shopping-cart">
<div class="content">
<sc-list />
<sc-list is-mini style="flex: 0.6; height: var(--app-view-height)" />
<!-- <order-summary /> -->
</div>
<my-footer />
</div>
</template>
<script setup lang="ts">
import { computed, onMounted, defineComponent, onActivated } from 'vue'
import orderSummary from './order-summary.vue'
import scList from './sc-list.vue'
import myFooter from '@/components/Footer.vue'
</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;
--sc-list-header-top: var(--content-top);
}
> .order-summary {
position: sticky;
top: var(--content-top);
max-height: var(--app-view-height);
}
}
}
</style>

View File

@@ -0,0 +1,160 @@
<template>
<div class="order-summary">
<div class="title">Order Summary</div>
<div class="count">
<span class="label">Selected</span>
<span class="value">3</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.name">
<span class="label">{{ v.name }}</span>
<span class="value"
><span>{{ v.count }}</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">36 <span>mb</span></span>
</div>
<div class="hr"></div>
<br />
<div class="total">
<span class="label">Total</span>
<span class="value"><span>$45.00</span> HKD</span>
</div>
<div class="hr"></div>
<button class="checkout-btn" custom="black">CHECKOUT SELECTED</button>
<div class="tip">Digital assets. Creator retains copyright.</div>
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue'
const brandsList = ref([
{
name: 'Roaming Clouds',
count: 1
},
{
name: 'Off Grid Apparel',
count: 1
},
{
name: 'Ivory Muse Studio',
count: 1
}
])
</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>

View File

@@ -0,0 +1,441 @@
<template>
<!-- 购物车列表 -->
<div class="sc-list" :class="{ mini: isMini }">
<div class="header">
<div class="title">
<span class="text">Shopping Cart</span>
<span class="close" v-if="isMini" @click="onClose"
><svg-icon name="close" size="13"
/></span>
</div>
<div class="options" v-if="!isMini">
<div class="left">
<el-checkbox v-model="check" :indeterminate="true" />
<span class="count">3 Selected</span>
<div class="hr"></div>
<div class="btn">Select All</div>
<div class="btn">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="item" v-for="v in list" :key="v.id">
<el-checkbox v-model="v.checked" v-if="!isMini" />
<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">
<span v-for="tag in v.tags" :key="tag" class="tag">{{ tag }}</span>
</div>
<div class="size">
<div class="icon"><svg-icon name="order-file" size="18" /></div>
<div class="text">
{{ v.fileSize }}kb{{
isMini ? '' : ` &nbsp;&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;${v.date}`
}}
</div>
</div>
</div>
<div class="right">
<div class="amount">${{ v.amount }}<span> HKD</span></div>
<div class="remove">
<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">
<span class="label">Total</span>
<span class="value">$45.00<span> HKD</span></span>
</div>
<button custom="black">CHECKOUT</button>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue'
const emit = defineEmits(['close'])
const props = defineProps({
isMini: {
type: Boolean,
default: false
}
})
const check = ref(true)
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: 'Feb 16, 2026, 11:34 PM',
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: 100, // kb
date: 'Feb 16, 2026, 11:34 PM',
amount: 9.99,
tags: ['female', 'skirt', 'blouse', 'outwear'],
checked: false
}
])
const onClose = () => {
emit('close')
}
</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-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);
}
}
}
}
&: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;
}
}
}
> .header {
border-bottom: 0.1rem solid #c4c4c4;
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;
> .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>