UNIAPP实战项目笔记68 购物车勾选到订单确认
思路
需要用到vuex
页面间传值
案例截图
订单结算页面
购物车页面
确认订单页面
支付页面
代码
前端代码 购物车页面 shopcart.vue
<template>
<view class="shop-cart">
<template v-if=" list.length > 0 ">
<!-- 自定义导航栏 -->
<uniNavBar
title="购物车"
:rightText=" isNavBar ? '完成' : '编辑'"
fixed="true"
statusBar="true"
@clickRight=" isNavBar = !isNavBar"
></uniNavBar>
<!-- 商品内容 -->
<view class="shop-item" v-for="(item,index) in list" :key="index">
<label for="" class="radio" @tap="selectedItem(index)">
<radio value="" color="#FF3333" :checked="item.checked" /> <text></text>
</label>
<image class="shop-img" :src="item.imgUrl" mode=""></image>
<view class="shop-text">
<view class="shop-name">
{{item.name}}
</view>
<view class="shop-color f-color">
{{item.color}}
</view>
<view class="shop-price">
<view class="">
¥{{item.pprice}}
</view>
<template v-if="!isNavBar">
<view>x {{item.num}}</view>
</template>
<template v-else>
<uniNumberBox
:value="item.num"
:min=1
@change="changeNumber($event,index,item)"
></uniNumberBox>
</template>
</view>
</view>
</view>
<!-- 底部 -->
<view class="shop-foot">
<label for="" class="radio foot-radio" @tap='checkedAllFn'>
<radio value="" color="#FF3333" :checked="checkedAll"></radio><text>全选</text>
</label>
<template v-if="!isNavBar">
<view class="foot-total">
<view class="foot-count">
合计:
<text class="f-active-color">
¥{{totalCount.pprice}}
</text>
</view>
<view class="foot-num" @tap="goConfirmOrder">
结算({{totalCount.num}})
</view>
</view>
</template>
<template v-else>
<view class="foot-total">
<view class="foot-num" style="background-color: black;">
移入收藏夹
</view>
<view class="foot-num" @tap="delGoodsFn">
删除
</view>
</view>
</template>
</view>
</template>
<template v-else>
<uniNavBar
title="购物车"
fixed="true"
statusBar="true"
></uniNavBar>
<view class="shop-else-list">
<text>囧~ 购物车还是空的~</text>
</view>
</template>
<Tabbar currentPage='shopcart'></Tabbar>
</view>
</template>
<script>
import $http from '@/common/api/request.js'
import uniNavBar from '@/components/uni/uni-nav-bar/uni-nav-bar.vue'
import uniNumberBox from '@/components/uni/uni-number-box/uni-number-box.vue'
import Tabbar from '@/components/common/Tabbar.vue';//引入
// 状态机引入
import {mapState,mapActions,mapGetters,mapMutations} from 'vuex'
export default {
data() {
return {
isNavBar:false,
}
},
computed:{
// 状态机数据处理
...mapState({
list:state=>state.cart.list,
selectedList:state=>state.cart.selectedList,
}),
...mapGetters(['checkedAll','totalCount'])
},
components:{
uniNavBar,uniNumberBox,Tabbar
},
onShow() {
this.getData();
},
methods: {
...mapActions(['checkedAllFn','delGoodsFn']),
...mapMutations(['selectedItem','initGetData']),
getData(){
$http.request({
url:'/selectCart',
method:"POST",
header:{
token:true
}
}).then((res)=>{
this.initGetData(res)
}).catch(()=>{
uni.showToast({
title:'请求失败',
icon:'none'
})
})
},
changeNumber(value,index,item){
if ( value == item.num ) return;
$http.request({
url:'/updateCart',
method:"POST",
header:{
token:true
},
data:{
goodsId:item.goods_id,
num:value
}
}).then((res)=>{
this.list[index].num = value;
}).catch(()=>{
uni.showToast({
title:'请求失败',
icon:'none'
})
})
},
// 进入确认订单
goConfirmOrder(){
if(this.selectedList.length === 0){
return uni.showToast({
title:"至少选择一件商品",
icon:"none"
})
}
uni.navigateTo({
url:`/pages/confirm-order/confirm-order?detail=${JSON.stringify(this.selectedList)}`
})
}
}
}
</script>
<style lang="scss">
.shop-list{
padding-bottom: 100rpx;
}
.shop-else-list{
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
background-color: #f7f7f7;
display: flex;
align-items: center;
justify-content: center;
}
.shop-item{
display: flex;
padding: 20rpx;
align-items: center;
background-color: #f7f7f7;
margin-bottom: 10rpx;
}
.shop-img{
width: 200rpx;
height: 200rpx;
}
.shop-text{
flex: 1;
padding-left: 20rpx;
}
.shop-color{
font-size: 24rpx;
}
.shop-price{
display: flex;
justify-content: space-between;
}
.shop-foot{
border-top: 2rpx solid #f7f7f7;
background-color: #FFFFFF;
position: fixed;
bottom: 0;
left: 0;
width: 100%;
height: 100rpx;
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 120rpx;
}
.foot-radio{
padding-left: 20rpx;
}
.foot-total{
display: flex;
}
.foot-count{
line-height: 100rpx;
padding: 0 20rpx;
font-size: 32rpx;
}
.foot-num{
background-color: #49bdfb;
color: #FFFFFF;
padding: 0 60rpx;
line-height: 100rpx;
}
</style>
确认订单页面代码 comfirm-order.vue
<template>
<view class="confirm-order bg-active-color">
<Lines></Lines>
<!-- 地址 -->
<view class="order-map" @tap="goPathList">
<block v-if="path">
<view class="map-title">
<view class="map-name">收件人:{{path.name}}</view>
<view class="">{{path.tel}}</view>
</view>
<view class="map-add">
收货地址:{{path.city}} {{path.detail}}
</view>
</block>
<block v-else>
<view class="map-title">
<view class="map-name">请选择地址</view>
</view>
</block>
</view>
<!-- 商品 -->
<view class="goods-list" v-for="(item,index) in goodsList" :key="index">
<view class="goods-content bg-active-color">
<image class="goods-img" :src="item.imgUrl" mode=""></image>
<view class="goods-text">
<view class="goods-name">
{{item.name}}
</view>
<view class="goods-size f-color">
颜色分类:黑色
</view>
<view class="f-active-color">
7天无理由
</view>
</view>
<view class="">
<view class="">
¥{{item.pprice}}
</view>
<view class="goods-size">
x {{item.num}}
</view>
</view>
</view>
</view>
<!-- 底部:提交订单 -->
<view class="order-foot">
<view class="total-price">
合计: <text class="f-active-color">${{totalCount.pprice}}</text>
</view>
<view class="confirm" @tap="goPayment">
提交订单
</view>
</view>
</view>
</template>
<script>
import Lines from '@/components/common/Lines.vue'
import {mapGetters,mapState} from 'vuex'
export default {
data() {
return {
path:false
};
},
computed:{
...mapState({
list:state=>state.cart.list
}),
...mapGetters(['defaultPath','totalCount']),
// 根据商品列表找到对应的e.detail 数据的 id 最终返回商品数据
goodsList(){
return this.item.map(id=>{
return this.list.find(v=>v.id == id)
})
}
},
onLoad(e) {
console.log(e.detail,this.list);
// 选中的商品id集合 [1,3]
this.item = JSON.parse(e.detail);
// 如果默认地址的一个赋值
if(this.defaultPath.length){
this.path = this.defaultPath[0];
}
// 如果出发自定义事件,on去接收值
uni.$on('selectPathItem',(res)=>{
// console.log(res);
this.path = res;
})
},
onUnload() {
uni.$off('selectPathItem',()=>{
console.log('移除了selectPathItem');
})
},
components:{
Lines
},
methods:{
// 跳转到地址管理页面
goPathList(){
uni.navigateTo({
url:'/pages/my-path-list/my-path-list?type=selectedPath'
})
},
// 确认支付
goPayment(){
uni.navigateTo({
url:'/pages/payment/payment'
})
}
}
}
</script>
<style lang="less">
.confirm-order{
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
}
.order-map{
margin-bottom: 10rpx;
padding: 20rpx;
background-color: #fff;
line-height: 50rpx;
}
.map-title{
display: flex;
justify-content: space-between;
}
.map-name{
font-weight: bold;
}
.goods-list{
background-color: #fff;
padding: 10rpx 0;
}
.goods-content{
padding: 10rpx 20rpx;
display: flex;
justify-content: space-between;
align-items: center;
}
.goods-text{
width: 360rpx;
padding: 0 10rpx;
font-size: 26rpx;
}
.goods-img{
width: 160rpx;
height: 160rpx;
}
.goods-size{
font-size: 24rpx;
}
.order-foot{
width: 100%;
height: 80rpx;
position: fixed;
bottom: 0;
left: 0;
background-color: #fff;
display: flex;
justify-content: flex-end;
align-items: center;
}
.confirm{
color: #fff;
background-color: #49bdfb;
padding: 10rpx 30rpx;
}
.total-price{
padding: 0 20rpx;
}
</style>
vuex 下的 cart.js
export default{
state:{
list:[
/* {
id:1,
name:"332经济法能聚聚会技能大赛 经济法能聚聚会技能大赛",
color:"颜色:嘿嘿嘿激活",
imgUrl:"../../static/logo.png",
pprice:"27",
num:1,
checked:false
},{
id:2,
name:"032经济法能聚聚会技能大赛 经济法能聚聚会技能大赛",
color:"颜色:嘿嘿嘿激活",
imgUrl:"../../static/logo.png",
pprice:"48",
num:6,
checked:false
} */
],
selectedList:[]
},
getters:{
// 判断是否 全选
checkedAll(state){
return state.list.length === state.selectedList.length;
},
// 合计 结算数量
totalCount(state){
let total = {
pprice:0,
num:0
}
state.list.forEach(v=>{
// 是否选中
if(state.selectedList.indexOf(v.id) > -1){
// 合计
total.pprice += v.pprice*v.num;
// 结算数量
total.num = state.selectedList.length;
}
})
return total;
}
},
mutations:{
// 请求到数据赋值操作
initGetData(state,list){
state.list = list;
},
// 全选
checkAll(state){
state.selectedList = state.list.map(v=>{
v.checked = true;
return v.id;
})
},
// 全不选
unCheckAll(state){
state.list.forEach(v=>{
v.checked = false;
})
state.selectedList = [];
},
// 单选
selectedItem(state,index){
let id = state.list[index].id;
let i = state.selectedList.indexOf(id);
// 如果selectList已经存在就代表他之前的选中状态,checked=false,并且在selectedList删除
if (i>-1) {
state.list[index].checked = false;
return state.selectedList.splice(i,1);
}
// 如果之前没有选中,checked=true,把当前的id添加到selectedList
state.list[index].checked = true;
state.selectedList.push(id);
},
//
delGoods(state){
state.list = state.list.filter(v=>{
return state.selectedList.indexOf(v.id) === -1;
})
},
// 加入购物车
addShopCart(state, goods){
state.list.push(goods);
}
},
actions:{
checkedAllFn({commit,getters}){
getters.checkedAll ? commit("unCheckAll") : commit("checkAll")
},
delGoodsFn({commit}){
commit('delGoods');
commit("unCheckAll");
uni.showToast({
title:'删除成功',
icon:"none"
})
}
}
}
目录结构
前端目录结构
-
manifest.json 配置文件: appid、logo…
-
pages.json 配置文件: 导航、 tabbar、 路由
-
main.js vue初始化入口文件
-
App.vue 全局配置:样式、全局监视
-
static 静态资源:图片、字体图标
-
page 页面
- index
- index.vue
- list
- list.vue
- my
- my.vue
- my-config
- my-config.vue
- my-config
- my-config.vue
- my-add-path
- my-add-path.vue
- my-path-list
- my-path-list.vue
- search
- search.vue
- search-list
- search-list.vue
- shopcart
- shopcart.vue
- details
- details.vue
- my-order
- my-order.vue
- confirm-order
- confirm-order.vue
- payment
- payment.vue
- payment-success
- payment-success.vue
- login
- login.vue
- login-tel
login-tel.vue
- login-code
login-code.vue
- index
-
components 组件
- index
- Banner.vue
- Hot.vue
- Icons.vue
- indexSwiper.vue
- Recommend.vue
- Shop.vue
Tabbar.vue
- common
- Card.vue
- Commondity.vue
- CommondityList.vue
- Line.vue
- ShopList.vue
- order
- order-list.vue
- uni
- uni-number-box
- uni-number-box.vue
- uni-icons
- uni-icons.vue
- uni-nav-bar
- uni-nav-bar.vue
- mpvue-citypicker
- mpvueCityPicker.vue
- uni-number-box
- index
-
common 公共文件:全局css文件 || 全局js文件
- api
- request.js
- common.css
- uni.css
- api
-
store vuex状态机文件
- modules
- cart.js
- path.js
- user.js
- index.js
- modules