vue快速入门的三个小实例

标签: vue.js javascript | 发表时间:2017-08-23 08:37 | 作者:守候你
出处:https://segmentfault.com/blogs

1.前言

用vue做项目也有一段时间了,之前也是写过关于vue和webpack构建项目的相关文章,大家有兴趣可以去看下 webpack+vue项目实战(一,搭建运行环境和相关配置)(这个系列一共有5篇文章,这是第一篇,其它几篇文章链接就不贴了)。但是关于vue入门基础的文章,我还没有写过,那么今天就写vue入门的三个小实例,这三个小实例是我刚接触vue的时候的练手作品,难度从很简单到简单,都是入门级的。希望能帮到大家更好的学习和了解vue,也是让自己能够复习一下vue。如果发现文章写得有什么不好,写错了,或者有什么建议!欢迎大家指点迷津!

1.本篇文章使用的vue版本是 2.4.2,大家要注意版本问题
2.现在我也是假设您有基础的html,css,javascript的知识,也已经看过了 官网的基本介绍,对vue有了一个大概的认识了,了解了常用的vue指令(v-model,v-show,v-if,v-for,v-on,v-bind等)!如果刚接触前端的话,你看着文章可能会蒙圈,建议先学习基础,掌握了基础知识再来看!
3.下面的实例,建议大家边看文章边动手做!这样思路会非常清晰,不易混乱!也不会觉得文章长!如果只看文章,你可能未必会看完,因为文章我讲得比较细,比较长!

2.什么是vue

vue是现在很火的一个前端MVVM框架,它以数据驱动和组件化的思想构建,与angular和react并称前端三大框架。相比angular和react,vue更加轻巧、高性能、也很容易上手。大家也可以移步,看一下vue的介绍和核心功能 官网介绍。简单粗暴的理解就是:用vue开发的时候,就是操作数据,然后vue就会处理,以数据驱动去改变DOM(不知道有没有理解错,理解错了指点下)。
下面就是一个最简单的说明例子

代码如下

html

  <div id="app">
  <p>{{ message }}</p>
  <input v-model="message">
</div>

js

  new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue!'
  }
})

栗子

相信也不难理解,就是 input绑定了 message这个值,然后在 input修改的时候, message就改了,由于双向绑定,同时页面的html( {{ message }})进行了修改!
好,下面进入例子学习!

3.选项卡

运行效果

clipboard.png

原理分析和实现

这个很简单,无非就是一个点击切换显示而已。但是大家也要实现。如果这个看明白了,再看下面两个!这个实例应该只是一个热身和熟悉的作用!

这个的步骤只有一步,原理也没什么。我直接在代码打注释,看了注释,大家就明白了!

完整代码

<!DOCTYPE html>
<html lang="en">
<head>

  <meta charset="UTF-8">
<title>Title</title>

</head>
<style>

  body{
    font-family:"Microsoft YaHei";
}
#tab{
    width: 600px;
    margin: 0 auto;
}
.tab-tit{
    font-size: 0;
    width: 600px;
}
.tab-tit a{
    display: inline-block;
    height: 40px;
    line-height: 40px;
    font-size: 16px;
    width: 25%;
    text-align: center;
    background: #ccc;
    color: #333;
    text-decoration: none;
}
.tab-tit .cur{
    background: #09f;
    color: #fff;
}
.tab-con div{
    border: 1px solid #ccc;
    height: 400px;
    padding-top: 20px;
}

</style>
<body>
<div id="tab">

  <div class="tab-tit">
    <!--点击设置curId的值  如果curId等于0,第一个a添加cur类名,如果curId等于1,第二个a添加cur类名,以此类推。添加了cur类名,a就会改变样式 @click,:class ,v-show这三个是vue常用的指令或添加事件的方式-->
    <a href="javascript:;" @click="curId=0" :class="{'cur':curId===0}">html</a>
    <a href="javascript:;" @click="curId=1" :class="{'cur':curId===1}">css</a>
    <a href="javascript:;" @click="curId=2" :class="{'cur':curId===2}">javascript</a>
    <a href="javascript:;" @click="curId=3" :class="{'cur':curId===3}">vue</a>
</div>
<div class="tab-con">
    <!--根据curId的值显示div,如果curId等于0,第一个div显示,其它三个div不显示。如果curId等于1,第二个div显示,其它三个div不显示。以此类推-->
    <div v-show="curId===0">
        html<br/>
    </div>
    <div v-show="curId===1">
        css
    </div>
    <div v-show="curId===2">
        javascript
    </div>
    <div v-show="curId===3">
        vue
    </div>
</div>

</div>
</body>
<script src="vue.min.js"></script>
<script>

  new Vue({
    el: '#tab',
    data: {
        curId: 0
    },
    computed: {},
    methods: {},
    mounted: function () {
    }
})

</script>
</html>

4.统计总价

运行效果

clipboard.png

原理分析和实现

首先,还是先把布局写好,和引入vue,准备vue实例,这个不多说,代码如下

  <!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        .fl{
            float: left;
        }
        .fr{
            float: right;
        }
       blockquote, body, dd, div, dl, dt, fieldset, form, h1, h2, h3, h4, h5, h6, img, input, li, ol, p, table, td, textarea, th, ul {
            margin: 0;
            padding: 0;
        }
       .clearfix{
          zoom: 1;
       }
        .clearfix:after {
            clear: both;
        }
        .clearfix:after {
            content: '.';
            display: block;
            overflow: hidden;
            visibility: hidden;
            font-size: 0;
            line-height: 0;
            width: 0;
            height: 0;
        }
        a{
            text-decoration: none;
            color: #333;
        }
        img{vertical-align: middle;}
        .page-shopping-cart {
            width: 1200px;
            margin: 50px auto;
            font-size: 14px;
            border: 1px solid #e3e3e3;
            border-top: 2px solid #317ee7; }
        .page-shopping-cart .cart-title {
            color: #317ee7;
            font-size: 16px;
            text-align: left;
            padding-left: 20px;
            line-height: 68px; }
        .page-shopping-cart .red-text {
            color: #e94826; }
        .page-shopping-cart .check-span {
            display: block;
            width: 24px;
            height: 20px;
            background: url("shopping_cart.png") no-repeat 0 0; }
        .page-shopping-cart .check-span.check-true {
            background: url("shopping_cart.png") no-repeat 0 -22px; }
        .page-shopping-cart .td-check {
            width: 70px; }
        .page-shopping-cart .td-product {
            width: 460px; }
        .page-shopping-cart .td-num, .page-shopping-cart .td-price, .page-shopping-cart .td-total {
            width: 160px; }
        .page-shopping-cart .td-do {
            width: 150px; }
        .page-shopping-cart .cart-product-title {
            text-align: center;
            height: 38px;
            line-height: 38px;
            padding: 0 20px;
            background: #f7f7f7;
            border-top: 1px solid #e3e3e3;
            border-bottom: 1px solid #e3e3e3; }
        .page-shopping-cart .cart-product-title .td-product {
            text-align: center;
            font-size: 14px; }
        .page-shopping-cart .cart-product-title .td-check {
            text-align: left; }
        .page-shopping-cart .cart-product-title .td-check .check-span {
            margin: 9px 6px 0 0; }
        .page-shopping-cart .cart-product {
            padding: 0 20px;
            text-align: center; }
        .page-shopping-cart .cart-product table {
            width: 100%;
            text-align: center;
            font-size: 14px; }
        .page-shopping-cart .cart-product table td {
            padding: 20px 0; }
        .page-shopping-cart .cart-product table tr {
            border-bottom: 1px dashed #e3e3e3; }
        .page-shopping-cart .cart-product table tr:last-child {
            border-bottom: none; }
        .page-shopping-cart .cart-product table .product-num {
            border: 1px solid #e3e3e3;
            display: inline-block;
            text-align: center; }
        .page-shopping-cart .cart-product table .product-num .num-do {
            width: 24px;
            height: 28px;
            display: block;
            background: #f7f7f7; }
        .page-shopping-cart .cart-product table .product-num .num-reduce span {
            background: url("shopping_cart.png") no-repeat -40px -22px;
            display: block;
            width: 6px;
            height: 2px;
            margin: 13px auto 0 auto; }
        .page-shopping-cart .cart-product table .product-num .num-add span {
            background: url("shopping_cart.png") no-repeat -60px -22px;
            display: block;
            width: 8px;
            height: 8px;
            margin: 10px auto 0 auto; }
        .page-shopping-cart .cart-product table .product-num .num-input {
            width: 42px;
            height: 28px;
            line-height: 28px;
            border: none;
            text-align: center; }
        .page-shopping-cart .cart-product table .td-product {
            text-align: left;
            font-size: 12px;
            line-height: 20px; }
        .page-shopping-cart .cart-product table .td-product img {
            border: 1px solid #e3e3e3;
            margin-right: 10px; }
        .page-shopping-cart .cart-product table .td-product .product-info {
            display: inline-block;
            vertical-align: middle; }
        .page-shopping-cart .cart-product table .td-do {
            font-size: 12px; }
        .page-shopping-cart .cart-product-info {
            height: 50px;
            line-height: 50px;
            background: #f7f7f7;
            padding-left: 20px; }
        .page-shopping-cart .cart-product-info .delect-product {
            color: #666; }
        .page-shopping-cart .cart-product-info .delect-product span {
            display: inline-block;
            vertical-align: top;
            margin: 18px 8px 0 0;
            width: 13px;
            height: 15px;
            background: url("shopping_cart.png") no-repeat -60px 0; }
        .page-shopping-cart .cart-product-info .product-total {
            font-size: 14px;
            color: #e94826; }
        .page-shopping-cart .cart-product-info .product-total span {
            font-size: 20px; }
        .page-shopping-cart .cart-product-info .check-num {
            color: #333; }
        .page-shopping-cart .cart-product-info .check-num span {
            color: #e94826; }
        .page-shopping-cart .cart-product-info .keep-shopping {
            color: #666;
            margin-left: 40px; }
        .page-shopping-cart .cart-product-info .keep-shopping span {
            display: inline-block;
            vertical-align: top;
            margin: 18px 8px 0 0;
            width: 15px;
            height: 15px;
            background: url("shopping_cart.png") no-repeat -40px 0; }
        .page-shopping-cart .cart-product-info .btn-buy {
            height: 50px;
            color: #fff;
            font-size: 20px;
            display: block;
            width: 110px;
            background: #ff7700;
            text-align: center;
            margin-left: 30px; }
        .page-shopping-cart .cart-worder {
            padding: 20px; }
        .page-shopping-cart .cart-worder .choose-worder {
            color: #fff;
            display: block;
            background: #39e;
            width: 140px;
            height: 40px;
            line-height: 40px;
            border-radius: 4px;
            text-align: center;
            margin-right: 20px; }
        .page-shopping-cart .cart-worder .choose-worder span {
            display: inline-block;
            vertical-align: top;
            margin: 9px 10px 0 0;
            width: 22px;
            height: 22px;
            background: url("shopping_cart.png") no-repeat -92px 0; }
        .page-shopping-cart .cart-worder .worker-info {
            color: #666; }
        .page-shopping-cart .cart-worder .worker-info img {
            border-radius: 100%;
            margin-right: 10px; }
        .page-shopping-cart .cart-worder .worker-info span {
            color: #000; }

        .choose-worker-box {
            width: 620px;
            background: #fff; }
        .choose-worker-box .box-title {
            height: 40px;
            line-height: 40px;
            background: #F7F7F7;
            text-align: center;
            position: relative;
            font-size: 14px; }
        .choose-worker-box .box-title a {
            display: block;
            position: absolute;
            top: 15px;
            right: 16px;
            width: 10px;
            height: 10px;
            background: url("shopping_cart.png") no-repeat -80px 0; }
        .choose-worker-box .box-title a:hover {
            background: url("shopping_cart.png") no-repeat -80px -22px; }
        .choose-worker-box .worker-list {
            padding-top: 30px;
            height: 134px;
            overflow-y: auto; }
        .choose-worker-box .worker-list li {
            float: left;
            width: 25%;
            text-align: center;
            margin-bottom: 30px; }
        .choose-worker-box .worker-list li p {
            margin-top: 8px; }
        .choose-worker-box .worker-list li.cur a {
            color: #f70; }
        .choose-worker-box .worker-list li.cur a img {
            border: 1px solid #f70; }
        .choose-worker-box .worker-list li a:hover {
            color: #f70; }
        .choose-worker-box .worker-list li a:hover img {
            border: 1px solid #f70; }
        .choose-worker-box .worker-list li img {
            border: 1px solid #fff;
            border-radius: 100%; }
    </style>
</head>
<body>
<div class="page-shopping-cart" id="shopping-cart">
    <h4 class="cart-title">购物清单</h4>
    <div class="cart-product-title clearfix">
        <div class="td-check fl"><span class="check-span fl check-all"></span>全选</div>
        <div class="td-product fl">商品</div>
        <div class="td-num fl">数量</div>
        <div class="td-price fl">单价(元)</div>
        <div class="td-total fl">金额(元)</div>
        <div class="td-do fl">操作</div>
    </div>
    <div class="cart-product clearfix">
        <table>
            <tbody><tr>
                <td class="td-check"><span class="check-span"></span></td>
                <td class="td-product"><img src="testimg.jpg" width="98" height="98">
                    <div class="product-info">
                        <h6>【斯文】甘油&nbsp;|&nbsp;丙三醇</h6>
                        <p>品牌:韩国skc&nbsp;&nbsp;产地:韩国</p>
                        <p>规格/纯度:99.7%&nbsp;&nbsp;起定量:215千克</p>
                        <p>配送仓储:上海仓海仓储</p>
                    </div>
                    <div class="clearfix"></div>
                </td>
                <td class="td-num">
                    <div class="product-num">
                        <a href="javascript:;" class="num-reduce num-do fl"><span></span></a>
                        <input type="text" class="num-input" value="3">
                        <a href="javascript:;" class="num-add num-do fr"><span></span></a>
                    </div>
                </td>
                <td class="td-price">
                    <p class="red-text">¥<span class="price-text">800</span>.00</p>
                </td>
                <td class="td-total">
                    <p class="red-text">¥<span class="total-text">800</span>.00</p>
                </td>
                <td class="td-do"><a href="javascript:;" class="product-delect">删除</a></td>
            </tr>
            <tr>
                <td class="td-check"><span class="check-span check-true"></span></td>
                <td class="td-product"><img src="testimg.jpg" width="98" height="98">
                    <div class="product-info">
                        <h6>【斯文】甘油&nbsp;|&nbsp;丙三醇</h6>
                        <p>品牌:韩国skc&nbsp;&nbsp;产地:韩国</p>
                        <p>规格/纯度:99.7%&nbsp;&nbsp;起定量:215千克</p>
                        <p>配送仓储:上海仓海仓储</p>
                    </div>
                    <div class="clearfix"></div>
                </td>
                <td class="td-num">
                    <div class="product-num">
                        <a href="javascript:;" class="num-reduce num-do fl"><span></span></a>
                        <input type="text" class="num-input" value="1">
                        <a href="javascript:;" class="num-add num-do fr"><span></span></a>
                    </div>
                </td>
                <td class="td-price">
                    <p class="red-text">¥<span class="price-text">800</span>.00</p>
                </td>
                <td class="td-total">
                    <p class="red-text">¥<span class="total-text">800</span>.00</p>
                </td>
                <td class="td-do"><a href="javascript:;" class="product-delect">删除</a></td>
            </tr>
            </tbody></table>
    </div>
    <div class="cart-product-info">
        <a class="delect-product" href="javascript:;"><span></span>删除所选商品</a>
        <a class="keep-shopping" href="#"><span></span>继续购物</a>
        <a class="btn-buy fr" href="javascript:;">去结算</a>
        <p class="fr product-total">¥<span>1600</span></p>
        <p class="fr check-num"><span>2</span>件商品总计(不含运费):</p>
    </div>
    <div class="cart-worder clearfix">
        <a href="javascript:;" class="choose-worder fl"><span></span>绑定跟单员</a>
        <div class="worker-info fl">
        </div>
    </div>
</div>
</body>
<script src="vue.min.js"></script>
<script>
    new Vue({
        el:'#shopping-cart',
        data:{

        },
        computed: {},
        methods:{
            
        }
    })
</script>
</html>

然后准备下列表数据,根据下面表格的箭头

clipboard.png

所以大家就知道吗,下面的数据大概是涨这样

  productList:[
    {
        'pro_name': '【斯文】甘油 | 丙三醇',//产品名称
        'pro_brand': 'skc',//品牌名称
        'pro_place': '韩国',//产地
        'pro_purity': '99.7%',//规格
        'pro_min': "215千克",//最小起订量
        'pro_depot': '上海仓海仓储',//所在仓库
        'pro_num': 3,//数量
        'pro_img': '../../images/ucenter/testimg.jpg',//图片链接
        'pro_price': 800//单价
    }
]

准备了这么多,大家可能想到,还缺少一个,就是记录产品是否有选中,但是这个字段,虽然可以在上面那里加,但是意义不大,比如在平常项目那里!后台的数据不会这样返回,数据库也不会有这个字段,这个字段应该是自己添加的。代码如下

  new Vue({
    el:'#shopping-cart',
    data:{
        productList:[
            {
                'pro_name': '【斯文】甘油 | 丙三醇',//产品名称
                'pro_brand': 'skc',//品牌名称
                'pro_place': '韩国',//产地
                'pro_purity': '99.7%',//规格
                'pro_min': "215千克",//最小起订量
                'pro_depot': '上海仓海仓储',//所在仓库
                'pro_num': 3,//数量
                'pro_img': '../../images/ucenter/testimg.jpg',//图片链接
                'pro_price': 800//单价
            }
        ]
    },
    computed: {},
    methods:{

    },
    mounted:function () {
        //为productList添加select(是否选中)字段,初始值为true
        this.productList.map(function(item){item.select=true;console.log(item)})
    }
})
          

步骤1

为了着重表示我修改了什么地方,代码我现在只贴出修改的部分,大家对着上面的布局,就很容易知道我改的是什么地方了!下面也是这样操作!

点击增加和减少按钮(箭头指向地方),所属列的金额改变(红框地方)
clipboard.png

执行步骤1之前,要先把列表的数据给铺出来。利用v-for指令。代码如下

  <tr v-for="item in productList">
    <td class="td-check"><span class="check-span"></span></td>
    <td class="td-product"><img :src="item.pro_img" width="98" height="98">
        <div class="product-info">
            <h6>{{item.pro_name}}</h6>
            <p>品牌:{{item.pro_brand}}&nbsp;&nbsp;产地:{{item.pro_place}}</p>
            <p>规格/纯度:{{item.pro_purity}}&nbsp;&nbsp;起定量:{{item.pro_min}}</p>
            <p>配送仓储:{{item.pro_depot}}</p>
        </div>
        <div class="clearfix"></div>
    </td>
    <td class="td-num">
        <div class="product-num">
            <a href="javascript:;" class="num-reduce num-do fl" @click="item.pro_num--"><span></span></a>
            <input type="text" class="num-input" v-model="item.pro_num">
            <a href="javascript:;" class="num-add num-do fr" @click="item.pro_num++"><span></span></a>
        </div>
    </td>
    <td class="td-price">
        <p class="red-text">¥<span class="price-text">{{item.pro_price.toFixed(2)}}</span></p>
    </td>
    <td class="td-total">
        <p class="red-text">¥<span class="total-text">{{item.pro_price*item.pro_num}}</span>.00</p>
    </td>
    <td class="td-do"><a href="javascript:;" class="product-delect">删除</a></td>
</tr>

这样,列表的数据就有了!

clipboard.png

也可以发现, clipboard.png这两个按钮的功能已经实现了,后面的金额也会发生变化!是不是感到很惊喜!其实这里没什么特别的,就是因为输入框利用v-model绑定了数量( pro_num),然后两个按钮分别添加了事件 @click="item.pro_num--"和@ click="item.pro_num++"。比如刚开始pro_num是3,点击 clipboard.pngpro_num就变成2,点击 clipboard.png
pro_num就变成4,然后后面的金额会改改,是因为 {{item.pro_price*item.pro_num}}。只要pro_price或者pro_num的值改变了,整一块也会改变,视图就会刷新,我们就能看到变化(这些事情是vue做的,这就是MVVM的魅力,数据驱动视图改变)。

步骤2

点击所属列选择按钮(箭头指向地方),总计的金额(红框地方)和已选产品的列数(蓝框地方)和全选(黄框地方)会改变(如果已经全选了,全选按钮自动变成全选,如果没有全选,全选按钮,自动取消全选)!

clipboard.png

首先,选择与取消选择,在这里只有两个操作(其实只有一个:改变这条记录的 select字段)。

clipboard.png

然后改变 clipboard.png,如果这条记录 selectfalse,就显示 clipboard.png,否则就显示 clipboard.png
代码如下

  <td class="td-check"><span class="check-span" @click="item.select=!item.select" :class="{'check-true':item.select}"></span></td>

其实就是等于添加了 @click="item.select=!item.select" :class="{'check-true':item.select}"这里。点击这个,这条数据的 select字段就取反(true->false或者false->true)。然后 :class="{'check-true':item.select}",就会根据这条数据的 select字段进行判断,是否添加 check-true类名,如果 select字段为true,就添加类名,显示 clipboard.png。否则不添加类名,显示
clipboard.png

然后, clipboard.png全选按钮,是否变成 clipboard.png。这里用一个computed(计算属性)就好。代码如下

html

  <div class="td-check fl"><span class="check-span fl check-all" :class="{'check-true':isSelectAll}"></span>全选</div>

js

  computed: {
    isSelectAll:function(){
        //如果productList中每一条数据的select都为true,返回true,否则返回false;
        return this.productList.every(function (val) { return val.select});
    }
}

代码我解释下,就是计算属性中,定义的isSelectAll依赖productList。只要productList改变,isSelectAll的返回值就会改变,然后 :class="{'check-true':isSelectAll}"根绝isSelectAll返回值是否添加 'check-true'类名,显示对应的样式!
最后, clipboard.png,这里的多少件产品和总价,也是使用计算属性,有了上一步的基础,给出代码,大家一看就明白了!
html

  <p class="fr product-total">¥<span>{{getTotal.totalPrice}}</span></p>
<p class="fr check-num"><span>{{getTotal.totalNum}}</span>件商品总计(不含运费):</p>

js

  computed: {
    //检测是否全选
    isSelectAll:function(){
        //如果productList中每一条数据的select都为true,返回true,否则返回false;
        return this.productList.every(function (val) { return val.select});
    },
    //获取总价和产品总件数
    getTotal:function(){
        //获取productList中select为true的数据。
        var _proList=this.productList.filter(function (val) { return val.select}),totalPrice=0;
        for(var i=0,len=_proList.length;i<len;i++){
            //总价累加
            totalPrice+=_proList[i].pro_num*_proList[i].pro_price;
        }
        //选择产品的件数就是_proList.length,总价就是totalPrice
        return {totalNum:_proList.length,totalPrice:totalPrice}
    }
},

代码很简单,html根据getTotal返回值显示数据,getTotal依赖productList的数据,只要productList改变,返回值会改变,视图也会改变!

clipboard.png

步骤3

点击全选按钮(箭头指向部分),会自动的对产品进行全选或者取消全选,下面的总计也会发生改变

clipboard.png
做到这一步,大家应该知道,全选或者取消全选,就是改变记录的 select。但是怎么知道现在的列表有没有全选呢?这个很贱,不需要在操作函数(全选与取消全选函数)里面遍历,大家应该还记得第二步的计算属性 isSelectAll(为true就是全选,否则不是全选),把这个传进操作函数就好,然后操作函数,根据参数,决定执行全选,还是取消全选操作。代码如下!
html

  <div class="td-check fl"><span class="check-span fl check-all" :class="{'check-true':isSelectAll}" @click="selectProduct(isSelectAll)"></span>全选</div>

js

   methods: {
    //全选与取消全选
    selectProduct:function(_isSelect){
        //遍历productList,全部取反
        for (var i = 0, len = this.productList.length; i < len; i++) {
            this.productList[i].select = !_isSelect;
        }
    }
},

clipboard.png

步骤4

点击删除产品,会删除已经选中的,全选按钮和下面的总计,都会变化!点击每条记录后面的删除,会删除当前的这条记录。全选按钮和下面的总计,也都会变化!

clipboard.png

首先,点击删除产品,删除已经选中。这个大家知道了怎么做了!就是遍历productList,如果哪条记录的select为true,就删除。
然后,点击每条记录后面的删除,删除当前的这条记录。这个在html遍历productList的时候。顺便带上索引,然后把索引当成参数,传进操作函数,然后根据索引参数,删除productList的哪一条记录。即可实现!代码如下!
html

  <!--遍历的时候带上索引-->
<tr v-for="(item,index) in productList">
    <td class="td-check"><span class="check-span" @click="item.select=!item.select" :class="{'check-true':item.select}"></span></td>
    <td class="td-product"><img :src="item.pro_img" width="98" height="98">
        <div class="product-info">
            <h6>{{item.pro_name}}</h6>
            <p>品牌:{{item.pro_brand}}&nbsp;&nbsp;产地:{{item.pro_place}}</p>
            <p>规格/纯度:{{item.pro_purity}}&nbsp;&nbsp;起定量:{{item.pro_min}}</p>
            <p>配送仓储:{{item.pro_depot}}</p>
        </div>
        <div class="clearfix"></div>
    </td>
    <td class="td-num">
        <div class="product-num">
            <a href="javascript:;" class="num-reduce num-do fl" @click="item.pro_num--"><span></span></a>
            <input type="text" class="num-input" v-model="item.pro_num">
            <a href="javascript:;" class="num-add num-do fr" @click="item.pro_num++"><span></span></a>
        </div>
    </td>
    <td class="td-price">
        <p class="red-text">¥<span class="price-text">{{item.pro_price.toFixed(2)}}</span></p>
    </td>
    <td class="td-total">
        <p class="red-text">¥<span class="total-text">{{item.pro_price*item.pro_num}}</span>.00</p>
    </td>
    <td class="td-do"><a href="javascript:;" class="product-delect" @click="deleteOneProduct(index)">删除</a></td>
</tr>
...
<a class="delect-product" href="javascript:;" @click="deleteProduct"><span></span>删除所选商品</a>

js

  //删除已经选中(select=true)的产品
deleteProduct:function () {
    this.productList=this.productList.filter(function (item) {return !item.select})
},
//删除单条产品
deleteOneProduct:function (index) {
    //根据索引删除productList的记录
    this.productList.splice(index,1);
},

完整代码

样式图片

clipboard.png

  <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
        <style>
            .fl {
                float: left;
            } 
    
            .fr {
                float: right;
            }
    
            blockquote, body, dd, div, dl, dt, fieldset, form, h1, h2, h3, h4, h5, h6, img, input, li, ol, p, table, td, textarea, th, ul {
                margin: 0;
                padding: 0;
            }
    
            .clearfix {
                zoom: 1;
            }
    
            .clearfix:after {
                clear: both;
            }
    
            .clearfix:after {
                content: '.';
                display: block;
                overflow: hidden;
                visibility: hidden;
                font-size: 0;
                line-height: 0;
                width: 0;
                height: 0;
            }
    
            a {
                text-decoration: none;
                color: #333;
            }
    
            img {
                vertical-align: middle;
            }
    
            .page-shopping-cart {
                width: 1200px;
                margin: 50px auto;
                font-size: 14px;
                border: 1px solid #e3e3e3;
                border-top: 2px solid #317ee7;
            }
    
            .page-shopping-cart .cart-title {
                color: #317ee7;
                font-size: 16px;
                text-align: left;
                padding-left: 20px;
                line-height: 68px;
            }
    
            .page-shopping-cart .red-text {
                color: #e94826;
            }
    
            .page-shopping-cart .check-span {
                display: block;
                width: 24px;
                height: 20px;
                background: url("shopping_cart.png") no-repeat 0 0;
            }
    
            .page-shopping-cart .check-span.check-true {
                background: url("shopping_cart.png") no-repeat 0 -22px;
            }
    
            .page-shopping-cart .td-check {
                width: 70px;
            }
    
            .page-shopping-cart .td-product {
                width: 460px;
            }
    
            .page-shopping-cart .td-num, .page-shopping-cart .td-price, .page-shopping-cart .td-total {
                width: 160px;
            }
    
            .page-shopping-cart .td-do {
                width: 150px;
            }
    
            .page-shopping-cart .cart-product-title {
                text-align: center;
                height: 38px;
                line-height: 38px;
                padding: 0 20px;
                background: #f7f7f7;
                border-top: 1px solid #e3e3e3;
                border-bottom: 1px solid #e3e3e3;
            }
    
            .page-shopping-cart .cart-product-title .td-product {
                text-align: center;
                font-size: 14px;
            }
    
            .page-shopping-cart .cart-product-title .td-check {
                text-align: left;
            }
    
            .page-shopping-cart .cart-product-title .td-check .check-span {
                margin: 9px 6px 0 0;
            }
    
            .page-shopping-cart .cart-product {
                padding: 0 20px;
                text-align: center;
            }
    
            .page-shopping-cart .cart-product table {
                width: 100%;
                text-align: center;
                font-size: 14px;
            }
    
            .page-shopping-cart .cart-product table td {
                padding: 20px 0;
            }
    
            .page-shopping-cart .cart-product table tr {
                border-bottom: 1px dashed #e3e3e3;
            }
    
            .page-shopping-cart .cart-product table tr:last-child {
                border-bottom: none;
            }
    
            .page-shopping-cart .cart-product table .product-num {
                border: 1px solid #e3e3e3;
                display: inline-block;
                text-align: center;
            }
    
            .page-shopping-cart .cart-product table .product-num .num-do {
                width: 24px;
                height: 28px;
                display: block;
                background: #f7f7f7;
            }
    
            .page-shopping-cart .cart-product table .product-num .num-reduce span {
                background: url("shopping_cart.png") no-repeat -40px -22px;
                display: block;
                width: 6px;
                height: 2px;
                margin: 13px auto 0 auto;
            }
    
            .page-shopping-cart .cart-product table .product-num .num-add span {
                background: url("shopping_cart.png") no-repeat -60px -22px;
                display: block;
                width: 8px;
                height: 8px;
                margin: 10px auto 0 auto;
            }
    
            .page-shopping-cart .cart-product table .product-num .num-input {
                width: 42px;
                height: 28px;
                line-height: 28px;
                border: none;
                text-align: center;
            }
    
            .page-shopping-cart .cart-product table .td-product {
                text-align: left;
                font-size: 12px;
                line-height: 20px;
            }
    
            .page-shopping-cart .cart-product table .td-product img {
                border: 1px solid #e3e3e3;
                margin-right: 10px;
            }
    
            .page-shopping-cart .cart-product table .td-product .product-info {
                display: inline-block;
                vertical-align: middle;
            }
    
            .page-shopping-cart .cart-product table .td-do {
                font-size: 12px;
            }
    
            .page-shopping-cart .cart-product-info {
                height: 50px;
                line-height: 50px;
                background: #f7f7f7;
                padding-left: 20px;
            }
    
            .page-shopping-cart .cart-product-info .delect-product {
                color: #666;
            }
    
            .page-shopping-cart .cart-product-info .delect-product span {
                display: inline-block;
                vertical-align: top;
                margin: 18px 8px 0 0;
                width: 13px;
                height: 15px;
                background: url("shopping_cart.png") no-repeat -60px 0;
            }
    
            .page-shopping-cart .cart-product-info .product-total {
                font-size: 14px;
                color: #e94826;
            }
    
            .page-shopping-cart .cart-product-info .product-total span {
                font-size: 20px;
            }
    
            .page-shopping-cart .cart-product-info .check-num {
                color: #333;
            }
    
            .page-shopping-cart .cart-product-info .check-num span {
                color: #e94826;
            }
    
            .page-shopping-cart .cart-product-info .keep-shopping {
                color: #666;
                margin-left: 40px;
            }
    
            .page-shopping-cart .cart-product-info .keep-shopping span {
                display: inline-block;
                vertical-align: top;
                margin: 18px 8px 0 0;
                width: 15px;
                height: 15px;
                background: url("shopping_cart.png") no-repeat -40px 0;
            }
    
            .page-shopping-cart .cart-product-info .btn-buy {
                height: 50px;
                color: #fff;
                font-size: 20px;
                display: block;
                width: 110px;
                background: #ff7700;
                text-align: center;
                margin-left: 30px;
            }
    
            .page-shopping-cart .cart-worder {
                padding: 20px;
            }
    
            .page-shopping-cart .cart-worder .choose-worder {
                color: #fff;
                display: block;
                background: #39e;
                width: 140px;
                height: 40px;
                line-height: 40px;
                border-radius: 4px;
                text-align: center;
                margin-right: 20px;
            }
    
            .page-shopping-cart .cart-worder .choose-worder span {
                display: inline-block;
                vertical-align: top;
                margin: 9px 10px 0 0;
                width: 22px;
                height: 22px;
                background: url("shopping_cart.png") no-repeat -92px 0;
            }
    
            .page-shopping-cart .cart-worder .worker-info {
                color: #666;
            }
    
            .page-shopping-cart .cart-worder .worker-info img {
                border-radius: 100%;
                margin-right: 10px;
            }
    
            .page-shopping-cart .cart-worder .worker-info span {
                color: #000;
            }
    
            .choose-worker-box {
                width: 620px;
                background: #fff;
            }
    
            .choose-worker-box .box-title {
                height: 40px;
                line-height: 40px;
                background: #F7F7F7;
                text-align: center;
                position: relative;
                font-size: 14px;
            }
    
            .choose-worker-box .box-title a {
                display: block;
                position: absolute;
                top: 15px;
                right: 16px;
                width: 10px;
                height: 10px;
                background: url("shopping_cart.png") no-repeat -80px 0;
            }
    
            .choose-worker-box .box-title a:hover {
                background: url("shopping_cart.png") no-repeat -80px -22px;
            }
    
            .choose-worker-box .worker-list {
                padding-top: 30px;
                height: 134px;
                overflow-y: auto;
            }
    
            .choose-worker-box .worker-list li {
                float: left;
                width: 25%;
                text-align: center;
                margin-bottom: 30px;
            }
    
            .choose-worker-box .worker-list li p {
                margin-top: 8px;
            }
    
            .choose-worker-box .worker-list li.cur a {
                color: #f70;
            }
    
            .choose-worker-box .worker-list li.cur a img {
                border: 1px solid #f70;
            }
    
            .choose-worker-box .worker-list li a:hover {
                color: #f70;
            }
    
            .choose-worker-box .worker-list li a:hover img {
                border: 1px solid #f70;
            }
    
            .choose-worker-box .worker-list li img {
                border: 1px solid #fff;
                border-radius: 100%;
            }
        </style>
    </head>
    <body>
    <div class="page-shopping-cart" id="shopping-cart">
        <h4 class="cart-title">购物清单</h4>
        <div class="cart-product-title clearfix">
            <div class="td-check fl"><span class="check-span fl check-all" :class="{'check-true':isSelectAll}" @click="selectProduct(isSelectAll)"></span>全选</div>
            <div class="td-product fl">商品</div>
            <div class="td-num fl">数量</div>
            <div class="td-price fl">单价(元)</div>
            <div class="td-total fl">金额(元)</div>
            <div class="td-do fl">操作</div>
        </div>
        <div class="cart-product clearfix">
            <table>
                <tbody>
                <!--遍历的时候带上索引-->
                <tr v-for="(item,index) in productList">
                    <td class="td-check"><span class="check-span" @click="item.select=!item.select" :class="{'check-true':item.select}"></span></td>
                    <td class="td-product"><img :src="item.pro_img" width="98" height="98">
                        <div class="product-info">
                            <h6>{{item.pro_name}}</h6>
                            <p>品牌:{{item.pro_brand}}&nbsp;&nbsp;产地:{{item.pro_place}}</p>
                            <p>规格/纯度:{{item.pro_purity}}&nbsp;&nbsp;起定量:{{item.pro_min}}</p>
                            <p>配送仓储:{{item.pro_depot}}</p>
                        </div>
                        <div class="clearfix"></div>
                    </td>
                    <td class="td-num">
                        <div class="product-num">
                            <a href="javascript:;" class="num-reduce num-do fl" @click="item.pro_num--"><span></span></a>
                            <input type="text" class="num-input" v-model="item.pro_num">
                            <a href="javascript:;" class="num-add num-do fr" @click="item.pro_num++"><span></span></a>
                        </div>
                    </td>
                    <td class="td-price">
                        <p class="red-text">¥<span class="price-text">{{item.pro_price.toFixed(2)}}</span></p>
                    </td>
                    <td class="td-total">
                        <p class="red-text">¥<span class="total-text">{{item.pro_price*item.pro_num}}</span>.00</p>
                    </td>
                    <td class="td-do"><a href="javascript:;" class="product-delect" @click="deleteOneProduct(index)">删除</a></td>
                </tr>
                </tbody>
            </table>
        </div>
        <div class="cart-product-info">
            <a class="delect-product" href="javascript:;" @click="deleteProduct"><span></span>删除所选商品</a>
            <a class="keep-shopping" href="#"><span></span>继续购物</a>
            <a class="btn-buy fr" href="javascript:;">去结算</a>
            <p class="fr product-total">¥<span>{{getTotal.totalPrice}}</span></p>
            <p class="fr check-num"><span>{{getTotal.totalNum}}</span>件商品总计(不含运费):</p>
        </div>
    </div>
    </body>
    <script src="vue.min.js"></script>
    <script>
        new Vue({
            el: '#shopping-cart',
            data: {
                productList: [
                    {
                        'pro_name': '【斯文】甘油 | 丙三醇',//产品名称
                        'pro_brand': 'skc',//品牌名称
                        'pro_place': '韩国',//产地
                        'pro_purity': '99.7%',//规格
                        'pro_min': "215千克",//最小起订量
                        'pro_depot': '上海仓海仓储',//所在仓库
                        'pro_num': 3,//数量
                        'pro_img': '../../images/ucenter/testimg.jpg',//图片链接
                        'pro_price': 800//单价
                    },
                    {
                        'pro_name': '【斯文】甘油 | 丙三醇',//产品名称
                        'pro_brand': 'skc',//品牌名称
                        'pro_place': '韩国',//产地
                        'pro_purity': '99.7%',//规格
                        'pro_min': "215千克",//最小起订量
                        'pro_depot': '上海仓海仓储',//所在仓库
                        'pro_num': 3,//数量
                        'pro_img': '../../images/ucenter/testimg.jpg',//图片链接
                        'pro_price': 800//单价
                    },
                    {
                        'pro_name': '【斯文】甘油 | 丙三醇',//产品名称
                        'pro_brand': 'skc',//品牌名称
                        'pro_place': '韩国',//产地
                        'pro_purity': '99.7%',//规格
                        'pro_min': "215千克",//最小起订量
                        'pro_depot': '上海仓海仓储',//所在仓库
                        'pro_num': 3,//数量
                        'pro_img': '../../images/ucenter/testimg.jpg',//图片链接
                        'pro_price': 800//单价
                    }
                ]
            },
            computed: {
                //检测是否全选
                isSelectAll:function(){
                    //如果productList中每一条数据的select都为true,返回true,否则返回false;
                    return this.productList.every(function (val) { return val.select});
                },
                //获取总价和产品总件数
                getTotal:function(){
                    //获取productList中select为true的数据。
                    var _proList=this.productList.filter(function (val) { return val.select}),totalPrice=0;
                    for(var i=0,len=_proList.length;i<len;i++){
                        //总价累加
                        totalPrice+=_proList[i].pro_num*_proList[i].pro_price;
                    }
                    //选择产品的件数就是_proList.length,总价就是totalPrice
                    return {totalNum:_proList.length,totalPrice:totalPrice}
                }
            },
            methods: {
                //全选与取消全选
                selectProduct:function(_isSelect){
                    //遍历productList,全部取反
                    for (var i = 0, len = this.productList.length; i < len; i++) {
                        this.productList[i].select = !_isSelect;
                    }
                },
                //删除已经选中(select=true)的产品
                deleteProduct:function () {
                    this.productList=this.productList.filter(function (item) {return !item.select})
                },
                //删除单条产品
                deleteOneProduct:function (index) {
                    //根据索引删除productList的记录
                    this.productList.splice(index,1);
                },
            },
            mounted: function () {
                var _this=this;
                //为productList添加select(是否选中)字段,初始值为true
                this.productList.map(function (item) {
                    _this.$set(item, 'select', true);
                })
            }
        })
    </script>
    </html>  
          

5.todoList

运行效果

clipboard.png

原理分析和实现

首先,还是先把布局写好,和引入vue,准备vue实例,这个不多说,代码如下

  <!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
        <style>
            body{font-family: "微软雅黑";font-size: 14px;}
            input{font-size: 14px;}
            body,ul,div,html{padding: 0;margin: 0;}
            .hidden{display: none;}
            .main{width: 800px;margin: 0 auto;}
            li{list-style-type: none;line-height: 40px;position: relative;border: 1px solid transparent;padding: 0 20px;}
            li .type-span{display: block;width: 10px;height: 10px;background: #ccc;margin: 14px 10px 0 0 ;float: left;}
            li .close{position: absolute;color: #f00;font-size: 20px;line-height: 40px;height: 40px;right: 20px;cursor: pointer;display: none;top: 0;}
            li:hover{border: 1px solid #09f;}
            li:hover .close{display: block;}
            li .text-keyword{height: 40px;padding-left: 10px;box-sizing: border-box;margin-left: 10px;width: 80%;display: none;}
            .text-keyword{box-sizing: border-box;width: 100%;height: 40px;padding-left: 10px;outline: none;}
        </style>
    </head>
    <body>
        <div id="app" class="main">
            <h2>小目标列表</h2>
            <div class="list">
                <h3>添加小目标</h3>
                <input type="text" class="text-keyword" placeholder="输入小目标后,按回车确认"/>
                <p>共有N个目标</p>
                <p>
                    <input type="radio" name="chooseType" checked="true"/><label>所有目标</label>
                    <input type="radio" name="chooseType"/><label>已完成目标</label>
                    <input type="radio" name="chooseType"/><label>未完成目标</label>
                </p>
            </div>
            <ul>
                <li class="li1">
                    <div>
                        <span class="type-span"></span>
                        <span>html5</span>
                        <span class="close">X</span>
                    </div>
                </li>
                <li class="li1">
                    <div>
                        <span class="type-span"></span>
                        <span>css3</span>
                        <span class="close">X</span>
                    </div>
                </li>
            </ul>
        </div>
    </body>
    <script src="vue2.4.2.js"></script>
    <script type="text/javascript">
    new Vue({
        el: "#app",
        data: {
        },
        computed:{
            
        },
        methods:{
            
        }
    });
    </script>
</html>

布局有了,相当于一个骨架就有了,下面实现功能,一个一个来

步骤1

输入并回车,多一条记录。下面的记录文字也会改变

clipboard.png

首先,大的输入框回车要添加纪录,那么输入框必须绑定一个值和一个添加纪录的方法。
代码如下:
然后,下面的记录也要改变,所以,下面的记录也要帮一个值,因为这个记录可能会有多个,这个值就是一个数组,也可以看到,记录除了名称,还有记录是否完成的状态,所以,绑定记录的这个值肯定是一个对象数组!代码如下
最后,记录文字 clipboard.png要改变。这个只是一个当前记录的长度即可!

为了着重表示我修改了什么地方,代码我现在只贴出修改的部分,大家对着上面的布局,就很容易知道我改的是什么地方了!下面也是这样操作!

html代码

  <!--利用v-model把addText绑定到input-->
<input type="text" class="text-keyword" placeholder="输入小目标后,按回车确认" @keyup.13='addList' v-model="addText"/>
<p>共有{{prolist.length}}个目标</p>
<!--v-for遍历prolist-->
<li class="li1" v-for="list in prolist">
    <div>
        <span class="type-span"></span>
        <span>{{list.name}}</span>
        <span class="close">X</span>
    </div>
</li>

js代码

  new Vue({
    el: "#app",
    data: {
        addText:'',
        //name-名称,status-完成状态
       prolist:[
               {name:"HTML5",status:false},
               {name:"CSS3",status:false},
               {name:"vue",status:false},
               {name:"react",status:false}
        ]
    },
    computed:{
        
    },
    methods:{
        addList(){
            //添加进来默认status=false,就是未完成状态
            this.prolist.push({
                name:this.addText,
                status:false
            });
            //添加后,清空addText
            this.addText="";
        }
    }
});

测试一下,没问题

clipboard.png

步骤2

点击切换,下面记录会改变

clipboard.png

看到三个选项,也很简单,无非就是三个选择,一个是所有的目标,一个是所有已经完成的目标,一个是所有没完成的目标。
首先.新建一个新的变量(newList),储存prolist。遍历的时候不再遍历prolist,而是遍历newList。改变也是改变newList。
然后.选择所有目标的时候,显示全部prolist,把prolist赋值给newList。
然后.选择所有已经完成目标的时候,只显示prolist中,status为true的目标,把prolist中,status为true的项赋值给newList,
最后.选择所有未完成目标的时候,只显示status为false的目标,把prolist中,status为false的项赋值给newList。

代码如下

html

   <ul>
    <li class="li1" v-for="list in newList">
        <div>
            <span class="status-span"></span>
            <span>{{list.name}}</span>
            <span class="close" @click='delectList(index)'>X</span>
        </div>
    </li>
</ul>

js

  new Vue({
    el: "#app",
    data: {
        addText:'',
        //name-名称,status-完成状态
       prolist:[
               {name:"HTML5",status:false},
               {name:"CSS3",status:false},
               {name:"vue",status:false},
               {name:"react",status:false}
        ],
        newList:[]
    },
    computed:{
        noend:function(){
            return this.prolist.filter(function(item){
                return !item.status
            }).length;
        }
    },
    methods:{
        addList(){
            //添加进来默认status=false,就是未完成状态
            this.prolist.push({
                name:this.addText,
                status:false
            });
            //添加后,清空addText
            this.addText="";
        },
        chooseList(type){
            //type=1时,选择所有目标
            //type=2时,选择所有已完成目标
            //type=3时,选择所有未完成目标
            switch(type){
                case 1:this.newList=this.prolist;break;
                case 2:this.newList=this.prolist.filter(function(item){return item.status});break;
                case 3:this.newList=this.prolist.filter(function(item){return !item.status});break;
            }
        },
        delectList(index){
            //根据索引,删除数组某一项
            this.prolist.splice(index,1);
            //更新newList  newList可能经过this.prolist.filter()赋值,这样的话,删除了prolist不会影响到newList  那么就要手动更新newList
            this.newList=this.prolist;
        },
    },
    mounted(){
        //初始化,把prolist赋值给newList。默认显示所有目标
        this.newList=this.prolist;
    }
});

运行结果

clipboard.png

步骤3

红色关闭标识,点击会删除该记录。前面按钮点击会切换该记录完成状态,颜色也改变,记录文字也跟着改变

clipboard.png

首先点击红色关闭标识,点击会删除该记录。这个应该没什么问题,就是删除prolist的一条记录!
然后前面按钮点击会切换该记录完成状态。这个也没什么,就是改变prolist的一条记录的status字段!
最后记录文字的改变,就是记录prolist中status为false的有多少条,prolist中status为true的有多少条而已

html代码

  <!--如果noend等于0,就是全部完成了就显示‘全部完成了’,如果没有就是显示已完成多少条(prolist.length-noend)和未完成多少条(noend)-->
<p>共有{{prolist.length}}个目标,{{noend==0?"全部完成了":'已完成'+(prolist.length-noend)+',还有'+noend+'条未完成'}}</p>
  <ul>
    <li class="li1" v-for="(list,index) in newList">
        <div>
            <span class="status-span" @click="list.status=!list.status" :class="{'status-end':list.status}"></span>
            <span>{{list.name}}</span>
            <span class="close" @click='delectList(index)'>X</span>
        </div>
    </li>
</ul>

js

  new Vue({
    el: "#app",
    data: {
        addText:'',
        //name-名称,status-完成状态
       prolist:[
               {name:"HTML5",status:false},
               {name:"CSS3",status:false},
               {name:"vue",status:false},
               {name:"react",status:false}
        ],
        newList:[]
    },
    computed:{
        //计算属性,返回未完成目标的条数,就是数组里面status=false的条数
        noend:function(){
            return this.prolist.filter(function(item){
                return !item.status
            }).length;
        }
    },
    methods:{
        addList(){
            //添加进来默认status=false,就是未完成状态
            this.prolist.push({
                name:this.addText,
                status:false
            });
            //添加后,清空addText
            this.addText="";
        },
        chooseList(type){
            switch(type){
                case 1:this.newList=this.prolist;break;
                case 2:this.newList=this.prolist.filter(function(item){return item.status});break;
                case 3:this.newList=this.prolist.filter(function(item){return !item.status});break;
            }
        },
        delectList(index){
            //根据索引,删除数组某一项
            this.prolist.splice(index,1);
            //更新newList  newList可能经过this.prolist.filter()赋值,这样的话,删除了prolist不会影响到newList  那么就要手动更新newList
            this.newList=this.prolist;
        },
    },
    mounted(){
        this.newList=this.prolist;
    }
});

运行结果

clipboard.png

步骤4

文字双击会出现输入框,可输入文字,如果回车或者失去焦点,就改变文字,如果按下ESC就恢复原来的文字

clipboard.png

首先.双击出现输入框,就是双击文字后,给当前的li设置一个类名(‘ eidting’),然后写好样式。当li出现这个类名的时候,就出现输入框,并且隐藏其它内容。
然后.回车或者失去焦点,就改变文字这个只需要操作一个,就是把类名(‘ eidting’)清除掉。然后输入框就会隐藏,其它内容显示!
最后.按下ESC就恢复原来的文字,就是出现输入框的时候,用一个变量(‘ beforeEditText’)先保存当前的内容,然后按下了ESC,就把变量(‘ beforeEditText’)赋值给当前操作的值!

代码如下:

html

  <ul>
    <li class="li1" v-for="(list,index) in newList" :class="{'eidting':curIndex===index}">
        <div>
            <span class="status-span" @click="list.status=!list.status" :class="{'status-end':list.status}"></span>
            <span @dblclick="curIndex=index">{{list.name}}</span>
            <span class="close" @click='delectList(index)'>X</span>
        </div>
        <input type="text" class="text2" v-model='list.name' @keyup.esc='cancelEdit(list)' @blur='edited' @focus='editBefore(list.name)' @keyup.enter='edited'/>
    </li>
</ul>

css(加上)

  li div{display: block;}
li.eidting div{display: none;}
li .text2{height: 40px;padding-left: 10px;box-sizing: border-box;margin-left: 10px;width: 80%;display: none;}
li.eidting .text2{display: block;}

js

  methods:{
        addList(){
            //添加进来默认status=false,就是未完成状态
            this.prolist.push({
                name:this.addText,
                status:false
            });
            //添加后,清空addText
            this.addText="";
        },
        chooseList(type){
            //type=1时,选择所有目标
            //type=2时,选择所有已完成目标
            //type=3时,选择所有未完成目标
            switch(type){
                case 1:this.newList=this.prolist;break;
                case 2:this.newList=this.prolist.filter(function(item){return item.status});break;
                case 3:this.newList=this.prolist.filter(function(item){return !item.status});break;
            }
        },
        delectList(index){
            //根据索引,删除数组某一项
            this.prolist.splice(index,1);
            //更新newList  newList可能经过this.prolist.filter()赋值,这样的话,删除了prolist不会影响到newList  那么就要手动更新newList
            this.newList=this.prolist;
        },
        //修改前
        editBefore(name){
            //先记录当前项(比如这一项,{name:"HTML5",status:false})
            //beforeEditText="HTML5"
            this.beforeEditText=name;
        },
        //修改完成后
        edited(){
            //修改完了,设置curIndex="",这样输入框就隐藏,其它元素就会显示。因为在li元素 写了::class="{'eidting':curIndex===index}"  当curIndex不等于index时,eidting类名就清除了!
            //输入框利用v-model绑定了当前项(比如这一项,{name:"HTML5",status:false})的name,当在输入框编辑的时候,比如改成‘HTML’,实际上当前项的name已经变成了‘HTML’,所以,这一步只是清除eidting类名,隐藏输入框而已
            //还有一个要注意的就是虽然li遍历的是newList,比如改了newList的这一项({name:"HTML5",status:false}),比如改成这样({name:"HTML",status:true})。实际上prolist的这一项({name:"HTML5",status:false}),也会被改成({name:"HTML",status:true})。因为这里是一个对象,而且公用一个堆栈!修改其中一个,另一个会被影响到
            this.curIndex="";
        },
        //取消修改
        cancelEdit(val){
            //上面说了输入框利用v-model绑定了当前项(比如这一项,{name:"HTML5",status:false})的name,当在输入框编辑的时候,比如改成‘HTML’,实际上当前项的name已经变成了‘HTML’,所以,这一步就是把之前保存的beforeEditText赋值给当前项的name属性,起到一个恢复原来值得作用!
            val.name=this.beforeEditText;
            this.curIndex="";
        }
 },

运行结果

clipboard.png

还有一个小细节,大家可能注意到了,就是双击文字,出来输入框的时候,还要自己手动点击一下,才能获得焦点,我们想双击了,输入框出来的时候,自动获取焦点,怎么办?自定义指令就行了!

  computed:{...},
methods:{...},
mounted(){...},
directives:{
    "focus":{
        update(el){
            el.focus();
        }
    }
}

然后html 调用指令

  <input type="text" class="text2" v-model='list.name' @keyup.esc='cancelEdit(list)' @blur='edited' @focus='editBefore(list.name)' @keyup.enter='edited' v-focus/>

完整代码

  <!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
        <style>
            body{font-family: "微软雅黑";font-size: 14px;}
            input{font-size: 14px;}
            body,ul,div,html{padding: 0;margin: 0;}
            .hidden{display: none;}
            .main{width: 800px;margin: 0 auto;}
            li{list-style-type: none;line-height: 40px;position: relative;border: 1px solid transparent;padding: 0 20px;}
            li .status-span{display: block;width: 10px;height: 10px;background: #ccc;margin: 14px 10px 0 0 ;float: left;}
            li .status-span.status-end{
                background: #09f;
            }
            li .close{position: absolute;color: #f00;font-size: 20px;line-height: 40px;height: 40px;right: 20px;cursor: pointer;display: none;top: 0;}
            li:hover{border: 1px solid #09f;}
            li:hover .close{display: block;}
            li div{display: block;}
            li.eidting div{display: none;}
            li .text2{height: 40px;padding-left: 10px;box-sizing: border-box;margin-left: 10px;width: 80%;display: none;}
            li.eidting .text2{display: block;}
            li .text-keyword{height: 40px;padding-left: 10px;box-sizing: border-box;margin-left: 10px;width: 80%;display: none;}
            .text-keyword{box-sizing: border-box;width: 100%;height: 40px;padding-left: 10px;outline: none;}
        </style>
    </head>
    <body>
        <div id="app" class="main">
            <h2>小目标列表</h2>
            <div class="list">
                <h3>添加小目标</h3>
                <input type="text" class="text-keyword" placeholder="输入小目标后,按回车确认" @keyup.13='addList' v-model="addText"/>
                <!--如果noend等于0,就是全部完成了就显示‘全部完成了’,如果没有就是显示已完成多少条(prolist.length-noend)和未完成多少条(noend)-->
                <p>共有{{prolist.length}}个目标,{{noend==0?"全部完成了":'已完成'+(prolist.length-noend)+',还有'+noend+'条未完成'}}</p>
                <p>
                    <input type="radio" name="chooseType" checked="true" @click='chooseList(1)'/><label>所有目标</label>
                    <input type="radio" name="chooseType" @click='chooseList(2)'/><label>已完成目标</label>
                    <input type="radio" name="chooseType" @click='chooseList(3)'/><label>未完成目标</label>
                </p>
            </div>
            <ul>
                <li class="li1" v-for="(list,index) in newList" :class="{'eidting':curIndex===index}">
                    <div>
                        <span class="status-span" @click="list.status=!list.status" :class="{'status-end':list.status}"></span>
                        <span @dblclick="curIndex=index">{{list.name}}</span>
                        <span class="close" @click='delectList(index)'>X</span>
                    </div>
                    <input type="text" class="text2" v-model='list.name' @keyup.esc='cancelEdit(list)' @blur='edited' @focus='editBefore(list.name)' @keyup.enter='edited' v-focus/>
                </li>
            </ul>
        </div>
    </body>
    <script src="vue2.4.2.js"></script>
    <script type="text/javascript">
    new Vue({
        el: "#app",
        data: {
            addText:'',
            //name-名称,status-完成状态
           prolist:[
                   {name:"HTML5",status:false},
                   {name:"CSS3",status:false},
                   {name:"vue",status:false},
                   {name:"react",status:false}
            ],
            newList:[],
            curIndex:'',
               beforeEditText:""
        },
        computed:{
            //计算属性,返回未完成目标的条数,就是数组里面status=false的条数
            noend:function(){
                return this.prolist.filter(function(item){
                    return !item.status
                }).length;
            }
        },
        methods:{
            addList(){
                //添加进来默认status=false,就是未完成状态
                this.prolist.push({
                    name:this.addText,
                    status:false
                });
                //添加后,清空addText
                this.addText="";
            },
            chooseList(type){
                //type=1时,选择所有目标
                //type=2时,选择所有已完成目标
                //type=3时,选择所有未完成目标
                switch(type){
                    case 1:this.newList=this.prolist;break;
                    case 2:this.newList=this.prolist.filter(function(item){return item.status});break;
                    case 3:this.newList=this.prolist.filter(function(item){return !item.status});break;
                }
            },
            delectList(index){
                //根据索引,删除数组某一项
                this.prolist.splice(index,1);
                //更新newList  newList可能经过this.prolist.filter()赋值,这样的话,删除了prolist不会影响到newList  那么就要手动更新newList
                this.newList=this.prolist;
            },
            //修改前
            editBefore(name){
                //先记录当前项(比如这一项,{name:"HTML5",status:false})
                //beforeEditText="HTML5"
                this.beforeEditText=name;
            },
            //修改完成后
            edited(){
                //修改完了,设置curIndex="",这样输入框就隐藏,其它元素就会显示。因为在li元素 写了::class="{'eidting':curIndex===index}"  当curIndex不等于index时,eidting类名就清除了!
                //输入框利用v-model绑定了当前项(比如这一项,{name:"HTML5",status:false})的name,当在输入框编辑的时候,比如改成‘HTML’,实际上当前项的name已经变成了‘HTML’,所以,这一步只是清除eidting类名,隐藏输入框而已
                //还有一个要注意的就是虽然li遍历的是newList,比如改了newList的这一项({name:"HTML5",status:false}),比如改成这样({name:"HTML",status:true})。实际上prolist的这一项({name:"HTML5",status:false}),也会被改成({name:"HTML",status:true})。因为这里是一个对象,而且公用一个堆栈!修改其中一个,另一个会被影响到
                this.curIndex="";
            },
            //取消修改
            cancelEdit(val){
                //上面说了输入框利用v-model绑定了当前项(比如这一项,{name:"HTML5",status:false})的name,当在输入框编辑的时候,比如改成‘HTML’,实际上当前项的name已经变成了‘HTML’,所以,这一步就是把之前保存的beforeEditText赋值给当前项的name属性,起到一个恢复原来值得作用!
                val.name=this.beforeEditText;
                this.curIndex="";
            }
        },
        mounted(){
            //初始化,把prolist赋值给newList。默认显示所有目标
            this.newList=this.prolist;
        },
        directives:{
        "focus":{
            update(el){
                el.focus();
            }
        }
    }
    });
    </script>
</html>

6.小结

好了,三个小实例在这里就说完了!别看文章这么长,其实都是基础,可能是我比较啰嗦而已!如果大家能熟透这几个小实例,相信用vue做项目也是信手拈来。基础的语法在这里了,有了基础,高级的写法也不会很难学习!如果以后,我有什么要分享的,我会继续分享。最后一句老话,如果觉得我哪里写错了,写得不好,欢迎指点!

相关 [vue 实例] 推荐:

vue快速入门的三个小实例

- - SegmentFault 最新的文章
用vue做项目也有一段时间了,之前也是写过关于vue和webpack构建项目的相关文章,大家有兴趣可以去看下 webpack+vue项目实战(一,搭建运行环境和相关配置)(这个系列一共有5篇文章,这是第一篇,其它几篇文章链接就不贴了). 但是关于vue入门基础的文章,我还没有写过,那么今天就写vue入门的三个小实例,这三个小实例是我刚接触vue的时候的练手作品,难度从很简单到简单,都是入门级的.

ssr vuejs/vue-hackernews-2.0: HackerNews clone built with Vue 2.0, vue-router & vuex, with server-side rendering

- -
This is a demo primarily aimed at explaining how to build a server-side rendered Vue app, as a companion to our SSR documentation. #install dependenciesnpm install#or yarn#serve in dev mode, with hot reload at localhost:8080npm run dev#build for productionnpm run build#serve in production modenpm start.

Vue 移动端框架

- - IT瘾-jianshu
vonic 一个基于 vue.js 和 ionic 样式的 UI 框架,用于快速构建移动端单页应用,很简约. 中文文档| github地址| 在线预览. vux 基于WeUI和Vue(2.x)开发的移动端UI组件库. 基于webpack+vue-loader+vux可以快速开发移动端页面,配合vux-loader方便你在WeUI的基础上定制需要的样式.

vue路由权限校验

- - 掘金前端
做后台系统的时候,难免会有用户权限的判断. admin可以查看全部菜单,user只能查看部分菜单. 一开始接触这个需求的时候,完全是纯前端做的. 在配置路由的时候,加一个roles的属性,通过判断用户的roles是否与路由的roles属性相匹配来作为显示隐藏的依据. // 过滤路由 menuList-菜单 roles-用户角色 const checkMenuList = (menuList, roles) => { for (let i = 0; i < menuList.length; i++) {.

微豆 - Vue 2.0 实现豆瓣 Web App 教程

- - SegmentFault 最新的文章
一个使用 Vue.js 与 Material Design 重构 豆瓣 的项目. 项目网站 http://vdo.ralfz.com/. # 克隆项目到本地 git clone https://github.com/RalfZhang/Vdo.git # 安装依赖 npm install # 在 localhost:8080 启动项目 npm run dev.

浅谈Vue组件在实际项目中的应用

- - JDC | 京东设计中心
Vue.js 是一套构建用户界面的渐进式框架,目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件. 虽然目前 Vue 已经很火了,但不可否认的是,仍有很多人刚刚开始学习使用 Vue 来构建前端项目,从生疏的初学者到熟练运用 Vue 的过程中,不可避免地会走一些弯路. 为了实现某个功能,也许尝试过很多方法,最终蓦然回首,才发现当初犯下的错误是那么幼稚.

vue父子组件通信高级用法

- - SegmentFault 最新的文章
vue项目的一大亮点就是组件化. 使用组件可以极大地提高项目中代码的复用率,减少代码量. 但是使用组件最大的难点就是父子组件之间的通信. . . // 参数就是子组件传递出来的数据.

Vue 组件数据通信方案总结

- - IT瘾-dev
(给前端大全加星标,提升前端技能). 作者:政采云前端团队 公号 / 季节 (本文来自作者投稿). 初识 Vue.js ,了解到组件是 Vue 的主要构成部分,但组件内部的作用域是相对独立的部分,组件之间的关系一般如下图:. 组件 A 与组件 B 、C 之间是父子组件,组件 B 、C 之间是兄弟组件,而组件 A 、D 之间是隔代的关系.

vue 文件多了, webpack 热加载很慢? - V2EX

- -
点我最近刚好在看 webpack,如果楼主时间多可以去我博客看一下 webpack 相关的三篇文章,不要脸的放一下博客. 如果时间不够只是想解决这个问题,那么我尝试正儿八经回答一下. 1.把 webpack 升级到 4.0. 3.使用 DLLplugin,happypack 等构建加速插件. 4.检查 loader/eslint 是否配置错误 /不合理.

总结4个方面优化Vue项目

- - IT瘾-jianshu
1、使用v-if代替v-show. 两者的区别是:v-if不渲染DOM,v-show会预渲染DOM. 除以下情况使用v-show,其他情况尽量使用v-if. 2、v-for必须加上key,并避免同时使用v-if. 一般我们在两种常见的情况下会倾向于这样做:. 为了过滤一个列表中的项目. 比如 v-for="user in users" v-if="user.isActive".