如何充分利用Composition API对Vue3项目进行代码抽离「建议收藏」

如何充分利用Composition API对Vue3项目进行代码抽离「建议收藏」然而,这项目只是实现了一些功能,但我感觉并没有很好地利用Composition API去对代码进行整合管理。要知道,Composition API的出现就是为了解决Options API导致相同功能代码分散的现象,也有很多大佬对其做了很多的动画展示(这里我借用一下大帅搞全栈大佬…

大家好,欢迎来到IT知识分享网。

本文代码略多,希望大家耐心观看

背景介绍

在2020年,Vue3的学习一直被我鸽到了11月份,在学完以后,我自己做了一个Vue3的小项目nav-url,也整理了我对于如何快速上手Vue3的几篇博客,很高兴受到了大家的指点和喜欢:

  • 自己设计的Vue3的实用项目(内含对项目亮点的实现思路与介绍)(237+ 个👍)

在上一篇博客中,我详细介绍了一下我发的第一版项目的特色、亮点以及所有核心功能的实现,希望大家可以前往阅读体验一下(记得用电脑打开,因为这是一个PC端的项目)

然而,这项目只是实现了一些功能,但我感觉并没有很好地利用Composition API去对代码进行整合管理。要知道,Composition API的出现就是为了解决Options API导致相同功能代码分散的现象,也有很多大佬对其做了很多的动画展示(这里我借用一下大帅搞全栈大佬精心制作的动画,他的这篇文章可以说是好评连连,大家可以观摩一下:做了一夜动画,就为让大家更好的理解Vue3的Composition Api)

如何充分利用Composition API对Vue3项目进行代码抽离「建议收藏」

如何充分利用Composition API对Vue3项目进行代码抽离「建议收藏」

看了一下我项目初版的代码,简直是没有体现出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>

IT知识分享网

上述代码是我项目中侧边栏中所有的变量以及方法,虽说变量和方法都同时存在于setup函数中了,但是仍看起来杂乱无章,若是这个组件的业务需求越来越复杂,这个setup内的代码可能更乱了

于是,我便开始构思如何抽离我的代码。后来在掘金的沸点上说了一下我的思路,并且询问了一下其他掘友的建议

如何充分利用Composition API对Vue3项目进行代码抽离「建议收藏」

如何充分利用Composition API对Vue3项目进行代码抽离「建议收藏」

如何充分利用Composition API对Vue3项目进行代码抽离「建议收藏」

其实最后一位老哥的回答对我启发很大,因此我也借鉴了一下它的思路对我的项目代码进行了抽离

准备工作

首先我得思考一个问题:抽离代码时,是按照组件单独抽离?还是按照整体功能抽离?

如何充分利用Composition API对Vue3项目进行代码抽离「建议收藏」

最后我决定按照整体的功能去抽离代码,具体功能列表如下:

  • 搜索功能
  • 新增/修改标签功能
  • 新增/修改网址功能
  • 导入配置功能
  • 导出配置功能
  • 编辑功能

开始抽出代码

上述的每一个功能都会通过一个JS文件去存储该功能对应的变量以及方法。然后所有的JS文件都是放在src/use下的,如图

如何充分利用Composition API对Vue3项目进行代码抽离「建议收藏」

就拿 新增/修改标签功能 来举例子,用一个动图给大家看看该功能的全部效果

如何充分利用Composition API对Vue3项目进行代码抽离「建议收藏」

很明显,我是做了一个弹窗组件,当点击侧边栏中的 + 号后,弹窗显示;然后我输入了想要新增标签的名称,并且选择了合适的图标,最后点击了确认,于是一个标签就添加好了,弹窗也随之隐藏;

最后我又去编辑模式下点击修改标签,弹窗再次显示,与此同时把对应标签的名称与图标都渲染了出来;待我修改了名字后,点击了确认,于是标签的信息就被我改好了,弹窗又随之隐藏了。

所以总结以下涉及到的功能就有以下几个:

  1. 弹窗的展示
  2. 弹窗的隐藏
  3. 点击确认后新增或修改标签内容

按照传统的写法,实现上述三个功能是这个样子的(我修改并简化了代码,大家理解意思就行):

  • 侧边栏组件内容
IT知识分享网<!-- 侧边栏组件内容 -->
<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文件中的大致结构是这样的:

IT知识分享网// 引入依赖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是在某个组件中定义的,同时标签组件也需要获取这个变量来控制展示的状态,这之间用到了父子组件通信,那么我们不妨把这个变量写在一个公共的文件中,无论哪个组件需要用到的时候,只需要导入获取就好了,因为每次获取到的都是同一个变量

如何充分利用Composition API对Vue3项目进行代码抽离「建议收藏」

这样一来,岂不是连父子组件通信都省了嘛?

我们把刚刚封装好的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://yundeesoft.com/9445.html

(0)
上一篇 2023-02-05 13:00
下一篇 2023-02-05 15:00

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

关注微信