vue实现完整的购物车功能(包括单选全选,删除商品和结算商品功能)

19 篇文章 0 订阅
4 篇文章 0 订阅

首先来看看效果图

预览地址:http://xy.xxiaoyuan.top/demo/shopCart/#/

 

项目开始之前需要选安装好node跟vue和vue-cli
如果还没安装的可以看这里(mac环境下的)https://blog.csdn.net/weixin_39644462/article/details/86302579
 

1. 首先创建一个vue项目(命令行如下)

vue init webpack shopcart

然后可以一路回车或者Y

2. 创建成功后进入到shopcart目录

cd shopcart

3. 安装项目所需要的依赖

我这项目用到axios请求自己写点json模拟数据,UI框架用到饿了么的Mint-UI框架,使用less作为css预处理器

//安装axios
npm install axios --save
//安装Mint-UI_
npm install mint-ui --save
//安装less
npm install less less-loader --save-dev

好了,可以先运行项目看看

npm run dev

如果浏览器没有自动打开浏览器的话,可以自己手动在浏览器输入localhost:8080  ,或者设置/config/index.js(如下图)

默认样式效果

下面配置一下安装的依赖

配置axios 和 Mint-UI (打开/src/main.js)

配置less(在build/webpack.base.conf.js 的module.exports.module.rules 里面添加如下代码)

{
  test: /\.less$/,
  loader: 'style-loader!css-loader!less-loader'
}

为了移动端自配,将引入外部flexible.js进行适配,将flexible.js放在/static/js/目录下,然后在/src/main.js下引入

import '../static/js/flexible.js'

并在/src创建一个目录styles(mixin.less用来做计算rem尺寸,源码文章后面有)

接下来就真正的搞事情了/components/shopCart.vue

<template>
  <div class="page">
		<header>
			<h3>购物车</h3>
		</header>
    <div class="shop_car_body">
    	<ul>
    		<li v-for="(item, index) in  list">
    			<div class="car_list_top">
    				<div class="car_list_t_l">
    					<!-- 选择商品 -->
    					<div class="input_check">
    						<span class="ico_gou"
								:class="{'ico_gou_on':item.checked}" 
    						@click="selectGoods(item)"></span>
    					</div>
    					<!-- 选择商品 end -->
    					<P>商家: {{item.shop_name}}</P>
    				</div>
    				<div class="car_list_t_r">
    					<p class="ico_del" @click="delGoods(item.goods_id,index)"></p>
    				</div>
    			</div>
    			<div class="car_list_center">
    				<div class="car_list_c_l">
    					<img :src="'http:'+item.goods_img" alt="">
    				</div>
    				<div class="car_list_c_r">
    					<p>{{item.goods_name}}</p>
    					<div class="goods_intor">
    						<p>¥{{item.price}}</p>
    						<p class="select_num_input">
    							<span class="ico_sub" @click="sub(item.sales_num,index)"></span>
    							<input type="number" :value="item.sales_num" disabled>
    							<span class="ico_plus" @click="plus(item.sales_num,index)"></span>
    						</p>
    					</div>
    				</div>
    			</div>
    		</li>
    	</ul>
    	<div class="car_footer">
    		<div class="car_footer_l" v-show="!checkAllFlag">
    			<span class="ico_gou" @click="checkAll(true)"></span>
    			<p>全选</p>
    		</div>
    		<div class="car_footer_l" v-show="checkAllFlag">
    			<span class="ico_gou_on" @click="checkAll(false)"></span>
    			<p>取消全选</p>
    		</div>
    		<div class="car_footer_r">
    			<span>合计:{{$setNum.Dec(totalMoney,2)}}</span>
    			<p @click="toDo()">结算({{checkNum}})</p>
    		</div>
    	</div>
    </div>
  </div>
</template>

<script>
import { Toast, Indicator, MessageBox} from 'mint-ui'
export default {
  data () {
    return {
      list:[],
    	selectId:[],  //选中的商品id
    	totalMoney: 0,  //总价
    	checkNum: 0,  //选择的商品数量(结算需要显示的数量)
    	checkAllFlag:false,  //是否全选
    }
  },
	computed: {
	},
	methods: {
		// 点击结算
		toDo(){
			if(this.checkNum <= 0){
				Toast('先选中需要结算的商品');
			}
			else{
				// 结算选中的商品
				var isList = [];
				for(var i in this.list){
					if(this.list[i].checked){
						isList.push(this.list[i]);
					}
				}				
				console.log(isList);
			}
		},
		// (单选)选择商品
		selectGoods(item){
			//判断是否未定义,如果没点击过按钮是没有注册的,则需要先注册checked属性
			if(typeof item.checked =='undefined'){
				this.$set(item,"checked",true);
				this.checkNum ++;  //结算需要显示的数量
			}else{
				//  如果已经注册,则设置checked否(这里不能设置为false,因为当已经注册过之后再点击为flase,那么再点击一次则为true)
				item.checked = !item.checked;
				item.checked ? this.checkNum ++ : this.checkNum --;
			}
			// 求总价
			this.totalPrice();
			// 当所有的商品都选择的时候,自动默认为全选
			this.list.length == this.checkNum ? this.checkAll(true) : this.checkAllFlag = false;
		},
		// 全选与取消全选,点击全选时flag为true,取消时为false
		checkAll(flag){
			this.checkAllFlag = flag;
			var _this = this;
			flag ? this.checkNum = this.list.length : this.checkNum = 0;
			this.list.forEach(function(item,index){
				if(typeof item.checked == 'undefined'){//也要防止未定义
					_this.$set(item,"checked",_this.checkAllFlag);//通过set来给item添加属性checked
				}else{
					item.checked = _this.checkAllFlag;
				}
			});
			this.totalPrice();
		},
		// 求总价
		totalPrice(){
			var _this = this;
			this.totalMoney = 0;
			this.list.forEach((item,index)=>{
				if(item.checked){
					_this.totalMoney += this.$setNum.accMul(item.price,item.sales_num);
				}
			});
		},
		// 删除商品
		delGoods(id,index){
			MessageBox.confirm('',{
				title:'',
				message:'确定删除该商品吗?',
				confirmButtonText:'确定',
				cancelButtonText:'取消'
			}).then(action => {
				if (action == 'confirm') {
					// 刷新类表
					this.getList();
					// 取消全选
					this.checkAll(false);
				}
			}).catch(error =>{});
		},
		// 数量减方法
		sub(num,index){
			if(parseInt(num) <= 1){
				this.list[index].sales_num = 1;
			}
			else{
				this.list[index].sales_num = parseInt(this.list[index].sales_num) - 1;
			}
			this.totalPrice();
		},
		// 数量加方法
		plus(num,index){
			this.list[index].sales_num = parseInt(this.list[index].sales_num) + 1;
			this.totalPrice();
		},
		// 获取购物车列表
		getList(){
			var _this = this;
			this.$http.get("/static/data/data.json").then(function(res){
				_this.list = res.data.list;
			});
		}
	},
	mounted(){
		this.getList();
	}
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="less">
@import "./../styles/index.less";
.page{
	.mt(88);
	.mb(110);
	.pt(20);
}
header{
	.h(88);
	.lh(88);
	background: @base-color;
	color: #fff;
	.fs(32);
	text-align: center;
	position: fixed;
	.left(0);
	.top(0);
	width: 100%;
	z-index: 100;
}
.shop_car_body{
	.w-calc(48);
	.ml(24);
	ul{
		li{
			.b-radius(16);
			background: #fff;
			.mb(20);
			.fs(28);
			.padding(0,24);
			.pb(24);
			.car_list_top{
				.fs(30);
				display: flex;
				justify-content: space-between;
				align-items: center;
				.padding(24,0);
				.car_list_t_l{
					display: flex;
					justify-content: flex-start;
					align-items: center;
					.input_check{
						position: relative;
						.mr(20);
						.w(40);
						.h(40);
						span{
							display: block;
							position: absolute;
							.top(4);
							.left(4);
							.w(36);
							.h(36);
							.bgs(36,36);
						}
					}
				}
				.car_list_t_r{
					p.ico_del{
						.w(36);
						.h(40);
						.bgs(36,40);
					}
				}
			}
			.car_list_center{
				display: flex;
				justify-content: flex-start;
				.car_list_c_l{
					img{
						display: block;
						.w(160);
						.h(160);
						object-fit: cover;
					}
				}
				.car_list_c_r{
					.ml(20);
					display: flex;
					flex-direction: column;
					justify-content: space-around;
					align-items: flex-start;
					.w-calc(140);
					p:nth-child(1){
						text-align: left;
						overflow: hidden;
						text-overflow: ellipsis;
						display: -webkit-box;
						-webkit-line-clamp: 2;
						-webkit-box-orient: vertical;
					}
					.goods_intor{
						display: flex;
						justify-content: space-between;
						align-items: center;
						width: 100%;
						.select_num_input{
							display: flex;
							justify-content: center;
							align-items: center;
							span{
								display: flex;
								justify-content: center;
								align-items: center;
								@ico_s:40;
								.w(@ico_s);
								.h(@ico_s);
								.bgs(@ico_s,@ico_s);
								&:active{
									opacity: .5;
								}
							}
							input[type=number]{
								.w(60);
								text-align: center;
								background: none;
							}						
						}
					}
				}
			}
		}
	}
	// footer
	.car_footer{
		.border-top(2,#eee);
		position: fixed;
		.left(0);
		.bottom(0);
		.w-calc(48);
		background: #fff;
		.h(88);
		.fs(30);
		display: flex;
		justify-content: space-between;
		align-items: center;
		.padding(0,24);
		.car_footer_l{
			.fs(28);
			color: @base-color;
			display: flex;
			justify-content: flex-start;
			align-items: center;
			p{
				.ml(20);
			}
			span{
				display: block;
				.w(36);
				.h(36);
				.bgs(36,36);
			}
		}
		.car_footer_r{
			display: flex;
			justify-content: flex-end;
			align-items: center;
			span{
				display: block;
				.mr(30);
			}
			p{
				display: flex;
				justify-content: center;
				align-items: center;
				color: #fff;
				.h(60);
				.padding(0,30);
				.b-radius(30);
				background: linear-gradient(45deg, #ff6924, #fe8701);				
			}
		}
	}
}
</style>

主要代码在上面,下面讲解一下主要部分

获取列表数据(因为购物车列表数据是通过自己模拟json数据来实现的,模拟的数据放在/static/data/data.json)

{
	"list": [
		{
			"classify_id": 1,
			"goods_img": "//gd4.alicdn.com/imgextra/i2/726671139/O1CN01UsYSFL1KHhdcGkTfo_!!726671139.jpg_400x400.jpg",
			"goods_name": "连衣裙ins夏chic2019新款很仙的法国小众吊带网纱超仙a字裙两件套",
			"goods_id": 61,
			"shop_name": "恋上公主",
			"sales_num": 1,
			"price": "105.00"
		},
		{
			"classify_id": 2,
			"goods_img": "//gd3.alicdn.com/imgextra/i3/726671139/O1CN01P4vwli1KHhdYOfl9j_!!726671139.jpg_400x400.jpg",
			"goods_name": "防晒衬衫女长袖薄款设计感小众百搭外穿2019新款超薄透气雪纺衬衣",
			"goods_id": 62,
			"shop_name": "恋上公主",
			"sales_num": 2,
			"price": "116.00"
		},
		{
			"classify_id": 2,
			"goods_img": "//gd2.alicdn.com/imgextra/i1/726671139/O1CN010EFA6U1KHhdODcpz0_!!726671139.jpg_400x400.jpg",
			"goods_name": "2019网红时尚连衣裙很仙的夏款网纱小清新温柔波点吊带T恤裙套装",
			"goods_id": 63,
			"shop_name": "恋上公主",
			"sales_num": 3,
			"price": "129.00"
		},
		{
			"classify_id": 4,
			"goods_img": "//gd4.alicdn.com/imgextra/i4/726671139/O1CN01nonA501KHhctJZ7Tk_!!726671139.jpg_400x400.jpg",
			"goods_name": "新款衬衫女时尚洋气超仙设计感小众2019长袖收腰雪纺韩版chic衬衣",
			"goods_id": 64,
			"shop_name": "恋上公主",
			"sales_num": 3,
			"price": "69.00"
		}
	]
}

然后通过axios的get请求获取到数据(在methods钩子里写getList方法获取,在mounted钩子初始化)

数量加和数量减的方法

选择商品(单选)

下面代码有注释,应该是比较详细的了

全选

求总价(查找列表,如果存在checked == true,则将其价格剩余数量,下面用到了$setNum.accMul(int1,int2),这个方法是自己封装的一个乘法方法,因为在js中的浮点类型相乘并不能完全得到准确的数据,会存在一定的误差,该方法在/src/assets/js/setDec.js里)

删除商品

点击结算(可以在控制台查看,选中的商品)

最后打包,修改/config/index.js文件下的module.exports下的build的assetsPublicPath,设置为'./'

最后预览地址:http://xy.xxiaoyuan.top/demo/shopCart/#/

源码下载地址在gitHud: https://github.com/YanGo520/shopCart(源码好像没有修改打包后的相对静态资源文件,可以参考上面修改assetsPublicPath为 './' )

码云地址:https://gitee.com/yango520/shopCart

最后允许我打个小广告(虽然很多猿兄没女朋友这是种生物[/滑稽脸]):恋上公主(里面的衣服贼漂亮)

  • 32
    点赞
  • 224
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值