feat: 商品列表、商品详情优化
This commit is contained in:
@@ -1,13 +1,18 @@
|
||||
import Vue from 'vue'
|
||||
import Vant from 'vant'
|
||||
import { createPinia, PiniaVuePlugin } from 'pinia'
|
||||
import 'vant/lib/index.css'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
|
||||
Vue.use(Vant)
|
||||
Vue.use(PiniaVuePlugin)
|
||||
Vue.config.productionTip = false
|
||||
|
||||
const pinia = createPinia()
|
||||
|
||||
new Vue({
|
||||
router,
|
||||
pinia,
|
||||
render: h => h(App)
|
||||
}).$mount('#app')
|
||||
|
||||
@@ -28,7 +28,8 @@ export default new Router({
|
||||
path: '/joyful-selection',
|
||||
component: JoyfulSelectionLayout,
|
||||
children: [
|
||||
{ path: '', component: JoyfulSelectionHome },
|
||||
{ path: '', redirect: 'home' },
|
||||
{ path: 'home', component: JoyfulSelectionHome },
|
||||
{ path: 'products', component: JoyfulSelectionProductList },
|
||||
{ path: 'product-detail', component: JoyfulSelectionProductDetail },
|
||||
{ path: 'cart', component: JoyfulSelectionCart },
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
const CART_KEY = 'joyful-selection-cart'
|
||||
|
||||
const readCartItems = () => {
|
||||
try {
|
||||
return JSON.parse(localStorage.getItem(CART_KEY) || '[]')
|
||||
} catch (error) {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
const saveCartItems = items => {
|
||||
localStorage.setItem(CART_KEY, JSON.stringify(items))
|
||||
}
|
||||
|
||||
export const useCartStore = defineStore('joyfulSelectionCart', {
|
||||
state: () => ({
|
||||
items: []
|
||||
}),
|
||||
getters: {
|
||||
totalCount: state => state.items.reduce((total, item) => total + Number(item.count || 0), 0),
|
||||
totalPrice: state => state.items.reduce((total, item) => total + Math.round(Number(item.price) * 100) * Number(item.count || 0), 0)
|
||||
},
|
||||
actions: {
|
||||
loadCart() {
|
||||
this.items = readCartItems()
|
||||
},
|
||||
persistCart() {
|
||||
saveCartItems(this.items)
|
||||
},
|
||||
addItem(item) {
|
||||
const index = this.items.findIndex(cartItem => cartItem.cartId === item.cartId)
|
||||
|
||||
if (index > -1) {
|
||||
this.items[index].count += item.count
|
||||
} else {
|
||||
this.items.unshift(item)
|
||||
}
|
||||
|
||||
this.persistCart()
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -68,7 +68,7 @@ export default {
|
||||
{ id: 'daily', image: bannerDaily, alt: '精选商品每日推荐' }
|
||||
],
|
||||
suppliers: [
|
||||
{ id: 'joy', name: '悦享优选', image: supplierJoy, description: '精选好物,悦享品质生活', tags: ['正品保障', '极速发货', '售后无忧'], productCount: 6800, sales: '18万+', backgroundColor: '#f4f7ff', tagColor: '#4e83ef', tagBackground: '#edf3ff', buttonColor: '#ff651d', path: '/joyful-selection' },
|
||||
{ id: 'joy', name: '悦享优选', image: supplierJoy, description: '精选好物,悦享品质生活', tags: ['正品保障', '极速发货', '售后无忧'], productCount: 6800, sales: '18万+', backgroundColor: '#f4f7ff', tagColor: '#4e83ef', tagBackground: '#edf3ff', buttonColor: '#ff651d', path: '/joyful-selection/home' },
|
||||
{ id: 'home', name: '美家生活馆', image: supplierHome, description: '家居好物,点亮品质生活', tags: ['家居日用', '品质精选', '售后保障'], productCount: 5200, sales: '12万+', backgroundColor: '#f1fbf9', tagColor: '#18bfae', tagBackground: '#e9faf7', buttonColor: '#ff651d', path: '/home-living' },
|
||||
{ id: 'happy', name: '乐购甄选', image: supplierHappy, description: '天天低价,乐享购物', tags: ['低价好物', '限时优惠', '极速配送'], productCount: 7600, sales: '22万+', backgroundColor: '#f7f4ff', tagColor: '#7b61ee', tagBackground: '#f0edff', buttonColor: '#ff651d', path: '/happy-shopping' },
|
||||
{ id: 'fruit', name: '鲜果优品', image: supplierFruit, description: '新鲜直达,优选好果', tags: ['产地直采', '新鲜直达', '坏果包赔'], productCount: 3500, sales: '9万+', backgroundColor: '#fff9ed', tagColor: '#f39d16', tagBackground: '#fff5df', buttonColor: '#ff651d', path: '/fresh-fruit' },
|
||||
|
||||
@@ -3,33 +3,48 @@
|
||||
<van-nav-bar title="购物车" right-text="编辑" @click-right="$router.push('/joyful-selection/cart/edit')" />
|
||||
<van-notice-bar text="满 199 元包邮,快去凑单吧~" left-icon="info-o" />
|
||||
<section class="cart-list">
|
||||
<van-card
|
||||
v-for="item in goods"
|
||||
:key="item.id"
|
||||
:title="item.name"
|
||||
:desc="item.desc"
|
||||
:price="item.price"
|
||||
:thumb="item.image"
|
||||
>
|
||||
<template #tags><van-tag plain type="danger">{{ item.tag }}</van-tag></template>
|
||||
<template #footer><van-stepper v-model="item.count" theme="round" button-size="22" /></template>
|
||||
</van-card>
|
||||
<van-empty v-if="!items.length" description="购物车还是空的" />
|
||||
<template v-else>
|
||||
<van-card
|
||||
v-for="item in items"
|
||||
:key="item.cartId"
|
||||
:title="item.name"
|
||||
:desc="item.desc"
|
||||
:price="item.price"
|
||||
:thumb="item.image"
|
||||
>
|
||||
<template #tags><van-tag plain type="danger">{{ item.tag }}</van-tag></template>
|
||||
<template #footer><van-stepper v-model="item.count" theme="round" button-size="22" @change="persistCart" /></template>
|
||||
</van-card>
|
||||
</template>
|
||||
</section>
|
||||
<van-submit-bar :price="29790" button-text="去结算(3)" @submit="$router.push('/joyful-selection/checkout')">
|
||||
<van-submit-bar :price="totalPrice" :button-text="submitText" @submit="$router.push('/joyful-selection/checkout')">
|
||||
<van-checkbox v-model="checked">全选</van-checkbox>
|
||||
</van-submit-bar>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { joyfulProducts } from '../product-data'
|
||||
import { mapActions, mapState } from 'pinia'
|
||||
import { useCartStore } from '@/store/cart'
|
||||
|
||||
export default {
|
||||
name: 'JoyfulSelectionCart',
|
||||
data: () => ({
|
||||
checked: true,
|
||||
goods: joyfulProducts.slice(0, 3).map(item => ({ ...item, count: 1 }))
|
||||
})
|
||||
checked: true
|
||||
}),
|
||||
computed: {
|
||||
...mapState(useCartStore, ['items', 'totalCount', 'totalPrice']),
|
||||
submitText() {
|
||||
return `去结算(${this.totalCount})`
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadCart()
|
||||
},
|
||||
methods: {
|
||||
...mapActions(useCartStore, ['loadCart', 'persistCart'])
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<template><main class="success-page"><van-icon name="checked" color="#20bd58" size="68"/><h2>支付成功</h2><b>¥ 297.90</b><section><p>订单编号 <span>YX2024051812345678</span></p><p>支付方式 <span>微信支付</span></p><p>支付时间 <span>2024-05-18 14:35:28</span></p></section><div><van-button round @click="$router.push('/joyful-selection/orders')">查看订单</van-button><van-button round type="danger" @click="$router.push('/joyful-selection')">继续购物</van-button></div></main></template>
|
||||
<template><main class="success-page"><van-icon name="checked" color="#20bd58" size="68"/><h2>支付成功</h2><b>¥ 297.90</b><section><p>订单编号 <span>YX2024051812345678</span></p><p>支付方式 <span>微信支付</span></p><p>支付时间 <span>2024-05-18 14:35:28</span></p></section><div><van-button round @click="$router.push('/joyful-selection/orders')">查看订单</van-button><van-button round type="danger" @click="$router.push('/joyful-selection/home')">继续购物</van-button></div></main></template>
|
||||
<script>export default{name:'JoyfulSelectionPaymentSuccess'}</script>
|
||||
<style scoped>.success-page{min-height:100vh;padding:80px 30px;text-align:center;background:#fff}.success-page h2{margin:15px 0 10px;font-size:17px}.success-page>b{font-size:22px}.success-page section{margin-top:42px;text-align:left;color:#888;font-size:12px}.success-page section p{display:flex;justify-content:space-between}.success-page section span{color:#333}.success-page>div{display:flex;gap:10px;margin-top:35px}.success-page .van-button{flex:1}</style>
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<main class="joyful-layout">
|
||||
<router-view />
|
||||
<van-tabbar v-if="showTabbar" route active-color="#ff5b20" inactive-color="#777">
|
||||
<van-tabbar-item icon="wap-home-o" to="/joyful-selection">首页</van-tabbar-item>
|
||||
<van-tabbar-item icon="wap-home-o" to="/joyful-selection/home">首页</van-tabbar-item>
|
||||
<van-tabbar-item icon="apps-o" to="/joyful-selection/products">分类</van-tabbar-item>
|
||||
<van-tabbar-item icon="cart-o" to="/joyful-selection/cart">购物车</van-tabbar-item>
|
||||
<van-tabbar-item icon="user-o" to="/joyful-selection/profile">我的</van-tabbar-item>
|
||||
@@ -15,7 +15,7 @@ export default {
|
||||
name: 'JoyfulSelectionLayout',
|
||||
computed: {
|
||||
showTabbar() {
|
||||
return ['/joyful-selection', '/joyful-selection/products', '/joyful-selection/cart', '/joyful-selection/profile'].includes(this.$route.path)
|
||||
return ['/joyful-selection/home', '/joyful-selection/products', '/joyful-selection/cart', '/joyful-selection/profile'].includes(this.$route.path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,62 @@
|
||||
<template>
|
||||
<main class="detail-page">
|
||||
<van-nav-bar title="商品详情" left-arrow @click-left="$router.back()" />
|
||||
<van-image class="product-image" :src="product.image" fit="cover" />
|
||||
<header class="detail-top">
|
||||
<van-icon name="arrow-left" @click="$router.back()" />
|
||||
<h1>商品详情</h1>
|
||||
<div>
|
||||
<van-icon name="star-o" />
|
||||
<van-icon name="share-o" />
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section class="image-section">
|
||||
<van-image class="product-image" :src="product.image" fit="cover" />
|
||||
<span class="image-index">1/5</span>
|
||||
</section>
|
||||
|
||||
<section class="product-info">
|
||||
<h1>{{ product.name }}</h1>
|
||||
<h2>{{ product.name }}</h2>
|
||||
<p>{{ product.detail }}</p>
|
||||
<div><b>¥{{ product.price }}</b><del>¥{{ product.originPrice }}</del><van-tag type="danger" plain>{{ product.tag }}</van-tag></div>
|
||||
<div class="price-row">
|
||||
<b>¥{{ product.price }}</b>
|
||||
<del>¥{{ product.originPrice }}</del>
|
||||
<van-tag class="point-tag" plain type="danger">{{ product.tag }}</van-tag>
|
||||
</div>
|
||||
</section>
|
||||
<van-cell-group inset title="商品服务">
|
||||
<van-cell title="正品保障" value="极速发货 · 售后无忧" />
|
||||
<van-cell title="配送" value="满 99 元免配送费" />
|
||||
</van-cell-group>
|
||||
|
||||
<section class="option-card">
|
||||
<van-cell title="选择规格" value="默认规格" is-link />
|
||||
<van-cell title="商品数量">
|
||||
<template #right-icon>
|
||||
<div class="quantity-box">
|
||||
<van-stepper v-model="count" integer min="1" button-size="28" input-width="42" />
|
||||
<span>库存充足</span>
|
||||
</div>
|
||||
</template>
|
||||
</van-cell>
|
||||
</section>
|
||||
|
||||
<section class="service-card">
|
||||
<h3>商品服务</h3>
|
||||
<van-cell title="正品保障" value="品质保证,安心兑换" is-link>
|
||||
<template #icon><van-icon class="service-icon" name="shield-o" /></template>
|
||||
</van-cell>
|
||||
<van-cell title="配送" value="满 99 元免配送费" is-link>
|
||||
<template #icon><van-icon class="service-icon" name="logistics" /></template>
|
||||
</van-cell>
|
||||
</section>
|
||||
|
||||
<section class="detail-content">
|
||||
<h2>商品详情</h2>
|
||||
<p>{{ product.desc }}</p>
|
||||
<h3>商品详情</h3>
|
||||
<div class="feature-grid">
|
||||
<article v-for="item in features" :key="item.title">
|
||||
<span><van-icon :name="item.icon" /></span>
|
||||
<b>{{ item.title }}</b>
|
||||
<p>{{ item.desc }}</p>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<van-goods-action>
|
||||
<van-goods-action-icon icon="chat-o" text="客服" dot />
|
||||
<van-goods-action-icon icon="cart-o" text="购物车" badge="5" @click="$router.push('/joyful-selection/cart')" />
|
||||
@@ -30,19 +72,268 @@ import { getJoyfulProduct } from '../product-data'
|
||||
|
||||
export default {
|
||||
name: 'JoyfulSelectionProductDetail',
|
||||
data: () => ({
|
||||
count: 1,
|
||||
features: [
|
||||
{ icon: 'music-o', title: '舒适半入耳设计', desc: '长时间佩戴更舒适' },
|
||||
{ icon: 'bar-chart-o', title: '高清音质', desc: '清晰稳定的聆听体验' },
|
||||
{ icon: 'battery-o', title: '长续航', desc: '单次可用约 5 小时' },
|
||||
{ icon: 'cluster-o', title: '蓝牙 5.0', desc: '稳定连接,低延迟' }
|
||||
]
|
||||
}),
|
||||
computed: {
|
||||
product() { return getJoyfulProduct(this.$route.query.id) }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.detail-page { min-height: 100vh; padding-bottom: 56px; background: #f7f7f7; }
|
||||
.product-image { display: block; width: 100%; height: 310px; background: #fff; }
|
||||
.product-info, .detail-content { margin: 10px 12px; padding: 14px; border-radius: 10px; background: #fff; }
|
||||
.product-info h1 { margin: 0 0 8px; font-size: 17px; }
|
||||
.product-info p, .detail-content p { color: #777; font-size: 12px; line-height: 1.6; }
|
||||
.product-info b { margin-right: 8px; color: #ff5b20; font-size: 21px; }
|
||||
.product-info del { margin-right: 9px; color: #aaa; font-size: 11px; }
|
||||
.detail-content h2 { margin: 0; font-size: 15px; }
|
||||
<style scoped lang="less">
|
||||
.detail-page {
|
||||
min-height: 100vh;
|
||||
padding-bottom: 60px;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.detail-top {
|
||||
position: fixed;
|
||||
z-index: 10;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
height: 54px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 18px;
|
||||
color: #111;
|
||||
background: rgba(247, 248, 250, .88);
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
|
||||
.detail-top > .van-icon {
|
||||
font-size: 26px;
|
||||
}
|
||||
|
||||
.detail-top h1 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.detail-top div {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.detail-top div .van-icon {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.image-section {
|
||||
position: relative;
|
||||
padding-top: 54px;
|
||||
background: #eef0f4;
|
||||
}
|
||||
|
||||
.product-image {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 365px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.image-index {
|
||||
position: absolute;
|
||||
right: 18px;
|
||||
bottom: 26px;
|
||||
min-width: 44px;
|
||||
padding: 5px 10px;
|
||||
border-radius: 16px;
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
line-height: 1;
|
||||
text-align: center;
|
||||
background: rgba(0, 0, 0, .36);
|
||||
}
|
||||
|
||||
.product-info,
|
||||
.option-card,
|
||||
.service-card,
|
||||
.detail-content {
|
||||
margin: 10px 12px 0;
|
||||
border-radius: 12px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.product-info {
|
||||
margin-top: -4px;
|
||||
padding: 18px 16px 16px;
|
||||
}
|
||||
|
||||
.product-info h2 {
|
||||
margin: 0 0 12px;
|
||||
color: #111;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.product-info p {
|
||||
margin: 0;
|
||||
color: #777;
|
||||
font-size: 14px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.price-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-top: 17px;
|
||||
}
|
||||
|
||||
.price-row b {
|
||||
color: #ff5b20;
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.price-row del {
|
||||
color: #999;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.point-tag {
|
||||
color: #ff2f3a;
|
||||
border-color: #ff2f3a;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.option-card {
|
||||
overflow: hidden;
|
||||
padding: 4px 16px;
|
||||
}
|
||||
|
||||
.option-card ::v-deep .van-cell {
|
||||
padding: 15px 0;
|
||||
}
|
||||
|
||||
.option-card ::v-deep .van-cell::after {
|
||||
right: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.option-card ::v-deep .van-cell__title {
|
||||
color: #222;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.option-card ::v-deep .van-cell__value {
|
||||
color: #888;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.quantity-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.quantity-box span {
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.service-card {
|
||||
overflow: hidden;
|
||||
padding: 16px 16px 9px;
|
||||
}
|
||||
|
||||
.service-card h3,
|
||||
.detail-content h3 {
|
||||
margin: 0 0 13px;
|
||||
color: #111;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.service-card ::v-deep .van-cell {
|
||||
padding: 11px 0;
|
||||
}
|
||||
|
||||
.service-card ::v-deep .van-cell::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.service-card ::v-deep .van-cell__title {
|
||||
color: #222;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.service-card ::v-deep .van-cell__value {
|
||||
color: #888;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.service-icon {
|
||||
margin-right: 10px;
|
||||
color: #ff5b20;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.detail-content {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.feature-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.feature-grid article {
|
||||
min-width: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.feature-grid span {
|
||||
display: flex;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 auto 10px;
|
||||
border-radius: 50%;
|
||||
color: #2d558c;
|
||||
background: #f0f2f6;
|
||||
}
|
||||
|
||||
.feature-grid span .van-icon {
|
||||
font-size: 25px;
|
||||
}
|
||||
|
||||
.feature-grid b {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
color: #222;
|
||||
font-size: 12px;
|
||||
line-height: 1.35;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.feature-grid p {
|
||||
display: -webkit-box;
|
||||
overflow: hidden;
|
||||
margin: 4px 0 0;
|
||||
color: #888;
|
||||
font-size: 11px;
|
||||
line-height: 1.35;
|
||||
text-overflow: ellipsis;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,33 +2,43 @@
|
||||
<main class="product-page">
|
||||
<div class="fixed-top">
|
||||
<van-nav-bar title="商品列表" left-arrow @click-left="$router.back()" />
|
||||
<van-tabs v-model="active" color="#ff5b20" title-active-color="#ff5b20" title-inactive-color="#333">
|
||||
<van-tab v-for="tab in tabs" :key="tab" :title="tab" />
|
||||
</van-tabs>
|
||||
<div class="search-row">
|
||||
<van-search v-model="keyword" shape="round" placeholder="搜索商品名称" />
|
||||
<button type="button" class="filter-button">
|
||||
<van-icon name="filter-o" />
|
||||
<span>筛选</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="layout">
|
||||
<aside>
|
||||
<span
|
||||
<div class="layout-aside">
|
||||
<span
|
||||
v-for="category in categories"
|
||||
:key="category"
|
||||
:class="{ active: category === selected }"
|
||||
@click="selected = category"
|
||||
:key="category.id"
|
||||
:class="{ active: category.id === selectedCategoryId }"
|
||||
@click="selectedCategoryId = category.id"
|
||||
>
|
||||
{{ category }}
|
||||
</span>
|
||||
{{ category.name }}
|
||||
</span>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<div class="goods">
|
||||
<article v-for="item in visibleGoods" :key="item.id" class="goods-item" @click="goProductDetail(item)">
|
||||
<van-image class="goods-thumb" :src="item.image" fit="cover" />
|
||||
<div class="goods-info">
|
||||
<h3>{{ item.name }}</h3>
|
||||
<van-tag class="goods-tag" plain type="danger">{{ item.tag }}</van-tag>
|
||||
<div class="goods-title">
|
||||
<van-tag class="goods-tag" plain type="danger">{{ item.tag }}</van-tag>
|
||||
<h3>{{ item.name }}</h3>
|
||||
</div>
|
||||
<div class="goods-bottom">
|
||||
<b>¥{{ item.price }}</b>
|
||||
<span>已售{{ item.sales }}</span>
|
||||
<button type="button" class="cart-button" @click.stop="$router.push('/joyful-selection/cart')">
|
||||
<div class="price-stack">
|
||||
<div><b>¥{{ item.price }}</b></div>
|
||||
<div><span>已售{{ item.sales }}</span></div>
|
||||
</div>
|
||||
<button type="button" class="cart-button" @click.stop="openSku(item)">
|
||||
<van-icon name="cart-o" />
|
||||
</button>
|
||||
</div>
|
||||
@@ -37,91 +47,232 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<van-sku
|
||||
v-model="showSku"
|
||||
:sku="sku"
|
||||
:goods="skuGoods"
|
||||
:goods-id="currentGoods.id"
|
||||
:initial-sku="initialSku"
|
||||
:hide-stock="sku.hide_stock"
|
||||
buy-text="立即购买"
|
||||
add-cart-text="加入购物车"
|
||||
@buy-clicked="onBuyClicked"
|
||||
@add-cart="onAddCartClicked"
|
||||
/>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Toast } from 'vant'
|
||||
import { joyfulProducts } from '../product-data'
|
||||
import { useCartStore } from '@/store/cart'
|
||||
|
||||
const productImages = joyfulProducts.map(item => item.image)
|
||||
|
||||
const categorySeeds = {
|
||||
居家生活: [
|
||||
const categories = [
|
||||
{
|
||||
id: 'recommend',
|
||||
name: '推荐',
|
||||
products: []
|
||||
},
|
||||
{
|
||||
id: 'homeLiving',
|
||||
name: '居家生活',
|
||||
products: [
|
||||
['cream-oven', '奶油白家用迷你烤箱', '小巧不占地,早餐烘焙好帮手', '129.00', '1.2万件', productImages[1]],
|
||||
['cotton-bedding', '100%纯棉舒适四件套', '柔软亲肤,安心睡眠', '179.00', '6453件', productImages[1]],
|
||||
['aroma-gift', '橙意香薰礼盒套装', '淡雅留香,装点居家空间', '59.90', '8321件', productImages[3]],
|
||||
['desktop-vase', '简约北欧陶瓷花瓶', '清新配色,点亮桌面角落', '39.90', '3180件', productImages[3]]
|
||||
],
|
||||
数码家电: [
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'digitalAppliance',
|
||||
name: '数码家电',
|
||||
products: [
|
||||
['earbuds-air2', '小米蓝牙耳机 Air2 SE', '轻盈半入耳,通勤运动都好听', '69.90', '1.2万件', productImages[0]],
|
||||
['smart-speaker-pro', '黑曜智能音箱 Pro', '语音唤醒,沉浸听感', '99.00', '2.1万件', productImages[2]],
|
||||
['electric-toothbrush', '智能声波电动牙刷', '高频洁净,呵护口腔', '129.00', '2.1万件', productImages[0]],
|
||||
['dual-charger', '双口快充充电器 30W', '小巧便携,多设备快充', '69.00', '5632件', productImages[2]]
|
||||
],
|
||||
美妆个护: [
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'beautyCare',
|
||||
name: '美妆个护',
|
||||
products: [
|
||||
['skin-care-box', '植萃保湿护肤礼盒', '水润修护,清爽不粘腻', '99.90', '4820件', productImages[3]],
|
||||
['aroma-wash', '香氛洗护旅行套装', '温和清洁,清新留香', '59.90', '7932件', productImages[3]],
|
||||
['makeup-mirror', '便携补光化妆镜', '高清镜面,随时补妆', '49.90', '3651件', productImages[2]],
|
||||
['hand-cream', '滋润护手霜三支装', '细腻滋养,轻柔香气', '39.90', '5280件', productImages[3]]
|
||||
],
|
||||
食品饮料: [
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'foodDrink',
|
||||
name: '食品饮料',
|
||||
products: [
|
||||
['orange-gift', '橙意惊喜零食礼盒', '精致包装,送礼更有心意', '59.90', '6324件', productImages[3]],
|
||||
['coffee-cup', '咖啡随行杯组合', '锁温防漏,醇香随行', '49.90', '3520件', productImages[0]],
|
||||
['tea-snack', '下午茶点心礼盒', '多种风味,一盒满足', '69.90', '4891件', productImages[3]],
|
||||
['fruit-drink', '轻享果饮礼盒', '清爽酸甜,聚会常备', '79.90', '2976件', productImages[3]]
|
||||
],
|
||||
母婴玩具: [
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'motherBaby',
|
||||
name: '母婴玩具',
|
||||
products: [
|
||||
['baby-bottle', '安心防摔儿童水杯', '轻巧易握,出门好携带', '39.90', '4250件', productImages[0]],
|
||||
['toy-blocks', '益智拼搭积木套装', '色彩柔和,陪伴成长', '89.00', '2186件', productImages[3]],
|
||||
['kids-night-light', '柔光儿童陪伴夜灯', '温和光线,安心入睡', '69.90', '3548件', productImages[2]],
|
||||
['baby-towel', '纯棉柔软浴巾两件装', '亲肤吸水,日常必备', '59.90', '2761件', productImages[1]]
|
||||
],
|
||||
服饰鞋包: [
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'fashionBag',
|
||||
name: '服饰鞋包',
|
||||
products: [
|
||||
['laptop-backpack', '大容量双肩电脑背包', '分区收纳,通勤出行轻松装', '149.00', '1.6万件', productImages[3]],
|
||||
['mini-bag', '轻巧通勤斜挎小包', '简洁百搭,日常实用', '79.90', '5321件', productImages[3]],
|
||||
['silk-scarf', '柔软撞色丝巾礼盒', '细腻亲肤,点亮穿搭', '69.90', '2680件', productImages[3]],
|
||||
['canvas-tote', '简约帆布托特包', '大容量开口,随手能装', '49.90', '6412件', productImages[3]]
|
||||
],
|
||||
运动户外: [
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'sportsOutdoor',
|
||||
name: '运动户外',
|
||||
products: [
|
||||
['sport-bottle', '运动便携保温水杯', '长效锁温,户外随行', '49.90', '7342件', productImages[0]],
|
||||
['neck-massager', '筋膜放松按摩仪 Mini', '轻巧便携,舒缓酸胀', '99.00', '3921件', productImages[2]],
|
||||
['camping-light', '户外便携露营灯', '多档照明,夜晚更安心', '79.00', '2568件', productImages[2]],
|
||||
['fitness-band', '弹力健身拉力带套装', '居家训练,轻松塑形', '29.90', '4396件', productImages[1]]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
const goods = Object.entries(categorySeeds).flatMap(([category, items]) =>
|
||||
items.map(([id, name, desc, price, sales, image]) => ({
|
||||
id,
|
||||
name,
|
||||
desc,
|
||||
price,
|
||||
sales,
|
||||
image,
|
||||
category,
|
||||
tag: '赠品'
|
||||
}))
|
||||
const productCategories = categories.filter(category => category.id !== 'recommend')
|
||||
|
||||
const goods = productCategories.flatMap(category =>
|
||||
category.products.map(([id, name, desc, price, sales, image]) => ({
|
||||
id,
|
||||
name,
|
||||
desc,
|
||||
price,
|
||||
sales,
|
||||
image,
|
||||
categoryId: category.id,
|
||||
tag: '赠品'
|
||||
}))
|
||||
)
|
||||
|
||||
const colorOptions = [
|
||||
{ id: 'cream', name: '奶油白' },
|
||||
{ id: 'blue', name: '雾霾蓝' },
|
||||
{ id: 'orange', name: '暖橙色' }
|
||||
]
|
||||
|
||||
const sizeOptions = [
|
||||
{ id: 'standard', name: '标准款' },
|
||||
{ id: 'plus', name: '升级款' }
|
||||
]
|
||||
|
||||
const defaultGoods = goods[0]
|
||||
|
||||
export default {
|
||||
name: 'JoyfulSelectionProductList',
|
||||
data: () => ({
|
||||
active: 0,
|
||||
selected: '推荐',
|
||||
tabs: ['综合', '销量', '价格↕', '筛选⌄'],
|
||||
categories: ['推荐', '居家生活', '数码家电', '美妆个护', '食品饮料', '母婴玩具', '服饰鞋包', '运动户外'],
|
||||
selectedCategoryId: 'recommend',
|
||||
showSku: false,
|
||||
keyword: '',
|
||||
currentGoods: defaultGoods,
|
||||
categories,
|
||||
goods
|
||||
}),
|
||||
computed: {
|
||||
visibleGoods() {
|
||||
return this.selected === '推荐'
|
||||
? this.goods.slice(0, 10)
|
||||
: this.goods.filter(item => item.category === this.selected)
|
||||
const categoryGoods = this.selectedCategoryId === 'recommend'
|
||||
? this.goods.slice(0, 10)
|
||||
: this.goods.filter(item => item.categoryId === this.selectedCategoryId)
|
||||
return this.keyword
|
||||
? categoryGoods.filter(item => item.name.includes(this.keyword))
|
||||
: categoryGoods
|
||||
},
|
||||
skuGoods() {
|
||||
return {
|
||||
title: this.currentGoods.name,
|
||||
picture: this.currentGoods.image
|
||||
}
|
||||
},
|
||||
initialSku() {
|
||||
return {
|
||||
s1: colorOptions[0].id,
|
||||
s2: sizeOptions[0].id
|
||||
}
|
||||
},
|
||||
sku() {
|
||||
const price = Math.round(Number(this.currentGoods.price) * 100)
|
||||
return {
|
||||
tree: [
|
||||
{
|
||||
k: '颜色',
|
||||
k_s: 's1',
|
||||
v: colorOptions
|
||||
},
|
||||
{
|
||||
k: '尺寸',
|
||||
k_s: 's2',
|
||||
v: sizeOptions
|
||||
}
|
||||
],
|
||||
list: colorOptions.flatMap(color =>
|
||||
sizeOptions.map(size => ({
|
||||
id: `${this.currentGoods.id}-${color.id}-${size.id}`,
|
||||
s1: color.id,
|
||||
s2: size.id,
|
||||
price,
|
||||
stock_num: 99
|
||||
}))
|
||||
),
|
||||
price: this.currentGoods.price,
|
||||
stock_num: 99,
|
||||
none_sku: false,
|
||||
hide_stock: true
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
goProductDetail(item) {
|
||||
this.$router.push({ path: '/joyful-selection/product-detail', query: { id: item.id } })
|
||||
},
|
||||
openSku(item) {
|
||||
this.currentGoods = item
|
||||
this.showSku = true
|
||||
},
|
||||
onBuyClicked() {
|
||||
Toast('请选择规格后购买')
|
||||
},
|
||||
onAddCartClicked(data) {
|
||||
const selectedSku = data.selectedSkuComb || {}
|
||||
const selectedColor = colorOptions.find(item => item.id === selectedSku.s1) || colorOptions[0]
|
||||
const selectedSize = sizeOptions.find(item => item.id === selectedSku.s2) || sizeOptions[0]
|
||||
const count = data.selectedNum || 1
|
||||
|
||||
const cartStore = useCartStore()
|
||||
|
||||
cartStore.addItem({
|
||||
cartId: selectedSku.id || `${this.currentGoods.id}-${selectedColor.id}-${selectedSize.id}`,
|
||||
id: this.currentGoods.id,
|
||||
name: this.currentGoods.name,
|
||||
desc: `${selectedColor.name} / ${selectedSize.name}`,
|
||||
price: this.currentGoods.price,
|
||||
image: this.currentGoods.image,
|
||||
tag: this.currentGoods.tag,
|
||||
count,
|
||||
sku: {
|
||||
color: selectedColor.name,
|
||||
size: selectedSize.name
|
||||
}
|
||||
})
|
||||
|
||||
Toast('已加入购物车')
|
||||
this.showSku = false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -130,8 +281,8 @@ export default {
|
||||
<style scoped lang="less">
|
||||
.product-page {
|
||||
min-height: 100vh;
|
||||
padding: 90px 0 56px;
|
||||
background: #fff;
|
||||
padding: 110px 0 58px;
|
||||
background: #f8f8f8;
|
||||
}
|
||||
|
||||
.fixed-top {
|
||||
@@ -144,34 +295,68 @@ export default {
|
||||
}
|
||||
|
||||
.fixed-top ::v-deep .van-nav-bar {
|
||||
height: 46px;
|
||||
height: 50px;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.fixed-top ::v-deep .van-nav-bar__title {
|
||||
font-size: 15px;
|
||||
color: #111;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.fixed-top ::v-deep .van-nav-bar .van-icon {
|
||||
color: #111;
|
||||
font-size: 23px;
|
||||
}
|
||||
|
||||
.search-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 11px;
|
||||
padding: 12px 18px 10px;
|
||||
}
|
||||
|
||||
.search-row ::v-deep .van-search {
|
||||
flex: 1;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.search-row ::v-deep .van-search__content {
|
||||
height: 38px;
|
||||
align-items: center;
|
||||
padding-left: 14px;
|
||||
border-radius: 22px;
|
||||
background: #f5f6f8;
|
||||
}
|
||||
|
||||
.search-row ::v-deep .van-field__left-icon {
|
||||
margin-right: 7px;
|
||||
color: #646971;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.search-row ::v-deep .van-field__control {
|
||||
color: #222;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.filter-button {
|
||||
display: flex;
|
||||
height: 38px;
|
||||
flex: none;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
border: 0;
|
||||
color: #111;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.fixed-top ::v-deep .van-tabs__wrap {
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
.fixed-top ::v-deep .van-tab {
|
||||
color: #333;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.fixed-top ::v-deep .van-tab--active {
|
||||
color: #ff5b20;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.fixed-top ::v-deep .van-tabs__line {
|
||||
width: 18px;
|
||||
height: 2px;
|
||||
border-radius: 2px;
|
||||
background: #ff5b20;
|
||||
.filter-button .van-icon {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.layout {
|
||||
@@ -181,30 +366,38 @@ export default {
|
||||
|
||||
.layout aside {
|
||||
position: sticky;
|
||||
top: 90px;
|
||||
width: 78px;
|
||||
height: calc(100vh - 146px);
|
||||
top: 110px;
|
||||
width: 90px;
|
||||
height: calc(100vh - 168px);
|
||||
padding: 10px 10px 0 0;
|
||||
flex: none;
|
||||
overflow-y: auto;
|
||||
background: #f8f8f8;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.layout-aside {
|
||||
background-color: #f5f6f8;
|
||||
border-top-right-radius: 15px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.layout aside span {
|
||||
display: flex;
|
||||
min-height: 48px;
|
||||
min-height: 50px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 8px;
|
||||
color: #777;
|
||||
font-size: 12px;
|
||||
line-height: 1.2;
|
||||
padding: 0 10px;
|
||||
color: #252525;
|
||||
font-size: 14px;
|
||||
line-height: 1.25;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.layout aside .active {
|
||||
position: relative;
|
||||
background: #fff;
|
||||
color: #222;
|
||||
background: #ededed;;
|
||||
color: #111;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
@@ -212,9 +405,9 @@ export default {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
width: 3px;
|
||||
height: 18px;
|
||||
border-radius: 0 3px 3px 0;
|
||||
width: 4px;
|
||||
height: 28px;
|
||||
border-radius: 0 4px 4px 0;
|
||||
background: #ff5b20;
|
||||
content: '';
|
||||
transform: translateY(-50%);
|
||||
@@ -223,19 +416,22 @@ export default {
|
||||
.goods {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
padding: 8px 10px 12px;
|
||||
padding: 10px 10px 14px 12px;
|
||||
}
|
||||
|
||||
.goods-item {
|
||||
display: flex;
|
||||
min-height: 96px;
|
||||
gap: 10px;
|
||||
padding: 0 0 13px;
|
||||
min-height: 116px;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 10px 12px 10px 10px;
|
||||
border-radius: 14px;
|
||||
background: #fff;
|
||||
box-shadow: 0 4px 18px rgba(0, 0, 0, .045);
|
||||
}
|
||||
|
||||
.goods-item + .goods-item {
|
||||
margin-top: 13px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.goods-thumb {
|
||||
@@ -243,7 +439,7 @@ export default {
|
||||
height: 88px;
|
||||
flex: none;
|
||||
overflow: hidden;
|
||||
border-radius: 4px;
|
||||
border-radius: 6px;
|
||||
background: #f6f6f6;
|
||||
}
|
||||
|
||||
@@ -252,68 +448,97 @@ export default {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
padding-top: 2px;
|
||||
align-self: stretch;
|
||||
justify-content: space-between;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.goods-info h3 {
|
||||
.goods-title {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
align-items: flex-start;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.goods-title h3 {
|
||||
display: -webkit-box;
|
||||
overflow: hidden;
|
||||
min-height: 34px;
|
||||
margin: 0 0 5px;
|
||||
color: #222;
|
||||
font-size: 13px;
|
||||
flex: 1;
|
||||
margin: 0;
|
||||
color: #111;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
line-height: 17px;
|
||||
line-height: 20px;
|
||||
text-overflow: ellipsis;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
}
|
||||
|
||||
.goods-tag {
|
||||
flex: none;
|
||||
width: fit-content;
|
||||
transform: scale(.86);
|
||||
transform-origin: left center;
|
||||
margin-top: 1px;
|
||||
color: #ff4b4b;
|
||||
border-color: #ff4b4b;
|
||||
font-size: 11px;
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
.goods-bottom {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.goods-bottom b {
|
||||
.price-stack {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.price-stack b {
|
||||
color: #ff5b20;
|
||||
font-size: 14px;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.goods-bottom span {
|
||||
.price-stack span {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
margin-left: 9px;
|
||||
color: #aaa;
|
||||
font-size: 10px;
|
||||
color: #9b9da3;
|
||||
font-size: 12px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.cart-button {
|
||||
display: flex;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
flex: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px solid #ff7a3c;
|
||||
border: 0;
|
||||
border-radius: 50%;
|
||||
color: #ff5b20;
|
||||
background: #fff;
|
||||
box-shadow: 0 2px 6px rgba(255, 91, 32, .12);
|
||||
color: #fff;
|
||||
background: #ff5b20;
|
||||
box-shadow: 0 4px 10px rgba(255, 91, 32, .22);
|
||||
}
|
||||
|
||||
.cart-button .van-icon {
|
||||
font-size: 17px;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.product-page ::v-deep .van-sku-actions .van-button--danger {
|
||||
background: #ff5b20;
|
||||
border-color: #ff5b20;
|
||||
}
|
||||
|
||||
.product-page ::v-deep .van-sku-row__item--active {
|
||||
color: #ff5b20;
|
||||
border-color: #ff5b20;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user