一、vue之分页组件(含勾选、过滤、ES6写法) <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>勾选和分页组件之vue2.6.10版</title> <script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script> <style> table{ border-collapse: collapse; border: 1px solid #cbcbcb; 1000px; } table td,table th { padding: 5px; border: 1px solid #cbcbcb; } table thead { background-color: #e0e0e0; color: #000; text-align: left; } .filter{ 998px; border:1px solid gray; padding:10px 0px; } .line{ display:flex } .group{ 330px; } .label{ display: inline-block; 120px; height: 24px; line-height: 24px; text-align: right; } .input{ display: inline-block; 180px; height: 24px; line-height: 24px; border-radius: 3px; } .select{ display: inline-block; 188px; height: 26px; line-height: 26x; border-radius: 3px; } </style> </head> <body> <div id="app"> <div style="padding-bottom:5px;color:red"> <button style="color:red" @click="checkDatasOne.getResultOfCheckAndFilter(divideDatasOne.isShowFilter,divideDatasOne.filterOptions)">获取勾选和过滤结果</button> <span>{{checkDatasOne.toServerDatas}}</span> </div> <div style="padding-bottom:5px"> <img :src="checkDatasOne.stateAllPages&&checkDatasOne.allExcludedIds.length===0?checkImg.yes:checkImg.no" @click="checkDatasOne.clickAllPages(divideDatasOne.tableDatas)"/> <span>{{checkDatasOne.textAllPages}}</span> </div> <div style="padding-bottom:5px"> <button @click="divideDatasOne.toggleShowFilter()">{{divideDatasOne.isShowFilter?'关闭过滤':'使用过滤'}}</button> <button @click="divideDatasOne.emptyFilterOptions({value5:10})">清空过滤</button> <button @click="divideDatasOne.request(1,divideDatasOne.eachPageItemsNum)">刷新</button> </div> <div style="margin-bottom:5px" class="filter" v-show="divideDatasOne.isShowFilter"> <div class="line"> <div class="group"> <label class="label">标签</label> <input class="input" type="text" v-model="divideDatasOne.filterOptions.value1" /> </div> <div class="group"> <label class="label">这就是长标签</label> <input class="input" type="text" v-model="divideDatasOne.filterOptions.value2" /> </div> <div class="group"> <label class="label">标签</label> <input class="input" type="text" v-model="divideDatasOne.filterOptions.value3" /> </div> </div> <div class="line" style="padding-top: 10px;"> <div class="group"> <label class="label">这就是长标签</label> <input class="input" type="text" v-model="divideDatasOne.filterOptions.value4" /> </div> <div class="group"> <label class="label">下拉框</label> <select class="select" v-model="divideDatasOne.filterOptions.value5"> <option v-for="item in selectOptions" :value="item.back">{{item.front}}</option> </select> </div> <div class="group"> <label class="label"></label> <button style="188px;height:28px" @click="divideDatasOne.request(1,divideDatasOne.eachPageItemsNum)">过滤</button> </div> </div> </div> <div style="1000px"> <table> <thead> <tr> <th><img :src="checkDatasOne.stateThisPage?checkImg.yes:checkImg.no" @click="checkDatasOne.clickThisPage(divideDatasOne.tableDatas,divideDatasOne.allItemsNum)"/></th> <th>序号</th> <th>数据1</th> <th>数据2</th> <th>数据3</th> <th>数据4</th> <th>数据5</th> <th>数据6</th> </tr> </thead> <tbody> <tr v-for="(data,index) in divideDatasOne.tableDatas"> <td><img :src="data.state?checkImg.yes:checkImg.no" @click="checkDatasOne.clickSingleItem(data,divideDatasOne.tableDatas,divideDatasOne.allItemsNum)"/></td> <td>{{(divideDatasOne.nowPageNum-1)*divideDatasOne.eachPageItemsNum + (index+1)}} </td> <td>{{ data.key1 }}</td> <td>{{ data.key2 }}</td> <td>{{ data.key3 }}</td> <td>{{ data.key4 }}</td> <td>{{ data.key5 }}</td> <td>{{ data.key6 }}</td> </tr> </tbody> </table> </div> <divide-page :divide-datas="divideDatasOne" :check-datas="checkDatasOne" :fixed-datas="fixedDatas"></divide-page> </div> </body> <script> new Vue({ el: '#app', data(){ return { divideDatasOne:{ nowPageNum:0, allPagesNum:0, allItemsNum:0, eachPageItemsNum:0, tableDatas:[], filterOptions:{value5:10}, isShowFilter:false, otherDatas:{} }, checkDatasOne:{ idKey: 'id',//每条数据的唯一标志 stateThisPage: false,//当前页所有项是否全选 allIncludedIds: [],//所有被选中数据的ID构成的数组 allExcludedIds: [],//所有没被选中数据的ID构成的数组 textAllPages: '全选未启用,没有选择任何项!',//复选框被点击后的提示文字。 stateAllPages: false,//复选框被点击后的提示文字。 toServerDatas: null, }, } }, methods: { }, created(){ this.fixedDatas = {}; this.selectOptions = [ { back: 10, front: '来' }, { back: 20, front: '来自于' }, { back: 30, front: '来自于国内' }, { back: 40, front: '来自于国内攻击' }, { back: 50, front: '来自于国内攻击-2' } ]; this.checkImg = { yes: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAADqADAAQAAAABAAAADgAAAAC98Dn6AAAA+UlEQVQoFZWSMU4DMRBF/584G7QSRcIxuAZKEykNEiUVHVTQRaKh4AIcgAvQpkukVDlBOAYNSGSlXXuwpViyYYFdS9aMZ/6bsezh5HZ3T2KhqkfosEhWqnjkyd1u3xWKdQMsfaEAB0Zilf8swfdU0w0klmpGpz1BvpbHcklbPf8Okts0CfJtWBTz/Yc++Jc8S3PZVQfKGwiuvMD6XYsMzm1dT/1jXKdQ8E0asHRrAzOzbC6UGINWHPQp1UQ/6wjF2LpmJSKfhti4Bi8+lhWP4I+gAqV1uqSi8j9WRuF3m3eMWVUJBeKxzUoYn7bEX7HDyPmB7QEHbRjyL+/+VnuXDUFOAAAAAElFTkSuQmCC', no: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAADqADAAQAAAABAAAADgAAAAC98Dn6AAAAbklEQVQoFWM8c+ZMLQMDQxUQcwAxMeAHUFEbC5CoYmNj02ZmZn5FjK6/f/+K/fr16ypIIwdIk7a29hdiNF69ehWkjIOJGMXY1IxqxBYqULEhFDiglPMDlIygKQKPryBSILUgPSCNbaC0B6RJSuQAbowizhJuOsAAAAAASUVORK5CYII=', } }, components: { dividePage: { props: { divideDatas: { type: Object, default: {} }, checkDatas: { type: Object, default: {} }, fixedDatas: { type: Object, default: {} } }, template: ` <div v-show="divideDatas.allPagesNum>=1" style="display:flex;1000px;margin-top:20px;"> <div> <button v-show="divideDatas.allPagesNum>10" @click="clickDividePage('front') " :disabled="divideDatas.nowPageNum===1" >上一页</button> <button :disabled="number==='...'" v-for="number in divideArray" @click="clickDividePage(number)" :style="{marginRight:'5px',color:number===divideDatas.nowPageNum?'red':'gray'}" >{{ number }}</button> <button v-show="divideDatas.allPagesNum>10" @click="clickDividePage('back')" :disabled="divideDatas.nowPageNum===divideDatas.allPagesNum" >下一页</button> </div> <div style="display:flex; flex:1; justify-content:flex-end;"> <div style="margin-right:20px;"> <span>转到第</span> <input type="text" v-model="customString" @keydown="clickDividePage('leap',$event)" style="30px;"> <span>页</span> <button @click="clickDividePage('leap',{which:13})">Go</button> </div> <div> <span>每页显示</span> <select v-model="divideDatas.eachPageItemsNum" @change="selectChange(divideDatas.eachPageItemsNum)"> <option v-for="item in numOptions" :value="item.back">{{item.front}}</option> </select> <span>条,</span> </div> <div> <span>{{frontMoreText}}</span> <span>{{totalText}}</span> <span>{{divideDatas.allItemsNum}}</span> <span>{{totalUnit}}</span> <span>{{backMoreText}}</span> </div> </div> </div `, data() { return { customString:'' } }, created(){ var that = this; //1、请求配置 this.url = this.fixedDatas.url || ''; this.method = this.fixedDatas.method || 'post'; this.isShowParams = this.fixedDatas.isShowParams || false;//显式还是隐式传参。有时需要在请求发出前手动改变。 //2、响应配置(前端通过这个配置,获取后台的数据) this.nowPageNum = this.fixedDatas.nowPageNum || 'nowPageNum';//来自服务器的当前页码 this.allPagesNum = this.fixedDatas.allPagesNum || 'allPagesNum';//来自服务器的所有页页数 this.allItemsNum = this.fixedDatas.allItemsNum || 'allItemsNum';//来自服务器的所有页数据数 this.eachPageItemsNum = this.fixedDatas.eachPageItemsNum || 'eachPageItemsNum';//来自服务器的每页最多数据数 this.tableDatas = this.fixedDatas.tableDatas || 'tableDatas';//来自服务器的表格数据 //3、以下配置使用哪种转圈方式(前端根据需要决定,不受后台影响) this.partCircle = this.fixedDatas.partCircle;//局部是否转圈。this.fixedDatas.partCircle=$scope.partCircle={isShow =false}。 this.isUsePartCircle = this.fixedDatas.isUsePartCircle;//局部是否转圈,由当前页的一个变量控制 this.isUseWholeCircle = this.fixedDatas.isUseWholeCircle;//全局是否转圈,由本项目的一个服务控制 //4、初始化以下数据,供页面使用(前端根据需要决定,不受后台影响) this.frontMoreText = this.fixedDatas.frontMoreText || "";//('文字 ')或者("文字 "+result.numOne+" 文字 ") this.totalText = this.fixedDatas.totalText || "";//'共' this.totalUnit = this.fixedDatas.totalUnit || '条';//总数据的单位 this.backMoreText = this.fixedDatas.backMoreText || "";//(' 文字')或者("文字 "+result.numThree+" 文字") this.numOptions = [ { back: 10, front: 10 }, { back: 20, front: 20 }, { back: 30, front: 30 }, { back: 40, front: 40 }, { back: 50, front: 50 } ]; this.request = this.divideDatas.request = function (nowPageNum,eachPageItemsNum) { //此处向后台发送请求, //1、返回正确结果result var data=[]; var allItemsNum = 193; var nowPageNum = nowPageNum||1; var eachPageItemsNum = eachPageItemsNum||10; var allPagesNum = Math.ceil(allItemsNum/eachPageItemsNum); for(var i=1;i<=allItemsNum;i++){ var obj={ id:'id'+i, key1:'数据'+(i+0), key2:'数据'+(i+1), key3:'数据'+(i+2), key4:'数据'+(i+3), key5:'数据'+(i+4), key6:'数据'+(i+5), key7:'数据'+(i+6), }; data.push(obj) } var tableDatas = data.slice((nowPageNum-1)*eachPageItemsNum,nowPageNum*eachPageItemsNum); if(that.divideDatas.trueCb){ that.divideDatas.trueCb() }else{ that.customString = nowPageNum; that.divideDatas.tableDatas = tableDatas; that.divideDatas.nowPageNum = nowPageNum; that.divideDatas.allPagesNum = allPagesNum; that.divideDatas.allItemsNum = allItemsNum; that.divideDatas.eachPageItemsNum = eachPageItemsNum; if(that.checkDatas && that.checkDatas.signCheckbox){ that.checkDatas.signCheckbox(that.divideDatas.tableDatas) } } that.createDividePage(); //2、返回错误结果 if(that.divideDatas.errorCb){ that.divideDatas.errorCb() } }; if (!this.divideDatas.isNoInit) { this.request(1,this.divideDatas.eachPageItemsNum); }; this.divideDatas.toggleShowFilter = function () { this.isShowFilter = !this.isShowFilter; if (!this.isShowFilter) { this.request(1,that.divideDatas.eachPageItemsNum); } }; this.divideDatas.emptyFilterOptions = function (extraObject) { //清空选项时,所有值恢复成默认 for(var key in this.filterOptions){ this.filterOptions[key] = undefined; }; if (extraObject) { //小部分选项的默认值不是undefined for(var key in extraObject){ this.filterOptions[key] = extraObject[key]; }; }; this.request(1,that.divideDatas.eachPageItemsNum); }; this.checkDatas.init=function(){//点击“刷新”、“过滤”、“清除过滤”时执行 this.idKey = idKey ? idKey : 'id'; this.allIncludedIds = []; this.allExcludedIds = []; this.textAllPages = '全选未启用,没有选择任何项!'; this.stateAllPages = false; this.stateThisPage = false; }; this.checkDatas.clickAllPages = function (itemArray) {//所有页所有条目全选复选框被点击时执行的函数 if(this.stateAllPages){ if(this.allExcludedIds.length>0){ this.stateAllPages = true; this.stateThisPage = true; this.textAllPages= '全选已启用,没有排除任何项!'; itemArray.forEach(function (item) { item.state = true; }); }else if(this.allExcludedIds.length==0){ this.stateAllPages = false; this.stateThisPage = false; this.textAllPages= '全选未启用,没有选择任何项!'; itemArray.forEach(function (item) { item.state = false; }); } }else{ this.stateAllPages = true; this.stateThisPage = true; this.textAllPages= '全选已启用,没有排除任何项!'; itemArray.forEach(function (item) { item.state = true; }); } this.allExcludedIds = []; this.allIncludedIds = []; }; this.checkDatas.clickThisPage = function (itemsArray,allItemsNum) {//当前页所有条目全选复选框被点击时执行的函数 var that = this; this.stateThisPage = !this.stateThisPage itemsArray.forEach(function (item) { item.state = that.stateThisPage; if (item.state) { that.delID(item[that.idKey], that.allExcludedIds); that.addID(item[that.idKey], that.allIncludedIds); } else { that.delID(item[that.idKey], that.allIncludedIds); that.addID(item[that.idKey], that.allExcludedIds); } }); if(this.stateAllPages){ if(this.stateThisPage && this.allExcludedIds.length === 0){ this.textAllPages = '全选已启用,没有排除任何项!'; }else{ this.textAllPages = '全选已启用,已排除'+ this.allExcludedIds.length + '项!排除项的ID为:' + this.allExcludedIds; } }else{ if(!this.stateThisPage && this.allIncludedIds.length === 0){ this.textAllPages='全选未启用,没有选择任何项!'; }else{ this.textAllPages = '全选未启用,已选择' + this.allIncludedIds.length + '项!选择项的ID为:' + this.allIncludedIds; } } }; this.checkDatas.clickSingleItem = function (item, itemsArray, allItemsNum) {//当前页单个条目复选框被点击时执行的函数 var that = this; item.state = !item.state; if (item.state) { this.stateThisPage = true; this.addID(item[this.idKey], this.allIncludedIds); this.delID(item[this.idKey], this.allExcludedIds); itemsArray.forEach( function (item) { if (!item.state) { that.stateThisPage = false; } }); } else { this.stateThisPage = false; this.addID(item[this.idKey], this.allExcludedIds); this.delID(item[this.idKey], this.allIncludedIds); } if(this.stateAllPages){ if(this.stateThisPage && this.allExcludedIds.length === 0){ this.textAllPages = '全选已启用,没有排除任何项!'; }else{ this.textAllPages = '全选已启用,已排除'+ this.allExcludedIds.length + '项!排除项的ID为:' + this.allExcludedIds; } }else{ if(!this.stateThisPage && this.allIncludedIds.length === 0){ this.textAllPages='全选未启用,没有选择任何项!'; }else{ this.textAllPages = '全选未启用,已选择' + this.allIncludedIds.length + '项!选择项的ID为:' + this.allIncludedIds; } } }; this.checkDatas.signCheckbox = function (itemsArray) {//标注当前页被选中的条目,在翻页成功后执行。 var that = this; if(this.stateAllPages){ this.stateThisPage = true; itemsArray.forEach(function (item) { var thisID = item[that.idKey]; var index = that.allExcludedIds.indexOf(thisID); if (index > -1) { item.state = false; that.stateThisPage = false; } else { item.state = true; } }); }else{ this.stateThisPage = true; itemsArray.forEach( function (item) { var thisID = item[that.idKey]; var index = that.allIncludedIds.indexOf(thisID); if (index === -1) { item.state = false; that.stateThisPage = false; } }); } }; this.checkDatas.addID = function (id, idArray) { var index = idArray.indexOf(id); if (index === -1) { idArray.push(id);//如果当前页的单项既有勾选又有非勾选,这时勾选当前页全选,需要这个判断,以免重复添加 } }; this.checkDatas.delID = function (id, idArray) { var index = idArray.indexOf(id); if (index > -1) { idArray.splice(index, 1) } }; this.checkDatas.getResultOfCheckAndFilter = function (isShowFilter,filterOptions) {//获取发送给后台的所有参数。 var toServerDatas; var allIncludedIds = that.deepClone(this.allIncludedIds); var allExcludedIds = that.deepClone(this.allExcludedIds); if (!this.stateAllPages) { if (allIncludedIds.length === 0) { //return 弹窗告知:没有勾选项 } toServerDatas = { isSelectAll: false, allIncludedIds: allIncludedIds, } }else { toServerDatas = { //exclude isSelectAll: true, allExcludedIds: allExcludedIds, }; } if (isShowFilter) { for(var key in filterOptions){ toServerDatas[key]=filterOptions[key] } } this.toServerDatas=toServerDatas;//这行代码在实际项目中不需要 return toServerDatas; } }, methods: { deepClone : function (arrayOrObject) { function isArray(value) { return {}.toString.call(value) === "[object Array]"; } function isObject(value) { return {}.toString.call(value) === "[object Object]"; } var target = null; if (isArray(arrayOrObject)) target = []; if (isObject(arrayOrObject)) target = {}; for (var key in arrayOrObject) { var value = arrayOrObject[key]; if (isArray(value) || isObject(value)) { target[key] = deepClone(value); } else { target[key] = value; } } return target; }, selectChange:function(eachPageItemsNum){ this.divideDatas.eachPageItemsNum = eachPageItemsNum; this.request(1,eachPageItemsNum); }, createDividePage : function () { var divideArray = []; var allPagesNum = this.divideDatas.allPagesNum; var nowPageNum = this.divideDatas.nowPageNum; if (allPagesNum >= 1 && allPagesNum <= 10) { for (var i = 1; i <= allPagesNum; i++) { divideArray.push(i); } } else if (allPagesNum >= 11) { if (nowPageNum > 6) { divideArray.push(1); divideArray.push(2); divideArray.push(3); divideArray.push('...'); divideArray.push(nowPageNum - 1); divideArray.push(nowPageNum); } else { for (i = 1; i <= nowPageNum; i++) { divideArray.push(i); } } // 以上当前页的左边,以下当前页的右边 if (allPagesNum - nowPageNum >= 6) { divideArray.push(nowPageNum + 1); divideArray.push(nowPageNum + 2); divideArray.push('...'); divideArray.push(allPagesNum - 2); divideArray.push(allPagesNum - 1); divideArray.push(allPagesNum); } else { for (var i = nowPageNum + 1; i <= allPagesNum; i++) { divideArray.push(i); } } } this.divideArray = divideArray; }, clickDividePage : function (stringOfNum, event) { var allPagesNum = this.divideDatas.allPagesNum; var nowPageNum = this.divideDatas.nowPageNum; if (stringOfNum === 'front' && nowPageNum != 1) { nowPageNum--; } else if (stringOfNum === 'back' && nowPageNum != allPagesNum) { nowPageNum++; } else if (stringOfNum === 'leap') { if (event.which != 13) return;//不拦截情形:(1)聚焦输入框、按“Enter”键时;(2)点击“GO”时 var customNum = Math.ceil(parseFloat(this.customString)); if (customNum < 1 || customNum == 'NaN') { nowPageNum = 1;//不给提示 } else if(customNum > allPagesNum) { nowPageNum = allPagesNum;//不给提示 } else { nowPageNum = customNum; } } else { nowPageNum = Math.ceil(parseFloat(stringOfNum)); } this.request(nowPageNum,this.divideDatas.eachPageItemsNum); }, } } }, }) </script> </html> 附:vue之分页组件(含勾选、过滤、ES6写法) <template> <div v-show="divideDatas.allPagesNum>=1" style="display:flex;1000px;margin-top:20px;"> </div> </template> <script> import comTab from '@/components/ComTab' export default { name: 'dividePage', components: { comTab }, props: { }, data() { return { } }, created(){ }, methods: { } } </script> <style rel="stylesheet/scss" lang="scss" scoped> </style> 二、Vue框架源码(Observer、Dep和Watcher) 1、自动执行混入 (1)执行initMixin(Vue);stateMixin(Vue);eventsMixin(Vue);lifecycleMixin(Vue);renderMixin(Vue); (2)其中执行initMixin(Vue),产生Vue.prototype._init 2、手动执行类 (1)执行new Vue(options);执行this._init(options);执行initState(vm);initLifecycle(vm);initEvents(vm);initRender(vm);callHook(vm,"beforeCreate");initInjections(vm);initProvide(vm);callHook(vm,"created"); (2)其中执行initState(vm);执行initProps();initData();initComputed();initMethods();initWatch();vm.$mount(vm.$options.el) 3、Vue响应式(Observer、Dep和Watcher) (1)initProps(vm,opts.props)用defineReactive$$1将属性定义为响应式; (2)initData(vm)用observe将数据定义为响应式,vue数据和Observer实例互相绑定; (3)initComputed(vm,opts.computed)执行new Watcher,vue实例和Watcher实例互相绑定,不执行this.get函数,用vm._computedWatchers[key]存储Watcher实例,用defineComputed定义计算属性为只读(get)响应式,页面渲染时才会触发get,此时所用的值都已确定; (4)initWatch或mountComponent执行,new Watcher执行,vue实例和Watcher实例互相绑定,执行this.get函数,给Dep.target赋值,执行this.getter函数,触发响应式的get函数,获取value的旧值,通过dep.depend把watcher实例存放到dep.subs里; (5)数据变化,触发响应式的set函数,通过dep.notify执行dep.subs里watcher实例的cb函数; (6)页面初次渲染时,用初始值和由初始值计算而来的计算属性值渲染页面;更新初始值时,执行watch监听函数,用更新值和由更新值计算而来的新计算属性值渲染页面。 4、给数组方法绑定响应式 var methodsToPatch = ["push","pop","shift","unshift","splice","sort","reverse"]; var arrayProto = Array.prototype; var arrayMethods = Object.create(arrayProto); {}.__proto__ = arrayMethods.__proto__ = arrayProto; methodsToPatch.forEach(function (method) { def(arrayMethods, method, function mutator() {}); }); ['a','b','c'].__proto__ = arrayMethods; function def(obj, key, val, enumerable) { Object.defineProperty(obj, key, { configurable: true, enumerable: !!enumerable, value: val, writable: true, }); } Object.defineProperty(myObj, "key", { configurable: false, enumerable: false, get: function () { console.log(this); return key+2; }, set: function (value) { console.log(this); key = value + 1; }, }); 三、Vue.set应用实例 Vue框架只对数组方法中的'push','pop','shift','unshift','splice','sort','reverse'实现了响应式。通过索引改变数组,没有执行发布函数,没法执行订阅函数,需要通过Vue.set来执行发布函数,实现响应式。 <!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head> <body> <div id="app2"> <p v-for="item in items" :key="item.id"> {{item.message}} </p> <button class="btn" @click="btn2Click()">动态赋值</button><br /> <button class="btn" @click="btn3Click()">为data新增属性</button> </div> </body> </html> <script> var vm2 = new Vue({ el: "#app2", data: { items: [ { message: "Test one", id: "1" }, { message: "Test two", id: "2" }, { message: "Test three", id: "3" } ] }, methods: { btn2Click: function () { Vue.set(this.items, 0, { message: "Change Test", id: '10' }) }, btn3Click: function () { var itemLen = this.items.length; Vue.set(this.items, itemLen, { message: "Test add attr", id: itemLen }); } } }); </script> <!DOCTYPE html> <html> <head> <title></title> <meta charset="utf-8"> <script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script> </head> <body> <div id="box">{{msg}}||{{reMsg}}</div> <script type="text/javascript"> var vm = new Vue({ el:'#box', data:{ msg:'12345' }, computed:{ reMsg:function(instance){ console.log(instance===this);//true return this.msg.split('').reverse().join('') } } }); </script> </body> </html> 四、基于ElementUI的vue自定义组件子改父 1、通过属性传函数参数来实现 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> <title>子改父:通过属性传参</title> <script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script> <script src="https://cdn.bootcss.com/element-ui/2.10.1/index.js"></script> <link href="https://cdn.bootcss.com/element-ui/2.10.1/theme-chalk/index.css" rel="stylesheet"> <style> #app{ display: flex; justify-content: space-between; } .parent, .child{ 45%; } .el-card{ height: 100%; } </style> </head> <body> <div style="margin: 30px 0;">本案例改编自https://www.bbsmax.com/A/kjdwmRaOJN/</div> <div style="margin-bottom: 30px;"> <div>总逻辑 </div> <div>父组件通过属性传参,给子组件传值 </div> <div>父组件通过属性传参,给子组件传属性函数 </div> <div>触发子组件的某个事件,执行属性函数,改变父组件的值 </div> </div> <div id="app"> <div class="parent"> <el-card> <div slot="header"> <span>父组件</span> </div> <el-input v-model="ParentMsg"></el-input> <el-button type="primary" @click="changeChild" style="margin-top: 44px">父组件改变子组件</el-button> </el-card> </div> <div class="child"> <el-card> <div slot="header"> <span>子组件</span> </div> <child :self-msg="childMsg" :fn="changeParent"></child> </el-card> </div> </div> </body> <script> new Vue({ el: '#app', data(){ return { ParentMsg:'父组件的内容', childMsg:'父组件传给子组件的内容' } }, methods: { changeParent(data){ this.ParentMsg = data, this.childMsg = '子-组件传给子组件的内容' }, changeChild(){ this.ParentMsg = '父-组件传给父组件的内容', this.childMsg = '父-组件传给子组件的内容' } }, components: { child:{ props: { selfMsg: { type: String, default: '' }, fn: { type: Function, default: function(){} } }, template: ` <div> <p>{{selfMsg}}</p> <el-button type='primary' @click='fromChild' style='margin-top: 30px'>子组件改变父组件</el-button> </div> `, data () { return { } }, methods:{ fromChild () { this.fn('子-组件传给父组件的内容') } } } }, }) </script> </html> 2、通过属性传自定义事件来实现 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> <title>Title</title> <script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script> <script src="https://cdn.bootcss.com/element-ui/2.10.1/index.js"></script> <link href="https://cdn.bootcss.com/element-ui/2.10.1/theme-chalk/index.css" rel="stylesheet"> <style> #app{ display: flex; justify-content: space-between; } .parent, .child{ 45%; } .el-card{ height: 100%; } </style> </head> <body> <div style="margin: 30px 0;">本案例改编自https://www.bbsmax.com/A/kjdwmRaOJN/</div> <div style="margin-bottom: 30px;"> <div>总逻辑 </div> <div>父组件通过属性传参,给子组件传值 </div> <div>父组件通过属性传参,给子组件传自定义事件及执行函数 </div> <div>触发子组件的某个事件,发射自定义事件,并给执行函数传参,改变父组件的值 </div> </div> <div id="app"> <div class="parent"> <el-card> <div slot="header"> <span>父组件</span> </div> <el-input v-model="ParentMsg"></el-input> <el-button type="primary" @click="changeChild" style="margin-top: 44px">父组件改变子组件</el-button> </el-card> </div> <div class="child"> <el-card> <div slot="header"> <span>子组件</span> </div> <child :self-msg="childMsg" @from-child="changeParent"></child> </el-card> </div> </div> </body> <script> new Vue({ el: '#app', data(){ return { ParentMsg:'父组件的内容', childMsg:'父组件传给子组件的内容' } }, methods: { changeParent(data){ this.ParentMsg = data, this.childMsg = '子-组件传给子组件的内容' }, changeChild(){ this.ParentMsg = '父-组件传给父组件的内容', this.childMsg = '父-组件传给子组件的内容' } }, components: { child:{ props: { selfMsg: { type: String, default: '' } }, template: ` <div> <p>{{selfMsg}}</p> <el-button type='primary' @click='fromChild' style='margin-top: 30px'>子组件改变父组件</el-button> </div> `, data () { return { } }, methods:{ fromChild () { this.$emit('from-child', '子-组件传给父组件的内容') } } } }, }) </script> </html> 五、el-dialog三层弹窗 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> <title>vue2.6.10组件el-dialog之三层弹窗</title> <script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script> <script src="https://cdn.bootcss.com/element-ui/2.10.1/index.js"></script> <link href="https://cdn.bootcss.com/element-ui/2.10.1/theme-chalk/index.css" rel="stylesheet"> <style> #app{ display: flex; justify-content: space-between; } .parent, .child{ 45%; } .el-card{ height: 100%; } </style> </head> <body> <div id="app"> <el-button type="text" @click="outerVisible = true">点击打开外层弹窗</el-button> <!-- 以下是外层 --> <el-dialog width="70%" title="外层" :visible.sync="outerVisible"> 这是外层 <!-- 以下是中层 --> <el-dialog width="50%" title="中层" :visible.sync="middleVisible" append-to-body> 这是中层 <!-- 以下是内层 --> <el-dialog width="30%" title="内层" :visible.sync="innerVisible" append-to-body> 这是内层 <div slot="footer" class="dialog-footer"> <el-button @click="innerVisible = false">关闭内层</el-button> <el-button type="primary" @click="innerVisible = false">关闭内层</el-button> </div> </el-dialog> <!-- 以上是内层 --> <div slot="footer" class="dialog-footer"> <el-button @click="middleVisible = false">关闭中层</el-button> <el-button type="primary" @click="innerVisible = true">打开内层</el-button> </div> </el-dialog> <!-- 以上是中层 --> <div slot="footer" class="dialog-footer"> <el-button @click="outerVisible = false">关闭外层</el-button> <el-button type="primary" @click="middleVisible = true">打开中层</el-button> </div> </el-dialog> <!-- 以上是外层 --> </div> </body> <script> new Vue({ el: '#app', data() { return { outerVisible: false, middleVisible: false, innerVisible: false }; }, methods: { }, components: { }, }) </script> </html> 六、elementUI各弹窗的区别 1、第1组(3秒钟后自动消失) (1)Message 消息提示,常用于主动操作后的反馈提示。 (2)Notification 通知,常用于系统级通知的被动提醒。 2、第2组(点击确认后消失) (1)MessageBox 弹窗,模拟系统的消息提示框alert、confirm和prompt而实现的一套模态对话框组件,用于消息提示、确认消息和提交内容。 (2)Dialog 对话框,弹出较为复杂的内容. 3、第3组(非悬停时消失) (1)Tooltip 文字提示,常用于展示鼠标hover时的提示信息。 (2)Popover 弹出框,与Tooltip类似。 七、Vue-CLI 1、Vue CLI一套基于插件的架构,package.json里的依赖都是以@vue/cli-plugin-开头的。 2、插件可以修改 webpack 的内部配置,也可以向 vue-cli-service 注入命令。 3、在项目创建的过程中,绝大部分列出的特性都是通过插件来实现的。 4、用config.module.rule('svg').exclude.add(resolve('src/icons')).end()对vue-cli-4.0里内置的'svg'模块规则进行修改 5、用config.module.rule('icons').test(/.svg$/).include.add(resolve('src/icons')).end()定义并向vue-cli-4.0里注入名为'icons'的模块规则