VUE3+TS遞歸組件實(shí)現(xiàn)TreeList設(shè)計(jì)實(shí)例詳解
前言
乘著活動(dòng),水一篇
雖然是標(biāo)題黨,但是不代表咱們的內(nèi)容不真誠,如果對您各位有用,請不要吝嗇您的小手,贊一贊!
今天和大家探討的問題是,怎樣設(shè)計(jì)一個(gè)類似vscode目錄系統(tǒng),也就是個(gè)treeList

不著急,您且聽我慢慢道來
功能分析
我們這個(gè)目錄系統(tǒng)的設(shè)計(jì),由于我司乃vue為主棧,我們就使用vue3為例開發(fā) ,在此感謝祖師爺尤大,讓我等小民有口飯吃
功能如下:
- 1、插件式開發(fā)
- 2、支持拖拽功能
- 3、支持展開收起
- 4、支持目錄名修改
- 5、目錄支持增刪改查
- 6、使用vue3開發(fā)
- 7、支持名字重復(fù)驗(yàn)證
- 8、支持完整事件
數(shù)據(jù)結(jié)構(gòu)
一個(gè)目錄結(jié)構(gòu),在數(shù)據(jù)結(jié)構(gòu)上的表示為一個(gè)樹,表示如下
export const list = [
{
id: 1,
isFolder: true,
title: 'src',
pid: null,
fileNameArr: ['src', 'dist', 'package.json', 'README.md'],
children: [
{
id: 7,
pid: 1,
isFolder: false,
fileNameArr: ['index.js', 'index.vue'],
title: 'index.js'
},
{
id: 8,
pid: 1,
isFolder: false,
fileNameArr: ['index.js', 'index.vue'],
title: 'index.vue'
}
]
},
{
id: 2,
isFolder: true,
title: 'dist',
pid: null,
fileNameArr: ['src', 'dist', 'package.json', 'README.md'],
children: [
{
id: 5,
pid: 2,
isFolder: false,
fileNameArr: ['index.html', 'index.js'],
title: 'index.html'
},
{
id: 6,
pid: 2,
isFolder: false,
fileNameArr: ['index.html', 'index.js'],
title: 'index.js'
},
]
},
{
id: 3,
pid: null,
title: 'package.json',
fileNameArr: ['src', 'dist', 'package.json', 'README.md'],
isFolder: false
}, {
id: 4,
pid: null,
title: 'README.md',
fileNameArr: ['src', 'dist', 'package.json', 'README.md'],
isFolder: false
}
]
此處我們需要注意幾個(gè)問題, 為了方便后期操作, 我們需要確保幾個(gè)字段 isFolder 是否是文件目錄
fileNameArr 同層級(jí)目錄名(為了防止新增名字重復(fù))pid 建立父子關(guān)系的pid
在一般情況下后端存儲(chǔ)的數(shù)據(jù)可能是一個(gè)數(shù)組,
const list = [
{
id: 1,
isFolder: true,
title: 'src',
pid: null,
fileNameArr: ['src', 'dist', 'package.json', 'README.md'],
},
{
id: 7,
pid: 1,
isFolder: false,
fileNameArr: ['index.js', 'index.vue'],
title: 'index.js'
},
{
id: 8,
pid: 1,
isFolder: false,
fileNameArr: ['index.js', 'index.vue'],
title: 'index.vue'
},
{
id: 2,
isFolder: true,
title: 'dist',
pid: null,
fileNameArr: ['src', 'dist', 'package.json', 'README.md'],
},
{
id: 5,
pid: 2,
isFolder: false,
fileNameArr: ['index.html', 'index.js'],
title: 'index.html'
},
{
id: 6,
pid: 2,
isFolder: false,
fileNameArr: ['index.html', 'index.js'],
title: 'index.js'
},
{
id: 3,
pid: null,
title: 'package.json',
fileNameArr: ['src', 'dist', 'package.json', 'README.md'],
isFolder: false
},
{
id: 4,
pid: null,
title: 'README.md',
fileNameArr: ['src', 'dist', 'package.json', 'README.md'],
isFolder: false
}
]
我們需要將數(shù)組裝成tree,此時(shí)祭出經(jīng)典算法
function list2tree(list) {
list.forEach(child => {
const pid = child.pid
if (pid) {
list.forEach(parent => {
if (parent.id === pid) {
parent.children = parent.children || []
parent.children.push(child)
}
})
}
})
return list.filter(n => !n.pid)
}
實(shí)現(xiàn)方式
本質(zhì)上來說,他是一個(gè)逐級(jí)遞歸的分層的數(shù)據(jù),并且每一層的數(shù)據(jù)和格式都大致相當(dāng),只是細(xì)節(jié)的不同,我們就可以使用vue的遞歸組件,來解決問題
這就符合關(guān)注度分離的原則 我們只關(guān)心當(dāng)前這一層的內(nèi)容,剩下的層級(jí)通過遞歸來實(shí)現(xiàn) 代碼如下:
<template>
<div class="vtl-node" :id="model.id" :class="{ 'vtl-leaf-node': !isFolder, 'vtl-tree-node': isFolder }">
<div :class="treeNodeClass" >
<div class="vtl-border-text">
<span class="vtl-node-content ellipsis" v-if="!editable && !model.isAdd">
{{ model.title }}
</span>
</div>
</div>
</div>
<div class="vtl-tree-margin" v-show="expanded" v-if="isFolder">
<!-- 遞歸treeList -->
<treeList v-for="newmodel in model.children"
:selected="selected" :model="newmodel" :key="newmodel.id">
</treeList>
</div>
</template>
<script setup lang="ts">
import { computed, ref, watchEffect } from 'vue'
interface IFileSystem {
id: string;
title: string;
pid: string;
isFolder: boolean;
isAdd: boolean;
children?: IFileSystem[];
}
// 吐出去的事件
const emit = defineEmits(['onClick', 'changeName', 'deleteNode', 'addNode', 'addFolder', 'onDrop', 'setDragEnterNode', 'setDragFile', 'setDragFolder', 'dragStart'])
// 拿到傳入的值
const props = withDefaults(defineProps<{
model: IFileSystem,
draggable?: boolean,
selected?: IFileSystem
}>(), {
draggable: true,
})
// 修改目錄名字
const editable = ref(false)
// 拖拽移入
const isDragEnterNode = ref(false)
// 是否拖拽文件
const isDragFile = ref(false)
// 是否展開
const expanded = ref(true)
// inputRef
const nodeInput = ref(null)
// 是否是文件夾
const isFolder = computed(() => {
return props.model.isFolder
})
const isSelected = computed(() => props.selected.id === props.model.id)
// 拖拽樣式
const treeNodeClass = computed(() => {
return {
'vtl-node-main': true,
'vtl-active': isDragEnterNode.value,
'vtl-active-file': isDragFile.value,
'selected': isSelected.value
}
})
// 最后一個(gè)移入的內(nèi)容保存為了防止重復(fù)移入
let lastenter = null;
// 刪除目錄
</script>
<style lang="scss">
.vtl-node {
.vtl-node-main {
display: flex;
align-items: center;
padding: 2px 0 2px 1rem;
cursor: pointer;
&:hover {
.vtl-border-text {
width: 80%;
}
}
.vtl-border-text {
flex: 1;
width: 100%;
.iconfont {
width: 16px;
height: 16px;
vertical-align: text-bottom;
}
}
&.selected {
background-color: rgb(36, 36, 36);
}
.vtl-input {
border: none;
max-width: 150px;
padding: 5px 0;
padding-left: 5px;
margin-left: 5px;
&:focus {
outline: none;
}
}
.vtl-node-content {
color: rgb(153, 153, 153);
padding-left: 5px;
font-size: 14px;
width: 80%;
display: inline-block;
vertical-align: bottom;
}
&:hover {
.vtl-node-content {
color: #fff;
overflow: hidden;
}
}
&.vtl-active {
* {
pointer-events: none;
}
}
&.vtl-active-file {
outline: 2px dashed #353f51;
}
.vtl-operation {
padding-right: 10px;
}
}
}
.vtl-tree-margin {
padding-left: 1em;
}
</style>
到這里,骨架算是搭建好了,效果如下:

接下來,就可以暢通無阻的實(shí)現(xiàn)功能了
插件式開發(fā)
先說最重要的一點(diǎn),如果在面試環(huán)境中 也是你需要表達(dá)的最多的一點(diǎn),你說的越花哨,你就越能唬住面試官
所謂插件式開發(fā),就是提供數(shù)據(jù),插件提供功能
其中有幾個(gè)關(guān)鍵的點(diǎn),務(wù)必需要表達(dá)清楚,(忽悠的越多,您啊可能就工資越高)
- 1、插件如何注冊
- 2、插件需要設(shè)計(jì)那些事件
- 3、插件需要傳入那些值,從而實(shí)現(xiàn)更大的靈活性
- 4、插件能包攬那些功能
我們一個(gè)個(gè)來解析
插件如何注冊
對于vue 來說,插件套路都一樣,支持全局注冊,和局部注冊
// index.ts
import fileSystem from './fileSystem.vue';
// 在install中注冊組件
function install(app) {
app.component('fileSystem', fileSystem)
}
export { fileSystem }
export default {
install
}
// 在使用的時(shí)候
import fileSystem from './components/index'
// 利用use方法倆完成全局組件注冊,這也是現(xiàn)在的插件通用套路
createApp(App).use(fileSystem).mount('#app')
插件需要設(shè)計(jì)那些事件
按照理論來說,你的每一步操作,其實(shí)都需要有一個(gè)事件拋出,就那我們當(dāng)前來說
點(diǎn)擊事件、拖拽事件、添加文件事件、拖拽事件、刪除文件事件、修改文件事件
<fileSystem :selected="selected" :list="listArr" @add-node="onAddNode" :draggable="true" @delete-node="onDeltet"
@on-click="onClick" @on-drop="drop" @change-name="onChangeName">
</fileSystem>
<script setup lang="ts">
//點(diǎn)擊目錄
const onClick = (node) => {
selected.value = node
}
// 拖拽結(jié)束
const drop = (node) => {
console.log(node)
}
// 修改名字
const onChangeName = (node) => {
console.log(node)
}
// 刪除
const onDeltet = (node) => {
console.log(node)
}
// 添加目錄
const onAddNode = (node) => {
console.log(node)
}
</script>
插件需要傳入那些值
從目前的需求來看, 我們只需要傳入四個(gè)參數(shù)
- 1、 treelist數(shù)據(jù)字段,這個(gè)是必須的list
- 2、 是否支持拖拽 draggable
- 3、是否支持修改isEdit
- 4、選中內(nèi)容 selected
- 5、插槽內(nèi)容
插槽內(nèi)容
之所以需要插槽內(nèi)容,是由于我們的圖標(biāo)不是固定,為了保證當(dāng)前的目錄的通用性
所以圖標(biāo)必須要放在插槽中,讓用戶自己定制
<fileSystem :selected="selected" :list="listArr" @add-node="onAddNode" :draggable="true" @delete-node="onDeltet"
@on-click="onClick" @on-drop="drop" @change-name="onChangeName">
<template #icon="{ item }">
<template v-if="item.isFolder">
<icon v-if="item.expanded" class="iconfont" iconName="icon-24gf-folderOpen"></icon>
<icon v-else class="iconfont" iconName="icon-bg-folder"></icon>
</template>
<treeIcon class="iconfont" :title="item.title" v-else></treeIcon>
</template>
<template #operation="{ type }">
<i class="iconfont icon-add_file" v-if="type == 'addFolder'"></i>
<i class="iconfont icon-xinzeng" v-if="type == 'addDocument'"></i>
<i class="iconfont icon-bianji" v-if="type == 'Editable'"></i>
<i class="iconfont icon-guanbi" v-if="type == 'deleteNode'"></i>
</template>
</fileSystem>
于是我們制定了兩個(gè)具名插槽,來分別承載,操作按鈕,和圖標(biāo),他就變成這樣了

需要注意的是,我們的插槽需要做透傳,因?yàn)榧热皇沁f歸組件,那么就需要他的插槽內(nèi)容發(fā)散到子組件的方方面面
我們需要這樣
<treeList v-for="model in list" v-bind="$attrs" :model="model" :key="model.id" @delete-node="onDeltet"
@add-node="onAddNode" @on-drop="drop" @add-folder="onAddFolder" @dragStart="dragStart">
<template #icon="slotProps">
<slot name="icon" v-bind="slotProps"></slot>
</template>
<template #operation="slotProps">
<slot name="operation" v-bind="slotProps"></slot>
</template>
</treeList>
支持拖拽功能
效果如下:

在實(shí)現(xiàn)拖拽之前,我們需要了解一些基礎(chǔ)問題
draggable
draggable 屬性規(guī)定元素是否可拖動(dòng)。
<div draggable="true"></div>
拖拽相關(guān)事件
開啟draggable 之后大家伙可以測試一下,他只有個(gè)型,也就是有個(gè)樣子,但是其實(shí)他還應(yīng)該有個(gè)功能,也就是我需要使用,一些操作之后的回調(diào), 來控制內(nèi)容, 從而實(shí)現(xiàn)我們的功能,這個(gè)時(shí)候這些個(gè)拖動(dòng)事件,必不可少
本次用到的事件如下
- 1、dragstart 當(dāng)用戶開始拖動(dòng)一個(gè)元素或者一個(gè)選擇文本觸發(fā)
- 2、dragenter 當(dāng)拖動(dòng)的元素或被選擇的文本進(jìn)入有效的放置目標(biāo)時(shí)觸發(fā)
- 3、dragover 當(dāng)元素或者選擇的文本被拖拽到一個(gè)有效的放置目標(biāo)上時(shí)觸發(fā)
- 4、dragleave當(dāng)一個(gè)被拖動(dòng)的元素或者被選擇的文本離開一個(gè)有效的拖放目標(biāo)時(shí)觸發(fā)
- 5、drop 當(dāng)一個(gè)元素或是選中的文字被拖拽釋放到一個(gè)有效的釋放目標(biāo)位置時(shí)觸發(fā)
利用以下事件的組合來使用,就能達(dá)成拖拽的目的
我們來說一下,實(shí)現(xiàn)思路
首先,由于是遞歸組件,我們需要在每一個(gè)組件的根div 上綁定事件
<div :draggable="draggable" @dragover="dragOver" @drop="drop" @dragstart="dragStart"
@dragenter="dragEnter" @dragleave="dragLeave"
>
<div class="vtl-border-text">
<span class="vtl-node-content ellipsis">
{{ model.title }}
</span>
</div>
</div>
接下來一個(gè)個(gè)來分析這些位事件
dragStart
dragStart 表示拖拽開始觸發(fā),這個(gè)時(shí)候我們需要保存當(dāng)前組件的數(shù)據(jù),但是我們不能保存在當(dāng)前組件,于是需要向上找,找到最外層,來保存內(nèi)容
// 拖拽開始
const dragStart = () => {
console.log(0)
emit('dragStart', {
...props.model
})
}
//最外層
// 拖拽開始選中node
const dragStart = (node) => {
compInOperation.value = node
}
dragOver
dragOver 當(dāng)元素或者選擇的文本被拖拽到一個(gè)有效的放置目標(biāo)上時(shí)觸發(fā)
這個(gè)事件就有意思了,其實(shí)他本來沒啥用,但是不用他還不行,因?yàn)樗麜?huì)使得drop事件不生效
const dragOver = (e) => {
// 需要組織默認(rèn)行為
e.preventDefault()
return true
}
dragEnter和dragLeave
dragEnter 當(dāng)拖動(dòng)的元素或被選擇的文本進(jìn)入有效的放置目標(biāo)時(shí)觸發(fā) dragleave當(dāng)一個(gè)被拖動(dòng)的元素或者被選擇的文本離開一個(gè)有效的拖放目標(biāo)時(shí)觸發(fā)
這倆是一對 ,一個(gè)移入一個(gè)移出,值得注意的是dragEnter 發(fā)生在 dragLeave 之前 并且如果 移動(dòng)到子元素,這兩個(gè)事件會(huì)再次執(zhí)行,于是我們需要做特殊處理
// 保存最新的進(jìn)入節(jié)點(diǎn), 為了解決移動(dòng)到子元素,這兩個(gè)事件會(huì)再次執(zhí)問題
let lastenter = null
const dragEnter = (e) => {
lastenter = e.target;
console.log('進(jìn)入', props.model.id)
// 由于 dragEnter 發(fā)生在 dragLeave 之前,導(dǎo)致必須要使用定時(shí)器做一個(gè)延時(shí)
setTimeout(() => {
if (isFolder.value) {
expanded.value = true
isDragFile.value = true
} else {
emit('setDragFile', true)
}
isDragEnterNode.value = true
emit('setDragEnterNode', true)
});
}
const dragLeave = (e) => {
// 為了防止多次選中問題
if (lastenter == e.target) {
console.log('離開', props.model.id)
if (isFolder.value) {
isDragFile.value = false
} else {
emit('setDragFile', false)
}
emit('setDragEnterNode', false)
isDragEnterNode.value = false
}
}
drop
drop 當(dāng)一個(gè)元素或是選中的文字被拖拽釋放到一個(gè)有效的釋放目標(biāo)位置時(shí)觸發(fā)
這個(gè)就比較重要了,他承載著拖拽結(jié)束之后,向外拋出事件, 直到跑到最外層
const drop = (e) => {
isDragFile.value = false
isDragEnterNode.value = false
emit('setDragEnterNode', false)
emit('setDragFile', false)
// 為了獲取路徑需要判斷是不是文件夾,如果不是文件夾向上找
if (isFolder.value) {
emit('onDrop', props.model
)
} else {
if (props.model.pid) {
emit('setDragFolder')
} else {
emit('onDrop', props.model)
}
}
}
在當(dāng)前需求中,由于我們相當(dāng)于是拖拽到文件夾中, 在拖拽中做響應(yīng)的判斷,為了拿到正確的組件數(shù)據(jù)
舉個(gè)例子,我移動(dòng)到一個(gè)文件中,那么我就需要向上尋找,找到上級(jí)文件夾,再去拋出事件
所以我們有了emit('setDragFolder') 來找到上級(jí)文件夾,拋出事件
// 找到文件夾
const setDragFolder = () => {
emit('onDrop', props.model)
}
這里需要注意的是,由于是個(gè)遞歸組件,我們需要將事件層層拋出,于是就有了透傳事件
<treeList @on-click="(depth) => $emit('onClick', depth)" @change-name="(depth) => $emit('changeName', depth)"
@delete-node="(depth) => $emit('deleteNode', depth)" @add-node="(depth) => $emit('addNode', depth)"
@on-drop="(depth) => $emit('onDrop', depth)" @add-folder="(depth) => $emit('addFolder', depth)"
@dragStart="(depth) => $emit('dragStart', depth)" @setDragEnterNode="setDragEnterNode"
@setDragFile="setDragFile" @setDragFolder="setDragFolder" v-for="newmodel in model.children"
:selected="selected" :model="newmodel" :key="newmodel.id">
</treeList>
那有人問了,為了不用v-bind='$attrs'來做透傳啊,這個(gè)招我也試過,但是不靈啊,官方還未解決issues
支持展開收起
支持展開收起,就比較簡單了 只需要根據(jù)之前isFolder 判斷是否是文件夾
// 是否展開
const expanded = ref(true)
// 是否是文件夾
const isFolder = computed(() => {
return props.model.isFolder
})
// 展開收起
const toggle = () => {
if (isFolder.value) {
expanded.value = !expanded.value
} else {
emit('onClick', {
...props.model
})
}
}
支持目錄名修改
這個(gè)就很簡單了通過v-if控制 input 是否顯示
<span class="vtl-node-content ellipsis" v-if="!editable && !model.isAdd">
{{ model.title }}
</span>
<input v-else class="vtl-input" type="text" ref="nodeInput" v-model="model.title"
@blur="setUnEditable" />
// 修改目錄名字
const setUnEditable = (e) => {
editable.value = false
props.model.title = e.target.value
emit('changeName', {
id: props.model.id,
pid: props.model.pid,
isAdd: props.model.isAdd,
newName: e.target.value,
eventType: 'blur',
isFolder: isFolder.value
})
}
目錄支持增刪改查
支持增刪改查,本質(zhì)上就是四個(gè)方法來來對元數(shù)據(jù)做修改,并且拋出事件
<div class="vtl-operation" v-show="isHover && !editable && !model.isAdd">
<span @click.stop.prevent="addChildFolder" v-if="isFolder">
<slot name="operation" type="addFolder"></slot>
</span>
<span @click.stop.prevent="addChildDocument" v-if="isFolder">
<slot name="operation" type="addDocument"></slot>
</span>
<span @click.stop.prevent="setEditable">
<slot name="operation" type="Editable"></slot>
</span>
<span @click.stop.prevent="delNode">
<slot name="operation" type="deleteNode"></slot>
</span>
</div>
// 刪除目錄
const delNode = () => {
emit('deleteNode', {
...props.model,
eventType: 'delete',
})
}
// 編輯目錄名字
const setEditable = () => {
editable.value = true
}
// 修改目錄名字
const setUnEditable = (e) => {
editable.value = false
props.model.title = e.target.value
emit('changeName', {
id: props.model.id,
pid: props.model.pid,
isAdd: props.model.isAdd,
newName: e.target.value,
eventType: 'blur',
isFolder: isFolder.value
})
}
// 添加目錄
const addChildFolder = () => {
emit('addFolder', {
id: props.model.id,
isFolder: true
})
}
// 添加文件
const addChildDocument = (node) => {
emit('addNode', {
id: props.model.id,
isFolder: false
})
}
支持名字重復(fù)驗(yàn)證
支持驗(yàn)證重復(fù),其實(shí)也很簡單,就是根據(jù) fileNameArr 字段來判斷
fileNameArr: ['src', 'dist', 'package.json', 'README.md'],
//判斷是否重復(fù)
if (props.model.fileNameArr.includes(e.target.value)) {
ElMessage({
message: isFolder.value ? `目錄名重復(fù)` : '文件名重復(fù)',
type: 'warning',
})
}
ok,設(shè)計(jì)一個(gè)插件的方方面面 以及實(shí)現(xiàn)思路都給您說到了
您如果有面試,只需要拿著我這個(gè)話術(shù),包您過關(guān)
總體就是思路就是按照當(dāng)前這個(gè)需求指定幾個(gè)實(shí)現(xiàn)方式,并且列出其中的難點(diǎn),設(shè)計(jì)好傳入值以及事件,就完事!
源碼
以上就是VUE3+TS遞歸組件實(shí)現(xiàn)TreeList設(shè)計(jì)實(shí)例詳解的詳細(xì)內(nèi)容,更多關(guān)于VUE3 TS遞歸TreeList的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Vue監(jiān)聽使用方法和過濾器實(shí)現(xiàn)
這篇文章主要介紹了Vue監(jiān)聽使用方法和過濾器實(shí)現(xiàn),過濾器為頁面中數(shù)據(jù)進(jìn)行強(qiáng)化,具有局部過濾器和全局過濾器2022-06-06
詳解如何使用vue實(shí)現(xiàn)可視化界面設(shè)計(jì)
Vue是一款流行的前端開發(fā)框架,它的響應(yīng)式數(shù)據(jù)綁定和組件化特性使得它成為了可視化界面設(shè)計(jì)的一個(gè)理想選擇,本文將介紹如何使用Vue實(shí)現(xiàn)可視化界面設(shè)計(jì),并且演示一個(gè)基于Vue的可視化界面設(shè)計(jì)案例,需要的朋友可以參考下2023-12-12
Vue實(shí)現(xiàn)動(dòng)態(tài)圓環(huán)百分比進(jìn)度條
這篇文章主要為大家詳細(xì)介紹了Vue實(shí)現(xiàn)動(dòng)態(tài)圓環(huán)百分比進(jìn)度條,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09
vue實(shí)現(xiàn)大轉(zhuǎn)盤抽獎(jiǎng)功能
這篇文章主要為大家詳細(xì)介紹了vue實(shí)現(xiàn)大轉(zhuǎn)盤抽獎(jiǎng)功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03
vue+element+oss實(shí)現(xiàn)前端分片上傳和斷點(diǎn)續(xù)傳
這篇文章主要介紹了vue+element+oss實(shí)現(xiàn)前端分片上傳和斷點(diǎn)續(xù)傳,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03
詳解vue beforeRouteEnter 異步獲取數(shù)據(jù)給實(shí)例問題
這篇文章主要介紹了vue beforeRouteEnter 異步獲取數(shù)據(jù)給實(shí)例問題,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08
Vue實(shí)現(xiàn)動(dòng)態(tài)顯示表單項(xiàng)填寫進(jìn)度功能
這篇文章主要介紹了Vue實(shí)現(xiàn)動(dòng)態(tài)顯示表單項(xiàng)填寫進(jìn)度功能,此功能可以幫助用戶了解表單填寫的進(jìn)度和當(dāng)前狀態(tài),提高用戶體驗(yàn),通常實(shí)現(xiàn)的方式是在表單中添加進(jìn)度條,根據(jù)用戶填寫狀態(tài)動(dòng)態(tài)更新進(jìn)度條,感興趣的同學(xué)可以參考下文2023-05-05
Vue 2.0學(xué)習(xí)筆記之使用$refs訪問Vue中的DOM
這篇文章主要介紹了Vue 2.0學(xué)習(xí)筆記之使用$refs訪問Vue中的DOM,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-12-12

