首先来看看效果图
预览地址: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
最后允许我打个小广告(虽然很多猿兄没女朋友这是种生物[/滑稽脸]):恋上公主(里面的衣服贼漂亮)