VUE實現Studio管理後台(完結):標籤式輸入、名值對輸入、對話框(modal dialog)

  • 2020 年 3 月 10 日
  • 筆記

一周的時間,幾乎每天都要工作十幾個小時,敲程式碼+寫作文,介面原型算是完成了,下一步是寫內核的HTML處理引擎,純JS實現。本次實戰展示告一段落,等RXEditor下一個版本完成,再繼續分享吧。
剩下的功能:標籤式輸入、名值對輸入、對話框(modal dialog),邊框輸入,全部完成。
css class輸入,樣式跟屬性輸入,效果:

對話框(model dialog效果)

前幾期功能效果總覽:

標籤輸入框用來輸入CSS class,名字一如既往的好聽,就叫RxLabelInput吧。
輸入值一個數組,因為有多處要操作數組,增、刪、改、克隆、比較等。比較好的一個方式是把Array類用繼承的方式重寫一下,把這寫方法加到裡面。但是RXEidtor內核用純JS實現,並放在一個iFrame裡面,它跟主介面只能通過windows message傳遞數據,帶有方法的類無法作為消息被傳遞,暫時先不用這個方法,只把相關功能抽取成獨立函數,放在valueOperate.js裡面。
如果以後數組操作量更大,再考慮轉成一個通用的數組類。
前幾期介紹過,使用計算屬性changed來標識數據是否被修改過,changed計算屬性內部,需要比較兩個值是否相等,普通字元串不會有問題,要比較數組用這樣的方式最方便,先排序、轉成字元串、比較字元串:

aValue.sort().toString() === bValue.sort().toString()

數組的sort方法會改變原來的數組值,會引發數據刷新,從而再次調用計算屬性,形成死循環,調試了很長時間,就算空數組也會死循環。所以,需要把數據複製一份出來,再比較:

if(Array.isArray(a) && Array.isArray(b)){    //複製數組    let aValue = a.concat()    //複製數組    let bValue = b.concat()    //比較數組    return aValue.sort().toString() === bValue.sort().toString()  }

 

組件程式碼:

<template>    <div class="label-list">      <div        class="label-item"        v-for = "val in inputValue"      >        {{val}}        <span          class="remove-button"          @click="remove(val)"        >×</span>      </div>      <div style="width: 100%"></div>      <div class="add-button"        @click="addClick"      >+</div>      <div style="width: 100%"></div>      <input        v-show="isAdding"        v-model="newValue"        autofocus="autofocus"        :placeholder="$t('widgets.enter-message')"        @keyup.13 = "finishAdd"        ref="inputControl"      />    </div>  </template>    <script>  import {addToArray, removeFromArray} from './valueOperate'    export default {    props:{      value:{ default:[] },    },    computed:{      inputValue: {        get:function() {          return this.value;        },        set:function(val) {          this.$emit('input', val);        },      },    },    data () {      return {        isAdding : false,        newValue : '',      }    },    methods: {      addClick(){        this.isAdding = true;        this.$refs.inputControl.style.display = 'block'        this.$refs.inputControl.focus()      },      finishAdd(){        if(this.newValue){          this.newValue.split(' ').forEach((val)=>{            if(val){              addToArray(val, this.inputValue)            }          })          this.newValue = ''        }          this.isAdding = false      },      remove(val){        removeFromArray(val, this.inputValue)      }      },  }  </script>    <style>   .label-list{      background: rgba(0,0,0, 0.15);      display: flex;      flex-flow: row;      flex-wrap: wrap;      padding:10px;    }      .label-list .label-item{      padding:0 3px;      background: rgba(255,255,255, 0.15);      margin:1px;      border-radius: 3px;      height: 24px;      display: flex;      align-items: center;    }      .label-list .remove-button{      cursor: pointer;      margin-left: 2px;    }      .label-list .add-button{      background: rgba(255,255,255, 0.15);      width: 24px;      height: 22px;      display: flex;      align-items: center;      justify-content: center;      border-radius: 3px;      margin: 1px;      margin-top:3px;      font-size: 16px;      padding-bottom:3px;      cursor: pointer;    }      .label-list input{      outline: 0;      border: 0;      background: transparent;      color: #fff;      margin-top:4px;    }  </style>

用於輸入html屬性(attributes)和樣式(style)的名值對輸入控制項,也有一個拉風的名字:RxNameValueInput。

這個控制項的傳入值v-model是一個對象,作為一個對象,動態增刪屬性再加排序,會稍微有些不便,所以組件內部處理時,把這個對象轉換成一個二維數組:

mounted () {    for(var name in this.inputValue){      this.valueArray.push([name, this.inputValue[name]])    }  },

然後watch這個數組,當它有變化時,逆向轉化成對象,相當於完成一個雙向綁定,逆向轉化程式碼:

watch: {    valueArray() {      this.inputValue = {}      for(var i = 0; i < this.valueArray.length; i++){        let name = this.valueArray[i][0]        let value = this.valueArray[i][1]        this.inputValue[name] = value      }    }  }

 

整個組件的程式碼:

<template>    <div class="name-value-box">      <div class="name-value-row"        v-for="(item, i) in valueArray"      >        <div class="name-input">          <input v-model="item[0]"            @blur = "nameBlur(i)"          >        </div>        <div class="separator">:</div>        <div class="value-input">          <input v-model="item[1]">        </div>        <div class="clear-button"          @click="remove(i)"        >×</div>      </div>      <div class="name-value-row">        <div class="name-input">          <input            v-model="newName"            @keyup.13 = "addNew"            @blur = "newBlur"            ref="newName"          >        </div>        <div class="separator">:</div>        <div class="value-input">          <input            v-model="newValue"            @keyup.13 = "addNew"            @blur = "newBlur"          >        </div>        <div class="button-placeholder"        ></div>      </div>    </div>  </template>    <script>  export default {    props:{      value:{ default:{} },    },    computed:{      inputValue: {        get:function() {          return this.value;        },        set:function(val) {          this.$emit('input', val);        },      },    },    data () {      return {        valueArray : [],        newName : '',        newValue : '',      }    },    mounted () {      for(var name in this.inputValue){        this.valueArray.push([name, this.inputValue[name]])      }    },    methods: {      addClick(){      },        nameBlur(i){        this.valueArray[i][0] = this.valueArray[i][0].trim()        if(!this.valueArray[i][0]){          this.remove(i)        }      },        remove(i){        this.valueArray.splice(i, 1)      },        addNew(){        this.newName = this.newName.trim()        if(this.newName && !this.exist(this.newName)){          this.valueArray.push([this.newName, this.newValue])          this.newName = ''          this.newValue = ''          this.$refs.newName.focus()        }      },        newBlur(){        this.newName = this.newName.trim()        this.newValue = this.newValue.trim()        if(this.newName && this.newValue){          this.addNew()        }      },        exist(name){        for(var i = 0; i < this.valueArray.length; i++){          if(this.valueArray[i][0] === name){            return true          }        }        return false      }    },    watch: {      valueArray() {        this.inputValue = {}        for(var i = 0; i < this.valueArray.length; i++){          let name = this.valueArray[i][0]          let value = this.valueArray[i][1]          this.inputValue[name] = value        }      }    }    }  </script>    <style>   .name-value-box{      background: rgba(0,0,0, 0.15);      display: flex;      flex-flow: column;      padding:10px;    }      .name-value-box .add-button{      background: rgba(255,255,255, 0.15);      width: 24px;      height: 22px;      display: flex;      align-items: center;      justify-content: center;      border-radius: 3px;      margin: 1px;      margin-top:3px;      font-size: 16px;      padding-bottom:3px;      cursor: pointer;    }      .name-value-row{      width: 100%;      display: flex;      flex-flow: row;      height: 24px;      align-items: center;      font-size: 11px;    }      .name-value-row .name-input input, .name-value-row .value-input input{      width: 100%;      background: transparent;      color:#bababa;      outline: 0;      border: 0;    }      .name-value-row .separator{      width: 5px;      display: flex;      justify-content: center;      flex-shrink: 0;      color: #bababa;    }      .name-value-row .name-input{      flex: 1;    }      .name-value-row .value-input{      flex: 1.5;      padding-left:3px;    }      .name-value-row .clear-button{      display: flex;      align-items: center;      justify-content: center;      width: 20px;      height: 17px;      background: rgba(255,255,255,0.1);      border-radius: 3px;      margin:1px;      font-size: 12px;      padding-bottom: 3px;      cursor: pointer;    }      .name-value-row .button-placeholder{      width: 20px;      height: 20px;      background: transparent;    }    </style>

 

還實現了一個邊框輸入控制項,這個控制項沒有成長為通用控制項的潛力,就不介紹了,感興趣的直接看源碼,名字叫:RxBorderInput。

最後實現的一個控制項時對話框 ,Modal Dialog,目前有兩處地方用到它,一處時主題選擇對話框,一處時關於(about)對話框。

這兩處共用了通用對話框Modal,通過v-model傳入控制對話框是否顯示的值,通過卡槽Slot傳入對話框內容,Modal程式碼:

<template>    <div v-if="inputValue" class="modal-mask" @click="inputValue = false">      <div        class="modal"        :style="{          top : top,          left : left,          width :width,          height : height,        }"        @click="modalClick"      >        <slot></slot>      </div>    </div>  </template>    <script>  export default {    name: 'Modal',    props:{      value:{ default:'' },      width:{ default: '800px'},      height:{ default: 'calc(100vh - 80px)'},      top:{default: '40px'},      left:{default: 'calc(50% - 400px)'},    },    computed:{      inputValue: {        get:function() {          return this.value;        },        set:function(val) {          this.$emit('input', val);        },      },      },    data () {      return {      }    },      methods: {      modalClick(event){        event.stopPropagation()      },    },  }  </script>    <style>  .modal-mask{    position: fixed;    z-index: 9999;    top:0;    left: 0;    width: 100vw;    height: 100vh;    background: rgba(20, 20, 20, 0.9);  }  .modal-mask .modal{    position: fixed;    top:50%;    left:50%;    background: #fff;    box-shadow: 3px 3px 6px 3px rgba(0, 0, 0, 0.1);    transform: all 0.3s;    display: flex;    flex-flow: column;    color: #474747;  }    </style>

 

還可以通過屬性傳入對話框寬、高、位置等資訊。調用樣例,也是about對話框的程式碼:

<template>    <Modal v-model="inputValue"      width='600px'      height='400px'      top ="calc(50% - 200px)"      left ="calc(50% - 300px)"    >      <div class="dialog-head">        <div><i class="fas fa-question-circle"></i> {{$t('about.about-title')}} </div>        <span          class="close-button"          @click="inputValue = false"        >×</span>      </div>      <div class="dialog-body about-content">        本程式是RXEditor第二版的介面原型。<br/>        基於VUE實現,程式碼已轉入RXeditor項目。<br />        本原型不再維護,僅供學習參考。<br />        RXEditor是一個開源的,可視化的,HTML編輯工具,基於Bootstrap實現。<br />        RXEditor 程式碼地址:<a href="https://github.com/vularsoft/rxeditor" target="_blank">https://github.com/vularsoft/rxeditor</a>        演示地址:<a href="https://vular.cn/rxeditor/" target="_blank" >https://vular.cn/rxeditor</a>      </div>      <div class="dialog-footer">        <div class="dialog-button confirm-btn"          @click="inputValue = false"        >{{$t('about.close')}}</div>      </div>    </Modal>  </template>    <script>  import Modal from './Modal.vue'  export default {    name: 'AboutDialog',    components:{      Modal,    },    props:{      value:{ default:'' },    },    computed:{      inputValue: {        get:function() {          return this.value;        },        set:function(val) {          this.$emit('input', val);        },      },    },  }  </script>    <style>  .about-content{    display: flex;    justify-content: center;    align-items:flex-start;    font-size:14px;    line-height: 32px;    padding-left: 40px;  }    .about-content a{    color: #75b325;  }    .about-content a:hover{    color: #60921e;    text-decoration: underline;  }  </style>

 

到此為止,本是實戰項目全部完成,感謝大家的閱讀、關注。接下來會把這些程式碼應用在RxEditor中,具體是否要分享RxEditor內核,要看以後個人精力與時間。

本展示項目全部程式碼,請參考Github:https://github.com/vularsoft/studio-ui
若有有問題,請留言交流。