我们来做一下购物车的逻辑,首先我们来看全选,和全不选,包括单选,先点击左侧商品按钮,或者说下面的按钮,他可以实现全选或者全不选中状态,我们选择其单个商品,上下面都是不选中状态,如果我们将所有购物单选框全选上了,上下两个总按钮也会自动选上,这是一个联动效果,这一块会把性能拉满,这一节非常重要。
我们来看看,你怎么知道,哪些是选中的,还是没有选中,包括单选,换句话说,只要是我的单选中哪一个,哪一个不选中,我们只要知道他的状态就可以了,比如说,我们若是全选框都选中,我们的全选状态复选框也是选中状态,若单选框也是全选,我们全选框也是选中状态,我们的全选框未选中,你也是未选中状态,创建目标我们去努力就可以了,我们并不知道里面的数据是否被选中,还是未被选中,我们如何打印呢?
如果单选框全部选中,上下两个全选框也会自动选中,他有一个联动效果
性能拉满,业务稍微有点复杂,如果说通过简单代码实现他,这个逻辑一定是复杂的。
我们只要知道单选是否选中,自然而然我们就知道全选是否选中。全选是true,整体就是true,如果全选是false,单选就是false
全选,单选 实现代码如下:
1, src/views/Cart.vue
<template>
<div class="cart container">
<header @click="$router.back()">
<i class="iconfont icon-a-huaban1fuben44"></i>
<span>购物车</span>
<span>编辑</span>
</header>
<section>
<!-- <section v-if="list.length"> -->
<div class="cart-title">
<!-- {{isCheckedAll}} -->
<van-checkbox @click="checkAllFn" :value="isCheckedAll"></van-checkbox>
<span>商品</span>
</div>
<ul>
<li v-for="(item,index) in list" :key="index">
<div class="check">
<van-checkbox @click="checkItem(index)" v-model="item.checked"></van-checkbox>
</div>
<h2>
<img :src="item.imgUrl" alt="" />
</h2>
<div class="goods">
<div class="goods-title">
<span>{{item.goods_name}}</span>
<i class="iconfont icon-shanchu1"></i>
</div>
<div class="goods-price">¥{{item.goods_price}}</div>
<van-stepper v-model="item.goods_num" integer />
</div>
</li>
</ul>
<h1>
没有购物车数据
<router-link to="/home">去首页逛逛吧</router-link>
</h1>
<!-- <section v-else>
没有购物车数据
<router-link to="/home">去首页逛逛吧</router-link>
</section> -->
</section>
<footer v-if="list.length">
<div class="radio">
<van-checkbox v-model="checked"></van-checkbox>
</div>
<div class="total">
<div>共有
<span class="total-active">1</span>
件商品
</div>
<div>
<span>总计:</span>
<span class="total-active">¥128.00+0茶币</span>
</div>
</div>
<div class="order">去结算</div>
</footer>
<!-- <Tabbar></Tabbar> -->
</div>
</template>
<script>
import http from '@/common/api/request.js'
import {
mapState,
mapMutations,
mapActions,
mapGetters
} from 'vuex';
export default {
name: "Cart",
data() {
return {
checked: true
}
},
// 调用 获取 list 的值
computed: {
...mapState({
list: state => state.cart.list
}),
...mapGetters(['isCheckedAll'])
},
created() {
// console.log(11);
this.getData();
},
methods: {
// 使用这个方法
...mapMutations(['cartList', 'checkItem']),
...mapActions(['checkAllFn']),
// 前端给后端发送请求,查询某个用户
async getData() {
// 发送请求
let res = await http.$axios({
// 前端给后端查询购物车数据接口发送请求
url: '/api/selectCart',
method: 'POST',
// 查询当前用户购物车数据,要带上 token
headers: {
token: true
}
});
// 循环 前端添加一项 'checked' 因为前端给后端打交道 (交互)
// 将数据与结构进行了重构,前端添加了一项 'checked'
// 每一个购物车数据都有一个 'checked:true',通过他我们就可以知道他是选中的
res.data.forEach(v => {
// 每一项加一个 'checked'
v['checked'] = true;
})
// console.log(res.data);
this.cartList(res.data);
}
}
}
</script>
<style scoped lang="scss">
header {
display: flex;
// 左中右结构
justify-content: space-between;
// 垂直居中
align-items: center;
height: 44px;
color: #fff;
background-color: #b0352f;
i {
padding: 0 20px;
font-size: 24px;
}
span {
padding: 0 20px;
font-size: 16px;
}
}
section {
background-color: #f5f5f5;
.cart-title {
display: flex;
padding: 20px;
span {
padding: 0 15px;
font-size: 18px;
font-weight: 500;
}
}
ul {
display: flex;
flex-direction: column;
li {
display: flex;
justify-content: space-between;
align-items: center;
margin: 8px 0;
padding: 6px 20px;
background-color: #fff;
.check {
padding-right: 10px;
}
.goods {
display: flex;
flex-direction: column;
justify-content: space-between;
padding-left: 15px;
font-size: 12px;
.goods-title {
display: flex;
i {
font-size: 24px;
}
}
.goods-price {
padding: 3px 0;
color: #b0352f;
}
::v-deep .van-stepper {
text-align: right;
}
}
img {
width: 74px;
height: 74px;
}
}
}
}
footer {
display: flex;
justify-content: space-between;
align-items: center;
height: 50px;
border-top: 2px solid #ccc;
.radio {
padding: o 15px;
}
.total {
flex: 1;
font-size: 18px;
font-weight: 500;
.total-active {
color: #b0352f;
}
}
.order {
width: 120px;
line-height: 50px;
font-size: 18px;
font-weight: 500;
color: #fff;
text-align: center;
background-color: #b0352f;
}
}
</style>
2, src/views/Detail.vue
<template>
<div class="detail">
<header>
<div class="header-returns" v-show="isShow">
<div @click="goBack">
<i class="iconfont icon-a-huaban1fuben44"></i>
</div>
<div>
<i class="iconfont icon-kefu"></i>
</div>
</div>
<div class="header-bar" v-show="!isShow" :style="styleOption">
<div @click="goBack">
<i class="iconfont icon-a-huaban1fuben44"></i>
</div>
<div>
<span>商品详情</span>
<span>商品评价</span>
</div>
<div>
<i class="iconfont icon-kefu"></i>
</div>
</div>
</header>
<section ref="wrapper">
<div>
<div class="swiper-main">
<swiper :options="swiperOption">
<!-- slides -->
<swiper-slide v-for="(item,index) in goods" :key="index">
<img :src="item.imgUrl" alt="" />
</swiper-slide>
<!-- Optional controls -->
<div class="swiper-pagination" slot="pagination"></div>
</swiper>
</div>
<div class="goods-name">
<h1>{{goods.name}}</h1>
<!-- <h1>雨前珍稀白茶1号</h1>
<div>性价首先,茶感十足,鲜醇耐泡的大众口粮茶</div> -->
</div>
<div class="goods-price">
<div class="oprice">
<span>¥</span>
<b>{{goods.price}}</b>
<!-- <b>88</b> -->
</div>
<div class="pprice">
<span>价格:</span>
<b>{{goods.price}}</b>
<!-- <del>¥139</del> -->
</div>
</div>
<div>
<div>
<img style="width: 100%;height: 500px;" :src="goods.imgUrl" alt="" />
<!-- <img style="width: 100%;height: 500px;" src="/images/goods-list1.png" alt="" />
<img style="width: 100%;height: 500px;" src="/images/goods-list2.png" alt="" />
<img style="width: 100%;height: 500px;" src="/images/goods-list3.png" alt="" />
<img style="width: 100%;height: 500px;" src="/images/goods-list4.png" alt="" /> -->
</div>
</div>
</div>
</section>
<footer>
<div class="add-cart" @click="addCart">加入购物车</div>
<div>立即购买</div>
</footer>
</div>
</template>
<script>
import 'swiper/dist/css/swiper.css'
import {
swiper,
swiperSlide
} from 'vue-awesome-swiper'
import BetterScroll from 'better-scroll'
import http from '@/common/api/request.js'
import {
Toast
} from 'mint-ui';
export default {
name: "Detail",
data() {
return { // 赋值
id: 0,
goods: {},
styleOption: {},
BetterScroll: '',
isShow: true,
swiperOption: {
autoplay: {
delay: 3000
},
loop: true,
speed: 1000,
pagination: {
el: '.swiper-pagination',
type: 'fraction'
}
}
// swiperList: [{
// imgUrl: './images/goods-list1.png'
// }, {
// imgUrl: './images/goods-list2.png'
// }, {
// imgUrl: './images/goods-list3.png'
// }, {
// imgUrl: './images/goods-list4.png'
// }]
}
},
components: {
swiper,
swiperSlide
},
// 接收首页传递的数据
created() {
this.id = this.$route.query.id;
// console.log(111);
// let id = this.$route.query.id
this.getData();
// console.log(this.$route); // 隐式
// console.log(this.$route.query.id); // 显式 {id: '4'}
},
mounted() {
this.BetterScroll = new BetterScroll(this.$refs.wrapper, {
movable: true,
zoom: true,
click: true,
probeType: 3,
bounce: false
})
// 滑动事件
this.BetterScroll.on('scroll', (pos) => {
// console.log(pos);
let posY = Math.abs(pos.y);
let opacity = posY / 180;
// 如果 opacity 的值大于1,就让opacity的值等于1,否则就一直动态改变
opacity = opacity > 1 ? 1 : opacity;
// console.log(posY / 180);
// 将opacity动态值赋值给opacity
this.styleOption = {
opacity: opacity
}
// 去判断
if (posY >= 50) {
this.isShow = false;
} else {
this.isShow = true;
}
})
},
activated() {
// let id = this.$route.query.id;
// console.log(this.id, this.$route.query.id);
// 用动态ID和存储ID做比较 如果相等就不发送请求,如果不等就再次发送请求
// 判断当前Url的id和之前的id是否一致
if (this.$route.query.id != this.id) {
this.getData();
this.id = this.$route.query.id; // 重新赋值
}
},
methods: {
async getData() {
// 接收到的路径传值参数
let id = this.$route.query.id;
let res = await http.$axios({
url: '/api/goods/id',
params: { // 前端给后端传递数据
id
}
});
// 直接返回对象,然后将对象直接渲染到页面上
this.goods = res;
// console.log(res);
// this.items = Object.freeze(res.topBar);
// this.newData = Object.freeze(res.data);
},
// 发送一个请求->加入购物车
addCart() {
let id = this.$route.query.id;
http.$axios({
url: '/api/addCart',
method: 'post',
data: {
goodsId: id
},
headers: {
token: true
}
}).then(res => {
// 后端返回的数据
// console.log(res);
if (res.success) {
// 提示信息
Toast('添加购物车成功');
}
});
},
goBack() {
// 返回上一页
this.$router.back();
}
}
}
</script>
<style scoped lang="scss">
.detail {
display: flex;
flex-direction: column;
width: 100vw;
height: 100vh;
overflow: hidden;
}
header {
position: fixed;
left: 0;
top: 0;
z-index: 999;
width: 100%;
height: 44px;
.header-returns {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
height: 44px;
div {
margin: 0 10px;
width: 35px;
line-height: 34px;
text-align: center;
border-radius: 50%;
background-color: rgba(0, 0, 0, 0.3);
}
i {
color: #fff;
font-size: 24px;
}
}
.header-bar {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
height: 44px;
background-color: #fff;
font-size: 16px;
font-weight: 400;
span {
padding: 0 10px;
}
i {
padding: 0 10px;
font-size: 26px;
}
}
}
section {
flex: 1;
overflow: hidden;
}
.swiper-main {
position: relative;
width: 100%;
height: 375px;
overflow: hidden;
/* margin-top: 120px; */
}
.swiper-container {
width: 100%;
height: 375px;
}
.swiper-main img {
width: 100%;
height: 375px;
}
.swiper-pagination {
bottom: 10px;
width: 100%;
/* 轮播图的小圆点放右边 */
text-align: right;
color: #FFFFFF;
font-size: 18PX;
}
::v-deep.swiper-pagination-fraction,
.swiper-pagination-custom,
.swiper-container-horizontal>.swiper-pagination-bullets {
left: -20px
}
::v-deep .swiper-pagination-bullet-active {
background-color: #b0352f;
}
::v-deep .swiper-pagination-bullet {
margin: 3px;
}
.goods-name {
padding: 20px 10px;
border-bottom: 1px solid #cccccc;
h1 {
font-size: 24px;
font-weight: 500;
}
div {
padding: 3px 0;
font-size: 18px;
color: #999999;
}
}
.goods-price {
padding: 20px 10px;
.oprice {
color: red;
span {
font-size: 12px;
}
}
.pprice {
color: #999999;
font-size: 16px;
}
}
footer {
display: flex;
justify-content: center;
align-items: center;
position: fixed;
bottom: 0;
left: 0;
width: 100%;
height: 49px;
border-top: 1px solid #cccccc;
background-color: #fff;
div {
width: 50%;
line-height: 49px;
font-size: 16px;
text-align: center;
color: #fff;
background-color: #b0352f;
&.add-cart {
background-color: #FF9500;
}
}
}
</style>
3, src/store/modules/cart.js
import {
CART_LIST,
CHECK_ALL,
UN_CHECK_ALL,
CHECK_ITEM
} from "./mutations-types"
export default {
state: {
list: [], // 是购物车的数据
selectList: [] // 选中数据的 id 值
},
getters: {
isCheckedAll(state) {
return state.list.length == state.selectList.length;
},
//
isCheckedItem(state) {
return state.list.length != state.selectList.length;
},
},
mutations: {
[CART_LIST](state, cartArr) {
state.list = cartArr;
// console.log(state.list);
// 添加值 初始化
cartArr.forEach(v => {
state.selectList.push(v.id);
})
},
// 全选 循环遍历 list,还要向selectList数组里面赋值为新的id数组
[CHECK_ALL](state) {
state.selectList = state.list.map(v => {
v.checked = true;
return v.id;
})
},
// 全不选
[UN_CHECK_ALL](state) {
state.list.forEach(v => {
v.checked = false;
})
// 且赋值为空数组
state.selectList = [];
},
// 单选
[CHECK_ITEM](state, index) {
// 获取 id
let id = state.list[index].id;
// 通过 id 去找到 selectList 的 id
let i = state.selectList.indexOf(id);
// 判断 能在 selectList 找到对应的 id,就删除
if (id > -1) {
// console.log(state.selectList);
return state.selectList.splice(i, 1);
}
// 如果之前没有选中,就给selectList添加一个ID进去
state.selectList.push(id);
// console.log(state.list[index].id, state.selectList);
}
},
actions: {
// 提交 什么时候相等,什么时候不相等
checkAllFn({
commit,
getters
}) {
getters.isCheckedAll ? commit('unCheckAll') : commit('checkAll');
}
}
}
3, src/store/modules/mutations-types.js
// 用户
export const USER_LOGIN = 'userLogin';
export const INIT_USER = 'initUser';
export const LOGIN_OUT = 'loginOut';
// 购物车
export const CART_LIST = 'cartList';
export const CHECK_ALL = 'checkAll';
export const UN_CHECK_ALL = 'unCheckAll';
export const CHECK_ITEM = 'checkItem';
// export const USER_LOGIN = 'USER_LOGIN';
// export const INIT_USER = 'INIT_USER';
// export const LOGIN_OUT = 'LOGIN_OUT';
单选

全选

全选或全不选
1,引入 mapActions

2,使用 mapActions(['checkAllFn'])

3, 添加 @click='checkAllFn' 事件

后端返回

问题

解决方案
v-model 是一个双向绑定数据流,而我们的VUEX是单向数据流,所以我们需要将
'v-model' 改为 ':value'
问题完美解决

以上就是全选与全不选实现的代码和效果图