This article is a little more code, I hope you have patience to watch

background

In 2020, I did not learn Vue3 until November. After that, I made a small Vue3 project, nav-URL, and also sorted out my blog posts on how to get a quick start on Vue3. I was very glad to receive your advice and love:

  • Vue3 practical project designed by myself (including implementation ideas and introduction of project highlights) (237+ 👍)

In my last blog post, I detailed the features, highlights and implementation of all the core functions of the first version of the project I posted. I hope you can go and read it and experience it (remember to open it on your computer, because this is a PC project).

However, the project only implemented some features, but I felt that it did not make good use of the Composition API to integrate and manage the code. The Composition API was created to solve the problem of the Options API causing the same function code to be scattered, and there are a lot of animators to animate it. I worked on animation all night just to give you a better understanding of the Composition Api of Vue3.

Taking a look at the code for the first version of my project, which simply didn’t take advantage of the Composition API, I can show you the code within a component

<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>Quick search</span>
          </li>
          <li class="tab tab-save" @click="showSaveConfigAlert">
              <i class="fas fa-share-square tab-icon"></i>
              <span>Save the configuration</span>
          </li>
          <li class="tab tab-import" @click="showImportConfigAlert">
              <i class="fas fa-cog tab-icon"></i>
              <span>Import the configuration</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>
      <! -- Add TAB popup -->
      <tabAlert />
      <! -- Save the configuration popup -->
      <save-config @closeSaveConfigAlert="closeSaveConfigAlert" :isShow="isShowSaveAlert"/>
      <! -- Import configuration popbox -->
      <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    // State object of Vuex
        let isShowSaveAlert = ref(false)           // Save whether the configuration dialog box is displayed
        let isShowImportAlert = ref(false)         // Whether to display the import configuration dialog box
        
        // Display "Add label popup"
        function addTabShow() {
            store.commit('changeTabInfo'[{key: 'isShowAddTabAlert'.value: true},
                {key: 'alertType'.value: 'Add tags'})}// Close "Save Configuration popbox"
        function closeSaveConfigAlert(value) {
            isShowSaveAlert.value = value
        }
        // Display "Save configuration popbox"
        function showSaveConfigAlert() {
            isShowSaveAlert.value = true
        }
        // Display "Import Configuration popbox"
        function showImportConfigAlert() {
            isShowImportAlert.value = true
        }
        // Close the Import Configuration dialog box
        function closeImportConfigAlert(value) {
            isShowImportAlert.value = value
        }
        // Display the search box
        function showSearch() {
            if(store.state.moduleSearch.isSearch) {
                store.commit('changeIsSearch'.false)
                store.commit('changeSearchWord'.' ')}else {
                store.commit('changeIsSearch'.true)}}// Jump to the specified label
        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>
Copy the code

The above code is all of the variables and methods in the sidebar of my project, and while both variables and methods exist in the Setup function, it still looks messy, perhaps even more so as the business requirements of this component become more complex

So I began to figure out how to pull away from my code. I talked about my thoughts on the nuggets’ boiling point and asked other nuggets for advice

In fact, the last elder brother’s answer inspired me a lot, so I also borrowed its ideas on my project code for separation

The preparatory work

First I have to think about a question: when I pull out the code, do I pull it out separately by component? Or is it separated by the overall function?

Finally, I decided to extract the code according to the overall function, and the specific function list is as follows:

  • The search function
  • Added or modified the label function
  • Added/modified URL function
  • Importing configuration
  • Exporting configuration
  • Editing features

Start pulling out the code

Each of the above functions will be stored through a JS file to the corresponding variables and methods of the function. Then all JS files are placed under SRC /use, as shown in the figure

Take the new/modified TAB feature as an example, and use a GIF to show you the full effect of this feature

Obviously, I made a popover component, and when I click the + sign in the sidebar, it pops up; Then I enter the name of the label I want to add and select the appropriate icon. Finally, I click OK. A label is added and the popover is hidden.

Finally, I went to edit mode and clicked “Modify label”. The popup window displayed again and rendered the corresponding label name and icon at the same time. After I changed the name, I clicked OK, so the information of the label was changed by me, and the popover was hidden.

Therefore, the following functions are summarized as follows:

  1. Popover display
  2. Popover hiding
  3. Click Ok to add or modify the label content

Traditionally, the implementation of the above three functions looks like this (I’ve modified and simplified the code, so you get the idea) :

  • Sidebar component content
<! -- Sidebar component content -->
<template>
    <aside>
    	<div @click="show">The new labels</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() {
    	// Display of the storage label pop-up
    	const isShow = ref(false)   
        
        // Display the label pop-up box
        function show() {
            isShow.value = true
        }
        
        // Hide the label pop - up
        function close() {
            isShow.value = false
        }
        
        return { isShow, show, close }
    }
}
</script>
Copy the code
  • Label Component content of the box
<! -- TAB popup component content -->
<template>
    <div v-show="isShow">
    	<! -- Omitted some unimportant content code -->
        <div @click="close">cancel</div>
        <div @click="confirm">confirm</div>
    </div>
</template>

<script>
export default {
    name: "tab".props: {
    	isShow: {
            type: Boolean.default: false}},setup(props, {emit}) {
    
    	// Hide the label pop - up
    	function close() {
            emit('close')}// Click ok
        function confirm() {
        
            /* Omit the business code */ that updates the label content after clicking the confirm button
            
            close()
        }
        
        
        return { close, confirm }
    }
}
</script>
Copy the code

After looking at my example above, we can find that the implementation of a simple function involves two components, and the parent and child components need to communicate with each other to control some states, so the function is scattered, that is, not enough aggregation. So when I separate the functionality code by functionality, I create a tabalert.js file for them, which stores all the variables and methods for the functionality.

The general structure in the tabalert.js file looks like this:

// Introduce dependency apis
import { ref } from 'vue'

// Define some variables
const isShow = ref(false)     // Displays the status of the label pop-up

export default function tabAlertFunction() {
    /* Define some methods */
    
    // Display the label pop-up box
    function show() {
    	isShow.value = true
    }
    
    // Close the label dialog box
    function close() {
    	isShow.value = false
    }
    
    // Click ok
    function confirm() {
        /* Omit the business code */ that updates the label content after clicking the confirm button
        
        close()
    }
    
    return {
    	isShow,
        show,
        close,
        confirm,
    }
}
Copy the code

For the reason of designing such a structure, I first put all the methods related to this function in a function, and finally export it by return. It is because sometimes these methods will depend on other external variables, so they are wrapped with functions. For example:

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

From this file we can see that both log1 and log2 methods depend on the variable num, but we did not define num in this file, so we can import this file in other components by passing num to the outermost exampleFunction method

<template>
    <button @click="log1">Print plus 1</button>
    <button @click="log2">Print plus 2</button>
</template>

<script>
import exampleFunction from './example'
import { num } from './getNum'  // Assume that num is fetched from another module
export default {
    setup() {
    	let { log1, log2 } = exampleFunction(num)
    	
        return { log1, log2 }
    }
}
</script>
Copy the code

And then we’ll talk about why the definition of a variable is outside of our derived function. Continue to see me on my project in TAB in the example, used to store the tag isShow bounced display state variables are defined in a component, at the same time label components also need to get this variable to control the display of status, the communication with the father and son between components, so we might as well write this variable in a public document, Whenever any component needs it, you just import the fetch, because you get the same variable every time

Wouldn’t even parent-child component communication be saved?

Tabalert.js is a component we just wrapped

  • Sidebar component content
<! -- Sidebar component content -->
<template>
    <aside>
    	<div @click="show">The new labels</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>
Copy the code
  • Label Component content of the box
<! -- TAB popup component content -->
<template>
    <div v-show="isShow">
    	<! -- Omitted some unimportant content code -->
        <div @click="close">cancel</div>
        <div @click="confirm">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>
Copy the code

Look back at the original code and see if you can see how much cleaner the code is and how much less code there is in the component.

This method of clustering variables and code by function is easier to manage in my opinion. If you want to add small requirements to this function one day, just go to the tabalert.js file and write methods and variables in it

Showcase,

This is how I pulled out my original code. Here are a few groups of code comparisons before and after pulling out

Compared to a

  • Before pulling away
<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">Import the configuration</div>
      <div class="import-config-alert-remind">Note: Save the exported XXX. Json configuration file before uploading the file. The information in the file completely overwrites the current information</div>
      <form action="" class="form">
        <label for="import_config_input" class="import-config-label">Uploading a Configuration File<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">Confirm to upload</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')     // The import result
        const isUpload = ref(false)    // Determine whether to upload the configuration file
        const isImport = ref(false)    // Check whether the configuration is imported successfully
        const isLoading = ref(false)   // Check whether the button is loaded
        const inputFile = ref(null)    // Get the file label
        const hasFile = ref(0)         // Check the incoming file. 0: no input. 1: format is incorrect. 2: format is correct
        const $message = inject('message')
        // Import the configuration
        function importConfig() {
          let reader = new FileReader()
          let files = inputFile.value.files
          if(hasFile.value == 0) {
            $message({
              type: 'warning'.content: 'Please upload configuration file first'})}else if(hasFile.value == 1) {
            $message({
              type: 'warning'.content: 'Please upload a file in the correct format, such as xx.json'})}else if(hasFile.value == 2) {
            reader.readAsText(files[0])
            reader.onload = function() {
              let data = this.result
              window.localStorage.navInfos = data
              location.reload()
            }
          }
        }
        // Close the popover
        function closeAlert() {
          emit('closeImportConfigAlert'.false)
          hasFile.value = 0
        }
        function fileChange(e) {
          let files = e.target.files
          if(files.length === 0) {
            $message({
              type: 'warning'.content: 'Please upload configuration file first'})}else {
            let targetFile = files[0]
            if(!/\.json$/.test(targetFile.name)) {
              hasFile.value = 1
              $message({
                type: 'warning'.content: 'Please make sure the file format is correct'})}else {
              hasFile.value = 2
              $message({
                type: 'success'.content: 'File format is correct'})}}}return {
          result, 
          isUpload,
          isImport, 
          isLoading,
          importConfig, 
          closeAlert,
          inputFile,
          fileChange,
          hasFile
        }
    }
}
</script>
Copy the code
  • After pulling away
<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">Import the configuration</div>
      <div class="import-config-alert-remind">Note: Save the exported XXX. Json configuration file before uploading the file. The information in the file completely overwrites the current information</div>
      <form action="" class="form">
        <label for="import_config_input" class="import-config-label">Uploading a Configuration File<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">Confirm to upload</lp-button>
    </div>
  </div>
</template>

<script>
/* API */
import { inject } from 'vue'
/ * * / components
import lpButton from '@/components/public/lp-button/lp-button'
/* Function module */
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>
Copy the code
  • Extract the code file
// Import the configuration function
import { ref } from 'vue'

const isShowImportAlert = ref(false),   // Whether to display the import configuration dialog box
      result = ref('none'),             // The import result
      isUpload = ref(false),            // Determine whether to upload the configuration file
      isImport = ref(false),            // Check whether the configuration is imported successfully
      isLoading = ref(false),           // Check whether the button is loaded
      inputFile = ref(null),            // Get the file element
      hasFile = ref(0)                  // Check the incoming file. 0: no input. 1: format is incorrect. 2: format is correct
      
export default function importConfigFunction($message) {
  
    // Control the display of the frame
    function handleImportConfigAlert(value) {
        isShowImportAlert.value = value
        if(! value) hasFile.value =0
    }

    // The content of the uploaded file has changed
    function fileChange(e) {
        let files = e.target.files
        if(files.length === 0) {
            $message({
            type: 'warning'.content: 'Please upload configuration file first'})}else {
            let targetFile = files[0]
            if(!/\.json$/.test(targetFile.name)) {
                hasFile.value = 1
                $message({
                    type: 'warning'.content: 'Please make sure the file format is correct'})}else {
            hasFile.value = 2
                $message({
                    type: 'success'.content: 'File format is correct'})}}}// Import the configuration
    function importConfig() {
        let reader = new FileReader()
        let files = inputFile.value.files
        if(hasFile.value == 0) {
          $message({
            type: 'warning'.content: 'Please upload configuration file first'})}else if(hasFile.value == 1) {
          $message({
            type: 'warning'.content: 'Please upload a file in the correct format, such as 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,
    }
}
Copy the code

Compare the two

  • Before pulling away
<template>
    <! -- Here because of too much code, temporarily omitted, details can click on the end of the project source view -->
</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)
        
        
        // The dialog box for adding the URL is displayed
        function addMoreUrl(id) {
            store.commit('changeUrlInfo'[{key: 'isShow'.value: true},
                {key: 'whichTag'.value: id},
                {key: 'alertType'.value: 'New url'})}// Process images with no icon or failed icon loading to use the default SVG icon
        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'
        }
        // Enter edit mode
        function enterEdit(id) {
            if(id ! = editWhich.value) { editWhich.value = id }else {
                editWhich.value = -1}}// The label modification dialog box is displayed
        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: 'Modify label'})}// Delete the tag and all urls under the tag
        function deleteTag(id) {
            $confirm({
                content: 'Are you sure you want to delete this TAB and all urls under this TAB? '
            })
            .then(() = > {
                store.commit('remove', id)
                $message({
                    type: 'success'.content: 'Tabs and suburls deleted successfully'
                })
            })
            .catch(() = >{})}// Delete a url
        function deleteUrl(id) {
            $confirm({
                content: 'Are you sure you want to delete this url? '
            })
            .then(() = > {
                store.commit('remove', id)
                $message({
                    type: 'success'.content: 'Url deleted successfully'
                })
            })
            .catch(() = >{})}// The dialog box for modifying the URL is displayed
        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: 'Modify url'})}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   // The address box element to be moved
        let elementNodeLocated = null  // Move in the address box element
        let draggedId = -1   // Id of the address box to be moved
        
        // The address box starts dragging
        function urlBoxDragStart(e) {
            const el = e.target
            if(el.nodeName ! = ='LI') return;
            // Records the current address box element being dragged
            elementNodeDragged = el    
            // Hide the dragged object
            el.style.display = 'fixed'
            el.style.opacity = 0
        }
        // The address box drag is complete
        function urlBoxDragEnd(e) {
            let el = e.target
            el.style.display = 'inline-block'
            el.style.opacity = 1
            // Get the order of all urls in the tag currently being edited
            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)}// The dragged address box touches another address box
        function urlBoxEnter(e, tabId) {
            if(tabId ! = editWhich.value)return;
            let el = e.target
            while(el.nodeName ! = ='LI') el = el.parentNode;        // If the child triggers the dragenter event, the parent li tag is found
            if(el === elementNodeDragged) return;     // Avoid dragging yourself into your situation
            if(elementNodeLocated ! == el) elementNodeLocated = el// Records the address box element that was moved in
            exchangeElements(elementNodeDragged, el)     // Address box position swap
        }
        return {
            catalogue, 
            addMoreUrl, 
            moduleUrl, 
            moduleSearch,
            imgLoadErr,
            imgLoadSuccess, 
            enterEdit, 
            editTagAlertShow,
            deleteTag,
            deleteUrl,
            editUrl,
            editWhich,
            judgeTabIsShow,
            judgeUrlIsShow,
            urlBoxDragStart,
            urlBoxDragEnd,
            urlBoxEnter,
        }
    }
}
</script>
Copy the code
  • After pulling away
<template>
  <! -- Here because of too much code, temporarily omitted, details can click on the end of the project source view -->
</template>

<script>
/* API */
import { inject } from 'vue'
import { useStore } from 'vuex'
/ * * / components
import urlAlert from '@/components/public/urlAlert/index'
import tabAlert from '@/components/public/tabAlert/index'
import carousel from './carousel'
import search from './search'
/* Function module */
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')

        // Some basic methods
        let { imgLoadErr, imgLoadSuccess } = baseFunction()

        // Edit related variables and functions in the URL box
        let { 
            editWhich, 
            handleEdit, 
            deleteTab, 
            deleteUrl, 
            urlBoxDragStart, 
            urlBoxDragEnd, 
            urlBoxEnter 
        } = editFunction($message, $confirm)

        // The Add and Modify URL dialog boxes are displayed
        let { showNewUrlAlert, showEditUrlAlert } = urlAlertFunction()

        // Search for function-related variables and methods
        let { moduleSearch, judgeTabIsShow, judgeUrlIsShow } = searchFunction()

        // Displays the dialog box for modifying TAB
        let { showEditAddTab } = tabAlertFunction()

        return{ catalogue, showNewUrlAlert, moduleSearch, imgLoadErr, imgLoadSuccess, handleEdit, showEditAddTab, deleteTab, deleteUrl, showEditUrlAlert, editWhich, judgeTabIsShow, judgeUrlIsShow, urlBoxDragStart, urlBoxDragEnd, urlBoxEnter, }}}</script>
Copy the code
  • Abstracted code files (there are many modules involved here, so I will show only two)
// Search function
import{}from 'vue'
import store from '@/store/index'

/ / variable
const moduleSearch = store.state.moduleSearch     // Search for relevant global status

export default function searchFunction() {

    // The input to the search box has changed
    function inputSearchContent(value) {
        store.commit('changeSearchWord', value)
    }

    // Controls the display of the search box
    function handleSearchBox() {
        if(moduleSearch.isSearch) {
            store.commit('changeIsSearch'.false)
            store.commit('changeSearchWord'.' ')}else {
            store.commit('changeIsSearch'.true)}}// Check whether the label is displayed
    function judgeTabIsShow(i) {
        return store.getters.judgeTabIsShow(i)
    }

    // Check whether the URL is displayed
    function judgeUrlIsShow(i, j) {
        return store.getters.judgeUrlIsShow(i, j)
    }

    return {
        moduleSearch,
        inputSearchContent,
        handleSearchBox,
        judgeTabIsShow,
        judgeUrlIsShow,
    }
}
Copy the code
// Drag-and-drop arrangement of url boxes
import { ref } from 'vue'
import { exchangeElements, debounce } from '@/utils/utils'
import store from '@/store/index'

/ / variable
let elementNodeDragged = null.// The address box element to be moved
    elementNodeLocated = null.// Move in the address box element
    editWhich = ref(-1)            // Record the TAB index being edited

export default function editFunction($message, $confirm) {

        // Control the editing state
        function handleEdit(id) {
            if(id ! = editWhich.value) { editWhich.value = id }else {
                editWhich.value = -1}}// Delete the tag and all urls under the tag
        function deleteTab(id) {
            $confirm({
                content: 'Are you sure you want to delete this TAB and all urls under this TAB? '
            })
            .then(() = > {
                store.commit('remove', id)
                $message({
                    type: 'success'.content: 'Tabs and suburls deleted successfully'
                })
            })
            .catch(() = >{})}// Delete a url
        function deleteUrl(id) {
            $confirm({
                content: 'Are you sure you want to delete this url? '
            })
            .then(() = > {
                store.commit('remove', id)
                $message({
                    type: 'success'.content: 'Url deleted successfully'
                })
            })
            .catch(() = >{})}// The address box starts dragging
        function urlBoxDragStart(e) {
            const el = e.target
            if(el.nodeName ! = ='LI') return;
            // Records the current address box element being dragged
            elementNodeDragged = el    
            // Hide the dragged object
            el.style.display = 'fixed'
            el.style.opacity = 0
        }

        // Update the correct sort in Vuex after drag
        let dragEndToUpdate = debounce(function() {
            // Get the order of all urls in the tag currently being edited
            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)

        // The address box drag is complete
        function urlBoxDragEnd(e) {
            let el = e.target
            el.style.display = 'inline-block'
            el.style.opacity = 1
            dragEndToUpdate()
        }

        // The dragged address box touches another address box
        function urlBoxEnter(e, tabId) {
            if(tabId ! = editWhich.value)return;
            let el = e.target
            while(el.nodeName ! = ='LI') el = el.parentNode;          // If the child triggers the dragenter event, the parent li tag is found
            if(el === elementNodeDragged) return;                    // Avoid dragging yourself into your situation
            if(elementNodeLocated ! == el) elementNodeLocated = el// Records the address box element that was moved in
            exchangeElements(elementNodeDragged, el)                 // Address box position swap
        }

    return {
        editWhich,
        handleEdit,
        deleteTab,
        deleteUrl,
        urlBoxDragStart,
        urlBoxDragEnd,
        urlBoxEnter,
    }
}
Copy the code

The last

Careful partners should find, just to show you the code, there is a variety of drag implementation method, yes!! In my spare time, I added the drag-and-drop arrangement function in edit mode to my project, which is also one of the suggestions you gave me before. Welcome to experience the new function ~

Project Experience Link

After the experience, I hope that interested friends can mention Issues to me on Github, I will reply as soon as I see it (if there are many friends urging me to make account function, I may consider adding it later).

Project source link (welcome to Star, more comments, more exchange ah ~)

If you have better comments or ideas, please leave a comment, or add me to vx: Lpyexplore333 for private discussion

Finally, thank you for your patience

Writing an article is not easy, I hope you give me a lot of comments, don’t forget to point a praise 👍 oh ~