主頁 > 前端設計 > 如何充分利用Composition API對Vue3專案進行代碼抽離

如何充分利用Composition API對Vue3專案進行代碼抽離

2021-01-08 11:23:51 前端設計

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

背景介紹

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

  • 自己設計的Vue3的實用專案(內含對專案亮點的實作思路與介紹)(237+ 個👍)
  • 公眾號:前端印象
  • 不定時有送書活動,記得關注~
  • 關注后回復對應文字領取:【面試題】、【前端必看電子書】、【資料結構與演算法完整代碼】、【前端技術交流群】

在上一篇博客中,我詳細介紹了一下我發的第一版專案的特色、亮點以及所有核心功能的實作,希望大家可以前往閱讀體驗一下(記得用電腦打開,因為這是一個PC端的專案)

然而,這專案只是實作了一些功能,但我感覺并沒有很好地利用Composition API去對代碼進行整合管理,要知道,Composition API的出現就是為了解決Options API導致相同功能代碼分散的現象,也有很多大佬對其做了很多的影片展示(這里我借用一下大帥搞全堆疊大佬精心制作的影片,他的這篇文章可以說是好評連連,大家可以觀摩一下:做了一夜影片,就為讓大家更好的理解Vue3的Composition Api)

在這里插入圖片描述
在這里插入圖片描述

看了一下我專案初版的代碼,簡直是沒有體現出Composition API的優勢,可以給大家看一下某個組件內的代碼

<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>匯入配置</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>
      <!--    添加標簽彈框     -->
      <tabAlert />
      <!--    保存配置彈框     -->
      <save-config @closeSaveConfigAlert="closeSaveConfigAlert" :isShow="isShowSaveAlert"/>
      <!--    匯入配置彈框     -->
      <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)         // 匯入配置彈框是否展示
        
        // 展示"添加標簽彈框"
        function addTabShow() {
            store.commit('changeTabInfo', [
                {key: 'isShowAddTabAlert', value: true},
                {key: 'alertType', value: '新增標簽'}
            ])
        }
        // 關閉"保存配置彈框"
        function closeSaveConfigAlert(value) {
            isShowSaveAlert.value = value
        }
        // 展示"保存配置彈框"
        function showSaveConfigAlert() {
            isShowSaveAlert.value = true
        }
        // 展示"匯入配置彈框"
        function showImportConfigAlert() {
            isShowImportAlert.value = true
        }
        // 關閉"匯入配置彈框"
        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)
            }
                      
        }
        // 跳轉到指定標簽
        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>

上述代碼是我專案中側邊欄中所有的變數以及方法,雖說變數和方法都同時存在于setup函式中了,但是仍看起來雜亂無章,若是這個組件的業務需求越來越復雜,這個setup內的代碼可能更亂了

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

其實最后一位老哥的回答對我啟發很大,因此我也借鑒了一下它的思路對我的專案代碼進行了抽離

準備作業

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

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

  • 搜索功能
  • 新增/修改標簽功能
  • 新增/修改網址功能
  • 匯入配置功能
  • 匯出配置功能
  • 編輯功能

開始抽出代碼

上述的每一個功能都會通過一個JS檔案去存盤該功能對應的變數以及方法,然后所有的JS檔案都是放在src/use下的,如圖

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

在這里插入圖片描述

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

最后我又去編輯模式下點擊修改標簽,彈窗再次顯示,與此同時把對應標簽的名稱與圖示都渲染了出來;待我修改了名字后,點擊了確認,于是標簽的資訊就被我改好了,彈窗又隨之隱藏了,

所以總結以下涉及到的功能就有以下幾個:

  1. 彈窗的展示
  2. 彈窗的隱藏
  3. 點擊確認后新增或修改標簽內容

按照傳統的寫法,實作上述三個功能是這個樣子的(我修改并簡化了代碼,大家理解意思就行):

  • 側邊欄組件內容
<!-- 側邊欄組件內容 -->
<template>
    <aside>
    	<div @click="show">新增標簽</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() {
    	// 存盤標簽彈框的展示情況
    	const isShow = ref(false)   
        
        // 展示標簽彈框
        function show() {
            isShow.value = true
        }
        
        // 隱藏標簽彈框
        function close() {
            isShow.value = false
        }
        
        return { isShow, show, close }
    }
}
</script>
  • 標簽彈框組件內容
<!-- 標簽彈框組件內容 -->
<template>
    <div v-show="isShow">
    	<!-- 此處省略一部分不重要的內容代碼 -->
        <div @click="close">取消</div>
        <div @click="confirm">確認</div>
    </div>
</template>

<script>
export default {
    name: "tab",
    props: {
    	isShow: {
            type: Boolean,
            default: false
        }
    },
    setup(props, {emit}) {
    
    	// 隱藏標簽彈框
    	function close() {
            emit('close')
        }
        
        // 點擊確認后的操作
        function confirm() {
        
            /* 此處省略點擊確認按鈕后更新標簽內容的業務代碼 */
            
            close()
        }
        
        
        return { close, confirm }
    }
}
</script>

看完了我上面舉例的代碼后可以發現,簡簡單單的一個功能的實作,卻涉及到兩個組件,而且還需要父子組件相互通信來控制一些狀態,這樣不就把功能打散了嘛,即不夠聚合,所以按照功能來抽離這些功能代碼時,我會為他們創建一個 tabAlert.js 檔案,里面存盤著關于這個功能所有的變數與方法,

tabAlert.js檔案中的大致結構是這樣的:

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

// 定義一些變數
const isShow = ref(false)     // 存盤標簽彈框的展示狀態

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

對于為何設計這樣的結構,先從匯出的方法來說,我把跟該功能相關的所有方法放在了一個函式中,最后通過return匯出,是因為有時候這些方法會依賴于外部其它的變數,所以用函式包裹了一層,例如:

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

從這個檔案中我們發現,log1log2方法都是依賴于變數num的,但我們并沒有在該檔案中定義變數num,那么可以在別的組件中引入該檔案時,給最外層的exampleFunction方法傳遞一個引數num即可

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

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

然后再來說說為什么變數的定義在我們匯出函式的外部,再繼續看我上面舉的我專案中標簽頁功能的例子吧,用于存盤標簽彈框展示狀態的變數isShow是在某個組件中定義的,同時標簽組件也需要獲取這個變數來控制展示的狀態,這之間用到了父子組件通信,那么我們不妨把這個變數寫在一個公共的檔案中,無論哪個組件需要用到的時候,只需要匯入獲取就好了,因為每次獲取到的都是同一個變數

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

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

  • 側邊欄組件內容
<!-- 側邊欄組件內容 -->
<template>
    <aside>
    	<div @click="show">新增標簽</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>
  • 標簽彈框組件內容
<!-- 標簽彈框組件內容 -->
<template>
    <div v-show="isShow">
    	<!-- 此處省略一部分不重要的內容代碼 -->
        <div @click="close">取消</div>
        <div @click="confirm">確認</div>
    </div>
</template>

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

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

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

展示環節

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

對比一

  • 抽離前
<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">匯入配置</div>
      <div class="import-config-alert-remind">說明:需要上傳之前保存匯出的xxx.json組態檔,檔案中的資訊會完全覆寫當前資訊</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">確認上傳</lp-button>
    </div>
  </div>
</template>

<script>
import {ref, inject} from 'vue'
import lpButton from '../../public/lp-button/lp-button'
export default {
    props: {
      isShow: {
        type: Boolean,
        default: true
      }
    },
    components: {
        lpButton
    },
    setup(props, {emit}) {
        const result = ref('none')     // 匯入的結果
        const isUpload = ref(false)    // 判斷是否上傳組態檔
        const isImport = ref(false)    // 判斷配置是否匯入成功
        const isLoading = ref(false)   // 判斷按鈕是否處于加載狀態
        const inputFile = ref(null)    // 獲取檔案標簽
        const hasFile = ref(0)         // 判斷檔案的傳入情況,0:未傳入  1: 格式錯誤  2:格式正確
        const $message = inject('message')
        // 匯入配置
        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()
            }
          }
        }
        // 關閉彈窗
        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: '請確認檔案格式是否正確'
              })
            } 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">匯入配置</div>
      <div class="import-config-alert-remind">說明:需要上傳之前保存匯出的xxx.json組態檔,檔案中的資訊會完全覆寫當前資訊</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">確認上傳</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>
  • 抽離出的代碼檔案
// 匯入配置功能
import { ref } from 'vue'

const isShowImportAlert = ref(false),   // 匯入配置彈框是否展示
      result = ref('none'),             // 匯入的結果
      isUpload = ref(false),            // 判斷是否上傳組態檔
      isImport = ref(false),            // 判斷配置是否匯入成功
      isLoading = ref(false),           // 判斷按鈕是否處于加載狀態
      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
    }

    // 上傳的檔案內容發生改變
    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: '請確認檔案格式是否正確'
                })
            } else {
            hasFile.value = 2
                $message({
                    type: 'success',
                    content: '檔案格式正確'
                })
            }
        }
    }

    // 匯入配置
    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>
    <!-- 此處因代碼太多,暫時省略,詳情可以點擊文末的專案原始碼查看 -->
</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'
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', value: true},
                {key: 'whichTag', value: id},
                {key: 'alertType', value: '新增網址'}
            ])
        }
        // 處理無icon或icon加載失敗的圖片,令其使用默認svg圖示
        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'
        }
        // 進入編輯狀態
        function enterEdit(id) {
            if(id != editWhich.value) {
                editWhich.value = id
            } else {
                editWhich.value = -1
            }     
        }
        // 修改標簽彈框彈出
        function editTagAlertShow(tab) {
            store.commit('changeTabInfo', [
                {key: 'isShowAddTabAlert', value: true},
                {key: 'tagName', value: tab.name},
                {key: 'trueIcon', value: tab.icon},
                {key: 'isSelected', value: true},
                {key: 'currentIcon', value: tab.icon},
                {key: 'id', value: tab.id},
                {key: 'alertType', value: '修改標簽'}
            ])
        }
        // 洗掉標簽以及標簽下的所有網址
        function deleteTag(id) {
            $confirm({
                content: '確定洗掉該標簽以及該標簽下所有網址嗎?'
            })
            .then(() => {
                store.commit('remove', id)
                $message({
                    type: 'success',
                    content: '標簽頁及子網址洗掉成功'
                })
            })
            .catch(() => {})
        }
        // 洗掉某個網址
        function deleteUrl(id) {
            $confirm({
                content: '確定洗掉該網址嗎?'
            })
            .then(() => {
                store.commit('remove', id)
                $message({
                    type: 'success',
                    content: '網址洗掉成功'
                })
            })
            .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', value: true},
                {key: 'alertType', value: '修改網址'}
            ])
        }
        
        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()) !== -1) return true;
            }
            return false
        }
        function judgeUrlIsShow(i, j) {
            const url = catalogue[i]['URLS'][j]
            if(url.name.toLowerCase().indexOf(moduleSearch.searchWord.toLowerCase()) !== -1) return 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;
            // 記錄當前被拖拽地址框元素
            elementNodeDragged = el    
            // 將被拖拽物件隱藏
            el.style.display = 'fixed'
            el.style.opacity = 0
        }
        // 地址框拖拽結束
        function urlBoxDragEnd(e) {
            let el = e.target
            el.style.display = 'inline-block'
            el.style.opacity = 1
            // 獲取當前正在編輯標簽中所有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;        // 若子元素觸發dragenter事件,則查找到父元素li標簽
            if(el === elementNodeDragged) return;     // 避免自己拖拽進入自己的情況
            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>
  <!-- 此處因代碼太多,暫時省略,詳情可以點擊文末的專案原始碼查看 -->
</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')

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

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

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

        // 搜索功能相關的變數及方法
        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     // 搜索相關的全域狀態

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)
        }         
    }

    // 判斷標簽是否顯示
    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) {

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

        // 洗掉標簽以及標簽下的所有網址
        function deleteTab(id) {
            $confirm({
                content: '確定洗掉該標簽以及該標簽下所有網址嗎?'
            })
            .then(() => {
                store.commit('remove', id)
                $message({
                    type: 'success',
                    content: '標簽頁及子網址洗掉成功'
                })
            })
            .catch(() => {})
        }

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

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

        // 拖拽后更新Vuex中的正確排序
        let dragEndToUpdate = debounce(function() {
            // 獲取當前正在編輯標簽中所有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)

        // 地址框拖拽結束
        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;          // 若子元素觸發dragenter事件,則查找到父元素li標簽
            if(el === elementNodeDragged) return;                    // 避免自己拖拽進入自己的情況
            if(elementNodeLocated !== el) elementNodeLocated = el    // 記錄被移入的地址框元素
            exchangeElements(elementNodeDragged, el)                 //  地址框位置互換
        }

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

最后

細心的小伙伴應該發現了,剛才給大家展示的代碼中,有一段是各種拖拽的實作方法,沒錯!!我在閑暇之余給我的專案加上了編輯模式下的 拖拽排列功能 ,也算是完成了之前大家對我提的建議之一啦,歡迎各位前去體驗新功能~

專案體驗鏈接

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

專案原始碼鏈接(歡迎各位Star,多提意見,多交流啊~)

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

最后謝謝各位的耐心觀看

寫文章不容易,希望各位多多留言給我提提意見,別忘了點個👍哦~

轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/246189.html

標籤:其他

上一篇:更加方便獲取eid和fp的一種方式-通過HTML檔案【京東飛天茅臺1499搶購】

下一篇:前端H5如何實作分享截圖

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • vue移動端上拉加載

    可能做得過于簡單或者比較low,請各位大佬留情,一起探討技術 ......

    uj5u.com 2020-09-10 04:38:07 more
  • 優美網站首頁,頂部多層導航

    一個個人用的瀏覽器首頁,可以把一下常用的網站放在這里,平常打開會比較方便。 第一步,HTML代碼 <script src=https://www.cnblogs.com/szharf/p/"js/jquery-3.4.1.min.js"></script> <div id="navigate"> <ul> <li class="labels labels_1"> ......

    uj5u.com 2020-09-10 04:38:47 more
  • 頁面為要加<!DOCTYPE html>

    最近因為寫一個js函式,需要用到$(window).height(); 由于手寫demo的時候,過于自信,其實對前端方面的認識也不夠體系,用文本檔案直接敲出來的html代碼,第一行沒有加上<!DOCTYPE html> 導致了$(window).height();的結果直接是整個document的高 ......

    uj5u.com 2020-09-10 04:38:52 more
  • WordPress網站程式手動升級要做好資料備份

    WordPress博客網站程式在進行升級前,必須要做好網站資料的備份,這個問題良家佐言是遇見過的;在剛開始接觸WordPress博客程式的時候,因為升級問題和博客網站的修改的一些嘗試,良家佐言是吃盡了苦頭。因為購買的是西部數碼的空間和域名,每當佐言把自己的WordPress博客網站搞到一塌糊涂的時候 ......

    uj5u.com 2020-09-10 04:39:30 more
  • WordPress程式不能升級為5.4.2版本的原因

    WordPress是一款個人博客系統,受到英文博客愛好者和中文博客愛好者的追捧,并逐步演化成一款內容管理系統軟體;它是使用PHP語言和MySQL資料庫開發的,用戶可以在支持PHP和MySQL資料庫的服務器上使用自己的博客。每一次WordPress程式的更新,就會牽動無數WordPress愛好者的心, ......

    uj5u.com 2020-09-10 04:39:49 more
  • 使用CSS3的偽元素進行首字母下沉和首行改變樣式

    網頁中常見的一種效果,首字改變樣式或者首行改變樣式,效果如下圖。 代碼: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, ......

    uj5u.com 2020-09-10 04:40:09 more
  • 關于a標簽的講解

    什么是a標簽? <a> 標簽定義超鏈接,用于從一個頁面鏈接到另一個頁面。 <a> 元素最重要的屬性是 href 屬性,它指定鏈接的目標。 a標簽的語法格式:<a href=https://www.cnblogs.com/summerxbc/p/"指定要跳轉的目標界面的鏈接">需要展示給用戶看見的內容</a> a標簽 在所有瀏覽器中,鏈接的默認外觀如下: 未被訪問的鏈接帶 ......

    uj5u.com 2020-09-10 04:40:11 more
  • 前端輪播圖

    在需要輪播的頁面是引入swiper.min.js和swiper.min.css swiper.min.js地址: 鏈接:https://pan.baidu.com/s/15Uh516YHa4CV3X-RyjEIWw 提取碼:4aks swiper.min.css地址 鏈接:https://pan.b ......

    uj5u.com 2020-09-10 04:40:13 more
  • 如何設定html中的背景圖片(全屏顯示,且不拉伸)

    1 <style>2 body{background-image:url(https://uploadbeta.com/api/pictures/random/?key=BingEverydayWallpaperPicture); 3 background-size:cover;background ......

    uj5u.com 2020-09-10 04:40:16 more
  • Java學習——HTML詳解(上)

    HTML詳解 初識HTML Hyper Text Markup Language(超文本標記語言) 1 <!--DOCTYPE:告訴瀏覽器我們要使用什么規范--> 2 <!DOCTYPE html> 3 <html lang="en"> 4 <head> 5 <!--meta 描述性的標簽,描述一些 ......

    uj5u.com 2020-09-10 04:40:33 more
最新发布
  • 我的第一個NPM包:panghu-planebattle-esm(胖虎飛機大戰)使用說明

    好家伙,我的包終于開發完啦 歡迎使用胖虎的飛機大戰包!! 為你的主頁添加色彩 這是一個有趣的網頁小游戲包,使用canvas和js開發 使用ES6模塊化開發 效果圖如下: (覺得圖片太sb的可以自己改) 代碼已開源!! Git: https://gitee.com/tang-and-han-dynas ......

    uj5u.com 2023-04-20 07:59:23 more
  • 生產事故-走近科學之消失的JWT

    入職多年,面對生產環境,盡管都是小心翼翼,慎之又慎,還是難免捅出簍子。輕則滿頭大汗,面紅耳赤。重則系統停擺,損失資金。每一個生產事故的背后,都是寶貴的經驗和教訓,都是專案成員的血淚史。為了更好地防范和遏制今后的各類事故,特開此專題,長期更新和記錄大大小小的各類事故。有些是親身經歷,有些是經人耳傳口授 ......

    uj5u.com 2023-04-18 07:55:04 more
  • 記錄--Canvas實作打飛字游戲

    這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 打開游戲界面,看到一個畫面簡潔、卻又富有挑戰性的游戲。螢屏上,有一個白色的矩形框,里面不斷下落著各種單詞,而我需要迅速地輸入這些單詞。如果我輸入的單詞與螢屏上的單詞匹配,那么我就可以獲得得分;如果我輸入的單詞錯誤或者時間過長,那么我就會輸 ......

    uj5u.com 2023-04-04 08:35:30 more
  • 了解 HTTP 看這一篇就夠

    在學習網路之前,了解它的歷史能夠幫助我們明白為何它會發展為如今這個樣子,引發探究網路的興趣。下面的這張圖片就展示了“互聯網”誕生至今的發展歷程。 ......

    uj5u.com 2023-03-16 11:00:15 more
  • 藍牙-低功耗中心設備

    //11.開啟藍牙配接器 openBluetoothAdapter //21.開始搜索藍牙設備 startBluetoothDevicesDiscovery //31.開啟監聽搜索藍牙設備 onBluetoothDeviceFound //30.停止監聽搜索藍牙設備 offBluetoothDevi ......

    uj5u.com 2023-03-15 09:06:45 more
  • canvas畫板(滑鼠和觸摸)

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>canves</title> <style> #canvas { cursor:url(../images/pen.png),crosshair; } #canvasdiv{ bo ......

    uj5u.com 2023-02-15 08:56:31 more
  • 手機端H5 實作自定義拍照界面

    手機端 H5 實作自定義拍照界面也可以使用 MediaDevices API 和 <video> 標簽來實作,和在桌面端做法基本一致。 首先,使用 MediaDevices.getUserMedia() 方法獲取攝像頭媒體流,并將其傳遞給 <video> 標簽進行渲染。 接著,使用 HTML 的 < ......

    uj5u.com 2023-01-12 07:58:22 more
  • 記錄--短視頻滑動播放在 H5 下的實作

    這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 短視頻已經無數不在了,但是主體還是使用 app 來承載的。本文講述 H5 如何實作 app 的視頻滑動體驗。 無聲勝有聲,一圖頂百辯,且看下圖: 網址鏈接(需在微信或者手Q中瀏覽) 從上圖可以看到,我們主要實作的功能也是本文要講解的有: ......

    uj5u.com 2023-01-04 07:29:05 more
  • 一文讀懂 HTTP/1 HTTP/2 HTTP/3

    從 1989 年萬維網(www)誕生,HTTP(HyperText Transfer Protocol)經歷了眾多版本迭代,WebSocket 也在期間萌芽。1991 年 HTTP0.9 被發明。1996 年出現了 HTTP1.0。2015 年 HTTP2 正式發布。2020 年 HTTP3 或能正... ......

    uj5u.com 2022-12-24 06:56:02 more
  • 【HTML基礎篇002】HTML之form表單超詳解

    ??一、form表單是什么

    ??二、form表單的屬性

    ??三、input中的各種Type屬性值

    ??四、標簽 ......

    uj5u.com 2022-12-18 07:17:06 more