10、Vue之分页组件(含勾选、过滤)、五级联动、simple-dialog多层弹窗组件(含插槽slot)、el-dialog多层弹窗、ElementUI之70个组件(表格倒计时)、报错与解决、效果与实现(深度选择器、透明、飞走、聚焦focus、传值v-model、收缩动画展开、任务队列执行栈nextTick、图片预览)、Vue与React的异同(3450行)

一、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;
      width:1000px;
    }
    table td,table th {
      padding: 5px;
      border: 1px solid #cbcbcb;
    }
    table thead {
      background-color: #e0e0e0;
      color: #000;
      text-align: left;
    }
    .filter{
      width:998px;
      border:1px solid gray;
      padding:10px 0px;
    }
    .line{
      display:flex
    }
    .group{
      width:330px;
    }
    .label{
      display: inline-block;
      width:120px;
      height: 24px;
      line-height: 24px;
      text-align: right;
    }
    .input{
      display: inline-block;
      width:180px;
      height: 24px;
      line-height: 24px;
      border-radius: 3px;
    }
    .select{
      display: inline-block;
      width: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="width:188px;height:28px" @click="divideDatasOne.request(1,divideDatasOne.eachPageItemsNum)">过滤</button>
      </div>
    </div>
  </div>
  <div style="width: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;width: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="width: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) {
            var isOnce = true;
            //1、向后台发送请求,
            //2、返回正确结果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);
            //3、使用正确结果result
            that.customString = nowPageNum;
            that.divideDatas.nowPageNum = nowPageNum;
            that.divideDatas.allPagesNum = allPagesNum;
            that.divideDatas.allItemsNum = allItemsNum;
            that.divideDatas.eachPageItemsNum = eachPageItemsNum;
            that.divideDatas.tableDatas = tableDatas;//正常逻辑
            if(that.divideDatas.once && isOnce){//只使用一次,常用于初始化一些数据,比如过滤条件
              that.divideDatas.once();
              isOnce = false;
            }
            if(that.divideDatas.trueCb){//异常逻辑
              that.divideDatas.trueCb()
            }
            if(that.checkDatas && that.checkDatas.signCheckbox){//处理勾选
              that.checkDatas.signCheckbox(that.divideDatas.tableDatas)
            }
            that.createDividePage();//创建分页
            //4、处理错误结果
            if(that.divideDatas.errorCb){
              that.divideDatas.errorCb()
            }
          };
          if (!this.divideDatas.isAbandonInit) {
            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写法,可模仿“单文件”

二、Vue之五级联动--省、市、县、乡、村(含3个版本)
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Vue之五级联动--省、市、县、乡、村(含3个版本)</title>
</head>
<style type="text/css">
  .edit{
    height:40px;
    line-height: 40px;
  }
  .paddingBottom{
    padding-bottom: 40px;
  }
  .question-select {
    height: 60px;
  }
  .question-select select {
    border-radius: 5px;
    box-shadow: 0 0 5px #666;
    appearance: none;
    -webkit-appearance: none;
    -moz-appearance: none;
    border: none;
    outline: none;
    height: 40px;
    padding: 0 20px;
    color: #333;
    font-size: 22px;
  }
  .question-select select.short {
    width: 120px;
  }
  .question-select select.long {
    width: 240px;
  }
  .birth-year{
    width:90px;
    margin-right: 20px;
    height: 100px;
    overflow-y: scroll;
  }
  .birth-month{
    width:70px;
    margin-right: 20px;
  }
  .birth-date{
    width:70px;
  }
</style>
<body>
  <!-- 以下是新版本(组件)HTML -->
  <div class="edit">以下是新版本(组件),符合实际应用场景</div>
  <div id="newVersionComponent">
    <five-grade :all-datas="allDatas"></five-grade>
  </div>
  <!-- 以下是新版本(普通)HTML -->
  <div class="edit">以下是新版本(普通),符合实际应用场景</div>
  <div id="newVersionCommon">
    <div class="question-select">
      <select v-model="singleProvince" class="short" @change="selectName(singleProvince)">
        <option v-for="key in allProvinces" :value="key" v-text="key"></option>
      </select>
      <select v-model="singleCity" v-show="singleProvince" class="short" @change="selectName(singleProvince,singleCity)">
        <option v-for="key in allCitys" :value="key" v-text="key"></option>
      </select>
      <select v-model="singleCounty" v-show="singleCity" class="short" @change="selectName(singleProvince,singleCity,singleCounty)">
        <option v-for="key in allCountys" :value="key" v-text="key"></option>
      </select>
      <select v-model="singleTown" v-show="singleCounty" class="long" @change="selectName(singleProvince,singleCity,singleCounty,singleTown)">
        <option v-for="key in allTowns" :value="key" v-text="key"></option>
      </select>
      <select v-model="singleVillage" v-show="singleTown" class="long" @change="selectName(singleProvince,singleCity,singleCounty,singleTown,singleVillage)">
        <option v-for="key in allVillages" :value="key" v-text="key"></option>
      </select>
    </div>
    <div class="paddingBottom">{{address}}</div>
  </div>
  <!-- 以下是旧版本(普通)HTML -->
  <div class="edit">以下是旧版本(普通),不符合实际应用场景</div>
  <div id="oldVersion">
    <div class="question-select">
      <select v-model="singleProvince" v-if="singleProvince" class="short">
        <option v-for="(value,key) in allProvinces" :value="key" v-text="key"></option>
      </select>
      <select v-model="singleCity" v-if="singleCity" class="short">
        <option v-for="(value,key) in allCitys" :value="key" v-text="key"></option>
      </select>
      <select v-model="singleCounty" v-if="singleCounty" class="short">
        <option v-for="(value,key) in allCountys" :value="key" v-text="key"></option>
      </select>
      <select v-model="singleTown" v-if="singleTown" class="long">
        <option v-for="(value,key) in allTowns" :value="key" v-text="key"></option>
      </select>
      <select v-model="singleVillage" v-if="singleVillage" class="long">
        <option v-for="(value,key) in allVillages" :value="key" v-text="key"></option>
      </select>
    </div>
    <div class="paddingBottom">{{address}}</div>
  </div>
  <div class="edit">以下是年月日分别选择</div>
  <div id="yearMonthDate">
    <div class="question-select">
      <select v-model="yearMonthDate.year" @click="clickYear" @change="changeYear" class="birth-year">
        <option v-for="item in years" :key="item.key" :label="item.key" :value="item.value">
        </option>
      </select>
      <select v-model="yearMonthDate.month" @click="clickMonth" @change="changeMonth" class="birth-month">
        <option v-for="item in months" :key="item.key" :label="item.key" :value="item.value">
        </option>
      </select>
      <select v-model="yearMonthDate.day" @click="clickDay" class="birth-date">
        <option v-for="item in days" :key="item.key" :label="item.key" :value="item.value">
        </option>
      </select>
    </div>
    <div class="paddingBottom">您选择的年月日是:{{yearMonthDate.year}}年{{yearMonthDate.month}}月{{yearMonthDate.day}}日</div>
  </div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<!-- Vue.js v2.6.10 -->
<script type="text/javascript">
  //以下是新版本(组件)JS
  Vue.component('five-grade', {
    template: `
      <div>
        <div class="question-select">
          <select v-model="singleProvince" class="short" @change="selectName(singleProvince)">
            <option v-for="key in allProvinces" :value="key" v-text="key"></option>
          </select>
          <select v-model="singleCity" v-show="singleProvince" class="short" @change="selectName(singleProvince,singleCity)">
            <option v-for="key in allCitys" :value="key" v-text="key"></option>
          </select>
          <select v-model="singleCounty" v-show="singleCity" class="short" @change="selectName(singleProvince,singleCity,singleCounty)">
            <option v-for="key in allCountys" :value="key" v-text="key"></option>
          </select>
          <select v-model="singleTown" v-show="singleCounty" class="long" @change="selectName(singleProvince,singleCity,singleCounty,singleTown)">
            <option v-for="key in allTowns" :value="key" v-text="key"></option>
          </select>
          <select v-model="singleVillage" v-show="singleTown" class="long" @change="selectName(singleProvince,singleCity,singleCounty,singleTown,singleVillage)">
            <option v-for="key in allVillages" :value="key" v-text="key"></option>
          </select>
        </div>
        <div class="paddingBottom">{{address}}</div>
      </div>
    `,
    props: {      
      allDatas: {
        type: Object
      }
    },
    data: function(){
      return {
        allProvinces: [],
        singleProvince: '',
        allCitys: [],
        singleCity: '',
        allCountys: [],
        singleCounty: '',
        allTowns: [],
        singleTown: '',
        allVillages: [],
        singleVillage: '',
        address: '',
      }
    },
    beforeMount: function () {
      this.selectName();
    },
    methods: {
      selectName: function(singleProvince,singleCity,singleCounty,singleTown,singleVillage){
        var siteArray = [singleProvince,singleCity,singleCounty,singleTown,singleVillage];
        var modelArray = ['singleProvince','singleCity','singleCounty','singleTown','singleVillage'];
        var optionsArray = ['allProvinces','allCitys','allCountys','allTowns','allVillages'];
        var allDatasNext;
        var address = "你选择的地址是:";
        for(var i=0;i<siteArray.length;i++){//遍历数组所有项
          if(!siteArray[i]) this[modelArray[i]] = '';
        }
        for(var i=0;i<siteArray.length;i++){//遍历数组至undefined第1次出现时停止
          allDatasNext = i == 0 ? this.allDatas : allDatasNext[siteArray[i-1]];
          if(!siteArray[i]) {
            var array = [];
            for (var key in allDatasNext) {
              array.push(key)
            }
            this[optionsArray[i]] = array;
            break;
          }
        }
        if(this.singleProvince) address += this.singleProvince;
        if(this.singleCity) address += '-' + this.singleCity;
        if(this.singleCounty) address += '-' + this.singleCounty;
        if(this.singleTown) address += '-' + this.singleTown;
        if(this.singleVillage) address += '-' + this.singleVillage;
        this.address = address;
      }
    },
  });
  new Vue({
    el: '#newVersionComponent',
    data(){
      return {
        allDatas : makeallDatas()
      }
    },
    methods:{
    }
  })
  //以下是新版本(普通)JS
  var allDatas = makeallDatas();
  var vm = new Vue({
    el: '#newVersionCommon',
    data: {
      allDatas: allDatas,
      allProvinces: [],
      singleProvince: '',
      allCitys: [],
      singleCity: '',
      allCountys: [],
      singleCounty: '',
      allTowns: [],
      singleTown: '',
      allVillages: [],
      singleVillage: '',
      address: '',
    },
    beforeMount: function () {
      this.selectName()
    },
    methods: {
      selectName: function(singleProvince,singleCity,singleCounty,singleTown,singleVillage){
        var siteArray = [singleProvince,singleCity,singleCounty,singleTown,singleVillage];
        var modelArray = ['singleProvince','singleCity','singleCounty','singleTown','singleVillage'];
        var optionsArray = ['allProvinces','allCitys','allCountys','allTowns','allVillages'];
        var allDatasNext;
        var address = "你选择的地址是:";
        for(var i=0;i<siteArray.length;i++){//遍历数组所有项
          if(!siteArray[i]) this[modelArray[i]] = '';
        }
        for(var i=0;i<siteArray.length;i++){//遍历数组至undefined第1次出现时停止
          allDatasNext = i == 0 ? this.allDatas : allDatasNext[siteArray[i-1]];
          if(!siteArray[i]) {
            var array = [];
            for (var key in allDatasNext) {
              array.push(key)
            }
            this[optionsArray[i]] = array;
            break;
          }
        }
        if(this.singleProvince) address += this.singleProvince;
        if(this.singleCity) address += '-' + this.singleCity;
        if(this.singleCounty) address += '-' + this.singleCounty;
        if(this.singleTown) address += '-' + this.singleTown;
        if(this.singleVillage) address += '-' + this.singleVillage;
        this.address = address;
      }
    }
  });
  //以下是旧版本(普通)JS
  var allProvinces = makeallDatas();
  var vm = new Vue({
    el: '#oldVersion',
    data: {
      allProvinces: allProvinces,
      singleProvince: '北京市',
      allCitys: {},
      singleCity: '',
      allCountys: {},
      singleCounty: '',
      allTowns: {},
      singleTown: '',
      allVillages: {},
      singleVillage: '',
      address: '',
    },
    beforeMount: function () {
      this.update("allProvinces","singleProvince","allCitys","singleCity")
    },
    methods: {
      update: function (thisall,thisName,nextall,nextName) {
        //1个上级更新,会导致1个下级更新,进而导致1个下下级的更新...
        for (var key in this[thisall]) {
          if (key === this[thisName]) {
            this[nextall] = this[thisall][key];
            for (var key in this[nextall]) {
              this[nextName] = key;
              break;
            }
          }
        }
        var address = "你选择的地址是:";
        if(this.singleProvince) address += this.singleProvince;
        if(this.singleCity) address += '-' + this.singleCity;
        if(this.singleCounty) address += '-' + this.singleCounty;
        if(this.singleTown) address += '-' + this.singleTown;
        if(this.singleVillage) address += '-' + this.singleVillage;
        this.address = address;
      }
    },
    watch: {
      singleProvince: function () {
        this.update("allProvinces","singleProvince","allCitys","singleCity")
      },
      singleCity: function () {
        this.update("allCitys","singleCity","allCountys","singleCounty") 
      },
      singleCounty: function () {
        this.update("allCountys","singleCounty","allTowns","singleTown") 
      },
      singleTown: function () {
        this.update("allTowns","singleTown","allVillages","singleVillage")
      }
    }
  })
  //以下是年月日分别选择(普通)JS
  var vm = new Vue({
    el: '#yearMonthDate',
    data: function(){
      return {
        yearMonthDate: {
          year:1900,
          month:1,
          day:1,
        },
        years: [{
          key: 1900,
          value: 1900,
        }],
        months: [{
          key: 1,
          value: 1,
        }],
        days: [{
          key: 1,
          value: 1,
        }],
        address: '',
      }
    },
    beforeMount: function () {
      //this.selectName()
    },
    methods: {
      clickYear: function(){
        this.years.length = 0;
        var thisYear = new Date().getFullYear();
        for(var i = 1900; i <= thisYear; i++){
          this.years.push({
            key: i,
            value: i,
          })
        }
      },
      changeYear: function(){
        this.yearMonthDate.month = 1
        this.yearMonthDate.day = 1
      },
      clickMonth: function(){
        this.months.length = 0;
        for(var i = 1; i <= 12; i++){
          this.months.push({
            key: i,
            value: i,
          })
        }
        this.yearMonthDate.day = 1
      },
      changeMonth: function(){
        this.yearMonthDate.day = 1
      },
      clickDay: function(){
        this.days.length = 0;
        var dayNum = 0;
        var longMonth = [1,3,5,7,8,10,12];
        var shortMonth = [4,6,9,11];
        if (longMonth.indexOf(this.yearMonthDate.month) > -1){
          dayNum = 31;
        }else if (shortMonth.indexOf(yearMonthDate.month) > -1){
          dayNum = 30;
        }else if ( this.yearMonthDate.month === 2){//1900年及以后的闰年
          if(this.yearMonthDate.year != 1900 && this.yearMonthDate.year % 4 === 0){
            dayNum = 29;
          }else{
            dayNum = 28;
          }
        }
        for(var i = 1; i <= dayNum; i++){
          this.days.push({
            key: i,
            value: i,
          })
        }
      }
    }
  });
  //以下是所有版本JS
  function makeallDatas() {
    var allDatas = {
      "北京市": {
        "区": {
          "通州区": {
            "中仓街道办事处": {
              "滨河社区居委会": "110112001029",
              "运河湾社区居委会": "110112001030"
            },
            "漷县镇": {
              "后元化村委会": "110112106260",
              "前元化村委会": "110112106261"
            }
          },
          "昌平区": {
            "天通苑南街道办事处":{
              "东辰社区居委会":"110114009001",
              "佳运园社区居委会":"110114009002",
              "天通苑第二社区居委会":"110114009003",
              "天通西苑第一社区居委会":"110114009004",
              "天通东苑第一社区居委会":"110114009005",
              "天通东苑第二社区居委会":"110114009006",
              "天通苑第一社区居委会":"110114009007",
              "嘉诚花园社区居委会":"110114009008",
              "清水园社区居委会":"110114009009",
              "北方明珠社区居委会":"110114009010",
              "天通东苑第四社区居委会":"110114009011",
              "顶秀清溪社区居委会":"110114009012",
              "奥北中心社区居委会":"110114009013",
              "陈营村委会":"110114009201"
            },
            "霍营街道办事处": {
              "华龙苑南里社区居委会": "110114010001",
              "华龙苑北里社区居委会": "110114010002",
              "蓝天园社区居委会": "110114010003",
              "天鑫家园社区居委会": "110114010004",
              "霍营小区社区居委会": "110114010005",
              "上坡佳园社区居委会": "110114010006",
              "华龙苑中里社区居委会": "110114010007",
              "流星花园社区居委会": "110114010008",
              "龙回苑社区居委会": "110114010009",
              "和谐家园社区居委会": "110114010010",
              "田园风光雅苑社区居委会": "110114010011",
              "龙锦苑一区社区居委会": "110114010012",
              "龙锦苑东一区社区居委会": "110114010013",
              "龙锦苑东二区社区居委会": "110114010014",
              "龙锦苑东五区社区居委会": "110114010015",
              "龙锦苑东三区社区居委会": "110114010016",
              "龙锦苑东四区社区居委会": "110114010017",
              "紫金新干线社区居委会": "110114010018",
              "霍家营村委会": "110114010201"
            }
          }
        },
        "县": {
          "密云县": {
            "密云镇": {
              "小唐庄社区居委会": "110228100001",
              "李各庄社区居委会": "110228100002",
              "大唐庄社区居委会": "110228100003",
              "季庄村委会": "110228100205",
            },
            "溪翁庄镇": {
              "东智北村委会": "110228101209",
              "石墙沟村委会": "110228101210",
              "黑山寺村委会": "110228101211",
              "立新庄村委会": "110228101212",
            },
          },
          "延庆县": {
            "旧县镇": {
              "常里营村委会": "110229104215",
              "盆窑村委会": "110229104216",
              "团山村委会": "110229104217",
              "大柏老村委会": "110229104218",
            },
            "珍珠泉乡": {
              "双金草村委会": "110229214209",
              "小川村委会": "110229214210",
              "小铺村委会": "110229214211",
              "仓米道村委会": "110229214212",
            }
          }
        }
      },
      "河南省": {
        "郑州市": {
          "金水区": {
            "凤凰台街道办事处": {
              "凤凰台社区居民委员会": "410105013004",
              "王庄社区居民委员会": "410105013005",
              "张庄社区居民委员会": "410105013006",
              "凤凰城社区居民委员会": "410105013007",
            },
            "金光路街道办事处": {
              "徐庄村委会": "410105564201",
              "贾陈村委会": "410105564202",
              "柳园口村委会": "410105564203",
              "马楼村委会": "410105564204",
            }
          },
          "登封市": {
            "嵩阳街道办事处": {
              "玉溪路居委会": "410185001014",
              "苹果园居委会": "410185001015",
              "颖河路居委会": "410185001016",
              "守敬路居委会": "410185001017",
            },
            "少林街道办事处": {
              "塔沟居委会": "410185002001",
              "少林居委会": "410185002002",
              "耿庄村委会": "410185002203",
              "王庄村委会": "410185002204",
            },
            "送表矿区": {
              "东送表村委会": "410185400201",
              "梁庄村委会": "410185400202",
              "安庄村委会": "410185400203",
              "刘楼村委会": "410185400204",
            }
          }
        },
        "信阳市": {
          "浉河区": {
            "老城街道办事处": {
              "义阳居委会": "411502001001",
              "三里店居委会": "411502001003",
              "东方红居委会": "411502001004",
              "浉河居委会": "411502001007",
            },
            "民权街道办事处": {
              "民权居委会": "411502002001",
              "新生居委会": "411502002002",
              "成功居委会": "411502002003",
              "白果树居委会": "411502002004",
            },
            "车站街道办事处": {
              "工区东居委会": "411502003001",
              "工区路居委会": "411502003002",
              "六里棚居委会": "411502003003",
              "新公房居委会": "411502003004",
            }
          },
          "固始县": {
            "段集镇": {
              "街道居委会": "411525109001",
              "段集村委会": "411525109201",
              "棠树岗村委会": "411525109202",
              "青峰村委会": "411525109203",
              "下楼村委会": "411525109204",
              "桂岭村委会": "411525109205",
              "窑沟村委会": "411525109206",
              "五尖山村委会": "411525109207",
              "柳林村委会": "411525109208",
              "齐山村委会": "411525109209",
              "钓鱼台村委会": "411525109210",
              "蒋营村委会": "411525109211",
              "庙山村委会": "411525109212",
              "汪旱庄村委会": "411525109213",
              "乐道村委会": "411525109214",
              "姚老家村委会": "411525109215",
              "赵营村委会": "411525109216",
              "童庙村委会": "411525109217",
              "高庙村委会": "411525109218"
            },
            "武庙集镇": {
              "街道居委会": "411525113001",
              "汪小庄村委会": "411525113201",
              "黄土岭村委会": "411525113202",
              "平阳村委会": "411525113203",
              "长江河村委会": "411525113204",
              "锁口村委会": "411525113205",
              "皮冲村委会": "411525113206",
              "刘中楼村委会": "411525113207",
              "迎水寺村委会": "411525113208",
              "余楼村委会": "411525113209",
              "钱老楼村委会": "411525113210",
              "新店村委会": "411525113211",
              "徐小店村委会": "411525113212",
              "邓岭村委会": "411525113213",
              "太平村委会": "411525113214",
              "汪楼村委会": "411525113215",
              "李瓦房村委会": "411525113216"
            },
            "祖师庙镇": {
              "祖师庙居委会": "411525117001",
              "仰天洼社区居委会": "411525117002",
              "小店村委会": "411525117201",
              "王行村委会": "411525117202",
              "大冲村委会": "411525117203",
              "松林村委会": "411525117204",
              "刘楼村委会": "411525117205",
              "羁马村委会": "411525117206",
              "黄楼村委会": "411525117207",
              "彭畈村委会": "411525117208",
              "童圩村委会": "411525117209",
              "仓房村委会": "411525117210",
              "毛店村委会": "411525117211",
              "万岗村委会": "411525117212",
              "七冲村委会": "411525117213",
              "三区村委会": "411525117214",
              "杨楼村委会": "411525117215"
            }
          },
          "息县": {
            "杨店乡": {
              "杨店村委会": "411528204200",
              "安寨村委会": "411528204201",
              "何庄村委会": "411528204202",
              "李大庄村委会": "411528204203",
            },
            "张陶乡": {
              "张陶村委会": "411528205200",
              "曹林村委会": "411528205201",
              "陈圈行村委会": "411528205202",
              "大陈庄村委会": "411528205203",
            },
            "白土店乡": {
              "白土店村委会": "411528206200",
              "白衣阁村委会": "411528206201",
              "大江庄村委会": "411528206202",
              "桂庄村委会": "411528206203",
            },
            "岗李店乡": {
              "岗李店村委会": "411528207200",
              "大彭庄村委会": "411528207201",
              "方老庄村委会": "411528207202",
              "贾后寨村委会": "411528207203",
            }
          }
        },
      },
    };
    return allDatas;
  }
</script>
</html>

三、simple-dialog三层弹窗组件
1、不可拖拽(含插槽)
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Vue多层弹窗</title>
    <script src="https://cdn.staticfile.org/vue/2.2.2/vue.min.js"></script>
    <style>
      .simpleDialog {
        position: fixed;
        width: 100%;
        height: 100%;
        top: 0;
        left: 0;
        display: flex;
        justify-content: center;
        align-items: center;
      }
      .simpleDialog .mask {
        position: fixed;
        width: 100%;
        height: 100%;
        top: 0;
        left: 0;
        background: black;
        opacity: 0.5;
      }
      .simpleDialog .content {
        position: fixed;
        background: white;
        opacity: 1;
        display: flex;
        flex-direction: column;
      }
      .simpleDialog .content .title {
        display: flex;
        background: blue;
        color: white;
        padding: 10px;
        cursor: pointer;
      }
      .simpleDialog .content .conform {
        display: flex;
        justify-content: center;
        padding: 10px;
        background: blue;
      }
    </style>
  </head>
  <body>
    <div id="el">
      <button @click="clickButton()" style="margin-top: 30px;">
        点击-出现-弹窗
      </button>
      <simple-dialog :required-data="requiredDataOut">
        插槽一
        <simple-dialog :required-data="requiredDataMid">
          插槽二
          <simple-dialog :required-data="requiredDataIn">
            插槽三
          </simple-dialog>
        </simple-dialog>
      </simple-dialog>
    </div>
    <script>
      Vue.component('simple-dialog', {
        template: `
          <div>
            <div class="simpleDialog" v-show="requiredData.isShow">
              <div class="mask" v-show="requiredData.isShow"></div>
              <div class="content" v-show="requiredData.isShow">
                <div class="title">
                  <span>系统消息</span>
                </div>
                <div :style="{width:requiredData.width||'800px',height:requiredData.height||'400px'}">
                  <slot></slot>
                </div>
                <div class="conform">
                  <button v-on:click="close()">关闭</button>
                  <button v-on:click="open()">打开</button>
                </div>
              <div>
            </div>
          </div>
        `,
        props: {      
          requiredData: {
            type: Object
          }
        },
        data: function() {
          return {}
        },
        methods: {
          close: function () {
            this.requiredData.isShow = false;
            if(this.requiredData.closeFn) this.requiredData.closeFn();
          },
          open: function () {
            if(this.requiredData.openFn) this.requiredData.openFn();
          }                              
        }
      });
      new Vue({
        el: '#el',
        data(){
          var that = this;
          return {
            requiredDataOut : {
              isShow : false,
              width : '900px',
              height : '600px',
              openFn : function () {
                that.requiredDataMid.isShow = true;
              }
            },
            requiredDataMid : {
              isShow : false,
              width : '600px',
              height : '400px',
              openFn : function () {
                that.requiredDataIn.isShow = true;
              },
            },
            requiredDataIn : {
              isShow : false,
              width : '300px',
              height : '200px',
            },
            // 一层弹窗可以如下使用
            // requiredDataOut = {
            //   isShow : false,
            // };
            // clickButton() {
            //   requiredDataOut.isShow = true;
            // };
          }
        },
        methods:{
          clickButton() {
            this.requiredDataOut.isShow = true;
          }
        }
      })
    </script>
  </body>
</html>
2、可拖拽(含插槽)
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Vue多层弹窗</title>
    <script src="https://cdn.staticfile.org/vue/2.2.2/vue.min.js"></script>
    <script>
      function drag(wholeTitleId, wholeContentId) {
        var wholeTitleId = wholeTitleId||'titleId';
        var wholeContentId = wholeContentId||'contentId';
        var oDiv = document.getElementById(wholeContentId);
        if(!oDiv) return;
        oDiv.onmousedown = down;
        function processThis(fn, nowThis) {
          return function (event) {
            fn.call(nowThis, event);
          };
        }
        function down(event) {
          event = event || window.event;
          if (event.target.id != wholeTitleId) return;
          this.initOffsetLeft = this.offsetLeft;
          this.initOffsetTop = this.offsetTop;
          this.initClientX = event.clientX;
          this.initClientY = event.clientY;
          this.maxOffsetWidth =
            (document.documentElement.clientWidth || document.body.clientWidth) -
            this.offsetWidth;
          this.maxOffsetHeight =
            (document.documentElement.clientHeight ||
              document.body.clientHeight) - this.offsetHeight;
          if (this.setCapture) {
            this.setCapture();
            this.onmousemove = processThis(move, this);
            this.onmouseup = processThis(up, this);
          } else {
            document.onmousemove = processThis(move, this);
            document.onmouseup = processThis(up, this);
          }
        }
        function move(event) {
          var nowLeft = this.initOffsetLeft + (event.clientX - this.initClientX);
          var nowTop = this.initOffsetTop + (event.clientY - this.initClientY);
          this.style.left = nowLeft + 'px';
          this.style.top = nowTop + 'px';
        }
        function up() {
          if (this.releaseCapture) {
            this.releaseCapture();
            this.onmousemove = null;
            this.onmouseup = null;
          } else {
            document.onmousemove = null;
            document.onmouseup = null;
          }
        }
      };
    </script>
    <style>
      .simpleDialog {
        position: fixed;
        width: 100%;
        height: 100%;
        top: 0;
        left: 0;
        display: flex;
        justify-content: center;
        align-items: center;
      }
      .simpleDialog .mask {
        position: fixed;
        width: 100%;
        height: 100%;
        top: 0;
        left: 0;
        background: black;
        opacity: 0.5;
      }
      .simpleDialog .content {
        position: fixed;
        background: white;
        opacity: 1;
        display: flex;
        flex-direction: column;
      }
      .simpleDialog .content .title {
        display: flex;
        background: blue;
        color: white;
        padding: 10px;
        cursor: pointer;
      }
      .simpleDialog .content .conform {
        display: flex;
        justify-content: center;
        padding: 10px;
        background: blue;
      }
    </style>
  </head>
  <body>
    <div id="el">
      <button @click="clickButton()" style="margin-top: 30px;">
        点击-出现-弹窗
      </button>
      <simple-dialog :required-data="requiredDataOut">
        插槽一
        <simple-dialog :required-data="requiredDataMid">
          插槽二
          <simple-dialog :required-data="requiredDataIn">
            插槽三
          </simple-dialog>
        </simple-dialog>
      </simple-dialog>
    </div>
    <script>
      Vue.component('simple-dialog', {
        template: `
          <div>
            <div class="simpleDialog" v-show="requiredData.isShow">
              <div class="mask" v-show="requiredData.isShow"></div>
              <div class="content" v-show="requiredData.isShow" :id="requiredData.contentId||'contentId'">
                <div class="title" :id="requiredData.titleId||'titleId'">
                  <span>系统消息</span>
                </div>
                <div :style="{width:requiredData.width||'800px',height:requiredData.height||'400px'}">
                  <slot></slot>
                </div>
                <div class="conform">
                  <button v-on:click="close()">关闭</button>
                  <button v-on:click="open()">打开</button>
                </div>
              <div>
            </div>
          </div>
        `,
        props: {      
          requiredData: {
            type: Object
          }
        },
        data: function() {
          return {}
        },
        methods: {
          close: function () {
            this.requiredData.isShow = false;
            if(this.requiredData.closeFn) this.requiredData.closeFn();
            var content = this.requiredData.contentId;
            document.getElementById(content).style.cssText = "position:fixed;display:flex;";
          },
          open: function () {
            if(this.requiredData.openFn) this.requiredData.openFn();
          }                              
        },
      });
      new Vue({
        el: '#el',
        data(){
          var that = this;
          return {
            requiredDataOut : {
              isShow : false,
              width : '900px',
              height : '600px',
              titleId : "titleId1",
              contentId : "contentId1",
              openFn : function () {
                that.requiredDataMid.isShow = true;
                drag(that.requiredDataMid.titleId,that.requiredDataMid.contentId);
              }
            },
            requiredDataMid : {
              isShow : false,
              width : '600px',
              height : '400px',
              titleId : "titleId2",
              contentId : "contentId2",
              openFn : function () {
                that.requiredDataIn.isShow = true;
                drag(that.requiredDataIn.titleId,that.requiredDataIn.contentId);
              },
            },
            requiredDataIn : {
              isShow : false,
              width : '300px',
              height : '200px',
              titleId : "titleId3",
              contentId : "contentId3",
            },
            // 一层弹窗可以如下使用
            // requiredDataOut = {
            //   isShow : false,
            // };
            // clickButton() {
            //   requiredDataOut.isShow = true;
            // };
          }
        },
        methods:{
          clickButton() {
            this.requiredDataOut.isShow = true;
            drag(this.requiredDataOut.titleId,this.requiredDataOut.contentId);
          }
        }
      })
    </script>
  </body>
</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;
    }
  </style>
</head>
<body>
  <div id="app">
    <el-button type="text" @click="outerVisible = true">点击打开外层弹窗</el-button>
    <el-dialog
      width="70%"
      title="外层"
      :visible.sync="outerVisible"
    > 
      以下是外层弹窗的插槽<br/>
      <div style="height:200px;border:1px solid #ccc;padding:20px;">
        width="70%",决定弹窗的宽<br/>
        style="height:200px",通过给弹窗插槽的部分标签设置高,决定弹窗的高,如此框<br/>
        :visible.sync="middleVisible",传递引用,此处改变,别处也改变<br/>
        :visible="middleVisible",传递普通值,此处改变,别处不改变<br/>
        <el-dialog
          width="50%"
          title="中层"
          :visible.sync="middleVisible"
          append-to-body
        >
          以下是中层弹窗的插槽<br/>
          <el-dialog
            width="30%"
            title="内层"
            :visible.sync="innerVisible"
            append-to-body
          >
            以下是内层弹窗的插槽<br/>
            <div slot="footer" class="dialog-footer">
              <el-button @click="innerVisible = false">关闭内层</el-button>
              <el-button type="primary" @click="innerVisible = false">关闭内层</el-button>
            </div>
            以上是内层弹窗的插槽<br/>
          </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>
          以上是中层弹窗的插槽<br/>
        </el-dialog>
      </div>
      <div slot="footer" class="dialog-footer">
        <el-button @click="outerVisible = false">关闭外层</el-button>
        <el-button type="primary" @click="middleVisible = true">打开中层</el-button>
      </div>
      以上是外层弹窗的插槽(代码中,此句位于按钮下面)<br/>
    </el-dialog>
  </div>
</body>
<script>
  new Vue({
    el: '#app',
    data() {
      return {
        outerVisible: false,
        middleVisible: false,
        innerVisible: false
      };
    },
    methods: {
       
    },
    components: {
       
    },
  })
</script>
</html>
附、elementUI各弹窗的区别
1、第1组(不自动消失)
(1)Alert,不是浮层元素
  <div style="max-width: 600px">
    <el-alert title="Success alert" type="success" />
    <el-alert title="Info alert" type="info" />
    <el-alert title="Warning alert" type="warning" />
    <el-alert title="Error alert" type="error" />
  </div>
2、第2组(点击后消失)
(1)MessageBox,模拟alert、confirm和prompt,中断用户操作
  const open = () => {
    ElMessageBox.confirm(
      'proxy will permanently delete the file. Continue?',
      'Warning',
      {
        confirmButtonText: 'OK',
        cancelButtonText: 'Cancel',
        type: 'warning',
      }
    )
    .then(() => {
      ElMessage({
        type: 'success',
        message: 'Delete completed',
      })
    })
    .catch(() => {
      ElMessage({
        type: 'info',
        message: 'Delete canceled',
      })
    })
  }
3、第3组(3秒后自动消失)
(1)Message,主动操作后,提示
  const open2 = () => {
    ElMessage({
      message: 'Congrats, this is a success message.',
      type: 'success',
    })
  }
(2)Notification,系统级,通知
  const open2 = () => {
    ElNotification({
      title: 'Prompt',
      message: 'This is a message that does not automatically close',
      duration: 0,
    })
  }
4、第4组(出现在元素附近)
(1)Popconfirm,气泡确认框(触发方式,trigger="hover、click、focus、contextmenu" 或者 :visible="isVisible")
(2)Popover,弹出框(触发方式,trigger="hover、click、focus、contextmenu" 或者 :visible="isVisible")
(3)Tooltip,文字提示(触发方式,hover)

五、ElementUI之70个组件
/*
组件的四个配置
<div ref="btn" @click="clickButton()">我是一个按钮</div>
(1)属性名:
(2)事件名:@click,用户-点击-时-触发
(3)方法名:click,使-点击事件-触发,this.$refs.btn.click()
(4)插槽名:
*/
/*
1、Basic 基础组件
(1)Button 按钮
(2)Border 边框
(3)Color 色彩
(4)Container 布局容器
(5)Icon 图标
(6)Layout 布局
(7)Link 链接
(8)Scrollbar 滚动条
(9)Space 间距
(10)Typography 排版
2、配置组件
(1)Config Provider 全局配置
3、Form 表单组件
(1)Autocomplete 自动补全输入框
(2)Cascader 级联选择器
(3)Checkbox 多选框
(4)ColorPicker 取色器(颜色选择器)
(5)DatePicker 日期选择器
(6)DateTimePicker 日期时间选择器
(7)Form 表单
(8)Input 输入框
(9)Input Number 数字输入框
(10)Radio 单选框
(11)Rate 评分
(12)Select 选择器
(13)Select V2 虚拟列表选择器
(14)Slider 滑块
(15)Switch 开关
(16)TimePicker 时间选择器
(17)TimeSelect 时间选择
(18)Transfer 穿梭框
(19)Upload 上传
4、Data 数据展示
(1)Avatar 头像
(2)Badge 徽章
(3)Calendar 日历
(4)Card 卡片
(5)Carousel 走马灯
(6)Collapse 折叠面板
(7)Descriptions 描述列表
(8)Empty 空状态
(9)Image 图片
(10)Infinite Scroll 无限滚动
(11)Pagination 分页
(12)Progress 进度条
(13)Result 结果
(14)Skeleton 骨架屏
(15)Table 表格
(16)Virtualized Table 虚拟化表格
(17)Tag 标签
(18)Timeline 时间线
(19)Tree 树形控件
(20)TreeSelect 树形选择
(21)Tree V2 虚拟化树形控件
5、Navigation 导航
(1)Affix 固钉
(2)Backtop 回到顶部
(3)Breadcrumb 面包屑
(4)Dropdown 下拉菜单
(5)Menu 菜单
(6)Page Header 页头
(7)Steps 步骤条
(8)Tabs 标签页
6、Feedback 反馈组件
(1)Alert 提示
(2)Dialog 对话框
(3)Drawer 抽屉
(4)Loading 加载
(5)Message 消息提示
(6)MessageBox 消息弹框
(7)Notification 通知
(8)Popconfirm 气泡确认框
(9)Popover 弹出框(气泡卡片)
(10)Tooltip 文字提示
7、Others 其他
(1)Divider 分割线
*/
1、Basic 基础组件
(1)Button 按钮
  <el-button type="primary">Primary</el-button>
(2)Border 边框
  <template>
    <table class="demo-border">
      <tbody>
        <tr>
          <td class="text">Thickness</td>
          <td class="line">Demo</td>
        </tr>
      </tbody>
    </table>
  </template>
  <style scoped>
  :root {
    --green:green;
  }
  .demo-border .line div {
    width: 100%;
    height: 0;
    border-top: 1px solid var(--green);
  }
  </style>
(3)Color 色彩
(4)Container 布局容器
  <template>
    <div class="common-layout">
      <el-container>
        <el-aside width="200px">Aside</el-aside>
        <el-container>
          <el-header>Header</el-header>
          <el-main>Main</el-main>
        </el-container>
      </el-container>
    </div>
  </template>
(5)Icon 图标
  <el-icon>
    <Delete />
  </el-icon>
(6)Layout 布局
  <el-row>
    <el-col :span="12"><div class="grid-content ep-bg-purple" /></el-col>
    <el-col :span="12"><div class="grid-content ep-bg-purple-light" /></el-col>
  </el-row>
(7)Link 链接
  <el-link href="https://element.eleme.io" target="_blank">default</el-link>
(8)Scrollbar 滚动条
  通过height属性设置滚动条高度,若不设置则根据父容器高度自适应;当元素宽度大于滚动条宽度时,会显示横向滚动条
  <el-scrollbar height="400px">
    <p v-for="item in 20" :key="item" class="scrollbar-demo-item">{{ item }}</p>
  </el-scrollbar>
(9)Space 间距
  <el-space wrap>
    <el-card v-for="i in 3" :key="i" class="box-card" style="width: 250px">
      <template #header>
        <div class="card-header">
          <span>Card name</span>
          <el-button class="button" text>Operation button</el-button>
        </div>
      </template>
      <div v-for="o in 4" :key="o" class="text item">
        {{ 'List item ' + o }}
      </div>
    </el-card>
  </el-space>
(10)Typography 排版
  <script lang="ts" setup>
    import { isDark } from '~/composables/dark'
  </script>
  <template>
    <div v-if="!isDark" class="demo-term-box">
      <img src="/images/typography/term-helvetica.png" alt="" />
      <img src="/images/typography/term-arial.png" alt="" />
    </div>
    <div v-else class="demo-term-box">
      <img src="/images/typography/term-helvetica-dark.png" alt="" />
      <img src="/images/typography/term-arial-dark.png" alt="" />
    </div>
  </template>
2、配置组件
(1)Config Provider 全局配置
A、i18n 配置
  <template>
  <div>
    <el-button mb-2 @click="toggle">Switch Language</el-button><br />
    <el-config-provider :locale="locale">
      <el-table mb-1 :data="[]" />
      <el-pagination :total="100" />
    </el-config-provider>
  </div>
  </template>
  <script lang="ts" setup>
    import { computed, ref } from 'vue'
    import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
    import en from 'element-plus/dist/locale/en.mjs'
    const language = ref('zh-cn')
    const locale = computed(() => (language.value === 'zh-cn' ? zhCn : en))
    const toggle = () => {
      language.value = language.value === 'zh-cn' ? 'en' : 'zh-cn'
    }
  </script>
B、对按钮进行配置
  <template>
    <div>
      <div m="b-2">
        <el-checkbox v-model="config.autoInsertSpace">autoInsertSpace</el-checkbox>
      </div>
      <el-config-provider :button="config">
        <el-button>中文</el-button>
      </el-config-provider>
    </div>
  </template>
  <script lang="ts" setup>
  import { reactive } from 'vue'
  const config = reactive({
    autoInsertSpace: true,
  })
  </script>
C、对消息进行配置
  <template>
    <div>
      <el-config-provider :message="config">
        <el-button @click="open">OPEN</el-button>
      </el-config-provider>
    </div>
  </template>
  <script lang="ts" setup>
    import { reactive } from 'vue'
    import { ElMessage } from 'element-plus'
    const config = reactive({
      max: 3,
    })
    const open = () => {
      ElMessage('This is a message.')
    }
  </script>
3、Form 表单组件
(1)Autocomplete 自动补全输入框
  <el-autocomplete
    v-model="state1"
    :fetch-suggestions="querySearch"
    clearable
    class="inline-input w-50"
    placeholder="Please Input"
    @select="handleSelect"
  />
(2)Cascader 级联选择器
  注意,级联选择器、树形控件、树形选择的区别
  <el-cascader
    v-model="value"
    :options="options"
    :props="props"
    @change="handleChange"
  />
(3)Checkbox 多选框
  <div>
    <el-checkbox v-model="checked1" label="Option 1" size="large" />
    <el-checkbox v-model="checked2" label="Option 2" size="large" />
  </div>
(4)ColorPicker 取色器(颜色选择器)
  <div class="demo-color-block">
    <span class="demonstration">With default value</span>
    <el-color-picker v-model="color1" />
  </div>
(5)DatePicker 日期选择器
  <el-date-picker
    v-model="value1"
    type="date"
    placeholder="Pick a day"
    :size="size"
  />
(6)DateTimePicker 日期时间选择器
  <el-date-picker
    v-model="datetimeValue"
    type="datetimerange"
    range-separator="To"
    start-placeholder="Start date"
    end-placeholder="End date"
  />
  import { ref } from 'vue'
  const datetimeValue = ref<[Date, Date]>([
    new Date(2000, 10, 10, 10, 10),
    new Date(2000, 10, 11, 10, 10),
  ])
  /* 1、format 日期格式
      format="YYYY/MM/DD HH:mm:ss" //客户端24小时补0显示,
      format="YYYY/MM/DD hh:mm:ss a" //客户端12小时补0显示
      value-format="YYYY-MM-DD HH:mm:ss" //服务端24小时补0显示
      value-format="YYYY-MM-DD hh:mm:ss a" //服务端12小时补0显示,取值"x"为毫秒
      H,大写为24小时,小写为12小时
      时间字母,双写为补0,单写为不补0
      a,为am;A,为AM
    2、type 日期类型(year/month/date/datetime/ week/datetimerange/daterange)
      type="date" //日期
      type="datetime" //日期时间
      type="datetimerange" //日期时间范围
    3、range-separator="To" //范围的分隔符
    4、default-value 选择器打开时,默认显示的时间
    5、default-time 选择日期后,默认显示的时间,未指定时,默认为00:00:00
    6、size 输入框尺寸 large/default/small
    7、placeholder 非范围选择时的占位内容
    8、start-placeholder 范围选择时开始日期的占位内容
    9、end-placeholder 范围选择时结束日期的占位内容
  */
(7)Form 表单
  <el-form :model="form" label-width="120px">
    <el-form-item label="Activity name">
      <el-input v-model="form.name" />
    </el-form-item>
  </el-form>
(8)Input 输入框
  <el-input v-model="input" placeholder="Please input" />
(9)Input Number 数字输入框
  <el-input-number v-model="num" :min="1" :max="10" @change="handleChange" />
(10)Radio 单选框
  <el-radio-group v-model="radio1" class="ml-4">
    <el-radio label="1" size="large">Option 1</el-radio>
    <el-radio label="2" size="large">Option 2</el-radio>
  </el-radio-group>
(11)Rate 评分
  <div class="demo-rate-block">
    <span class="demonstration">Default</span>
    <el-rate v-model="value1" />
  </div>
(12)Select 选择器
  <el-select v-model="value" class="m-2" placeholder="Select" size="large">
    <el-option
      v-for="item in options"
      :key="item.value"
      :label="item.label"
      :value="item.value"
    />
  </el-select>
(13)Select V2 虚拟列表选择器
  <el-select-v2
    v-model="value"
    :options="options"
    placeholder="Please select"
    size="large"
  />
(14)Slider 滑块
  <div class="slider-demo-block">
    <span class="demonstration">Default value</span>
    <el-slider v-model="value1" />
  </div>
(15)Switch 开关
  <el-switch
    v-model="value2"
    class="ml-2"
    style="--el-switch-on-color: #13ce66; --el-switch-off-color: #ff4949"
  />
(16)TimePicker 时间选择器
  <el-time-picker
    v-model="value2"
    arrow-control
    placeholder="Arbitrary time"
  />
(17)TimeSelect 时间选择
  <el-time-select
    v-model="value"
    start="08:30"
    step="00:15"
    end="18:30"
    placeholder="Select time"
  />
(18)Transfer 穿梭框
  <el-transfer v-model="value" :data="data" />
(19)Upload 上传
  //利用插槽定义上传按钮和说明文字
  <el-upload
    v-model:file-list="fileList"
    class="upload-demo"
    action="https://run.mocky.io/v3/9d059bf9-4660-45f2-925d-ce80ad6c4d15"
    multiple
    :on-preview="handlePreview"
    :on-remove="handleRemove"
    :before-remove="beforeRemove"
    :limit="3"
    :on-exceed="handleExceed"
    :http-request="httpRequest"
  >
    <el-button type="primary">Click to upload</el-button>
    <template #tip>
      <div class="el-upload__tip">
        jpg/png files with a size less than 500KB.
      </div>
    </template>
  </el-upload>
  /*
    1、on-preview:点击文件列表中已上传的文件时的钩子
    2、on-remove:文件列表移除文件时的钩子
    3、on-success:文件上传成功时的钩子
    4、on-error:文件上传失败时的钩子
    5、on-progress:文件上传时的钩子
    6、on-change:文件状态改变时的钩子,添加文件、上传成功和上传失败时都会被调用
    7、on-exceed:当超出限制时,执行的钩子函数
    8、before-upload:上传文件之前的钩子,参数为上传的文件,若返回false或者返回Promise且被reject,则停止上传
    9、before-remove:删除文件之前的钩子,参数为上传的文件和文件列表,若返回false或者返回Promise且被reject,则停止删除
    10、http-request:覆盖默认的Xhr行为,允许自行实现上传文件的请求,如切片。
  */
  data() {
    return {
      fileList: [],
      chunkSize: 1024 * 1024 * 6, // 6M 为一个切片,超过6M,则采用上传大文件的方法进行文件上传
      percentage: {}, // 进度条
    }
  },
  httpRequest(params) { // 来源:https://blog.csdn.net/qq_33547169/article/details/127807333
    let {file: {size}} = params;
    size > this.chunkSize ? this.uploadBigFile(params, this.chunkSize) : this.uploadFile(params)
  },
  uploadBigFile(params, chunkSize) {
    let {file, filename, onSuccess} = params;
    let {size, type, name} = file;
    const chunkLength = Math.ceil(file.size / chunkSize)
    let chunks = Array.from({length: chunkLength}).map((v, i) => file.slice(i * chunkSize, i * chunkSize + chunkSize))
    let loadeds = [];
    let chunkRequests = chunks.map((chunk, i) => {
      const formData = new FormData();
      formData.append(filename, chunk);
      loadeds[i] = [];
      const config = {
        'Content-type': 'multipart/form-data',
        onUploadProgress: progress => {
          loadeds[i].push(progress.loaded)
          this.calculationPercentage(file, i, loadeds, size);
        }
      }
      return this.$post(this.$servers.openServer, 'tb/minio/upload', formData, config);
    })
    this.$axios.all(chunkRequests).then(res => {
      let fileNames = res.map(({data: {minioPath}}) => minioPath)
      const params = {fileName: name, contentType: type, fileSize: size, fileNames}
      this.$post(this.$servers.openServer, 'tb/minio/merge', params).then(onSuccess)
    })
  },
  uploadFile(params) {
    let {file, filename, onSuccess} = params;
    const formData = new FormData();
    formData.append(filename, file);
    const config = {
      'Content-type': 'multipart/form-data',
      onUploadProgress: progress => this.percentage = Math.floor(progress.loaded / progress.total * 100)
    }
    this.$post(this.$servers.openServer, 'tb/minio/upload', formData, config).then(onSuccess)
  },
4、Data 数据展示
(1)Avatar 头像
  <div v-for="size in sizeList" :key="size" class="block">
    <el-avatar :size="size" :src="circleUrl" />
  </div>
(2)Badge 徽章
  <el-badge :value="12" class="item">
    <el-button>comments</el-button>
  </el-badge>
(3)Calendar 日历
  <el-calendar v-model="value" />
(4)Card 卡片
  <el-card class="box-card">
    <template #header>
      <div class="card-header">
        <span>Card name</span>
        <el-button class="button" text>Operation button</el-button>
      </div>
    </template>
    <div v-for="o in 4" :key="o" class="text item">{{ 'List item ' + o }}</div>
  </el-card>
(5)Carousel 走马灯
  <el-carousel height="150px">
    <el-carousel-item v-for="item in 4" :key="item">
      <h3 class="small justify-center" text="2xl">{{ item }}</h3>
    </el-carousel-item>
  </el-carousel>
(6)Collapse 折叠面板
  <el-collapse v-model="activeNames" @change="handleChange">
    <el-collapse-item title="Consistency" name="1">
      <div>
        Consistent with real life: in line with the process and logic of real
        life, and comply with languages and habits that the users are used to;
      </div>
      <div>
        Consistent within interface: all elements should be consistent, such
        as: design style, icons and texts, position of elements, etc.
      </div>
    </el-collapse-item>
  </el-collapse>
(7)Descriptions 描述列表
  <el-descriptions title="User Info">
    <el-descriptions-item label="Username">kooriookami</el-descriptions-item>
  </el-descriptions>
(8)Empty 空状态
  <el-empty description="description" />
  <el-empty image="https://shadow.elemecdn.com/app/element/hamburger.9cf7b091-55e9-11e9-a976-7f4d0b07eef6.png"/>
(9)Image 图片
  <div v-for="fit in fits" :key="fit" class="block">
    <span class="demonstration">{{ fit }}</span>
    <el-image style="width: 100px; height: 100px" :src="url" :fit="fit" />
  </div>
(10)Infinite Scroll 无限滚动(无限滚动加载)
  <ul
    v-infinite-scroll="load"
    class="list"
    :infinite-scroll-disabled="disabled"
  >
    <li v-for="i in count" :key="i" class="list-item">{{ i }}</li>
  </ul>
  import { computed, ref } from 'vue'
  const count = ref(10)
  const loading = ref(false)
  const noMore = computed(() => count.value >= 20)
  const disabled = computed(() => loading.value || noMore.value) //为false,加载
  //(loading.value || noMore.value)与(!loading.value && !noMore.value)等效,都为false,执行
  const load = () => {
    loading.value = true
    setTimeout(() => {
      count.value += 2
      loading.value = false //为false
    }, 2000)
  }
(11)Pagination 分页
  <el-pagination
    v-model:current-page="currentPage"
    v-model:page-size="pageSize"
    :page-sizes="[100, 200, 300, 400]"
    :small="small"
    :disabled="disabled"
    :background="background"
    layout="total, sizes, prev, pager, next, jumper"
    :total="400"
    @size-change="handleSizeChange"
    @current-change="handleCurrentChange"
  />
  const currentPage = ref(4);
  const pageSize = ref(100);
  const handleSizeChange = (val: number) => {
    console.log(`${val} items per page`)
  }
  const handleCurrentChange = (val: number) => {
    console.log(`current page: ${val}`)
  }
(12)Progress 进度条
  <el-progress :percentage="100" status="success" />
(13)Result 结果
  不同颜色圆形背景下的,对号、错号、感叹号图标
  <el-result
    icon="success"
    title="Success Tip"
    sub-title="Please follow the instructions"
  >
    <template #extra>
      <el-button type="primary">Back</el-button>
    </template>
  </el-result>
(14)Skeleton 骨架屏
  <el-skeleton /> <br />
  <el-skeleton style="--el-skeleton-circle-size: 100px">
    <template #template>
      <el-skeleton-item variant="circle" />
    </template>
  </el-skeleton>
(15)Table 表格
  <template>
    <div class="el-col-right-title">
      <div class="el-col-right-title-item el-col-right-title-current">我的订单</div>
    </div>
    <div class="el-col-right-order-select">
      <div 
        class="el-col-right-order-select-item" 
        :class="{'el-col-right-order-select-item-current':orderStatus == item.state }"
        v-for="item in orderTabs" :key="item.state"
        @click="clickOrderState(item)"
      >{{ item.name }}</div>
    </div>
    <el-table
      v-for="(tablePay, indexRow) in rowPay"
      :data="tablePay"
      :header-cell-style="headerCellStyle"
      :cell-class-name="tableCellClassName"
      :span-method="spanMethod"
      class="profile-el-table"
      :key="indexRow"
    >
      <el-table-column
        v-for="(item, indexCol) in colPay[indexRow]"
        :key="indexCol"
        :prop="item.prop"
        :width="item.width"
        :label="item.label"
      > 
        <template #header v-if="indexCol === 4"><!-- 表头,第5栏。若下面为空,则为空 -->
          <span v-if="item.orderStatus===1">
            <span v-if="new Date().getTime() < item.expireTime">
              <span>待支付,剩余时间</span>
              <span class="forpay-countdown">{{item.label}}</span>
            </span>
            <span v-else>
              <span>订单已失效</span>
            </span>
          </span>
          <span v-else-if="item.orderStatus===2" style="color: #0058E6;">{{item.label}}</span>
          <span v-else>
            <span>{{item.label}}</span>
          </span>
        </template>
        <template #default="scope" v-if="indexCol === 1"><!-- 表体,第2栏。若下面为空,则用默认值 -->
          <div v-if="scope.row.isColspan" style="text-align: right;"><!-- 表体,第2行。若下面为空,则为空 -->
            <span v-if="scope.row.orderStatus===1">
              <span v-if="new Date().getTime() < scope.row.expireTime">
                <span>
                  <span>应付</span>
                  <span style="color:#DD0000">¥{{ scope.row.aaa }}</span>
                </span>
                <span style="padding-left: 40px;">
                  <el-button @click="cancelOrder(scope.row)" style="font-size: 12px;color:#000000;font-weight: 600;">取消订单</el-button>
                  <el-button @click="immediatePay(scope.row)" style="background-color: #0058E6;color:#ffffff;font-size: 12px;">立即支付</el-button>
                </span>
              </span>
            </span>
            <span v-else-if="scope.row.orderStatus===2">
              <span>共1件产品,总计</span>
              <span style="color:#DD0000">¥{{ scope.row.aaa }}</span>
            </span>
          </div>
        </template>
      </el-table-column>
    </el-table>
    <div class="noData" v-show="rowPay.length===0">暂无数据</div>
    <div class="profile-el-pagination" v-show="total>0">
      <span class="profile-el-pagination-span">共{{total}}条</span>
      <el-pagination 
        layout=" ->, prev, pager, next" 
        :total="total"
        @current-change="clickOrderpagination"
      />
    </div>
  </template>
  <script setup>
    import { ref, reactive, onMounted } from 'vue'
    import { getOrderData, distanceDateTime } from '@/api/profile';
    import { orderCancel } from '@/api/pay';
    import * as filters from '@/filter';
    import useUserStore from '@/store/modules/user';
    const userStore = useUserStore();
    const userSid = userStore.user && userStore.user.zxktUser && userStore.user.zxktUser.sid;
    var total = ref(0)  
    var orderStatus = ref(0)
    var timerArray = reactive([])
    var colPay = reactive([]);
    var rowPay = reactive([]);
    var orderTabs = reactive([
      {
        name: "全部订单",
        state: 0,
      },
      {
        name: "待付款",
        state: 1,
      },
      {
        name: "支付成功",
        state: 2,
      },
    ]);
    var spanMethod = function({ row, column, rowIndex, columnIndex }){
      if (rowIndex === 1 && columnIndex === 1) {
        return {
          rowspan: 1,
          colspan: 4
        };  
      }
    };
    var headerCellStyle = reactive({color:'#b2b8c4',background:'#eaeef5'});
    var tableCellClassName = function({ row, column, rowIndex, columnIndex}){
      if (rowIndex === 1 && columnIndex === 0) {
        return 'grayColor'
      }else{
        return 'blackColor'
      }
    };
    //以下切换订单Tab
    var clickOrderState = function(item){
      orderStatus.value = item.state
      var page = item.page||1
      var allData = {
        page: page,
        size: 10,
        userId: userSid,
      };
      if(orderStatus.value == 1 ||orderStatus.value == 2){
        allData.orderStatus =  orderStatus.value
      }
      var totalIn = total
      getOrderData(allData).then(function(result){
        totalIn.value = result.data.total;
        colPay.length = 0;  
        rowPay.length = 0;
        var list = result.data.list;
        for(var i = 0; i<list.length; i++){
          var item = list[i];
          var singleCol = [//每个表头有5列
            {
              prop: 'date',
              label: '下单时间:' + distanceDateTime(item.orderTime).clientDateTime,
              width: '280px'
            },
            {
              prop: 'price',
              label: '原价(元)',
            },
            {
              prop: 'payAmount',
              label: item.orderStatus===2 ? '实付(元)' : '应付(元)',
            },
            {
              prop: 'favor',
              label: '优惠金额',
            },
            {
              prop: 'forPay',
              label: ['','','支付成功','已退款','退款失败','支付失败','订单已关闭'][item.orderStatus],
              width: '204px',
              expireTime: item.expireTime,
              orderStatus: item.orderStatus,
            },
          ];
          var singleRow = [//每个表体有2行
            {
              date: item.collectionTitle,
              price: filters.fmoney(item.price),
              payAmount: filters.fmoney(item.payAmount),
              favor: filters.fmoney(item.price - item.payAmount),
              forPay: item.forPay,
            },
            {
              date: '订单号:' + item.orderNo,
              isColspan: true,
              totalNum: item.totalNum,
              aaa: filters.fmoney(item.payAmount),//aaa不能换成payAmount
              orderNo: item.orderNo,
              sid: item.sid,
              payMethod: item.payMethod,
              expireTime: item.expireTime,
              orderStatus: item.orderStatus,
            }
          ];
          colPay.push(singleCol)
          rowPay.push(singleRow)
        }
        if(timerArray.length>0){
          for(var i = 0; i < timerArray.length; i++){
            clearInterval(timerArray[i]);
          }
        }
        var objArray = []
        timerArray.length = 0;
        for(var i = 0; i < colPay.length; i++){
          var obj = colPay[i][colPay[i].length-1];//获取每个表头的最后一项,里面包含-倒计时区域
          var total = 0;
          if(obj.orderStatus != 1 ) return
          if(obj.expireTime < new Date().getTime() ) return
          countDown(obj)//立即执行一次倒计时,以免-倒计时区域-出现1秒钟的空白
          objArray.push(obj)//存储所有的-倒计时区域
          //当有定时器清除时,clickOrderState会再次执行,与定时器相关的数据会重新生成
          var interval = setInterval(function(){//for循环结束时,objArray存储了所有的-倒计时区域,又过1秒钟后,多个定时器同时执行
            var index = total%objArray.length;//获取本定时器对应的-倒计时区域的序号,即定时器已运行次数与定时器总个数的模,
            countDown(objArray[index])//执行倒计时,传入倒计时区域
            total++
          },1000)
          obj.interval = interval
          timerArray.push(interval)
        }
      })
    }
    //以下是倒计时
    var countDown = function (obj) {
      /* console.log( '倒计时正在运行,请判断是否必要!!!' ); */
      function addZero(number) {
        return number.toString()[1] ? number : "0" + number;
      };
      var nowMilliseconds = new Date().getTime();
      var futureMilliseconds = obj.expireTime;
      if(nowMilliseconds >= futureMilliseconds){
        obj.label = '定时器出错了';
        clearInterval(obj.interval);
        clickOrderState({state: orderStatus.value})
        return
      }
      var seconds = Math.floor((futureMilliseconds - nowMilliseconds)/1000) 
      var str = "";
      var day = Math.floor(seconds / 86400);
      var hour = Math.floor((seconds % 86400) / 3600);
      var minute = Math.floor(((seconds % 86400) % 3600) / 60);
      var second = Math.floor(((seconds % 86400) % 3600) % 60);
      if ( day > 0) {
        str += day + "天";
      }
      if ( day > 0 || hour > 0) {
        str += addZero(hour) + "小时";
      }
      if ( day > 0 || hour > 0 || minute > 0) {
        str += addZero(minute) + "分";
      }
      str += addZero(second) + "秒";
      obj.label = str;
    }
    var clickOrderpagination = function(num){
      clickOrderState({
        state: orderStatus.value,
        page: num,
      })
    }
    var cancelOrder = function(row){
      var data ={
        orderNo: row.orderNo,
        userId: userSid,
      };
      orderCancel(data).then(function(){
        clickOrderState({state: orderStatus.value})
      })
    }
    import { useRouter } from "vue-router"
    const router = useRouter()
    var immediatePay = function(row){
      var payMethod = {
        WECHAT: 1,
        ALIPAY: 2,
      };
      router.push({
        path: '/pay',
        query: {
          collectionId: row.collectionId,
          channel: payMethod[row.payMethod],
          expiretime: row.expireTime,
        }
      })
    }
    const emit = defineEmits(['refreshPage'])
    onMounted(function() {
      clickOrderState({state:0})
      emit('refreshPage',router.currentRoute.value.name)
    });
    onBeforeUnmount(function() {
      if(timerArray.length>0){
        for(var i = 0; i < timerArray.length; i++){
          clearInterval(timerArray[i]);
        }
      }
    });
  </script>
  <style lang="scss">
    .blackColor{
      color:#000000;
    }
    .grayColor{
      color:#707889;
    }
  </style>
(16)Virtualized Table 虚拟化表格
  <template>
    <el-table-v2
      :columns="columns"
      :data="data"
      :width="700"
      :height="400"
      fixed
    />
  </template>
  <script lang="ts" setup>
    const generateColumns = (length = 10, prefix = 'column-', props?: any) =>
      Array.from({ length }).map((_, columnIndex) => ({
        ...props,
        key: `${prefix}${columnIndex}`,
        dataKey: `${prefix}${columnIndex}`,
        title: `Column ${columnIndex}`,
        width: 150,
      }))
    const generateData = (
      columns: ReturnType<typeof generateColumns>,
      length = 200,
      prefix = 'row-'
    ) =>
      Array.from({ length }).map((_, rowIndex) => {
        return columns.reduce(
          (rowData, column, columnIndex) => {
            rowData[column.dataKey] = `Row ${rowIndex} - Col ${columnIndex}`
            return rowData
          },
          {
            id: `${prefix}${rowIndex}`,
            parentId: null,
          }
        )
      })
    const columns = generateColumns(10)
    const data = generateData(columns, 1000)
  </script>
(17)Tag 标签
  <el-tag class="ml-2" type="warning">Tag 4</el-tag>
(18)Timeline 时间线
  <template>
    <el-timeline>
      <el-timeline-item
        v-for="(activity, index) in activities"
        :key="index"
        :timestamp="activity.timestamp"
      >
        {{ activity.content }}
      </el-timeline-item>
    </el-timeline>
  </template>
  <script lang="ts" setup>
    const activities = [
      {
        content: 'Event start',
        timestamp: '2018-04-15',
      },
      {
        content: 'Approved',
        timestamp: '2018-04-13',
      },
      {
        content: 'Success',
        timestamp: '2018-04-11',
      },
    ]
  </script>
(19)Tree 树形控件
  注意,级联选择器、树形控件、树形选择的区别
  <el-tree :data="data" :props="defaultProps" @node-click="handleNodeClick" />
(20)TreeSelect 树形选择
  注意,级联选择器、树形控件、树形选择的区别
  <el-tree-select
    v-model="value"
    :data="data"
    :render-after-expand="false"
    show-checkbox
  />
(21)Tree V2 虚拟化树形控件
  <el-tree-v2 :data="data" :props="props" :height="208" /> 
5、Navigation 导航
(1)Affix 固钉
  <el-affix :offset="120">
    <el-button type="primary">Offset top 120px</el-button>
  </el-affix>
(2)Backtop 回到顶部
  <el-backtop :right="100" :bottom="100" />
(3)Breadcrumb 面包屑
<template>
  <el-breadcrumb separator="/">
    <el-breadcrumb-item :to="{ path: '/' }">homepage</el-breadcrumb-item>
    <el-breadcrumb-item><a href="/">promotion management</a></el-breadcrumb-item>
    <el-breadcrumb-item>promotion list</el-breadcrumb-item>
    <el-breadcrumb-item>promotion detail</el-breadcrumb-item>
  </el-breadcrumb>
</template>
(4)Dropdown 下拉菜单
  <el-dropdown>
    <span class="el-dropdown-link">
      Dropdown List
      <el-icon class="el-icon--right">
        <arrow-down />
      </el-icon>
    </span>
    <template #dropdown>
      <el-dropdown-menu>
        <el-dropdown-item>Action 1</el-dropdown-item>
        <el-dropdown-item>Action 2</el-dropdown-item>
        <el-dropdown-item>Action 3</el-dropdown-item>
        <el-dropdown-item disabled>Action 4</el-dropdown-item>
        <el-dropdown-item divided>Action 5</el-dropdown-item>
      </el-dropdown-menu>
    </template>
  </el-dropdown>
(5)Menu 菜单
  <el-menu
    :default-active="activeIndex"
    class="el-menu-demo"
    mode="horizontal"
    @select="handleSelect"
  >
    <el-menu-item index="1">Processing Center</el-menu-item>
    <el-sub-menu index="2">
      <template #title>Workspace</template>
      <el-menu-item index="2-1">item one</el-menu-item>
      <el-menu-item index="2-2">item two</el-menu-item>
      <el-menu-item index="2-3">item three</el-menu-item>
      <el-sub-menu index="2-4">
        <template #title>item four</template>
        <el-menu-item index="2-4-1">item one</el-menu-item>
        <el-menu-item index="2-4-2">item two</el-menu-item>
        <el-menu-item index="2-4-3">item three</el-menu-item>
      </el-sub-menu>
    </el-sub-menu>
    <el-menu-item index="3" disabled>Info</el-menu-item>
    <el-menu-item index="4">Orders</el-menu-item>
  </el-menu>
(6)Page Header 页头
  <el-page-header @back="onBack">
    <template #breadcrumb>
      <el-breadcrumb separator="/">
        <el-breadcrumb-item :to="{ path: './page-header.html' }">
          homepage
        </el-breadcrumb-item>
        <el-breadcrumb-item
          ><a href="./page-header.html">route 1</a></el-breadcrumb-item
        >
        <el-breadcrumb-item>route 2</el-breadcrumb-item>
      </el-breadcrumb>
    </template>
    <template #content>
      <div class="flex items-center">
        <el-avatar
          class="mr-3"
          :size="32"
          src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png"
        />
        <span class="text-large font-600 mr-3"> Title </span>
        <span
          class="text-sm mr-2"
          style="color: var(--el-text-color-regular)"
        >
          Sub title
        </span>
        <el-tag>Default</el-tag>
      </div>
    </template>
    <template #extra>
      <div class="flex items-center">
        <el-button>Print</el-button>
        <el-button type="primary" class="ml-2">Edit</el-button>
      </div>
    </template>
    <el-descriptions :column="3" size="small" class="mt-4">
      <el-descriptions-item label="Username">kooriookami</el-descriptions-item>
      <el-descriptions-item label="Telephone">18100000000</el-descriptions-item>
      <el-descriptions-item label="Place">Suzhou</el-descriptions-item>
      <el-descriptions-item label="Remarks">
        <el-tag size="small">School</el-tag>
      </el-descriptions-item>
      <el-descriptions-item label="Address">No.1188, Wuzhong Avenue, Wuzhong District, Suzhou, Jiangsu Province
      </el-descriptions-item>
    </el-descriptions>
    <p class="mt-4 text-sm">
      Element Plus team uses <b>weekly</b> release strategy under normal
      circumstance, but critical bug fixes would require hotfix so the actual
      release number <b>could be</b> more than 1 per week.
    </p>
  </el-page-header>
(7)Steps 步骤条
  <el-steps :active="active" finish-status="success">
    <el-step title="Step 1" />
    <el-step title="Step 2" />
    <el-step title="Step 3" />
  </el-steps>
(8)Tabs 标签页
  <el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
    <el-tab-pane label="User" name="first">User</el-tab-pane>
    <el-tab-pane label="Config" name="second">Config</el-tab-pane>
    <el-tab-pane label="Role" name="third">Role</el-tab-pane>
    <el-tab-pane label="Task" name="fourth">Task</el-tab-pane>
  </el-tabs>
6、Feedback 反馈组件
(1)Alert 提示
  Alert不是浮层元素,不会自动消失或关闭
  <el-alert title="success alert" type="success" />
(2)Dialog 对话框
  <el-dialog
    v-model="dialogVisible"
    title="Tips"
    width="30%"
    :before-close="handleClose"
  >
    <span>This is a message</span>
    <template #footer>
      <span class="dialog-footer">
        <el-button @click="dialogVisible = false">Cancel</el-button>
        <el-button type="primary" @click="dialogVisible = false">
          Confirm
        </el-button>
      </span>
    </template>
  </el-dialog>
(3)Drawer 抽屉
  <el-drawer
    v-model="drawer"
    title="I am the title"
    :direction="direction"
    :before-close="handleClose"
  >
    <span>Hi, there!</span>
  </el-drawer>
(4)Loading 加载
  <el-table v-loading="loading" :data="tableData" style="width: 100%">
    <el-table-column prop="date" label="Date" width="180" />
    <el-table-column prop="name" label="Name" width="180" />
    <el-table-column prop="address" label="Address" />
  </el-table>
(5)Message 消息提示
  Message用于主动操作后的反馈提示
  Notification用于系统级通知的被动提醒
  <template>
    <el-button :plain="true" @click="open">Show message</el-button>
  </template>
  <script lang="ts" setup>
    import { h } from 'vue'
    import { ElMessage } from 'element-plus'
    const open = () => {
      ElMessage('this is a message.')
    }
    const open = () => {
      ElMessage({
        message: 'Warning, this is a warning message.',
        type: 'warning',
      })
    }
  </script>
(6)MessageBox 消息弹出框
  简略版的Dialog(自悟)
  <template>
    <el-button text @click="open">Click to open the Message Box</el-button>
  </template>
  <script lang="ts" setup>
    import { ElMessage, ElMessageBox } from 'element-plus'
    import type { Action } from 'element-plus'
    const open = () => {
      ElMessageBox.alert('This is a message', 'Title', {
        // if you want to disable its autofocus
        // autofocus: false,
        confirmButtonText: 'OK',
        callback: (action: Action) => {
          ElMessage({
            type: 'info',
            message: `action: ${action}`,
          })
        },
      })
    }
  </script>
(7)Notification 通知
  Message用于主动操作后的反馈提示
  Notification用于系统级通知的被动提醒
  <template>
    <el-button plain @click="open"> Success </el-button>
  </template>
  <script lang="ts" setup>
    import { ElNotification } from 'element-plus'
    const open = () => {
      ElNotification({
        title: 'Success',
        message: 'This is a success message',
        type: 'success',//可有、可无、可选
      })
    }
  </script>
(8)Popconfirm 气泡确认框
  在被点击元素旁边弹出一个简单的气泡确认框
  <template>
    <el-popconfirm title="Are you sure to delete this?">
      <template #reference>
        <el-button>Delete</el-button>
      </template>
    </el-popconfirm>
  </template>
(9)Popover 弹出框(气泡卡片)
  在被点击元素旁边弹出一个简单的气泡卡片
  <el-popover
    placement="top-start"
    title="Title"
    :width="200"
    trigger="hover"
    content="this is content, this is content, this is content"
  >
    <template #reference>
      <el-button class="m-2">Hover to activate</el-button>
    </template>
  </el-popover>
(10)Tooltip 文字提示
  在被悬停元素旁边弹出一个简单的气泡卡片
  <el-tooltip
    class="box-item"
    effect="dark"
    content="Top Left prompts info"
    placement="top-start"
  >
    <el-button>top-start</el-button>
  </el-tooltip>
7、Others 其他
(1)Divider 分割线
  <el-divider content-position="left">Rabindranath Tagore</el-divider>
  <el-divider>
    <el-icon>
      <star-filled />
    </el-icon>
  </el-divider>
  <el-divider content-position="right">Rabindranath Tagore</el-divider>

六、vue报错与解决
1、问题1
(1)现象,[Vue warn]: Invalid prop: custom validator check failed for prop "type".    vue.runtime.esm.js?
(2)解决,
A、这个问题不影响项目的正常运行
B、出现这个报错的原因是,iview支持数字类型的输入,vue自带的-vue错误检查工具-vue.runtime.esm.js-不支持数字类型的输入,因而报错
C、解决方案,开发者应当在浏览器安装-vue错误检查插件-https://github.com/vuejs/vue-devtools,取代vue.runtime.esm.js,给vue检查错误
2、问题2
(1)现象,el-select的blur事件不能通过{trigger:'blur'}触发,只能通过{@blur="selectBlur"}触发
(2)解决,
  <el-form-item label="所属页卡" prop="card_id" >
    <el-select  v-model="form.card_id" placeholder="请选择所属页卡" @blur="selectBlur">
      <el-option
        v-for="item in configType"
        :key="item.value"
        :label="item.label"
        :value="item.value">
      </el-option>
    </el-select>
  </el-form-item>
  rules: {//trigger: 'blur'无效,trigger: 'change'有效,
    card_id:[{ required: true, message: '请选择所属页卡', trigger: 'change', validator: function(rule, value, callback){
        if (!value) {
          callback(new Error());
        }else{
          callback()
        } 
      } 
    }],
  },
  selectBlur(){
    this.$refs["form"].validateField("card_id"); 
  },
3、问题3
(1)现象,:src 文件路径错误问题的解决方法
(2)解决,
  <template>
    <img :src="item.url" alt="logo" />
  </template>
  import background from '@/assets/image/background.png'
  var collect = reactive([
    {
      url: background, // url: '/src/assets/images/logo.png' 注释这样的写法会出错
      teacher: "讲师:李老师",
    }
  ]);,
4、问题4
(1)现象,一级swiper与二级swiper所需swiper包的版本不一致,运行“npm install”出现下面问题,
  问题来源,https://blog.csdn.net/weixin_52509007/article/details/124165325
  "dependencies": {
    "swiper": "^8.1.0",//一级插件
    "vue-awesome-swiper": "^4.1.1",//内部也有swiper二级插件,但版本与上面不一样
  },
  npm ERR! code ERESOLVE
  npm ERR! ERESOLVE unable to resolve dependency tree
  npm ERR!
  npm ERR! While resolving: vue2-standard-demo@0.1.0
  npm ERR! Found: swiper@8.1.0
  npm ERR! node_modules/swiper
  npm ERR!   swiper@"^8.1.0" from the root project
  npm ERR!
  npm ERR! Could not resolve dependency:
  npm ERR! peer swiper@"^5.2.0" from vue-awesome-swiper@4.1.1
  npm ERR! node_modules/vue-awesome-swiper
  npm ERR!   vue-awesome-swiper@"4.1.1" from the root project
  npm ERR!
  npm ERR! Fix the upstream dependency conflict, or retry
  npm ERR! this command with --force, or --legacy-peer-deps
  npm ERR! to accept an incorrect (and potentially broken) dependency resolution.
  npm ERR!
  npm ERR! See D:\app\nodejs\node_cache\eresolve-report.txt for a full report.
   
  npm ERR! A complete log of this run can be found in:
  npm ERR!     D:\app\nodejs\node_cache\_logs\2022-04-14T01_48_57_987Z-debug-0.log
(2)解决,
  初步解决:更改一级以适应二级,问题消失,但安装结束后,node_modules被自动删除;
  最终解决:运行“yarn”,彻底解决这个问题
5、问题5
(1)现象,把主分支拉到本地,直接运行npm run dev,出错
(2)解决,把mock文件夹里,新生成的压缩文件如fkdsfsdlsdf.js删除;或者运行node -v,看node版本是不是在16以上  
6、问题6,出现在vue2.6.0以后的版本中
  报错信息:[VUE ERROR] Invalid default value for prop "slides": 
    Props with type Object/Array must use a factory function to return the default value
  错误原因:当给子组件设置 props 属性时,如果参数类型是 Array 或 Object
    它的默认值必须是由工场函数返回,不能直接赋值
(1)现象,黄字错误
  props: {
    sizeRadio: {
      type: Array,
      default: []
    }
    sizeRadio: {
      type: Object,
      default: {}
    }
  },
(2)解决,
  props: {
    sizeRadio: {
      type: Array,
      default: function() {
        return []
      }
    }
    sizeRadio: {
      type: Object,
      default: function() {
        return {}
      }
    }
  },
7、问题7,用插件vod-js-sdk-v6上传视频时
 来源,https://cloud.tencent.com/developer/ask/260695
(1)现象,报错“Error: ugc upload | signature verify forbidden”
(2)原因,
  A、前端向后台索要签名,后台用小权限“腾讯账户”向腾讯索要签名,
  B、腾讯给后台小权限签名,后台给前端小权限签名,前端将小权限签名发给腾讯,腾讯给前端报错
(3)解决,让后台换用大权限“腾讯账户”

七、vue效果与实现
1、一行显示多个表单项
  <el-form :model="nowCard" :label-position="'right'" label-width="130px">
    <el-form-item label="内容配置" class="blue-label"></el-form-item>
    <el-row>
      <el-col :span="10">
        <el-form-item label="页卡名称">
          <el-input
            v-model="nowCard.card_name"
            type="text"
            maxlength="200"
            placeholder="请输入页卡名称" 
            style="width:300px;"
            :disabled="nowCard.create_user == 'system'"
          />
        </el-form-item>
      </el-col>
      <el-col :span="10">
        <el-form-item label="页卡序号">
          <el-input
            v-model="nowCard.order_index"
            type="text"
            maxlength="200"
            :placeholder="'请输入页卡序号('+ addPagecardText +'时不输入)'"
            style="width:300px;"
            :disabled="nowCard.isAdd"
          />
        </el-form-item>
      </el-col>
    </el-row>
    <div style="display: flex">
      <el-form-item label="是否显示" prop="coursePage" >
        <el-radio-group v-model="nowCard.is_show">
          <el-radio label="0" size="large">不显示</el-radio>
          <el-radio label="1" size="large">显示</el-radio>
        </el-radio-group>
      </el-form-item>
      <div style="flex: 1;"></div>
      <div class="generate-page" @click="generatePage">生成页面</div>
    </div>
  </el-form>
2、表单项不显示标签
  <el-form-item label-width="80px">
    <template #label>
      <span></span>
    </template>
    <el-button type="primary" size="mini" icon="el-icon-search" @click="handleQuery">查询</el-button>
  </el-form-item>
3、弹窗上下固定,中间滚动
  <el-dialog
    title="详情"
    :visible.sync="dialogPreviewVisible"
    width="1150px"
  >
    <div style="height:640px;overflow: auto;">
      <AlbumPreview :sid="item.f_id"></AlbumPreview>
    </div>
    <el-button>确定</el-button>
  </el-dialog>
4、点击按钮,出现气泡
  <el-popover placement="bottom" trigger="click" style="margin-right: 14px;">
    <div class="content-table-header-popover">
      <el-radio-group size="mini" v-model="sortRule" @input="sortRuleDidChange">
        <p>点赞数</p>
        <el-radio-button label=1 style="margin-right: 10px;">从高到低</el-radio-button>
        <el-radio-button label=2>从低到高</el-radio-button>
        <p>评论数</p>
        <el-radio-button label=3 style="margin-right: 10px;">从高到低</el-radio-button>
        <el-radio-button label=4>从低到高</el-radio-button>
      </el-radio-group>
    </div>
    <el-button slot="reference" type="primary" size="mini">排序</el-button>
  </el-popover>
5、下拉选项的配置
  <el-select 
    v-model="queryForm.course" 
    filterable //为true,允许手输,这是allow-create生效的前提条件
    allow-create //为true,手输内容成为‘唯一的’下拉选项;为false,只显示‘含有手输内容的’下拉选项
    placeholder="请选择购买课程" 
    size="mini" 
    style="width: 220px"
  >
    <el-option
      v-for="item in nameOptions"
      :label="item.name"
      :value="item.name"
    >
    </el-option>
  </el-select>
6、深度选择器
  (1)本页样式 >>> ,原生css支持,sass/less可能无法识别
    <style scoped>
      .wrap >>> .child {
        color: red;
      }
    </style>
  (2)本页样式 /deep/ ,sass/less可识别,在vue 3.0会报错
    <style scoped>
      .wrap /deep/ .child {
        color: red;
      }
    </style>
  (3)本页样式 ::v-deep ,vue 3.0支持,编译速度快
    <style scoped>
      .wrap ::v-deep .child {
        color: red;
      }
    </style>
  (4)示例  
    <el-date-picker type="date" v-model="queryForm.finishDate" size="small" format="yyyy-MM-dd" placeholder="请选择日期"></el-date-picker>
    <style scoped lang="scss">
      ::v-deep .el-input--small .el-input__inner {
        height: 30px !important;
        line-height: 30px !important;
      }
    </style>
    <style scoped>
      .el-input--small .el-input__inner {
        height: 30px !important;
        line-height: 30px !important;
      }
    </style>
7、el-table-column插槽处理
(1)用visibility代替v-show,以免透明、飞走、显示不全
(2)el-input显示即聚焦
  <el-table-column prop="title" label="名称">
    <template #default="scope">
      <div style="display: flex;" v-show="!scope.row.isShowEdit">
        <div class="document-title"  @click="cellClick(scope.row)">{{ scope.row.title }}</div>
        <div :style="{visibility:scope.row.isShowOperator}">
          <el-tooltip content="重命名" placement="top" effect="light">
            <svg-icon icon-class="rename" @click="renameClick(scope.row)"  class="svg-icon"></svg-icon>
          </el-tooltip>
          <span style="padding: 0 4px"></span>
          <el-popconfirm 
            confirm-button-text="确定"
            cancel-button-text="取消"
            cancel-button-type="default"
            width="180px"
            :enterable="true"
            placement="top"
            title="你确定要删除吗?" 
            effect="light"
            @confirm="deleteItem(scope.row)"
          >
            <template #reference>
              <span>
                <el-tooltip content="删除"	placement="top" effect="light">
                  <svg-icon icon-class="del" class="svg-icon"></svg-icon>
                </el-tooltip>
              </span>
            </template>
          </el-popconfirm>
        </div>
      </div>
      <div style="display: flex;" v-show="scope.row.isShowEdit">
        <el-input
          :ref="getRef"
          v-model="scope.row.title"
          placeholder="请输入"
          @blur="renameBlur(scope.row)"
        />
      </div>
    </template>
  </el-table-column>
  function getRef(row) {
    if(row) row.focus();
  }
8、表格之转圈、空数据、有数据
  <div
    v-loading="loading"
    v-if="loading||total>0"
  ><!-- 前者成立,此处显示转圈;后者成立,此处显示表格 -->
    <el-table
      :data="tableData"
      v-if="tableData.length>0"
    ></el-table>
    <el-pagination />
  </div>
  <div v-else><!-- 都不成立。请求结束,没有数据 -->
    <el-empty 
      description="暂无相关数据">
    </el-empty>
  </div>
9、子组件Feedback只包含一个弹窗(传值传参传数据)
(1)组件
  <template>
    <div class="search">
      <Toolbar title=""></Toolbar>
      <Feedback :show="isShowDialogPage" @set-show="setShow" :info="noObj"/>
    </div>
  </template>
  var isShowDialogPage = ref(false);
  function setShow(val) {
    isShowDialogPage.value = val;
  }
(2)子组件,Feedback
  A、情形一,组件的“传入属性”经ref包装后,能与组件内部的v-model绑定,因为该属性实际上成为组件内部变量的初始值
    v-model,只能内部定义
    <template>
      <el-dialog v-model="show" title="信息" :before-close="handleClose" width="548px">
        <p>组件的“传入属性”经ref包装后,能与组件内部的v-model绑定,因为该属性实际上成为组件内部变量的初始值</p>
      </el-dialog>
    </template>
    <script setup>
      const props = defineProps({
        isShow: false,
        info: {},
      });
      const show = ref(props.isShow);
      function handleClose() {
        show.value = false
      }
    </script>
  B、情形二,组件的“传入属性”能“直接”与组件内部的:model-value绑定,但只能通过父级改变;
    :model-value,既可以外部传入,也可以内部定义
    <template>
      <el-dialog :model-value="props.show" :model-value="show" @update:model-value="handleUpdate" title="信息" :before-close="handleClose"></el-dialog>
        <p></p>
      </el-dialog>
    </template>
    <script setup>
      const emits = defineEmits(["set-show"]);
      const props = defineProps({
        show: false,
        info: {},
      });
      const show = ref(props.show);
      function handleUpdate() {
        props.show = false;//黄色警告,show是只读属性,不能设置
      }
      function handleClose() {
        emits("set-show", false);
      }
    </script>
  C、情形三,组件的“传入属性”不能“直接”与组件内部的v-model绑定,因为组件内部不能改变组件的“传入属性”
    <template>
      <el-dialog v-model="props.show" v-model="show" title="信息" :before-close="handleClose" width="548px">
        <p>组件的“传入属性”不能与组件内部的v-model绑定,因为组件内部不能改变组件的“传入属性”</p>
        <p>v-model cannot be used on a prop, because local prop bindings are not writable.</p>
      </el-dialog>
    </template>
    <script setup>
      const props = defineProps({
        show: false,
        info: {},
      });
      function handleClose() {
        props.show = false
      }
    </script>
(3)标签与自定义标签中的v-model使用
  A、标签中的v-model使用
    <input v-model="searchText" />等同于
    <input
      :value="searchText"
      @input="fn"
    />
  B、自定义标签中的v-model使用
    <CustomInput v-model="searchText" />等同于
    <CustomInput
      :model-value="searchText"
      @update:model-value="fn"
    />
10、多行收缩为2行,点击则“用动画”展开
  (1)外层隐藏,不能触发点击事件
  (2)获取初始数据,内层显示初始区域,隐藏溢出区域
  (3)外层显示
  (4)缺少1、3步骤,页面会闪烁
  (5)点击箭头,内层“用动画”显示溢出区域,隐藏初始区域。
  <template>
    <div :style="{visibility: isWholeShow ? 'visible':'hidden'}">
      <div v-for="(item,index) in allNote" :style="{'background':item.readStatus?'#eaeef5':'#f5f6f9'}" class="mynote" @click="clickNoteItem(item)"  :key="index">
        <div class="mynote-item">
          <div>{{item.title}}</div>
          <div>
            <span style="padding-right: 30px;">{{millsecondsToDate(item.receiveTime)}}</span> 
            <el-icon :style="{visibility: item.isArrowShow ? 'visible':'hidden'}"  class="cursor">
              <ArrowRight style="color: gray" v-show="item.isFold" @click.stop="clickItemArrow(item)" />
              <ArrowDown style="color: gray" v-show="!item.isFold" @click.stop="clickItemArrow(item)" />
            </el-icon>
          </div>
        </div>
        <div v-show="isInitShow" class="isInitShow">{{item.content}}</div>
        <div v-show="item.isFold" class="fold_">{{item.content}}</div>
        <!-- 下面,from-height,to-height(过程伴随overflow:hidden,应当与style-height一致) -->
        <div v-show="!item.isFold" 
            :class="!item.isFold? 'divDown' + item.index:''" 
            :style="{'height':item.height + 'px','overflow':'hidden'}" 
        >{{item.content}}</div>
      </div>
    </div>
  </template>
  <script setup>
    var isWholeShow = ref(false);//防止闪烁
    var isInitShow = ref(false);//计算行高
    var clickItemArrow = function(item){
      item.isFold = !item.isFold;
    };
    var getData = function(){
      getAllNotes(data).then(function(result){
        isInitShow.value = true;
        allNote.length = 0;
        total.value = result.data.total;
        var list = result.data.list;
        for(var i = 0; i<list.length; i++){
          list[i].isArrowShow = false; 
          list[i].isFold = false; 
          allNote.push(list[i])
        } 
        setTimeout(function(){
          var all = document.getElementsByClassName('isInitShow')
          for(var i = 0; i<all.length; i++){
            if(all[i].clientHeight > 50) {
              allNote[i].isArrowShow = true;
              allNote[i].isFold = true;
              allNote[i].index = i;
              allNote[i].height = all[i].clientHeight;
              document.styleSheets[0].insertRule(
                "@keyframes moveDown" + i +
                "{" +
                  "from { height: 40px; }" +
                  "to { height: " + all[i].clientHeight + "px; }" +
                "}"
              )
              document.styleSheets[0].insertRule(
                ".divDown" + i + 
                "{" +
                  "animation: moveDown" + i + " 2s ease 0s 1 normal;" + /* 非常重要:2s前面有空格 */
                "}"
              )
            }
          }
          isInitShow.value = false;
          isWholeShow.value = true;
        });
      })
    };
    onMounted(function() {
      getData();
    })
  </script>
  <style lang="scss">
    .cursor{
      cursor: pointer;
    }
    .fold_{
      overflow: hidden;
      display: -webkit-box;
      -webkit-box-orient: vertical;
      -webkit-line-clamp: 2;
    }
  </style>
11、Vue.nextTick与任务队列
  (1)详细说明
    来源,https://blog.csdn.net/qq_39692513/article/details/123509911
    A、同步执行,
      数据A赋值,触发watcher,将异步事件推入到任务队列中;数据A再次赋值,不再触发,不再推入
      执行nextTick,将回调函数推入到任务队列中
    B、异步执行,
      主线程任务执行完毕,DOM更新,执行"任务队列",
      watch回调函数执行(会用到最终的数据A,但不能更改最终的数据A,以免陷入死循环)
      nextTick回调函数执行(会用到更新后的DOM)
    附、整个项目初始化时,存储相关回调函数,以备推入队列执行  
  (2)简略说明
    A、主线程执行nextTick,回调函数推入"任务队列"
    B、主线程任务执行完毕,DOM更新,执行"任务队列"的回调函数
  (3)示例,来源,https://blog.csdn.net/weixin_42333548/article/details/102606546
    <template lang="html">
      <div id="app">
        <div id="divBox" v-if="showText">测试文本内容</div>
        <button @click="getText">获取div内容</button>
      </div>
    </template>
    <script>
      export default {
        data () {
          return {
            showText: false
          }
        },
        mounted () {},
        methods: {
          getText () {
            this.showText = true;
            this.$nextTick(() => {//加上这层壳,下面内容在DOM更新后执行
              var innerHTML = document.getElementById('divBox').innerHTML;
              console.log(innerHTML);
            })
          }
        },
      }
    </script>
    <style lang="less"></style>
  附、任务队列为空
    来源,https://www.ruanyifeng.com/blog/2014/10/event-loop.html
    (1)所有的同步任务都在主线程的"执行栈"执行,所有的异步任务都放在"任务队列",
    (2)主线程同步任务执行完毕,就会读取"任务队列"的异步任务,该任务离开"任务队列",进入主线程的"执行栈"执行
    (3)主线程不断重复第2步,直到"任务队列"为空
12、表体超高,出现滚动条,常用于弹窗中
  <el-table :data="otherContent"  height="400"  overflow="auto">
13、图片预览
  (1)页面“有小图片占位的”预览
    <el-image 
      style="width: 100px; height: 100px"
      :src="url" 
      :preview-src-list="srcList">
    </el-image>
  (2)页面“无小图片占位的”预览
    <el-image-viewer @close="imgVisible = false" :url-list="[imgSrc]" v-if="imgVisible" />
    .el-image-viewer__canvas img{//让图片显示为圆角且描白边
      border-radius:10px;
      border:1px solid #fff
    }
  附、图标-宽固定,高自适应
    <svg-icon :icon-class="scope.row.docType" style="font-size: 24px" v-show="scope.row.docType !== 'image'" />
    <div style="display: inline-block; width:24px" v-show="scope.row.docType == 'image'">
      <img :src="scope.row.publicUrl" style="width: 100%" />
    </div>
	
八、Vue与React的异同 
1、相同点:
(1)用于创建UI的js库
(2)单向数据流
(3)用虚拟DOM
(4)基于组件
2、不同点:
(1)vue使用html模板;react使用jsx模板
(2)vue有双向绑定语法;react没有
(3)vue有computed和watch;react没有
  
  
  
  

  

posted @ 2020-10-26 23:40  WEB前端工程师_钱成  阅读(8933)  评论(0编辑  收藏  举报