<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          如何充分利用Composition API對Vue3項目進(jìn)行代碼抽離

          共 75200字,需瀏覽 151分鐘

           ·

          2021-03-10 11:26

          本文代碼略多,希望大家耐心觀看

          背景介紹

          在2020年,Vue3的學(xué)習(xí)一直被我鴿到了11月份,在學(xué)完以后,我自己做了一個Vue3的小項目nav-url,也整理了我對于如何快速上手Vue3的幾篇博客,很高興受到了大家的指點(diǎn)和喜歡:

          在上一篇博客中,我詳細(xì)介紹了一下我發(fā)的第一版項目的特色、亮點(diǎn)以及所有核心功能的實(shí)現(xiàn),希望大家可以前往閱讀體驗(yàn)一下(記得用電腦打開,因?yàn)檫@是一個PC端的項目)

          然而,這項目只是實(shí)現(xiàn)了一些功能,但我感覺并沒有很好地利用Composition API去對代碼進(jìn)行整合管理。要知道,Composition API的出現(xiàn)就是為了解決Options API導(dǎo)致相同功能代碼分散的現(xiàn)象,也有很多大佬對其做了很多的動畫展示(這里我借用一下大帥搞全棧大佬精心制作的動畫,他的這篇文章可以說是好評連連)

          看了一下我項目初版的代碼,簡直是沒有體現(xiàn)出Composition API的優(yōu)勢,可以給大家看一下某個組件內(nèi)的代碼

          <template>
            <aside id="tabs-container">
                <div id="logo-container">
                    {{ navInfos.navName }}
                </div>
                <ul id="tabs">
                    <li class="tab tab-search" @click="showSearch">
                        <i class="fas fa-search tab-icon"/>
                        <span>快速搜索</span>
                    </li>
                    <li class="tab tab-save" @click="showSaveConfigAlert">
                        <i class="fas fa-share-square tab-icon"></i>
                        <span>保存配置</span>
                    </li>
                    <li class="tab tab-import" @click="showImportConfigAlert">
                        <i class="fas fa-cog tab-icon"></i>
                        <span>導(dǎo)入配置</span>
                    </li>
                    <br>
                    <li v-for="(item, index) in navInfos.catalogue" 
                        :key="index"
                        class="tab"
                        @click="toID(item.id)">

                          <span class="li-container">
                            <i :class="['fas', `fa-${item.icon}`, 'tab-icon']" />
                            <span>{{ item.name }}</span>
                            <i class="fas fa-angle-right tab-icon tab-angle-right"/>
                          </span>
                    </li>
                    <li class="tab add-tab" @click="addTabShow">
                        <i class="fas fa-plus"/>
                    </li>
                </ul>
                <!--    添加標(biāo)簽彈框     -->
                <tabAlert />
                <!--    保存配置彈框     -->
                <save-config @closeSaveConfigAlert="closeSaveConfigAlert" :isShow="isShowSaveAlert"/>
                <!--    導(dǎo)入配置彈框     -->
                <import-config @closeImportConfigAlert="closeImportConfigAlert" :isShow="isShowImportAlert"/>
            </aside>
          </template>

          <script>
          import {ref} from 'vue'
          import {useStore} from 'vuex'
          import tabAlert from '../public/tabAlert/tabAlert'
          import saveConfig from './childCpn/saveConfig'
          import importConfig from './childCpn/importConfig'
          export default {
              name'tabs',
              components: {
                  tabAlert,
                  saveConfig,
                  importConfig
              },
              setup() {
                  const store = useStore()     
                  let navInfos = store.state    // Vuex的state對象
                  let isShowSaveAlert = ref(false)           // 保存配置彈框是否展示
                  let isShowImportAlert = ref(false)         // 導(dǎo)入配置彈框是否展示
                  
                  // 展示"添加標(biāo)簽彈框"
                  function addTabShow({
                      store.commit('changeTabInfo', [
                          {key'isShowAddTabAlert'valuetrue},
                          {key'alertType'value'新增標(biāo)簽'}
                      ])
                  }
                  // 關(guān)閉"保存配置彈框"
                  function closeSaveConfigAlert(value{
                      isShowSaveAlert.value = value
                  }
                  // 展示"保存配置彈框"
                  function showSaveConfigAlert({
                      isShowSaveAlert.value = true
                  }
                  // 展示"導(dǎo)入配置彈框"
                  function showImportConfigAlert({
                      isShowImportAlert.value = true
                  }
                  // 關(guān)閉"導(dǎo)入配置彈框"
                  function closeImportConfigAlert(value{
                      isShowImportAlert.value = value
                  }
                  // 展示搜索框
                  function showSearch({
                      if(store.state.moduleSearch.isSearch) {
                          store.commit('changeIsSearch'false)
                          store.commit('changeSearchWord''')
                      } else {
                          store.commit('changeIsSearch'true)
                      }
                                
                  }
                  // 跳轉(zhuǎn)到指定標(biāo)簽
                  function toID(id{
                      const content = document.getElementById('content')
                      const el = document.getElementById(`${id}`)
                      let start = content.scrollTop
                      let end = el.offsetTop - 80
                      let each = start > end ? -1 * Math.abs(start - end) / 20 : Math.abs(start - end) / 20
                      let count = 0
                      let timer = setInterval(() => {
                          if(count < 20) {
                              content.scrollTop += each
                              count ++
                          } else {
                              clearInterval(timer)
                          }
                      }, 10
                  }
                  
                  return {
                      navInfos,
                      addTabShow, 
                      isShowSaveAlert, 
                      closeSaveConfigAlert, 
                      showSaveConfigAlert,
                      isShowImportAlert,
                      showImportConfigAlert,
                      closeImportConfigAlert,
                      showSearch,
                      toID
                  }
              }
          }
          </script>

          上述代碼是我項目中側(cè)邊欄中所有的變量以及方法,雖說變量和方法都同時存在于setup函數(shù)中了,但是仍看起來雜亂無章,若是這個組件的業(yè)務(wù)需求越來越復(fù)雜,這個setup內(nèi)的代碼可能更亂了

          于是,我便開始構(gòu)思如何抽離我的代碼。后來在掘金的沸點(diǎn)上說了一下我的思路,并且詢問了一下其他掘友的建議

          其實(shí)最后一位老哥的回答對我啟發(fā)很大,因此我也借鑒了一下它的思路對我的項目代碼進(jìn)行了抽離

          準(zhǔn)備工作

          首先我得思考一個問題:抽離代碼時,是按照組件單獨(dú)抽離?還是按照整體功能抽離?

          最后我決定按照整體的功能去抽離代碼,具體功能列表如下:

          • 搜索功能
          • 新增/修改標(biāo)簽功能
          • 新增/修改網(wǎng)址功能
          • 導(dǎo)入配置功能
          • 導(dǎo)出配置功能
          • 編輯功能

          開始抽出代碼

          上述的每一個功能都會通過一個JS文件去存儲該功能對應(yīng)的變量以及方法。然后所有的JS文件都是放在src/use下的,如圖

          就拿 新增/修改標(biāo)簽功能 來舉例子,用一個動圖給大家看看該功能的全部效果

          很明顯,我是做了一個彈窗組件,當(dāng)點(diǎn)擊側(cè)邊欄中的 + 號后,彈窗顯示;然后我輸入了想要新增標(biāo)簽的名稱,并且選擇了合適的圖標(biāo),最后點(diǎn)擊了確認(rèn),于是一個標(biāo)簽就添加好了,彈窗也隨之隱藏;

          最后我又去編輯模式下點(diǎn)擊修改標(biāo)簽,彈窗再次顯示,與此同時把對應(yīng)標(biāo)簽的名稱與圖標(biāo)都渲染了出來;待我修改了名字后,點(diǎn)擊了確認(rèn),于是標(biāo)簽的信息就被我改好了,彈窗又隨之隱藏了。

          所以總結(jié)一下涉及到的功能就有以下幾個:

          1. 彈窗的展示
          2. 彈窗的隱藏
          3. 點(diǎn)擊確認(rèn)后新增或修改標(biāo)簽內(nèi)容

          按照傳統(tǒng)的寫法,實(shí)現(xiàn)上述三個功能是這個樣子的(我修改并簡化了代碼,大家理解意思就行):

          • 側(cè)邊欄組件內(nèi)容
          <!-- 側(cè)邊欄組件內(nèi)容 -->
          <template>
              <aside>
               <div @click="show">新增標(biāo)簽</div>
                  <tab-alert :isShow="isShow" @closeTabAlert="close"/>
              </aside>
          </template>

          <script>
          import { ref } from 'vue'
          import tabAlert from '@/components/tabAlert/index'
          export default {
              name"tab",
              components: {
               tabAlert
              },
              setup() {
               // 存儲標(biāo)簽彈框的展示情況
               const isShow = ref(false)   
                  
                  // 展示標(biāo)簽彈框
                  function show({
                      isShow.value = true
                  }
                  
                  // 隱藏標(biāo)簽彈框
                  function close({
                      isShow.value = false
                  }
                  
                  return { isShow, show, close }
              }
          }
          </script>
          • 標(biāo)簽彈框組件內(nèi)容
          <!-- 標(biāo)簽彈框組件內(nèi)容 -->
          <template>
              <div v-show="isShow">
               <!-- 此處省略一部分不重要的內(nèi)容代碼 -->
                  <div @click="close">取消</div>
                  <div @click="confirm">確認(rèn)</div>
              </div>
          </template>

          <script>
          export default {
              name"tab",
              props: {
               isShow: {
                      typeBoolean,
                      defaultfalse
                  }
              },
              setup(props, {emit}) {
              
               // 隱藏標(biāo)簽彈框
               function close({
                      emit('close')
                  }
                  
                  // 點(diǎn)擊確認(rèn)后的操作
                  function confirm({
                  
                      /* 此處省略點(diǎn)擊確認(rèn)按鈕后更新標(biāo)簽內(nèi)容的業(yè)務(wù)代碼 */
                      
                      close()
                  }
                  
                  
                  return { close, confirm }
              }
          }
          </script>

          看完了我上面舉例的代碼后可以發(fā)現(xiàn),簡簡單單的一個功能的實(shí)現(xiàn),卻涉及到兩個組件,而且還需要父子組件相互通信來控制一些狀態(tài),這樣不就把功能打散了嘛,即不夠聚合。所以按照功能來抽離這些功能代碼時,我會為他們創(chuàng)建一個 tabAlert.js 文件,里面存儲著關(guān)于這個功能所有的變量與方法。

          tabAlert.js文件中的大致結(jié)構(gòu)是這樣的:

          // 引入依賴API
          import { ref } from 'vue'

          // 定義一些變量
          const isShow = ref(false)     // 存儲標(biāo)簽彈框的展示狀態(tài)

          export default function tabAlertFunction({
              /* 定義一些方法 */
              
              // 展示標(biāo)簽彈框
              function show({
               isShow.value = true
              }
              
              // 關(guān)閉標(biāo)簽彈框
              function close({
               isShow.value = false
              }
              
              // 點(diǎn)擊確認(rèn)按鈕以后的操作
              function confirm({
                  /* 此處省略點(diǎn)擊確認(rèn)按鈕后更新標(biāo)簽內(nèi)容的業(yè)務(wù)代碼 */
                  
                  close()
              }
              
              return {
               isShow,
                  show,
                  close,
                  confirm,
              }
          }

          對于為何設(shè)計這樣的結(jié)構(gòu),先從導(dǎo)出的方法來說,我把跟該功能相關(guān)的所有方法放在了一個函數(shù)中,最后通過return導(dǎo)出,是因?yàn)橛袝r候這些方法會依賴于外部其它的變量,所以用函數(shù)包裹了一層,例如:

          // example.js
          export default function exampleFunction(num{
           
              function log1({
               console.log(num + 1)
              }
              
              function log2({
               console.log(num + 2)
              }
              
              return {
               log1,
               log2,
              }
          }

          從這個文件中我們發(fā)現(xiàn),log1log2方法都是依賴于變量num的,但我們并沒有在該文件中定義變量num,那么可以在別的組件中引入該文件時,給最外層的exampleFunction方法傳遞一個參數(shù)num即可

          <template>
              <button @click="log1">打印加1</button>
              <button @click="log2">打印加2</button>
          </template>

          <script>
          import exampleFunction from './example'
          import { num } from './getNum'  // 假設(shè)num是從別的模塊中獲取到的
          export default {
              setup() {
               let { log1, log2 } = exampleFunction(num)
               
                  return { log1, log2 }
              }
          }
          </script>

          然后再來說說為什么變量的定義在我們導(dǎo)出函數(shù)的外部。再繼續(xù)看我上面舉的我項目中標(biāo)簽頁功能的例子吧,用于存儲標(biāo)簽彈框展示狀態(tài)的變量isShow是在某個組件中定義的,同時標(biāo)簽組件也需要獲取這個變量來控制展示的狀態(tài),這之間用到了父子組件通信,那么我們不妨把這個變量寫在一個公共的文件中,無論哪個組件需要用到的時候,只需要導(dǎo)入獲取就好了,因?yàn)槊看潍@取到的都是同一個變量

          這樣一來,豈不是連父子組件通信都省了嘛?

          我們把剛剛封裝好的tabAlert.js用到組件中去,看看是什么效果

          • 側(cè)邊欄組件內(nèi)容
          <!-- 側(cè)邊欄組件內(nèi)容 -->
          <template>
              <aside>
               <div @click="show">新增標(biāo)簽</div>
                  <tab-alert/>
              </aside>
          </template>

          <script>
          import tabAlert from '@/components/tabAlert/index'
          import tabAlertFunction from '@/use/tabAlert'
          export default {
              name"tab",
              components: {
               tabAlert
              },
              setup() {
               
                  let { show } = tabAlertFunction()
                  
                  return { show }
              }
          }
          </script>
          • 標(biāo)簽彈框組件內(nèi)容
          <!-- 標(biāo)簽彈框組件內(nèi)容 -->
          <template>
              <div v-show="isShow">
               <!-- 此處省略一部分不重要的內(nèi)容代碼 -->
                  <div @click="close">取消</div>
                  <div @click="confirm">確認(rèn)</div>
              </div>
          </template>

          <script>
          import tabAlertFunction from '@/use/tabAlert'
          export default {
              name"tab",
              setup() {
                  
                  let { isShow, close, confirm } = tabAlertFunction() 
                  
                  return { isShow, close, confirm }
              }
          }
          </script>

          這時候再翻上去看看最初的代碼,有沒有感覺代碼抽離后,變得非常規(guī)整,而且組件中少了很多的代碼量。

          這樣通過功能來將變量和代碼聚集在一起的方法,我個人認(rèn)為是比較好管理的,倘若之后有一天想在該功能上新增什么小需求,只要找到tabAlert.js這個文件,在里面寫方法和變量即可

          展示環(huán)節(jié)

          我就是按照這樣的方法,對我原本的代碼進(jìn)行了抽離,下面給大家看幾組抽離前抽離后的代碼對比

          對比一

          • 抽離前
          <template>
            <div class="import-config-container" v-show="isShow">
              <div class="import-config-alert">
                <div class="close-import-config-alert" @click="closeAlert"></div>
                <div class="import-config-alert-title">導(dǎo)入配置</div>
                <div class="import-config-alert-remind">說明:需要上傳之前保存導(dǎo)出的xxx.json配置文件,文件中的信息會完全覆蓋當(dāng)前信息</div>
                <form action="" class="form">
                  <label for="import_config_input" class="import-config-label">
                    上傳配置文件
                    <i v-if="hasFile == 1" class="fas fa-times-circle uploadErr uploadIcon"/>
                    <i v-else-if="hasFile == 2" class="fas fa-check-circle uploadSuccess uploadIcon"/>
                  </label>
                  <input id="import_config_input" type="file" class="select-file" ref="inputFile" @change="fileChange">
                </form>
                <lp-button type="primary" class="import-config-btn" @_click="importConfig">確認(rèn)上傳</lp-button>
              </div>
            </div>
          </template>

          <script>
          import {ref, inject} from 'vue'
          import lpButton from '../../public/lp-button/lp-button'
          export default {
              props: {
                isShow: {
                  typeBoolean,
                  defaulttrue
                }
              },
              components: {
                  lpButton
              },
              setup(props, {emit}) {
                  const result = ref('none')     // 導(dǎo)入的結(jié)果
                  const isUpload = ref(false)    // 判斷是否上傳配置文件
                  const isImport = ref(false)    // 判斷配置是否導(dǎo)入成功
                  const isLoading = ref(false)   // 判斷按鈕是否處于加載狀態(tài)
                  const inputFile = ref(null)    // 獲取文件標(biāo)簽
                  const hasFile = ref(0)         // 判斷文件的傳入情況。0:未傳入  1: 格式錯誤  2:格式正確
                  const $message = inject('message')
                  // 導(dǎo)入配置
                  function importConfig({
                    let reader = new FileReader()
                    let files = inputFile.value.files
                    if(hasFile.value == 0) {
                      $message({
                        type'warning',
                        content'請先上傳配置文件'
                      })
                    }
                    else if(hasFile.value == 1) {
                      $message({
                        type'warning',
                        content'請上傳正確格式的文件,例如xx.json'
                      })
                    }
                    else if(hasFile.value == 2) {
                      reader.readAsText(files[0])
                      reader.onload = function({
                        let data = this.result
                        window.localStorage.navInfos = data
                        location.reload()
                      }
                    }
                  }
                  // 關(guān)閉彈窗
                  function closeAlert({
                    emit('closeImportConfigAlert'false)
                    hasFile.value = 0
                  }
                  function fileChange(e{
                    let files = e.target.files
                    if(files.length === 0) {
                      $message({
                        type'warning',
                        content'請先上傳配置文件'
                      })
                    }
                    else {
                      let targetFile = files[0]
                      if(!/\.json$/.test(targetFile.name)) {
                        hasFile.value = 1
                        $message({
                          type'warning',
                          content'請確認(rèn)文件格式是否正確'
                        })
                      } else {
                        hasFile.value = 2
                        $message({
                          type'success',
                          content'文件格式正確'
                        })
                      }
                    }
                  }
                  
                  return {
                    result, 
                    isUpload,
                    isImport, 
                    isLoading,
                    importConfig, 
                    closeAlert,
                    inputFile,
                    fileChange,
                    hasFile
                  }
              }
          }
          </script>
          • 抽離后
          <template>
            <div class="import-config-container" v-show="isShowImportAlert">
              <div class="import-config-alert">
                <div class="close-import-config-alert" @click="handleImportConfigAlert(false)"></div>
                <div class="import-config-alert-title">導(dǎo)入配置</div>
                <div class="import-config-alert-remind">說明:需要上傳之前保存導(dǎo)出的xxx.json配置文件,文件中的信息會完全覆蓋當(dāng)前信息</div>
                <form action="" class="form">
                  <label for="import_config_input" class="import-config-label">
                    上傳配置文件
                    <i v-if="hasFile == 1" class="fas fa-times-circle uploadErr uploadIcon"/>
                    <i v-else-if="hasFile == 2" class="fas fa-check-circle uploadSuccess uploadIcon"/>
                  </label>
                  <input id="import_config_input" type="file" class="select-file" ref="inputFile" @change="fileChange">
                </form>
                <lp-button type="primary" class="import-config-btn" @_click="importConfig">確認(rèn)上傳</lp-button>
              </div>
            </div>
          </template>

          <script>
          /* API */
          import { inject } from 'vue'
          /* 組件 */
          import lpButton from '@/components/public/lp-button/lp-button'
          /* 功能模塊 */
          import importConfigFunction from '@/use/importConfig'
          export default {
              components: {
                  lpButton
              },
              setup() {
                  const $message = inject('message')
                  
                  const { 
                    isShowImportAlert,
                    handleImportConfigAlert,
                    result,  
                    isUpload, 
                    isImport, 
                    isLoading, 
                    importConfig, 
                    closeAlert, 
                    inputFile, 
                    fileChange, 
                    hasFile 
                  } = importConfigFunction($message)
                  
                  return {
                    isShowImportAlert,
                    handleImportConfigAlert,
                    result, 
                    isUpload,
                    isImport, 
                    isLoading,
                    importConfig, 
                    closeAlert,
                    inputFile,
                    fileChange,
                    hasFile
                  }
              }
          }
          </script>
          • 抽離出的代碼文件
          // 導(dǎo)入配置功能
          import { ref } from 'vue'

          const isShowImportAlert = ref(false),   // 導(dǎo)入配置彈框是否展示
                result = ref('none'),             // 導(dǎo)入的結(jié)果
                isUpload = ref(false),            // 判斷是否上傳配置文件
                isImport = ref(false),            // 判斷配置是否導(dǎo)入成功
                isLoading = ref(false),           // 判斷按鈕是否處于加載狀態(tài)
                inputFile = ref(null),            // 獲取文件元素
                hasFile = ref(0)                  // 判斷文件的傳入情況。0:未傳入  1: 格式錯誤  2:格式正確
                
          export default function importConfigFunction($message{
            
              // 控制彈框的展示
              function handleImportConfigAlert(value{
                  isShowImportAlert.value = value
                  if(!value) hasFile.value = 0
              }

              // 上傳的文件內(nèi)容發(fā)生改變
              function fileChange(e{
                  let files = e.target.files
                  if(files.length === 0) {
                      $message({
                      type'warning',
                      content'請先上傳配置文件'
                      })
                  }
                  else {
                      let targetFile = files[0]
                      if(!/\.json$/.test(targetFile.name)) {
                          hasFile.value = 1
                          $message({
                              type'warning',
                              content'請確認(rèn)文件格式是否正確'
                          })
                      } else {
                      hasFile.value = 2
                          $message({
                              type'success',
                              content'文件格式正確'
                          })
                      }
                  }
              }

              // 導(dǎo)入配置
              function importConfig({
                  let reader = new FileReader()
                  let files = inputFile.value.files
                  if(hasFile.value == 0) {
                    $message({
                      type'warning',
                      content'請先上傳配置文件'
                    })
                  }
                  else if(hasFile.value == 1) {
                    $message({
                      type'warning',
                      content'請上傳正確格式的文件,例如xx.json'
                    })
                  }
                  else if(hasFile.value == 2) {
                    reader.readAsText(files[0])
                    reader.onload = function({
                      let data = this.result
                      window.localStorage.navInfos = data
                      location.reload()
                    }
                  }
              }

              return {
                  isShowImportAlert,
                  result,
                  isUpload,
                  isImport,
                  isLoading,
                  inputFile,
                  hasFile,
                  handleImportConfigAlert,
                  fileChange,
                  importConfig,
              }
          }

          對比二

          • 抽離前
          <template>
              <!-- 此處因代碼太多,暫時省略,詳情可以點(diǎn)擊文末的項目源碼查看 -->
          </template>

          <script>
          import {ref, inject} from 'vue'
          import {useStore} from 'vuex'
          import urlAlert from '../public/urlAlert/urlAlert'
          import tagAlert from '../public/tabAlert/tabAlert'
          import carousel from './childCpn/carousel'
          import search from './childCpn/search'
          import { exchangeElements } from '../../utils/utils'
          <script>
          export default {
              components: {
                  urlAlert,
                  tagAlert,
                  carousel,
                  search,
              },
              setup() {
                  const store = useStore()
                  const catalogue = store.state.catalogue
                  const moduleUrl = store.state.moduleUrl
                  const moduleSearch = store.state.moduleSearch
                  const $message = inject('message')
                  const $confirm = inject('confirm')
                  const editWhich = ref(-1)
                  
                  
                  // 彈出添加URL的框
                  function addMoreUrl(id{
                      store.commit('changeUrlInfo', [
                          {key'isShow'valuetrue},
                          {key'whichTag'value: id},
                          {key'alertType'value'新增網(wǎng)址'}
                      ])
                  }
                  // 處理無icon或icon加載失敗的圖片,令其使用默認(rèn)svg圖標(biāo)
                  function imgLoadErr(e{
                      let el = e.target
                      el.style.display = 'none'
                      el.nextSibling.style.display = 'inline-block'
                  }
                  function imgLoadSuccess(e{
                      let el = e.target
                      el.style.display = 'inline-block'
                      el.nextSibling.style.display = 'none'
                  }
                  // 進(jìn)入編輯狀態(tài)
                  function enterEdit(id{
                      if(id != editWhich.value) {
                          editWhich.value = id
                      } else {
                          editWhich.value = -1
                      }     
                  }
                  // 修改標(biāo)簽彈框彈出
                  function editTagAlertShow(tab{
                      store.commit('changeTabInfo', [
                          {key'isShowAddTabAlert'valuetrue},
                          {key'tagName'value: tab.name},
                          {key'trueIcon'value: tab.icon},
                          {key'isSelected'valuetrue},
                          {key'currentIcon'value: tab.icon},
                          {key'id'value: tab.id},
                          {key'alertType'value'修改標(biāo)簽'}
                      ])
                  }
                  // 刪除標(biāo)簽以及標(biāo)簽下的所有網(wǎng)址
                  function deleteTag(id{
                      $confirm({
                          content'確定刪除該標(biāo)簽以及該標(biāo)簽下所有網(wǎng)址嗎?'
                      })
                      .then(() => {
                          store.commit('remove', id)
                          $message({
                              type'success',
                              content'標(biāo)簽頁及子網(wǎng)址刪除成功'
                          })
                      })
                      .catch(() => {})
                  }
                  // 刪除某個網(wǎng)址
                  function deleteUrl(id{
                      $confirm({
                          content'確定刪除該網(wǎng)址嗎?'
                      })
                      .then(() => {
                          store.commit('remove', id)
                          $message({
                              type'success',
                              content'網(wǎng)址刪除成功'
                          })
                      })
                      .catch(() => {})      
                  }
                  // 彈出修改URL的彈框
                  function editUrl(url{
                      store.commit('changeUrlInfo', [
                          {key'url'value: url.url},
                          {key'icon'value: url.icon},
                          {key'id'value: url.id},
                          {key'name'value: url.name},
                          {key'isShow'valuetrue},
                          {key'alertType'value'修改網(wǎng)址'}
                      ])
                  }
                  
                  function judgeTabIsShow(i{
                      const URLS = catalogue[i]['URLS']
                      let length = URLS.length
                      for(let j = 0; j < length; j++) {
                          if(moduleSearch.searchWord == ''return false;
                          else if(URLS[j].name.toLowerCase().indexOf(moduleSearch.searchWord.toLowerCase()) !== -1return true;
                      }
                      return false
                  }
                  function judgeUrlIsShow(i, j{
                      const url = catalogue[i]['URLS'][j]
                      if(url.name.toLowerCase().indexOf(moduleSearch.searchWord.toLowerCase()) !== -1return true;
                      return false;
                  }
                  let elementNodeDragged = null   // 被移動的地址框元素
                  let elementNodeLocated = null  // 移入的地址框元素
                  let draggedId = -1   // 被移動地址框的id
                  
                  // 地址框開始拖拽
                  function urlBoxDragStart(e{
                      const el = e.target
                      if(el.nodeName !== 'LI'return;
                      // 記錄當(dāng)前被拖拽地址框元素
                      elementNodeDragged = el    
                      // 將被拖拽對象隱藏
                      el.style.display = 'fixed'
                      el.style.opacity = 0
                  }
                  // 地址框拖拽結(jié)束
                  function urlBoxDragEnd(e{
                      let el = e.target
                      el.style.display = 'inline-block'
                      el.style.opacity = 1
                      // 獲取當(dāng)前正在編輯標(biāo)簽中所有url的排序
                      let timer = setTimeout(() => {
                          const result = []
                          const children = elementNodeLocated.parentNode.children
                          let length = children.length
                          for(let i = 0; i < length - 1; i++) {
                              result.push(children[i].getAttribute('data-id'))
                          }
                          store.commit('dragEndToUpdate', {tabId: editWhich.value, result})
                          clearTimeout(timer)
                      }, 500)
                  }
                  // 被拖動的地址框觸碰到其它的地址框
                  function urlBoxEnter(e, tabId{
                      if(tabId != editWhich.value) return;
                      let el = e.target
                      while(el.nodeName !== 'LI') el = el.parentNode;        // 若子元素觸發(fā)dragenter事件,則查找到父元素li標(biāo)簽
                      if(el === elementNodeDragged) return;     // 避免自己拖拽進(jìn)入自己的情況
                      if(elementNodeLocated !== el) elementNodeLocated = el    // 記錄被移入的地址框元素
                      exchangeElements(elementNodeDragged, el)     //  地址框位置互換
                  }
                  return {
                      catalogue, 
                      addMoreUrl, 
                      moduleUrl, 
                      moduleSearch,
                      imgLoadErr,
                      imgLoadSuccess, 
                      enterEdit, 
                      editTagAlertShow,
                      deleteTag,
                      deleteUrl,
                      editUrl,
                      editWhich,
                      judgeTabIsShow,
                      judgeUrlIsShow,
                      urlBoxDragStart,
                      urlBoxDragEnd,
                      urlBoxEnter,
                  }
              }
          }
          </script>
          • 抽離后
          <template>
            <!-- 此處因代碼太多,暫時省略,詳情可以點(diǎn)擊文末的項目源碼查看 -->
          </template>

          <script>
          /* API */
          import { inject } from 'vue'
          import { useStore } from 'vuex'
          /* 組件 */
          import urlAlert from '@/components/public/urlAlert/index'
          import tabAlert from '@/components/public/tabAlert/index'
          import carousel from './carousel'
          import search from './search'
          /* 功能模塊 */
          import baseFunction from '@/use/base'
          import editFunction from '@/use/edit'
          import urlAlertFunction from '@/use/urlAlert'
          import tabAlertFunction from '@/use/tabAlert'
          import searchFunction from '@/use/search'
          export default {
              components: {
                  urlAlert,
                  tabAlert,
                  carousel,
                  search,
              },
              setup() {
                  const catalogue = useStore().state.catalogue
                  const $message = inject('message')
                  const $confirm = inject('confirm')

                  // 一些基礎(chǔ)的方法
                  let { imgLoadErr, imgLoadSuccess } = baseFunction()

                  // url框編輯下的相關(guān)變量及功能
                  let { 
                      editWhich, 
                      handleEdit, 
                      deleteTab, 
                      deleteUrl, 
                      urlBoxDragStart, 
                      urlBoxDragEnd, 
                      urlBoxEnter 
                  } = editFunction($message, $confirm)

                  // 彈出 “新增”、“修改” url彈框
                  let { showNewUrlAlert, showEditUrlAlert } = urlAlertFunction()

                  // 搜索功能相關(guān)的變量及方法
                  let { moduleSearch, judgeTabIsShow, judgeUrlIsShow } = searchFunction()

                  // 展示修改tab的彈框
                  let { showEditAddTab } = tabAlertFunction()

                  return {
                      catalogue, 
                      showNewUrlAlert, 
                      moduleSearch,
                      imgLoadErr,
                      imgLoadSuccess, 
                      handleEdit, 
                      showEditAddTab,
                      deleteTab,
                      deleteUrl,
                      showEditUrlAlert,
                      editWhich,
                      judgeTabIsShow,
                      judgeUrlIsShow,
                      urlBoxDragStart,
                      urlBoxDragEnd,
                      urlBoxEnter,
                  }
              }
          }
          </script>
          • 抽離出的代碼文件(此處涉及到很多個模塊,因此只展示兩個吧)
          // 搜索功能
          import {  } from 'vue'
          import store from '@/store/index'

          // 變量
          const moduleSearch = store.state.moduleSearch     // 搜索相關(guān)的全局狀態(tài)

          export default function searchFunction({

              // 搜索框的輸入改變
              function inputSearchContent(value{
                  store.commit('changeSearchWord', value)
              }

              // 控制搜索框的展示
              function handleSearchBox({
                  if(moduleSearch.isSearch) {
                      store.commit('changeIsSearch'false)
                      store.commit('changeSearchWord''')
                  } else {
                      store.commit('changeIsSearch'true)
                  }         
              }

              // 判斷標(biāo)簽是否顯示
              function judgeTabIsShow(i{
                  return store.getters.judgeTabIsShow(i)
              }

              // 判斷url是否顯示
              function judgeUrlIsShow(i, j{
                  return store.getters.judgeUrlIsShow(i, j)
              }

              return {
                  moduleSearch,
                  inputSearchContent,
                  handleSearchBox,
                  judgeTabIsShow,
                  judgeUrlIsShow,
              }
          }
          // url框的拖拽排列
          import { ref } from 'vue'
          import { exchangeElements, debounce } from '@/utils/utils'
          import store from '@/store/index'

          //變量
          let elementNodeDragged = null,     // 被移動的地址框元素
              elementNodeLocated = null,     // 移入的地址框元素
              editWhich = ref(-1)            // 記錄正在編輯的tab索引

          export default function editFunction($message, $confirm{

                  // 控制編輯狀態(tài)
                  function handleEdit(id{
                      if(id != editWhich.value) {
                          editWhich.value = id
                      } else {
                          editWhich.value = -1
                      }     
                  }

                  // 刪除標(biāo)簽以及標(biāo)簽下的所有網(wǎng)址
                  function deleteTab(id{
                      $confirm({
                          content'確定刪除該標(biāo)簽以及該標(biāo)簽下所有網(wǎng)址嗎?'
                      })
                      .then(() => {
                          store.commit('remove', id)
                          $message({
                              type'success',
                              content'標(biāo)簽頁及子網(wǎng)址刪除成功'
                          })
                      })
                      .catch(() => {})
                  }

                  // 刪除某個網(wǎng)址
                  function deleteUrl(id{
                      $confirm({
                          content'確定刪除該網(wǎng)址嗎?'
                      })
                      .then(() => {
                          store.commit('remove', id)
                          $message({
                              type'success',
                              content'網(wǎng)址刪除成功'
                          })
                      })
                      .catch(() => {})      
                  }

                  // 地址框開始拖拽
                  function urlBoxDragStart(e{
                      const el = e.target
                      if(el.nodeName !== 'LI'return;
                      // 記錄當(dāng)前被拖拽地址框元素
                      elementNodeDragged = el    
                      // 將被拖拽對象隱藏
                      el.style.display = 'fixed'
                      el.style.opacity = 0
                  }

                  // 拖拽后更新Vuex中的正確排序
                  let dragEndToUpdate = debounce(function({
                      // 獲取當(dāng)前正在編輯標(biāo)簽中所有url的排序
                      const result = []
                      const children = elementNodeLocated.parentNode.children
                      let length = children.length
                      for(let i = 0; i < length - 1; i++) {
                          result.push(children[i].getAttribute('data-id'))
                      }
                      store.commit('dragEndToUpdate', {tabId: editWhich.value, result}) 
                  }, 500)

                  // 地址框拖拽結(jié)束
                  function urlBoxDragEnd(e{
                      let el = e.target
                      el.style.display = 'inline-block'
                      el.style.opacity = 1
                      dragEndToUpdate()
                  }

                  // 被拖動的地址框觸碰到其它的地址框
                  function urlBoxEnter(e, tabId{
                      if(tabId != editWhich.value) return;
                      let el = e.target
                      while(el.nodeName !== 'LI') el = el.parentNode;          // 若子元素觸發(fā)dragenter事件,則查找到父元素li標(biāo)簽
                      if(el === elementNodeDragged) return;                    // 避免自己拖拽進(jìn)入自己的情況
                      if(elementNodeLocated !== el) elementNodeLocated = el    // 記錄被移入的地址框元素
                      exchangeElements(elementNodeDragged, el)                 //  地址框位置互換
                  }

              return {
                  editWhich,
                  handleEdit,
                  deleteTab,
                  deleteUrl,
                  urlBoxDragStart,
                  urlBoxDragEnd,
                  urlBoxEnter,
              }
          }

          最后

          細(xì)心的小伙伴應(yīng)該發(fā)現(xiàn)了,剛才給大家展示的代碼中,有一段是各種拖拽的實(shí)現(xiàn)方法,沒錯?。∥以陂e暇之余給我的項目加上了編輯模式下的 拖拽排列功能 ,也算是完成了之前大家對我提的建議之一啦,歡迎各位前去體驗(yàn)新功能~

          項目體驗(yàn)鏈接http://lpyexplore.gitee.io/nav-infos/

          在體驗(yàn)完后,希望有心的小伙伴們能在github上給我提提Issues,我看到會第一時間回復(fù)的(如果催我做賬號功能的小伙伴多,我后期可能會考慮加上)

          項目源碼鏈接https://github.com/Lpyexplore/nav-url(歡迎各位Star,多提意見,多交流啊~)

          本文所闡述的代碼抽離方法是我改過很多遍后定下來的,不知道后面還會有什么問題,但目前看來,對于以后的維護(hù)和管理應(yīng)該是會方便很多的,如果大家有更好的意見或想法,可以留下評論,或者加我vx:Lpyexplore333私底下交流

          最后謝謝各位的耐心觀看

          寫文章不容易,希望各位多多留言給我提提意見,別忘了點(diǎn)個?? 和 在看哦~

          瀏覽 48
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲丁香网 | 五月婷婷激情四射 | 婷婷五月天激情网站 | 91爱搞搞 | 无码激情 |