本文转载于:猿2048网站→https://www.mk2048.com/blog/blog.php?id=hjchaaaaaa
1.前言
用vue做项目也有一段时间了。但是关于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.下面的实例,建议大家边看文章边动手做!这样思路会非常清晰,不易混乱!也不会觉得文章长(文章长是为了让大家看得更信息,贴了很多重复的代码,这些代码。html,css等完全可以跳过看)。如果只看文章,你可能未必会看完,因为文章我讲得比较细,比较长!
4.这几个实例,摘自我自己的平常练习的项目,代码已经提到github上面了(vue-demos)。欢迎大家star。!
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.选项卡
运行效果
原理分析和实现
这个很简单,无非就是一个点击切换显示而已。但是大家也要实现。如果这个看明白了,再看下面两个!这个实例应该只是一个热身和熟悉的作用!
这个的步骤只有一步,原理也没什么。我直接在代码打注释,看了注释,大家就明白了!
完整代码
<!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.统计总价
运行效果
原理分析和实现
首先,还是先把布局写好,和引入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>【斯文】甘油 | 丙三醇</h6> <p>品牌:韩国skc 产地:韩国</p> <p>规格/纯度:99.7% 起定量: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>【斯文】甘油 | 丙三醇</h6> <p>品牌:韩国skc 产地:韩国</p> <p>规格/纯度:99.7% 起定量: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>
然后准备下列表数据,根据下面表格的箭头
所以大家就知道吗,下面的数据大概是涨这样
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 var _this=this; //为productList添加select(是否选中)字段,初始值为true this.productList.map(function (item) { _this.$set(item, 'select', true); }) //要像上面这样写双向绑定才能起效,下面的写法是有问题的,双向绑定不起效的! //this.productList.map(function (item) {item.select=true}) } })
步骤1
为了着重表示我修改了什么地方,代码我现在只贴出修改的部分,大家对着上面的布局,就很容易知道我改的是什么地方了!下面也是这样操作!
点击增加和减少按钮(箭头指向地方),所属列的金额改变(红框地方)
执行步骤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}} 产地:{{item.pro_place}}</p> <p>规格/纯度:{{item.pro_purity}} 起定量:{{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>
这样,列表的数据就有了!
也可以发现,
这两个按钮的功能已经实现了,后面的金额也会发生变化!是不是感到很惊喜!其实这里没什么特别的,就是因为输入框利用v-model绑定了数量(pro_num
),然后两个按钮分别添加了事件@click="item.pro_num--"
和@click="item.pro_num++"
。比如刚开始pro_num是3,点击,pro_num
就变成2,点击,
pro_num
就变成4,然后后面的金额会改改,是因为{{item.pro_price*item.pro_num}}
。只要pro_price或者pro_num的值改变了,整一块也会改变,视图就会刷新,我们就能看到变化(这些事情是vue做的,这就是MVVM的魅力,数据驱动视图改变)。步骤2
点击所属列选择按钮(箭头指向地方),总计的金额(红框地方)和已选产品的列数(蓝框地方)和全选(黄框地方)会改变(如果已经全选了,全选按钮自动变成全选,如果没有全选,全选按钮,自动取消全选)!
首先,选择与取消选择,在这里只有两个操作(其实只有一个:改变这条记录的select
字段)。
然后改变
,如果这条记录select
为false
,就显示,否则就显示。代码如下
<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,就添加类名,显示
。
然后,
全选按钮,是否变成。这里用一个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长度为0 if(this.productList.length===0){ return false; } //如果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'
类名,显示对应的样式!
最后,
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长度为0 if(this.productList.length===0){ return false; } //如果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改变,返回值会改变,视图也会改变!
步骤3
点击全选按钮(箭头指向部分),会自动的对产品进行全选或者取消全选,下面的总计也会发生改变
做到这一步,大家应该知道,全选或者取消全选,就是改变记录的
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; } } },
步骤4
点击删除产品,会删除已经选中的,全选按钮和下面的总计,都会变化!点击每条记录后面的删除,会删除当前的这条记录。全选按钮和下面的总计,也都会变化!
首先,点击删除产品,删除已经选中。这个大家知道了怎么做了!就是遍历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}} 产地:{{item.pro_place}}</p> <p>规格/纯度:{{item.pro_purity}} 起定量:{{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); },
完整代码
样式图片
<!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}} 产地:{{item.pro_place}}</p> <p>规格/纯度:{{item.pro_purity}} 起定量:{{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>0?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(){ //如果长度为0,直接返回false if(this.productList.length===0){ return false; } //如果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
运行效果
原理分析和实现
首先,还是先把布局写好,和引入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
输入并回车,多一条记录。下面的记录文字也会改变
首先,大的输入框回车要添加纪录,那么输入框必须绑定一个值和一个添加纪录的方法。
代码如下:
然后,下面的记录也要改变,所以,下面的记录也要帮一个值,因为这个记录可能会有多个,这个值就是一个数组,也可以看到,记录除了名称,还有记录是否完成的状态,所以,绑定记录的这个值肯定是一个对象数组!代码如下
最后,记录文字
为了着重表示我修改了什么地方,代码我现在只贴出修改的部分,大家对着上面的布局,就很容易知道我改的是什么地方了!下面也是这样操作!
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=""; } } });
测试一下,没问题
步骤2
点击切换,下面记录会改变
看到三个选项,也很简单,无非就是三个选择,一个是所有的目标,一个是所有已经完成的目标,一个是所有没完成的目标。
首先.新建一个新的变量(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; } });
运行结果
步骤3
红色关闭标识,点击会删除该记录。前面按钮点击会切换该记录完成状态,颜色也改变,记录文字也跟着改变
首先点击红色关闭标识,点击会删除该记录。这个应该没什么问题,就是删除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; } });
运行结果
步骤4
文字双击会出现输入框,可输入文字,如果回车或者失去焦点,就改变文字,如果按下ESC就恢复原来的文字
首先.双击出现输入框,就是双击文字后,给当前的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=""; } },
运行结果
还有一个小细节,大家可能注意到了,就是双击文字,出来输入框的时候,还要自己手动点击一下,才能获得焦点,我们想双击了,输入框出来的时候,自动获取焦点,怎么办?自定义指令就行了!
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="changeType(index)" :class="{'status-end':list.status}"></span> <span @dblclick="curIndex=index">{{list.name}}</span> <span class="close" @click='delectList(list)'>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="vue.min.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:"", curType:0 }, 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时,选择所有未完成目标 this.curType=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; } }, /*改变单条数据的完成状态*/ changeType(index){ this.newList[index].status=!this.newList[index].status; //更新数据 this.chooseList(this.curType); }, delectList(list){ var index=this.prolist.indexOf(list); //根据索引,删除数组某一项 this.prolist.splice(index,1); //更新newList newList可能经过this.prolist.filter()赋值,这样的话,删除了prolist不会影响到newList 那么就要手动更新newList //this.newList=this.prolist; this.chooseList(this.curType); }, //修改前 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做项目也是信手拈来。基础的语法在这里了,有了基础,高级的写法也不会很难学习!如果以后,我有什么要分享的,我会继续分享。最后一句老话,如果觉得我哪里写错了,写得不好,欢迎指点!
-------------------------华丽的分割线--------------------
想了解更多,关注关注我的微信公众号:守候书阁
更多专业前端知识,请上【猿2048】www.mk2048.com