本文代碼略多,希望大家耐心觀看
背景介紹
在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下的,如圖

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

很明顯,我是做了一個彈窗組件,當點擊側邊欄中的 + 號后,彈窗顯示;然后我輸入了想要新增標簽的名稱,并且選擇了合適的圖示,最后點擊了確認,于是一個標簽就添加好了,彈窗也隨之隱藏;
最后我又去編輯模式下點擊修改標簽,彈窗再次顯示,與此同時把對應標簽的名稱與圖示都渲染了出來;待我修改了名字后,點擊了確認,于是標簽的資訊就被我改好了,彈窗又隨之隱藏了,
所以總結以下涉及到的功能就有以下幾個:
- 彈窗的展示
- 彈窗的隱藏
- 點擊確認后新增或修改標簽內容
按照傳統的寫法,實作上述三個功能是這個樣子的(我修改并簡化了代碼,大家理解意思就行):
- 側邊欄組件內容
<!-- 側邊欄組件內容 -->
<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,
}
}
從這個檔案中我們發現,log1和log2方法都是依賴于變數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如何實作分享截圖
