说说如何使用 Vue.js 开发购物车功能

购物车一般包含商品名称、单价、数量等信息,数量可以任意新增或减少,商品项也可删除,还可以支持全选或多选:

最终效果

我们把这个小项目分为三个文件:

  • index.html (页面)
  • index.js (Vue 脚本)
  • style.css (样式)

1 index.js

首先在 js 中初始化 Vue 实例,整体模板如下:

var app = new Vue({
    el: '#app',
    data: {
     ...
    },
    mounted: function () {
       ...
    },
    computed: {
      ...
    },
    methods: {
      ...
    }

});


一般来说,这里的 data 来源于服务端数据,这里为了简便,所以直接定义好的数据:

data: {
        /**
         * 购物车中的商品列表
         */
        list: [
            {
                id: 1,
                name: '韩国进口海牌海苔',
                price: 39.9,
                count: 1
            },
            {
                id: 2,
                name: '印尼进口 Nabati 丽巧克(Richoco)休闲零食 巧克力味 威化饼干',
                price: 11.8,
                count: 1
            },
            {
                id: 3,
                name: '菲律宾进口 道吉草 奶油夹',
                price: 6.5,
                count: 1
            }
        ],
        //选中的商品列表,用于计算总价
        checkList: []
    }
  • list 用于展示 购物车中的商品列表。
  • checkList 用于表示勾选中的商品列表,后面,我们会利用它来计算选中商品的总价。
mounted: function () {
        //默认全选
        this.checkAll();
        this.checkAllElement(document.querySelector(".checkAll"));
}

当 mounted 时,默认全选购物车内的所有商品。

computed: {
    /**
     * 总价
     * @returns {string}
     */
    totalPrice: function () {
        var total = 0;
        for (var i = 0; i < this.checkList.length; i++) {
            var item = this.checkList[i];
            total += item.price * item.count;
        }
        return total.toLocaleString();
    }
}

在计算属性中,我们定义了总价的计算方式,它会绑定勾选的 checkList 来计算总价。之所以使用 toLocaleString 方法,是因为小数部分会自动四舍五入,而且还会以千分位表示出来,很方便哦O(∩_∩)O~

methods: {
    /**
     * 减少购买数量
     * @param index
     */
    reduceCount: function (index) {
        if (this.list[index].count === 1) return;
        this.list[index].count--;
    },
    /**
     * 增加购买数量
     * @param index
     */
    addCount: function (index) {
        this.list[index].count++;
    },
    /**
     * 移除商品
     * @param index
     */
    remove: function (index) {
        console.log("remove-index:" + index);
        this.list.splice(index, 1);

        //获取商品序号
        var id = index + 1;
                //移除实际参与计算的商品
        var $checkList = this.checkList;
        for (var i = 0; i < $checkList.length; i++) {
            var item = $checkList[i];
            if (item.id == id) {
                $checkList.splice(i, 1);
            }
        }
    },
    /**
     * 全选或全不选
     * @param event
     */
    checkAllOrNot: function (event) {
        if (event.target.checked) {//全选
            this.checkAll();
            console.log("checkList:" + this.checkList);
        } else { // 全不选
            console.log("全不选");
            this.checkInItems('noCheckAll');
            this.checkList.splice(0);//清空数组
        }
    },
    /**
     * 全选
     */
    checkAll: function () {
        console.log("全选");
        this.checkInItems('checkAll');
        this.checkList = this.list.concat();//复制商品列表
    },
    /**
     * 全选或全不选
     * @param type checkAll:全选;其他:全不选
     */
    checkInItems: function (type) {
        var items = document.querySelectorAll('.checkItem');
        for (var i = 0; i < items.length; i++) {
            var item = items[i];
            if (type === 'checkAll') {
                item.checked = true;
            } else {
                item.checked = false;
            }
        }
    },
    /**
     * 勾选或不勾选
     */
    checkItem: function (event, index) {
        console.log("checkItem");
        var element = event.target;
        var $allCheck = document.querySelector(".checkAll");
        if (element.checked) {//勾选,加入已选择列表
            this.checkList.push(this.list[index]);
            this.checkAllElement($allCheck);
        } else {//不勾选,从已选择列表中去除
            this.checkList.splice(index, 1);
            $allCheck.checked = false;
        }
    },
    /**
     * 勾选全选框
     * @param element
     */
    checkAllElement: function (element) {
        //如果所有的商品都已被勾选,则勾选全选框
        if (this.checkList.length == this.list.length) {
            element.checked = true;
        }
    }
}

在 methods 中,我们定义了以下功能方法:

  • 减少与增加购买数量。在减少购买数量方法中,我们对当前所对应商品的数量进行了二次确认,让代码变得更加健壮(HTML 模板可能被修改,button 被替换为 div 或者 span,那么 disabled 样式就变得无效啦)。
  • 移除某件商品。因为购物车中的商品列表与实际勾选的商品列表数量上有可能存在差异,所以我们必须通过找到商品 ID 再进行删除。
  • 勾选相关操作(全选、全不选、单选、单不选等)

2 style.css

[v-cloak] {
    display: none;
}

table {
    border: 1px solid #e9e9e9;
    border-collapse: collapse;
    border-spacing: 0;
    empty-cells: show;
}



th {
    font: bold 12px "Trebuchet MS", Verdana, Arial, Helvetica, sans-serif;
    color: #4f6b72;
    border-right: 1px solid #C1DAD7;
    border-bottom: 1px solid #C1DAD7;
    border-top: 1px solid #C1DAD7;
    letter-spacing: 2px;
    text-transform: uppercase;
    text-align: left;
    padding: 6px 6px 6px 12px;
    background: #CAE8EA;
}

td {
    border-right: 1px solid #C1DAD7;
    border-bottom: 1px solid #C1DAD7;
    background: #fff;
    font-size:14px;
    padding: 6px 6px 6px 12px;
    color: #4f6b72;
}

这里定义了 v-cloak 样式,用于解决网络慢时的闪屏问题。还定义了表格的相关样式。

3 index.html

接着在 index.html 中引入 Vue 脚本与样式文件。基本模板如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>购物车</title>
    <link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
<div id="app" v-cloak>
   ...
</div>


<script src="https://cdn.bootcss.com/vue/2.2.2/vue.min.js"></script>
<script src="index.js"></script>
</body>
</html>

因为有可能购物车中的商品被全部删除,所以我们在此加了判断,如果列表为空,则给出友好提示:

<template v-if="list.length">
    ...
</template>
<!--当购物车为空时,则提示-->
<div v-else>购物车内暂时没有商品</div>

接着用 table 来展示购物车内的商品列表:

<table>
    <thead>
    <tr>
        <th><input id="checkAll" type="checkbox" class="checkAll" @click="checkAllOrNot($event)"></th>
        <th>序号</th>
        <th>商品</th>
        <th>单价</th>
        <th>数量</th>
        <th>操作</th>
    </tr>
    </thead>
    <tbody>
    <tr v-for="(item,index) in list">
        <td><input type="checkbox" class="checkItem" @click="checkItem($event,index)"></td>
        <td>{{index+1}}</td>
        <td>{{item.name}}</td>
        <td>{{item.price}}</td>
        <td>
            <button @click="reduceCount(index)" :disabled="item.count===1">-</button>
            {{item.count}}
            <button @click="addCount(index)">+</button>
        </td>
        <td>
            <button @click="remove(index)">删除</button>
        </td>
    </tr>
    </tbody>
</table>
<div>总价:¥{{totalPrice}}</div>
  • 使用 v-for 指令,循环迭代出商品列表。
  • 表格内的每一个勾选框与按钮都绑定了相应的事件。全选框与每一行的勾选框还传入了原生 DOM 事件 $event,用于获取当前所操作的元素。
    *这里对减少商品数量的按钮进行了判断,当相应商品的数量只剩下一个时,绑定 disabled 样式,让它变成不可用。

4 演示

本文示例 DEMO

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容