vue的多頁(yè)后臺(tái)管理系統(tǒng)搭建

前言

最近我在公司用的前端技術(shù)組合是layui+knockout.js的組合。為什么呢?因?yàn)檫@是原來(lái)公司前端留下來(lái)的,以我的前段底子還算摸得清楚,用起來(lái)也不算復(fù)雜,于是就用下去了。但是吧,越用越難用。為什么呢?之所以使用這個(gè)組合,就是希望使用knockout的雙向綁定功能取代dom操作,加上layui這樣一個(gè)相對(duì)比較全的ui庫(kù),組合起來(lái)看似完美。但是吧,有幾個(gè)組件,比如下拉選擇,單選多選等等,就是需要你對(duì)其添加單獨(dú)的綁定事件來(lái)處理數(shù)據(jù),很多時(shí)候就是需要你手動(dòng)刷新ui繪制才能有效果,這樣搞起來(lái)就很煩。尤其是現(xiàn)在,越來(lái)越多的細(xì)致的功能需要去做,而重用的內(nèi)容卻很難搞,就陷入了泥沼之中。
再說(shuō)說(shuō)為啥我沒有強(qiáng)改成vue。其實(shí)吧,我是研究過(guò)vue的,也練過(guò)手寫過(guò)幾篇博客在這個(gè)平臺(tái)的有http://www.itdecent.cn/p/29625af02d79。但是,一直以來(lái)我都沒有搭建成功過(guò)一個(gè)多頁(yè)應(yīng)用,或者說(shuō)可以自動(dòng)掃描多頁(yè)的應(yīng)用?,F(xiàn)在居然讓我碰到了一個(gè)模板,地址是:https://github.com/Plortinus/vue-multiple-pages。然后只需要npm install就可以了。接下來(lái)我們從項(xiàng)目結(jié)構(gòu)的分析開始吧。

基本情況

運(yùn)行

這個(gè)代碼down下來(lái)是可以直接運(yùn)行的,下面是一些基本的操作:

npm run serve # 運(yùn)行server進(jìn)行調(diào)試
npm run build # 構(gòu)建項(xiàng)目,會(huì)生成dist目錄

配置

這里介紹一下主要的配置文件。

/vue.config.js

這個(gè)應(yīng)該是vue項(xiàng)目的主要配置文件,這里可以看到對(duì)多頁(yè)目錄的 讀取,server的基本配置。

/server.js

這個(gè)應(yīng)該是運(yùn)行的服務(wù)的配置,可以看到其讀取的文件目錄,貌似使用的是一個(gè)叫做express的包,具體我也還沒有研究怎么用,后面再說(shuō)吧。

/title.js

這里配置各個(gè)頁(yè)面的標(biāo)題,它在vue.config.js中有被使用到。或許我們可以在頁(yè)面內(nèi)部解決標(biāo)題的問(wèn)題,這個(gè)后面研究了再說(shuō)。

目錄結(jié)構(gòu)

源代碼的頂級(jí)目錄分為public、src,其中public的內(nèi)容很簡(jiǎn)單,看頁(yè)面似乎是在js不運(yùn)行的情況下展示的報(bào)錯(cuò)頁(yè)面。src里面則是我們自己寫的各種內(nèi)容的頁(yè)面。

/src/assets

靜態(tài)資源目錄

/src/components

組件目錄,如果我們希望某個(gè)組件可以在不同的頁(yè)面之間進(jìn)行重用,把它放在這里。

/src/states

狀態(tài)管理邏輯,所有的狀態(tài)管理放在這里會(huì)有助于管理整個(gè)web端的狀態(tài),幫助理清思路。因?yàn)?,狀態(tài),理論上來(lái)說(shuō)是可以跨頁(yè)面、跨組件的,之和業(yè)務(wù)本身的生命周期有關(guān)。

/src/pages

這里就是各個(gè)頁(yè)面的代碼了,要注意的是,頁(yè)面是按照目錄結(jié)構(gòu)組織的,每個(gè)目錄下面的app.js是該頁(yè)面的入口,這個(gè)可以在vue.config.js文件中找到對(duì)應(yīng)的使用。而每個(gè)頁(yè)面是一個(gè)一個(gè)文件夾。訪問(wèn)頁(yè)面的時(shí)候url為:http://域名:端口/目錄結(jié)構(gòu).html,也即最后一個(gè)app.js所在的目錄名字加上.html就是它的url啦。

實(shí)戰(zhàn)demo

實(shí)際寫頁(yè)面的時(shí)候,我發(fā)現(xiàn)個(gè)問(wèn)題,就是它默認(rèn)的element-ui的版本太低了,于是我改成了最新的,2.13.2,這樣就和官網(wǎng)文檔的描述一致了。具體的修改方法是,在package.json文件中,找到element-ui,把后面的版本號(hào)改了。然后,在命令行里執(zhí)行,npm install,就可以了。后面我們開始寫頁(yè)面吧。

修改登陸密碼頁(yè)面

由于我這個(gè)項(xiàng)目是補(bǔ)充之前項(xiàng)目的頁(yè)面,所以不會(huì)從登陸、主頁(yè)這樣的寫,于是我挑了個(gè)最簡(jiǎn)單的頁(yè)面用來(lái)練手。修改登陸密碼頁(yè)面。先粘貼頁(yè)面代碼吧:

<template>
<div>
    <el-form ref="form" :model="form" label-width="80px">
        <el-form-item label="原密碼">
            <el-input v-model="form.oldPwd" show-password></el-input>
        </el-form-item>
        <el-form-item label="新密碼">
            <el-input v-model="form.newPwd" show-password></el-input>
        </el-form-item>
        <el-form-item>
            <el-button type="primary" @click="onSubmit">修改登陸密碼</el-button>
        </el-form-item>
    </el-form>
</div>
</template>

<script>
export default {
  data() {
    return {
      form: {
        oldPwd: '',
        newPwd: ''
      }
    }
  },
  methods: {
    onSubmit() {
      console.log('submit!')
    }
  }
}
</script>

<style>
</style>

上面的代碼,是只繪制了頁(yè)面的樣子的代碼,可以看到script里面只有空的data及methods,具體的業(yè)務(wù)邏輯都沒有實(shí)現(xiàn)。需要注意的是,在template里面使用到的data也好、methods也好,都需要至少進(jìn)行聲明,否則編譯不過(guò)去,也就無(wú)法調(diào)試了。具體的需要注意的細(xì)節(jié),也就只有el-input最后的show-password了。這是element-ui內(nèi)置的一個(gè)屬性,它幫助我們實(shí)現(xiàn)了一個(gè)密碼框,右側(cè)還有按鈕可以控制密碼框顯示明文還是點(diǎn)號(hào)。需要注意的是,這個(gè)效果就受限制與element-ui的版本,最初的那個(gè)版本就無(wú)法正常使用。

修改登陸密碼請(qǐng)求

這個(gè)頁(yè)面非常簡(jiǎn)單,唯一需要的業(yè)務(wù),也就是把修改密碼的請(qǐng)求提交到后臺(tái)。這里我們就不能用jquery了,我們將會(huì)使用一個(gè)叫做axios的框架。安裝命令如下:

npm install axios

在script中進(jìn)行引入,代碼如下:

import axios from 'axios'

然后就可以開始進(jìn)行ajax請(qǐng)求啦,樣例代碼如下:

      axios.post(
          'http://localhost:8080/api/sys/users/' + userId + '/modifyPassword',
          {
            userId: userId,
            oldPwd: 'admin123',
            newPwd: 'admin1234'
          },
          {
            headers: {
              'Content-Type': 'application/json',
              SessionToken:
                '123456'
            }
          }
        )
        .then(data => {
          console.log(data)
        })

可以看到我們這是進(jìn)行的post請(qǐng)求。它的請(qǐng)求格式其實(shí)是:axios#post(url[, data[, config]]) ,也就是說(shuō),第一個(gè)參數(shù)是要請(qǐng)求的url,這個(gè)是必填的,然后是請(qǐng)求的數(shù)據(jù)。這個(gè)data就是你要請(qǐng)求的json字符串。我不確定axios的請(qǐng)求默認(rèn)是json的,所以,我在后面的config里面加入了json的header,還加入了我用來(lái)做會(huì)話驗(yàn)證的SessionToken的頭。最后在then里面是回調(diào)。
這里需要說(shuō)明的是,這種調(diào)用方式肯定不是最終的調(diào)用方式,最終還需要進(jìn)行生產(chǎn)需要的封裝,以在寫業(yè)務(wù)的時(shí)候可以更少得關(guān)注這些細(xì)節(jié)。但是,有了這個(gè),可以說(shuō)我們就可以完成這個(gè)頁(yè)面了。剩下的只是,也只是如何使用vue及其生態(tài)了。

路徑別名

在代碼中import自己的js如果總是用相對(duì)路徑,其實(shí)韓式蠻煩人的一件事。但是,這里面的根路徑又很不好用,所以就需要引入路徑別名。由于我的項(xiàng)目是使用vue-cli 4.0的,所以它的配置文件是vue.config.js,添加如下代碼即可:

const path = require('path') //引入path模塊
function resolve( dir){
  return path.join(__dirname, dir) //path.join(__dirname)設(shè)置絕對(duì)路徑
}

module.exports = {
  ……
  chainWebpack: (config) => {
    config.plugins.delete('named-chunks')
    config.resolve.alias
      //set第一個(gè)參數(shù):設(shè)置的別名,第二個(gè)參數(shù):設(shè)置的路徑
      .set('@', resolve('./'))
      .set('components', resolve('./src/components'))
      .set('assets', resolve('./src/assets'))
      .set('pages', resolve('./src/pages'))
      .set('tools', resolve('./src/tools'))
  },
……

結(jié)合自己的代碼進(jìn)行改造吧。在使用的時(shí)候,我們使用@,就意味著在使用./,依此類推,就有了一些目錄的簡(jiǎn)短的縮寫了,方便使用。

加入統(tǒng)一認(rèn)證中心

認(rèn)證中心原理

最近能夠研究vue+element-ui的技術(shù)組合,也是源于設(shè)計(jì)完成了統(tǒng)一的認(rèn)證中心才可以在一個(gè)web端統(tǒng)一使用兩種不同的web技術(shù)來(lái)進(jìn)行組合。關(guān)鍵點(diǎn)有以下幾點(diǎn):

  • 一個(gè)驗(yàn)證會(huì)話的頁(yè)面:注意,這里是頁(yè)面,它被放在認(rèn)證中心,所有需要驗(yàn)證會(huì)話的頁(yè)面都知道它在哪里。
  • 需要驗(yàn)證會(huì)話的頁(yè)面中,創(chuàng)建一個(gè)不可見的iframe,里面用來(lái)訪問(wèn)驗(yàn)證回話的頁(yè)面。通信方式使用PostMessage進(jìn)行,這種通信是基于Html5標(biāo)準(zhǔn),同時(shí)可以跨域。通信的目的是為了從驗(yàn)證回話頁(yè)面同步會(huì)話信息到當(dāng)前域下面。
  • 基本的流程控制:由于是通過(guò)頁(yè)面進(jìn)行跨域通信的,所以整個(gè)通信都需要在頁(yè)面加載完成之后進(jìn)行。待驗(yàn)證頁(yè)面加載完成之后,才可以操作iframe的dom。iframe頁(yè)面加載完成之后才可以向其發(fā)送驗(yàn)證請(qǐng)求。而驗(yàn)證請(qǐng)求的發(fā)送和接收都是異步的,頁(yè)面并不會(huì)進(jìn)行阻塞等待。如何處理這個(gè)流程也是該功能是否流暢穩(wěn)定的關(guān)鍵。

統(tǒng)一模板組件

如何在每個(gè)頁(yè)面中加入統(tǒng)一認(rèn)證的過(guò)程,又不會(huì)在編寫頁(yè)面中引入過(guò)多的復(fù)雜度,這在vue的組件化體系中,我還是摸索了幾種想法的,現(xiàn)在把我的心路歷程整理下:

  • 每個(gè)頁(yè)面加入一個(gè)驗(yàn)證組件:以前我在寫vue的單頁(yè)應(yīng)用的例子的時(shí)候,登錄頁(yè)面和主頁(yè)是在同一個(gè)頁(yè)面中存在的,中間使用了一個(gè)登錄狀態(tài)標(biāo)志位進(jìn)行區(qū)分。整個(gè)效果是挺靈敏的,但是問(wèn)題就是每個(gè)頁(yè)面都會(huì)顯示得直到這個(gè)邏輯的存在,而且需要在html的結(jié)構(gòu)和js代碼中顯示得直到這個(gè)知識(shí),這是很討厭的一件事。但是,因?yàn)檫@個(gè)邏輯肯定可行,所以我后面的思路就變成了怎么把這個(gè)邏輯替換掉。
  • 頁(yè)面路由:上述方案肯定能實(shí)現(xiàn)這個(gè)過(guò)程,但是關(guān)于驗(yàn)證過(guò)程的順序要求就變得有些麻煩了。onload事件的順序沒有問(wèn)題,但是PostMessage的異步通信要怎么解決。因?yàn)檫@個(gè)時(shí)候,頁(yè)面本身可能也有需要在onload事件里執(zhí)行的請(qǐng)求,而且需要用到其最終結(jié)果,會(huì)話標(biāo)志。之前我思考到的方案是設(shè)置一個(gè)是否已驗(yàn)證的標(biāo)志位,在未驗(yàn)證的時(shí)候,所有的網(wǎng)絡(luò)請(qǐng)求會(huì)進(jìn)入一個(gè)隊(duì)列而不是直接請(qǐng)求。在收到驗(yàn)證結(jié)果后,依次執(zhí)行這個(gè)隊(duì)列里的請(qǐng)求。也是因?yàn)檫@個(gè)思路,我蠻不想用它的。而如果整個(gè)過(guò)程有一個(gè)頁(yè)面跳轉(zhuǎn)作為分隔,就變得簡(jiǎn)單多了。不過(guò),在我找頁(yè)面路由的實(shí)現(xiàn)方法時(shí),我找到了我現(xiàn)在用的方案。
  • 寫一個(gè)通用組件,里面加一個(gè)slot用來(lái)呈現(xiàn)頁(yè)面。在vue中有一種機(jī)制叫做slot,他可以讓我們改變組件里面的內(nèi)容。畢竟組件的使用形式是標(biāo)簽,而如果通過(guò)標(biāo)簽里面的內(nèi)容來(lái)改變組件的呈現(xiàn),這似乎就是我想要的。而因?yàn)榻M件還可以有自己的代碼邏輯,所以驗(yàn)證的過(guò)程就被有效得被隔離開了。于是,一切都變得那么自然,這個(gè)通用組件做我頁(yè)面的頂級(jí)元素就可以了。

組件實(shí)現(xiàn)與應(yīng)用

以下是template部分的代碼:

<template>
<needAuthTemplate>
    <el-form ref="form" :model="form" label-width="80px">
        <el-form-item label="原密碼">
            <el-input v-model="form.oldPwd" show-password></el-input>
        </el-form-item>
        <el-form-item label="新密碼">
            <el-input v-model="form.newPwd" show-password></el-input>
        </el-form-item>
        <el-form-item>
            <el-button type="primary" @click="onSubmit">修改登陸密碼</el-button>
        </el-form-item>
    </el-form>
</needAuthTemplate>
</template>

其中,needAuthTemplate是我定義的組件,它的代碼如下:

<template>
<div>
  <slot></slot>
</div>
</template>
<script>
import config from '@/config.js'
console.log(config)

export default {
  data() {
    return {
      // configData: config.authUrl
      configData: 'http://localhost/page/sys/sessionCheck.html'
    }
  },
  methods: {
    sendCheckCmd(e) {
      console.log('sendCheckCmd:' + e)
    }
  },
  mounted: function() {
    /**
     * 注冊(cè)監(jiān)聽事件
     */
    window.addEventListener(
      'message',
      function(e) {
        //如果e中含有type,說(shuō)明是系統(tǒng)的消息,否則則嘗試解析自定義消息
        if (e.data.type != undefined) {
          return
        }
        console.log(e.data)
        if (e == undefined || e == null) {
          return
        }

        var cmdData = JSON.parse(e.data) //獲取json對(duì)象
        //如果cmdType不存在,說(shuō)明入?yún)⒎欠?        if (cmdData.cmdType == undefined || cmdData == null) {
          return
        }

        switch (cmdData.cmdType) {
          case 'info':
            console.log(cmdData.sessionId)
            if (cmdData.sessionId == undefined || cmdData.sessionId == null) {
              //跳轉(zhuǎn)登錄
              window.location = 'http://localhost'
            } else {
              //刷新會(huì)話標(biāo)志
              localStorage.setItem('sessionId', cmdData.sessionId)
              //加載后續(xù)頁(yè)面
            }
            break
        }
      },
      false
    )
    //創(chuàng)建用來(lái)驗(yàn)證會(huì)話的iframe
    let bodyDoc = document.querySelector('body')
    let iframe = document.createElement('iframe')
    iframe.style.width = '0px'
    iframe.style.height = '0px'
    iframe.style.display = 'none'

    iframe.onload = () => {
      var cmdData = {}
      cmdData.cmdType = 'check'
      iframe.contentWindow.postMessage(JSON.stringify(cmdData), '*')
      console.log('加載完成') // 這樣每次都會(huì)觸發(fā)
    }
    iframe.src = this.configData

    //將dom附加到窗口中
    bodyDoc.appendChild(iframe)
    console.log('mounted')
  }
}
</script>
<style>
</style>

這個(gè)代碼相對(duì)較長(zhǎng),有這么而幾個(gè)點(diǎn)需要注意。

  • template部分,只是一個(gè)div嵌套了一個(gè)slot,也就是說(shuō)這個(gè)組件展示的內(nèi)容就是slot的內(nèi)容。
  • script部分,可以被分為兩個(gè)部分。第一段是給頁(yè)面添加消息監(jiān)聽事件,用來(lái)處理接收會(huì)話消息;第二段則是在當(dāng)前頁(yè)面添加iframe的dom,在這個(gè)dom里我們?cè)L問(wèn)會(huì)話驗(yàn)證頁(yè)面。而且,注意添加dom的順序,一定是一切都配置好了再添加到頁(yè)面上。

但是這個(gè)時(shí)候其實(shí)還是有問(wèn)題的:

  • vue的版本:這個(gè)問(wèn)題是我在讀slot的的文檔的時(shí)候看到的,我默認(rèn)的版本是2.5.x的版本,但是2.6.0以后,slot的標(biāo)簽語(yǔ)法發(fā)生了變化,所以,我升級(jí)到了最新的版本。需要注意的是vue和vue-template-compiler的版本要一起升級(jí),否則編譯不過(guò)。
  • 要讓template中可以使用這個(gè)標(biāo)簽,是需要在組件文件夾額外寫一個(gè)index.js文件的,文件內(nèi)容為:
import needAuthTemplateComponent from './needAuthTemplate.vue'

const needAuthTemplate = {
  install: function(Vue) {
    Vue.component('NeedAuthTemplate', needAuthTemplateComponent)
  }
}

export default needAuthTemplate

注意,標(biāo)簽的名字是Vue.component的第一個(gè)參數(shù)。而在使用的頁(yè)面的app.js的里面引入這個(gè)組件的代碼也從直接引入vue文件變成了引入組件的目錄,如下:

import NeedAuthTemplate from 'components/needAuthTemplate/'

Vue.use(NeedAuthTemplate)

認(rèn)證中心坑

從實(shí)踐來(lái)看,這次的實(shí)現(xiàn)依然完美實(shí)現(xiàn)多系統(tǒng)的統(tǒng)一認(rèn)證,原因有兩點(diǎn):

  1. 每次都加載頁(yè)面進(jìn)行驗(yàn)證,其效率并不高,整體性能還是比較慢的。
  2. 這種設(shè)計(jì)其實(shí)并沒有解決之前我說(shuō)的因?yàn)楫惒叫枰却膯?wèn)題。也就是,如果你切換用戶,原有頁(yè)面你要刷新兩次才能看到新用戶的信息。而如果進(jìn)行阻塞性的等待,則每次加載頁(yè)面用戶都會(huì)有顯示的等待時(shí)間。

基于上述原因,我還是在登錄的地方直接做了多域登錄,現(xiàn)在的方式就只作為會(huì)話同步的手段了。

vue填坑記

上面我們看到了搭建認(rèn)證中心過(guò)程中的坑。搭建完認(rèn)證中心后,由于我們已經(jīng)存在了主框架,所以接下來(lái)的重點(diǎn)是編寫每個(gè)頁(yè)面。而編寫過(guò)程中遇到的重要內(nèi)容,我將會(huì)記錄在此。

methods中的search方法

記住,方法名千萬(wàn)不能用search,這似乎是methods中的內(nèi)置方法名。我在用了這個(gè)方法名之后,一旦在mounted中調(diào)用,就會(huì)導(dǎo)致編譯報(bào)錯(cuò)。該錯(cuò)誤,我調(diào)試了一天,以此謹(jǐn)記。

通過(guò)props與子組件進(jìn)行通信

這次,我的場(chǎng)景是一個(gè)非常常見的場(chǎng)景,添加和修改的彈窗。這兩個(gè)彈窗我做成了一個(gè)組件。組件只是彈窗里面的內(nèi)容,并不包含彈窗,方便以后復(fù)用。我需要將記錄的ID傳入組件內(nèi),以方便根據(jù)是否存在需要加載的ID來(lái)判斷當(dāng)前是當(dāng)前是新增還是編輯。
所以,我需要解決這樣幾個(gè)問(wèn)題:

  • 將ID從父組件傳入進(jìn)來(lái)
  • 持續(xù)得監(jiān)聽I(yíng)D的變化

將ID從父組件傳入進(jìn)來(lái)

這件事情,說(shuō)來(lái)簡(jiǎn)單,但是由于官方文檔在介紹代碼的時(shí)候并不是以vue文件的方式來(lái)介紹的,而且在說(shuō)的時(shí)候不知道代碼是在父組件還是子組件,所以讓人困惑的老想做些別的事情。現(xiàn)整理如下:
首先是子組件的script部分,因?yàn)楹推渌糠譀]有關(guān)系,所以就不展示了。

export default {
  name: 'SaveLampSpecification',
  props: ['lampSpecificationId'],
  data() {
    let self = this
    return {
      form: {
        //燈具規(guī)格ID
        lampSpecificationId: self.lampSpecificationId,
        ……
      }
    }
  }
}

父組件的使用代碼如下:

<template>
……
  <el-dialog title="添加燈具規(guī)格" :visible.sync="control.saveFormVisible" width="500px">
    <SaveLampSpecification :lampSpecificationId="control.selectedLampSpecificationId"></SaveLampSpecification>
  </el-dialog>
……
</tempalte>
<script>
import SaveLampSpecification from 'components/basicData/saveLampSpecification/saveLampSpecification.vue'

export default {
  components: {
    SaveLampSpecification
  },
  data() {
    ……
      control: {
        //保存燈具規(guī)格的顯隱設(shè)置
        saveFormVisible: false,
        selectedLampSpecificationId: 1
      }
    }
  },
……
}
</script>

然后我們總體來(lái)說(shuō)一下。首先是在子組件中,需要聲明props以說(shuō)明都有哪些屬性可以使用。官網(wǎng)說(shuō)著這里有個(gè)駝峰轉(zhuǎn)橫杠的自動(dòng)命名轉(zhuǎn)換。親測(cè),沒有。然后,你聲明的這個(gè)屬性的訪問(wèn)方式其實(shí)和data里的是一樣的,在我的代碼中可以看到。但是,為了方便使用,我還是在data里聲明了另一個(gè)屬性,讓它等于這個(gè)屬性。這就是子組件的全部。而父組件,在使用的時(shí)候,將該屬性和data的某個(gè)值進(jìn)行綁定,也就形成了子組件和外部組件屬性的關(guān)聯(lián)。不過(guò)呢,這個(gè)時(shí)候你會(huì)發(fā)現(xiàn)一件事。當(dāng)你改變外面的屬性值后,里面的屬性值是不會(huì)跟著改變的。這里需要在子組件中添加一個(gè)監(jiān)聽代碼來(lái)搞定它:

  watch: {
    lampSpecificationId() {
      let self = this
      self.form.lampSpecificationId = self.lampSpecificationId
    }
  },

watch是vue的標(biāo)準(zhǔn)用法,我就不寫這段代碼的上下文了??傊?,這樣就完成了內(nèi)外屬性的關(guān)聯(lián)綁定了。

重復(fù)打開彈窗遇到的坑

首先,無(wú)意中發(fā)現(xiàn),默認(rèn)情況下彈窗在任意非彈窗位置點(diǎn)擊就關(guān)閉了。不過(guò),找到屬性,設(shè)置掉就可以了。另外我還遇見了另一個(gè)屬性,即每次關(guān)閉都銷毀相關(guān)的元素。調(diào)整后代碼如下:

  <el-dialog title="添加燈具規(guī)格" 
    :visible.sync="control.saveFormVisible" width="500px" 
    :close-on-click-modal="false" 
    :destroy-on-close = "false">
    <SaveLampSpecification 
      :lampSpecificationId="control.selectedLampSpecificationId" 
      v-on:saved="saveLampSpecification">
    </SaveLampSpecification>
  </el-dialog>

這些呢,只能算是小坑。后面預(yù)見一個(gè)大坑。編輯數(shù)據(jù)的時(shí)候,除了第一次,沒次打開都是上次的數(shù)據(jù)。這讓我很震驚。我是將加載數(shù)據(jù)寫在mounted里面的,難道這里有問(wèn)題?開始得時(shí)候我以為是因?yàn)楫惒郊虞d數(shù)據(jù)導(dǎo)致數(shù)據(jù)沒更新,就查了半天vue更新數(shù)據(jù)不更新界面怎么弄。后來(lái)我發(fā)現(xiàn),每次查詢數(shù)據(jù)的日志是在我關(guān)閉界面的時(shí)候打的,于是我懷疑生命周期不是我預(yù)想的樣子,于是把加載放到了上面寫的watch里面,一切就都好了。

重點(diǎn)說(shuō)明下destroy-on-close

上面的destroy-on-close屬性,默認(rèn)值就是false,只是我發(fā)現(xiàn)了它之后將它設(shè)置成了true。這將導(dǎo)致每次彈窗關(guān)閉后,其組件都會(huì)被銷毀,下次打開就需要重新渲染。

  • 你在頁(yè)面中是無(wú)法拿到這個(gè)組件的實(shí)例的,因?yàn)槊看尾伙@示的時(shí)候它都被銷毀了。
  • 由于我一個(gè)組件同時(shí)干了創(chuàng)建和編輯的事情,而我又無(wú)法獲取它的實(shí)例,于是上面的邏輯bug就很難處理。比如,我點(diǎn)開創(chuàng)建的時(shí)候,里面是空的。再點(diǎn)擊一個(gè)編輯,加載了數(shù)據(jù)。關(guān)閉后再點(diǎn)開同一個(gè)編輯,就是空的。為什么呢?因?yàn)閣atch沒有被觸發(fā),而組件又被銷毀了,所以它理所當(dāng)然的變成了初始的樣子。而將這個(gè)設(shè)置為false后,一切都o(jì)k了。

綜上所述,該屬性的使用場(chǎng)景需要仔細(xì)考慮。

input的點(diǎn)擊事件

很神奇的是,你會(huì)發(fā)現(xiàn),在element ui的文檔中,input是沒有點(diǎn)擊事件的。但是,其實(shí)這個(gè)事件我們用的并不少。于是找了下怎么加點(diǎn)擊事件,結(jié)果說(shuō),你可以使用原生的click事件就可以了,代碼大概如下:

<el-input v-on:click.native='clickSelectNode' ></el-input>

component 動(dòng)態(tài)切換

在我的一個(gè)功能點(diǎn)上,我需要某個(gè)位置的組件根據(jù)不同的數(shù)據(jù)進(jìn)行不同的展示。由于我數(shù)據(jù)都是按照產(chǎn)品型號(hào)進(jìn)行劃分的,所以不同的型號(hào)我做成了不同的組件。它們?cè)趧?dòng)態(tài)組件component標(biāo)簽上進(jìn)行切換。但是這種切換其實(shí)并不是說(shuō)換一個(gè)屬性就完了這么簡(jiǎn)單。為什么這么說(shuō)呢?首先,假設(shè),我有三個(gè)型號(hào),每個(gè)型號(hào)有三十條數(shù)據(jù)。當(dāng)我點(diǎn)開型號(hào)一的時(shí)候,它會(huì)觸發(fā)mounted事件。但是,當(dāng)我再點(diǎn)開另一條型號(hào)一的數(shù)據(jù)時(shí),這個(gè)事件就不會(huì)再被觸發(fā)了,vue把它存起來(lái)了。而在組件的生命周期中,我并沒有找到準(zhǔn)確得符合這個(gè)時(shí)機(jī)的事件。這就是它惡心的地方。
開始,我嘗試了一種簡(jiǎn)單粗暴的解決方法,設(shè)置component的ref,然后換了組件后直接通過(guò)ref調(diào)用里面一個(gè)我約定了的方法。結(jié)果就是,我得等下次才能夠調(diào)用到上次的ref。我沒有找到確切的原因,猜測(cè)是因?yàn)槲以O(shè)置的是組件的名稱,創(chuàng)建是異步的,所以我拿到的實(shí)際還是上一次的實(shí)例。
后來(lái),我又查到了直接實(shí)例化組件。其實(shí)我成功了,但是,我只是成功調(diào)用到了方法,它里面注入的組件,組件里的數(shù)據(jù)都不存在,也就是它沒有經(jīng)過(guò)完善的生命周期。
最后,我回到了原始的處理思路。設(shè)置一個(gè)props傳參,watch它,結(jié)合mounted事件,覆蓋了所有的變化時(shí)機(jī)。

element ui 表格行單擊和行內(nèi)按鈕重疊

鑒于我寫的是個(gè)管理系統(tǒng),表格就變成了交互的重點(diǎn)之一。為了減少需要使用的按鈕,我將查看記錄詳情的操作變成了單擊某行。但是,行間也有操作按鈕。這個(gè)時(shí)候點(diǎn)擊這些操作的按鈕就變成了它們既執(zhí)行自己的事件,也會(huì)執(zhí)行行單擊事件。這就有些蛋疼了。不過(guò)這件事是由于js的冒泡導(dǎo)致的。知道原因就好說(shuō)了。但是吧,要說(shuō)純js,好說(shuō),這使用vue和element ui封裝過(guò)的,怎么處理就有些討厭了。不過(guò)查了下,還是有簡(jiǎn)單的方式的。在操作按鈕那里套個(gè)div,注明組織點(diǎn)擊事件傳遞即可,代碼如下:

    <el-table-column
      label="操作">
      <template slot-scope="scope">
        <div @click.stop>
        <el-button v-on:click="modifyLampSpecification(scope.row.sensorId)">編輯</el-button>
        <el-button v-on:click="deleteLampSpecification(scope.row.sensorId)">刪除</el-button>
        </div>
      </template>
    </el-table-column>

這里只展示了一個(gè)column的代碼,不過(guò)應(yīng)該足夠看明白了。

element ui timepicker

其實(shí)這個(gè)組件使用起來(lái)相當(dāng)簡(jiǎn)單。讓我非常滿意的是很靈活。之前我用layui的時(shí)候,選擇時(shí)間,是無(wú)法配置只選擇小時(shí)、分鐘的,必須連秒一起選擇。而這里,通過(guò)配置format就可以控制只有小時(shí)和分鐘兩個(gè)選擇,棒棒噠。另外,值得一說(shuō)的就是,設(shè)置它的值默認(rèn)是js的時(shí)間對(duì)象,如果先用字符串,需要你設(shè)置value-format才可以。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容