uniapp 学习笔记二十八 购物车新增商品数据和逻辑完善
cart.vue
<template>
<view>
<view class="flex padding" v-for="(item,index) in cartList" :key="item.id+'-'+item.idx">
<view class="flex align-center">
<text @click="handleCheck(index)" :class="['iconfont','icon-youxiajiaogouxuan','margin-right',{'yellow':item.isCheck}]"></text>
<image class="poster margin-right" :src="item.img" mode=""></image>
</view>
<view class="flex justify-between info">
<view class="">
{{item.name}}
<view class="margin-tb-xs"> {{item.french}} </view>
Y{{item.list[item.idx].price}}
</view>
<view class="flex flex-direction align-end">
<view @click="handleEdit(index)" class="edit margin-bottom-xs">
<text class="iconfont icon-bianjishuru"></text>
</view>
{{item.list[item.idx].spec}}
X
{{item.num}}
</view>
</view>
</view>
<!-- 弹窗 -->
<u-overlay :show="show" @click="show=false">
<view class="bg-fff margin cover-cont" @click.stop>
<view class="padding">
<view class="flex justify-between info">
<image class="poster margin-right" src="" mode=""></image>
<view class="">
{{cartList[cartIdx].name}}
<view class="margin-tb-xs"> {{cartList[cartIdx].french}} </view>
Y{{checkedCartInfo.price}}
</view>
</view>
<view class="flex justify-between padding-tb u-border-bottom">
<view class="">
规格选择
</view>
<view class="drop">
<view @click="dropShow=true">
{{checkedCartInfo.spec}} - {{checkedCartInfo.edible}}
<text class="iconfont icon-xiangxiazhankai"></text>
</view>
<view v-if="dropShow" class="drop-list bg-fff">
<view
v-for="(item,index) in cartList[cartIdx].list"
class="padding-sm"
:key="index"
@click="handleDropList(index)"
>
{{item.spec}} - {{item.edible}}
</view>
</view>
</view>
</view>
<view class="flex justify-between align-center padding-tb u-border-bottom">
<view class="">
数量选择
</view>
<u-number-box button-size="36" @change="handleNum"></u-number-box>
</view>
</view>
<view class="flex margin-top">
<button @click="show=false" type="default" class="cu-btn lg bg-brown">取消</button>
<button @click="handleOk" type="default" class="cu-btn lg bg-yellow">确认</button>
</view>
</view>
</u-overlay>
<view class="fixed bg-fff flex">
<view class="flex flex-sub padding align-center">
<text
@click="handleAllCheck(allInfo.allCheck)"
:class="['iconfont', 'icon-youxiajiaogouxuan', 'margin-right-xs',{'yellow':allInfo.allCheck}]"></text>
全选
<view class="margin-left">
共计:{{allInfo.allPrice}}
</view>
</view>
<view class="bg-yellow padding text-center color-fff">
立即结算
</view>
</view>
</view>
</template>
<script>
import {mapState,mapMutations,mapGetters} from 'vuex'
export default {
data() {
return {
show:false,
dropShow:false,
cartIdx:0, // 主商品序号
num:1,//当前弹窗商品数量
dropIdx:0 // 子商品序号
}
},
computed: {
...mapState({
cartList:state=>state.cart.cartList
}),
...mapGetters({
allInfo:'cart/allInfo'
}),
checkedCartInfo(){ // 过滤下拉选中的商品对象
let {cartIdx,cartList,dropIdx} = this
return cartList[cartIdx].list[dropIdx]
}
},
methods:{
...mapMutations({
handleCheck:'cart/cartCheckMut',
handleAllCheck:'cart/cartAllCheckMut'
}),
handleEdit(idx){
// 弹窗控制
this.cartIdx = idx;
this.show=true;
this.dropIdx = this.cartList[idx].idx;
},
handleDropList(dropIdx){ //子商品下拉列表
this.dropShow = false;
this.dropIdx = dropIdx;
},
handleOk(){ //弹窗确定
this.show = false;
let {cartIdx,dropIdx,num} = this;
console.log(cartIdx,dropIdx,num);
this.$store.commit('cart/cartListCheckMut',{cartIdx,dropIdx,num});
},
handleNum({value}){
this.num = value;
}
}
}
</script>
<style lang="scss">
page{
padding-bottom: 100upx;
}
.poster{
width: 180upx;
height: 180upx;
background-color: #d8d8d8;
}
.info{
width: 60%;
.edit{
width: 80upx;
height: 80upx;
text-align: center;
line-height: 80upx;
background-color: #e6e6e6;
border-radius: 50%;
}
}
.icon-youxiajiaogouxuan{
width: 50upx;
height: 50upx;
text-align: center;
line-height: 50upx;
background-color: #e6e6e6;
border-radius: 20%;
}
.fixed {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
box-shadow: 0 0 10upx 2upx rgba(0, 0, 0, 0.2);
}
.yellow{
background-color: yellow;
}
.cu-btn.lg{
width: 50%;
}
.cover-cont{
position: absolute;
top: 50%;
left: 0;
width: 690upx;
transform: translateY(-50%);
border-radius: 10upx;
}
.drop{
position: relative;
.drop-list{
width:300upx;
position: absolute;
top: 60upx;
right: 0;
box-shadow: 0 0 10upx 2upx rgba(0, 0, 0, 0.2);
z-index: 10;
view:hover{
background-color: #e6e6e6;
}
}
}
</style>
cart,js
import Vue from 'vue'
export default {
namespaced:true,
state(){
return {
cartList:[{
id:"10090",
twoId:10089,
name:"拿破仑草莓恋爱",
french:"Napoleon aux fraises",
price:"208.00",
isCheck:false,
img:"/static/logo.png",
list:[
{id:10090,sku:"n0201",ahead:'提前5小时预定',edible:"2-3人食用",spec:"1磅",price:"218.00"},
{id:10091,sku:"n0202",ahead:'提前5小时预定',edible:"4-7人食用",spec:"2磅",price:"318.00"},
{id:10092,sku:"n0203",ahead:'提前5小时预定',edible:"8-12人食用",spec:"3磅",price:"458.00"},
{id:10093,sku:"n0204",ahead:'提前5小时预定',edible:"12-20人食用",spec:"5磅",price:"750.00"}
],
num:1,//商品数量
idx:0 //标记选中的子商品信息
},{
id:"10090",
twoId:10089,
name:"拿破仑草莓",
french:"Napoleon aux fraises",
price:"218.00",
tid:11,
tname:'限定',
isCheck:false,
img:"/static/logo.png",
list:[
{id:10090,sku:"n0201",ahead:'提前5小时预定',edible:"2-3人食用",spec:"1磅",price:"218.00"},
{id:10091,sku:"n0202",ahead:'提前5小时预定',edible:"4-7人食用",spec:"2磅",price:"318.00"},
{id:10092,sku:"n0203",ahead:'提前5小时预定',edible:"8-12人食用",spec:"3磅",price:"458.00"},
{id:10093,sku:"n0204",ahead:'提前5小时预定',edible:"12-20人食用",spec:"5磅",price:"750.00"}
],
num:1, //商品数量
idx:0 //标记选中的子商品信息
}
]
}
},
getters:{
/**
* 统计信息
*/
allInfo(state){
let allCheck = true
let allPrice = 0
state.cartList.forEach(item=>{
if(!item.isCheck){
allCheck = false
}
if(item.isCheck){
allPrice += item.list[item.idx].price * item.num
}
})
return {allCheck,allPrice}
}
},
mutations:{
cartCheckMut(state,idx){ // 单选
state.cartList[idx].isCheck = !state.cartList[idx].isCheck
},
cartAllCheckMut(state,bool){ // 全选 bool 是原本的全选状态
state.cartList.forEach(item=>{
item.isCheck = !bool
})
},
cartListCheckMut(state,{cartIdx,dropIdx,num}){ // 子商品下拉确认处理
state.cartList[cartIdx].idx = dropIdx
state.cartList[cartIdx].num = num
},
cartAddMut(state,goodObj){ // 新增商品
let {cartList} = state
let len = cartList.length
for(let i=0;i<len;i++){
// 购物车中已经有相同商品
let {id,idx} = goodObj
if(cartList[i].id==id&&cartList[i].idx==idx){
state.cartList[i].num++
return
}
}
// 非响应式数据挂载
// goodObj.isCheck = false
// goodObj.num = 1
// goodObj.idx = 0
// 响应式数据挂载
Vue.set(goodObj,'isCheck',true)
Vue.set(goodObj,'num',1)
// Vue.set(goodObj,'idx',0)
state.cartList.push(goodObj)
}
}
}
detail.vue
<template>
<view>
<swiper class="banner" :indicator-dots="true" :autoplay="true" :interval="3000" :duration="1000">
<swiper-item>
<view class="swiper-item">
<image :src="detail.img" mode="widthFix"></image>
</view>
</swiper-item>
</swiper>
<view class="flex justify-around">
<view
v-for="(item,index) in detail.list"
:key="index"
:class="['text-center padding-sm tab',{active:tabIdx}]"
@tap="tabIdx = index"
>
<view class="">{{item.spec}}</view>
<view class="">{{item.weight}}</view>
<view class="">{{item.edible}}</view>
</view>
</view>
<view class="padding fs-28 u-border-bottom">
¥{{detail.list[tabIdx].price}}
</view>
<view class="padding fs-28 u-border-bottom">
{{detail.list[tabIdx].ahead}}
<text class="margin-lr">{{detail.list[tabIdx].size}}</text>
{{detail.list[tabIdx].edible}}
</view>
<view class="padding fs-28 u-border-bottom">
{{detail.list[tabIdx].fittings}}
</view>
<view class="flex justify-around fixed padding-tb-sm">
<button @click="handleAdd({...detail,idx:tabIdx})" class="cu-btn bg-brown lg">加入购物车</button>
<button class="cu-btn bg-yellow lg">立即购买</button>
</view>
</view>
</template>
<script>
import {mapMutations} from 'vuex'
export default {
data() {
return {
tabIdx:0,
detail:null
}
},
methods: {
...mapMutations({
'handleAdd':'cart/cartAddMut'
})
},
onLoad() {
uni.getStorage({
key:'detail',
success:(res)=>{
// console.log(res.data);
this.detail = res.data
}
})
},
onShow() {
console.log('onShow 生命周期 执行');
},
onHide() {
console.log('onHide 生命周期 执行');
},
onLaunch() {
console.log('onLaunch 生命周期 执行');
}
}
</script>
<style>
.banner{
height: 600upx;
.swiper-item{
height: 600upx;
}
}
.fixed {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
box-shadow: 0 0 10upx 2upx rgba(0, 0, 0, 0.2);
}
</style>
cake.vue
<template>
<view>
<nav-custom></nav-custom>
<view class="cont">
<good-item v-for="(item,index) in glist" :gdata="item"></good-item>
</view>
<view class="fixed flex justify-center bg-fff padding-sm">
<view v-for="(item,index) in tabArr" :key="index" @tap="handleTab(item)"
class="flex justify-around align-center">
<view class="">{{item.name}}</view>
<u-line v-if="index<tabArr.length-1" direction="col" length="15" margin="30upx"></u-line>
</view>
</view>
<u-popup :show="show" mode="left" @close="handleClose">
<view class="pop-cont">
<view v-for="(item,index) in cfylist" class="padding-sm u-border-bottom">
{{item.bname}}
<view v-if="index==0">
<view
@tap="listShow=!listShow"
:class="['padding-tb-sm',{'u-border-bottom':!listShow}]"
>
口味筛选
</view>
<u-cell-group v-if="listShow">
<u-cell
v-for="(itm,idx) in item.list"
:title="itm.tname"
isLink
@click="handleList(itm,1)"
></u-cell>
</u-cell-group>
<view @tap="sceneShow=!sceneShow" class="padding-tb-sm u-border-top">
场景筛选
</view>
<u-cell-group v-if="sceneShow">
<u-cell
v-for="(itm,idx) in item.scene"
:title="itm.tname"
isLink
@click="handleList(itm,2)"
></u-cell>
</u-cell-group>
</view>
</view>
</view>
</u-popup>
</view>
</template>
<script>
export default {
data() {
return {
// condition:{ // 商品列表查询条件对象
// bcid:1,
// },
cfylist:[],
sceneShow:false,
listShow:false,
show: false,
glist: [],
page: 0,
tabArr: [{
name: '分类',
bcid: '',
target: ''
},
{
name: '蛋糕',
bcid: '1',
target: '/pages/cake'
},
{
name: '面包',
bcid: '11',
target: '/pages/bread'
},
{
name: '小食',
bcid: '6',
target: '/pages/food'
},
{
name: '购物车',
bcid: '',
target: '/pages/cart/cart'
}
]
}
},
computed:{
num(){
return this.$store.state.count.num
},
condition(){
return this.$store.state.condition.cond
}
},
methods: {
/* handleDetail(idx) {
console.log(idx);
uni.navigateTo({
url: '../detail/detail?idx=' + idx
})
}, */
loadData() {
let skip = this.page * 8
let wh = JSON.stringify(this.condition)
let url = `/1.1/classes/goods?where=${wh}&limit=8&skip=${skip}`;
this.$get(url).then(res => {
uni.stopPullDownRefresh()
let {
results
} = res
if (results.length) {
this.page++
this.glist = [
...this.glist,
...res.results
]
return
}
uni.showToast({
title: '这回真没了...',
icon: 'none'
})
})
},
handleTab(item) {
let {
bcid,
target
} = item
if (bcid) { // 商品列表数据更新
// this.glist = []
// this.page = 0
// this.condition.bcid = Number(bcid)
this.$store.commit('changeCondition',{
bcid:Number(bcid)
})
this.reloadData()
}
if (!bcid && !target) { // 侧边分类
this.show = true
}
if (!bcid && target) {
uni.navigateTo({
url:target
})
}
},
handleClose(){
this.show = false
},
handleList({bid,tid},type){
// 口味场景筛选
// console.log(typeof bid);
let obj = {bcid:bid}
type === 1 ? obj.fid=tid : obj.sid=tid
this.$store.commit('changeCondition',obj)
this.reloadData()
},
reloadData(){ //刷新页面数据
this.glist = []
this.page = 0
this.loadData()
}
},
onLoad() {
this.loadData()
this.$get('/1.1/classes/classify').then(res=>{
console.log(res)
this.cfylist = res.results
})
},
onReachBottom() {
console.log('触底了');
this.loadData()
},
onPullDownRefresh() {
this.glist = []
this.page = 0
this.loadData()
}
}
</script>
<style lang="scss">
page {
padding-top: 100upx;
padding-bottom: 120upx;
}
.cont {
display: flex;
flex-wrap: wrap;
padding: 15upx;
justify-content: space-between;
}
.fixed {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
box-shadow: 0 0 10upx 2upx rgba(0, 0, 0, 0.2);
}
.cu-bar{
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 2;
}
.pop-cont{
width: 400upx;
margin-top: 200upx;
}
</style>
goods-item.vue
<template>
<view class="cake-item">
<image @tap="handleDetail" class="poster" :src="gdata.img" mode=""></image>
<view class="info-cont">
<view class="info flex align-center justify-between">
<view class="">
<view class="fs-28">
{{gdata.name}}
</view>
<view class="fs-16">
{{gdata.french}}
</view>
</view>
<view @click.stop="handleCartAdd" class="cart-btn margin-right-sm">
<text class="iconfont icon-caigou"></text>
</view>
</view>
<view class="fs-18">
<text class="fs-14">Y</text>
{{gdata.price}}
</view>
</view>
</view>
</template>
<script>
export default {
name:"goods-item",
props:['gdata'],
data() {
return {
};
},
methods:{
handleDetail() {
uni.setStorage({
key:'detail',
data:this.gdata,
success: () => {
uni.navigateTo({
url: '/pages/detail/detail'
})
}
})
},
handleCartAdd(){
this.$store.commit('cart/cartAddMut',{
...this.gdata,
idx:0
})
}
}
}
</script>
<style lang="scss">
.cake-item{
width: 350upx;
.poster{
height: 350upx;
background-color: #f5f5f5;
}
.fs-28{
font-size: 28upx;
margin-top: 24upx;
}
.fs-16{
font-size: 16upx;
margin: 14upx 0;
}
.fs-18{
font-size: 18upx;
margin-bottom: 22upx;
}
.cart-btn{
width: 60upx;
height: 60upx;
border-radius: 50%;
background-color: #ffe32a;
text-align: center;
line-height: 60upx;
/* 在按钮处于活动状态时添加转换 */
.cart-btn.active {
transform: scale(0.98);
box-shadow: 3px 2px 22px 1px rgba(0, 0, 0, 0.24);
}
}
}
</style>