1,前言
最近這段時(shí)間在做一個(gè)新的模塊钢拧,其中有一個(gè)三層的樹結(jié)構(gòu),產(chǎn)品經(jīng)理提出了一個(gè)很古怪的需求炕横,整的我只能自己控制樹的交互源内,寫完之后,感覺對(duì)這個(gè)組件的用法有了不一樣的了解份殿,故而寫下來膜钓。
2,需求
- 如果上級(jí)節(jié)點(diǎn)勾選了卿嘲,則底下所有節(jié)點(diǎn)也勾選
- 如果是一個(gè)個(gè)勾選子級(jí)節(jié)點(diǎn)颂斜,直至勾選滿所有子級(jí),則該父級(jí)節(jié)點(diǎn)不能勾選拾枣,只能算選中狀態(tài)
- 已勾選的節(jié)點(diǎn)不能展開沃疮,如果是展開了再勾選的盒让,要自動(dòng)收縮回去
遇見問題:
問題1:后端數(shù)據(jù)不友好,無唯一key
值(有重復(fù)key
)司蔬,導(dǎo)致Tree
組件無唯一的key
問題2:后端數(shù)據(jù)不友好邑茄,第一層第二層的字段和第三層的字段不一致(第一層字段是dept_id
,子集字段是children
俊啼,第二層子集字段是porjs
肺缕,第三層字段又是porj_id
)
問題3:不能使用check-strictly
,也就是Tree
組件自帶的父子關(guān)聯(lián)吨些,只能手動(dòng)控制checkbox
的選中狀態(tài)
問題4:提交給后端的數(shù)據(jù)搓谆,如果一級(jí)二級(jí)節(jié)點(diǎn)被勾選炒辉,則不用傳遞其下層結(jié)構(gòu)豪墅,如果不是被勾選,則需要傳遞其下層結(jié)構(gòu)
如圖:
不過還好這個(gè)樹結(jié)構(gòu)只有三層黔寇,辦法還是有的偶器。(如果是未知層級(jí)就難了)
3,解決思路
問題1:無唯一key
值
這個(gè)好辦缝裤,接口請(qǐng)求到數(shù)據(jù)之后屏轰,深拷貝一份,遍歷一下憋飞,給id
手動(dòng)添加字符來使它們變成唯一的霎苗,最后提交的時(shí)候去掉前面添加的字符
// 將所有id根據(jù)層級(jí)加上壹,貳榛做,叁
handlePushLabel(data) {
try {
data.forEach(item1 => {
item1.dept_id += '壹'
if (item1.children && item1.children.length > 0) {
item1.children.forEach(item2 => {
item2.dept_id += '貳'
item2.parent_id += '壹'
if (item2.children.length > 0) {
item2.children.forEach(item3 => {
item3.dept_id += '叁'
item3.parent_id += '貳'
})
}
})
}
})
return data
} catch (error) {
console.warn(error)
}
}
// 將數(shù)據(jù)的key恢復(fù)為原來的
treeList.forEach(item1 => {
item1.dept_id = item1.dept_id.replace('壹', '')
if (item1.children.length > 0) {
item1.children.forEach(item2 => {
item2.dept_id = item2.dept_id.replace('貳', '')
item2.parent_id = item2.parent_id.replace('壹', '')
if (item2.children.length > 0) {
item2.children.forEach(item3 => {
item3.dept_id = item3.dept_id.replace('叁', '')
item3.parent_id = item3.parent_id.replace('貳', '')
})
}
})
}
})
問題2:第一層第二層的字段和第三層的字段不一致
這個(gè)也好辦唁盏,最好的辦法是后端調(diào)整成一樣的,但是如果碰見博主這樣的無法溝通的后端检眯,只能前端自己轉(zhuǎn)換字段了厘擂,這里采用的是forEach
遍歷,然后使用map
替換對(duì)象鍵名锰瘸。
// 將樹數(shù)據(jù)的projs字段和proj_id和proj_name改名
handleChangeKey(data) {
try {
const tree = data
tree.forEach(item => {
if (item.children) {
const arr = item.children
// 將projs字段轉(zhuǎn)為children
item.children = arr.map(item1 => {
if (item1.projs.length > 0) {
const obj = item1.projs
const parent_id = item1.dept_id
// 將proj_id字段轉(zhuǎn)為dept_id 將proj_name字段轉(zhuǎn)為dept_name
// 并添加depth深度和父節(jié)點(diǎn)id
item1.projs = obj.map(item2 => {
return {
dept_id: item2.proj_id,
dept_name: item2.proj_name,
depth: 3,
parent_id
}
})
}
return {
dept_id: item1.dept_id,
dept_name: item1.dept_name,
depth: item1.depth,
parent_id: item1.parent_id,
children: item1.projs
}
})
}
})
return this.handlePushLabel(tree)
} catch (error) {
console.warn(error)
}
}
問題3:不能使用check-strictly
這個(gè)就比較繁瑣了刽严,不能使用Tree
自帶的勾選父子關(guān)聯(lián)(原因看需求2),只能自己手寫一二三級(jí)節(jié)點(diǎn)的勾選邏輯避凝。這樣的話舞萄,二級(jí)和三級(jí)節(jié)點(diǎn)需要有個(gè)parent_id
字段,也就是其父級(jí)的id
管削,且有一個(gè)depth
字段鹏氧,代表其深度1,2佩谣,3
把还。
<el-tree
@check-change="handleTreeClick"
:data="treeList"
show-checkbox
:default-expand-all="false"
:check-strictly="true"
@node-expand="handleTreeOpen"
node-key="dept_id"
ref="tree"
highlight-current
:props="defaultProps"
/>
給Tree
組件加上ref
屬性,設(shè)置check-strictly
為true
,利用@check-change
監(jiān)聽節(jié)點(diǎn)勾選吊履,利用@node-expand
監(jiān)聽節(jié)點(diǎn)展開收起安皱,設(shè)置node-key
為每個(gè)節(jié)點(diǎn)的id
。
思路是:通過@check-change
的回調(diào)艇炎,拿到第一個(gè)參數(shù)data
酌伊,這個(gè)data
里包含該節(jié)點(diǎn)的數(shù)據(jù),通過這個(gè)數(shù)據(jù)可以拿到depth
判斷他是第幾層節(jié)點(diǎn)缀踪,還可以拿到parent_id
找到它的上級(jí)節(jié)點(diǎn)居砖。根據(jù)這個(gè)區(qū)分一二三級(jí)節(jié)點(diǎn),然后通過獲取到的id驴娃,使用this.$refs.tree.getNode(id)
可以獲取到節(jié)點(diǎn)Node
奏候。設(shè)置節(jié)點(diǎn)Node
的checked
為true
,則該節(jié)點(diǎn)會(huì)變成勾選狀態(tài)唇敞。設(shè)置它的indeterminate
為true
蔗草,則會(huì)變成選中狀態(tài),設(shè)置expanded
為true
疆柔,則是展開狀態(tài)咒精。也可以通過this.$refs.tree.setChecked(id, true)
來設(shè)置選中。
問題4:提交給后端的數(shù)據(jù)
這個(gè)就是坑了旷档,需要先把之前改變的key
變回去模叙,還有子級(jí)的鍵名改回去,然后根據(jù)是勾選還是只是單純的選中來拼接數(shù)據(jù)鞋屈。在這里用到了getCheckedNodes
來獲取目前被選中的節(jié)點(diǎn)所組成的數(shù)組范咨,也用到了getHalfCheckedNodes
獲取半選中的節(jié)點(diǎn)所組成的數(shù)組。
4谐区,完整代碼
export default {
// 將樹數(shù)據(jù)的projs字段和proj_id和proj_name改名
handleChangeKey(data) {
try {
const tree = data
tree.forEach(item => {
if (item.children) {
const arr = item.children
// 將projs字段轉(zhuǎn)為children
item.children = arr.map(item1 => {
if (item1.projs.length > 0) {
const obj = item1.projs
const parent_id = item1.dept_id
// 將proj_id字段轉(zhuǎn)為dept_id 將proj_name字段轉(zhuǎn)為dept_name
// 并添加depth深度和父節(jié)點(diǎn)id
item1.projs = obj.map(item2 => {
return {
dept_id: item2.proj_id,
dept_name: item2.proj_name,
depth: 3,
parent_id
}
})
}
return {
dept_id: item1.dept_id,
dept_name: item1.dept_name,
depth: item1.depth,
parent_id: item1.parent_id,
children: item1.projs
}
})
}
})
return this.handlePushLabel(tree)
} catch (error) {
console.warn(error)
}
},
// 將所有id根據(jù)層級(jí)加上壹湖蜕,貳,叁
handlePushLabel(data) {
try {
data.forEach(item1 => {
item1.dept_id += '壹'
if (item1.children && item1.children.length > 0) {
item1.children.forEach(item2 => {
item2.dept_id += '貳'
item2.parent_id += '壹'
if (item2.children.length > 0) {
item2.children.forEach(item3 => {
item3.dept_id += '叁'
item3.parent_id += '貳'
})
}
})
}
})
return data
} catch (error) {
console.warn(error)
}
},
/**
* 樹的選中狀態(tài)發(fā)生變化時(shí)
* @param {Object} data 該節(jié)點(diǎn)的數(shù)據(jù)
* @param {Object} on 節(jié)點(diǎn)本身是否被選中
* @param {Object} child 節(jié)點(diǎn)的子樹中是否有被選中的節(jié)點(diǎn)
*/
handleTreeClick(data, on, child) {
try {
this.form.tree = data
if (data.depth === 1) {
this.handleOneNode(on, data)
} else if (data.depth === 2) {
this.handleTwoNode(on, data)
} else if (data.depth === 3) {
this.handleThreeNode(on, data)
}
} catch (error) {
console.warn(error)
}
},
/**
* 一級(jí)節(jié)點(diǎn)處理
* @param {Boolean} on 是否被選中
* @param {Object} data 當(dāng)前節(jié)點(diǎn)的數(shù)據(jù)
*/
handleOneNode(on, data) {
try {
const tree = this.$refs.tree
// 如果當(dāng)前節(jié)點(diǎn)未被選中且為半選狀態(tài)
const node = tree.getNode(data.dept_id)
if (node.indeterminate && !node.checked) return
// 如果當(dāng)前節(jié)點(diǎn)被選中則不能展開
if (node.checked && node.expanded) node.expanded = false
// 勾選所有下級(jí)
let arr = []
if (data.children.length > 0) {
data.children.forEach(item => {
// 篩選出所有的下級(jí)key
arr.push(item.dept_id)
if (item.children.length > 0) {
item.children.forEach(child => {
// 篩選出所有的下下級(jí)key
arr.push(child.dept_id)
})
}
})
}
// 選中or取消
if (on) {
arr.forEach(dept => {
tree.setChecked(dept, true)
})
} else {
arr.forEach(dept => {
tree.setChecked(dept, false)
})
}
} catch (error) {
console.warn(error)
}
},
/**
* 二級(jí)節(jié)點(diǎn)處理
* @param {Boolean} on 是否被選中
* @param {Object} data 當(dāng)前節(jié)點(diǎn)的數(shù)據(jù)
*/
handleTwoNode(on, data) {
try {
const tree = this.$refs.tree
const node = tree.getNode(data.dept_id)
// 如果當(dāng)前是半選
if (node.indeterminate && !node.checked) return
// 如果當(dāng)前節(jié)點(diǎn)被選中則不能展開
if (node.checked && node.expanded) node.expanded = false
// 上級(jí)節(jié)點(diǎn)
const parentNode = tree.getNode(data.parent_id)
// 勾選所有下級(jí)
let arr = []
if (data.children.length > 0) {
data.children.forEach(item => {
// 篩選出所有的下級(jí)key
arr.push(item.dept_id)
})
}
// 選中or取消
if (on) {
arr.forEach(dept => {
tree.setChecked(dept, true)
})
// 如果上級(jí)節(jié)點(diǎn)不是被勾選則讓上級(jí)節(jié)點(diǎn)半勾選
if (!parentNode.checked) {
parentNode.indeterminate = true
}
} else {
// 先取消所有下級(jí)勾選
arr.forEach(dept => {
tree.setChecked(dept, false)
})
// 如果上級(jí)節(jié)點(diǎn)被勾選則讓上級(jí)節(jié)點(diǎn)半勾選
if (parentNode.checked) {
parentNode.indeterminate = true
// 如果上級(jí)是半選宋列,則循環(huán)判斷下級(jí)是否還存在勾選的昭抒,來決定上級(jí)是否需要去掉半選
} else if (parentNode.indeterminate) {
const parentData = parentNode.data || []
let bool = true
const children = parentData.children
const childArr = []
// 篩選出所有兄弟節(jié)點(diǎn)的key
if (children && children.length > 0) {
children.forEach(childItem => {
childArr.push(childItem.dept_id)
})
}
// 循環(huán)判斷
if (childArr.length > 0) {
for (let i of childArr) {
let thisNode = tree.getNode(i)
// 如果有一個(gè)是勾選或者半選
if (thisNode.checked || thisNode.indeterminate) {
bool = false
}
}
}
if (bool) {
parentNode.indeterminate = false
}
}
}
} catch (error) {
console.warn(error)
}
},
/**
* 三級(jí)節(jié)點(diǎn)處理
* @param {Boolean} on 是否被選中
* @param {Object} data 當(dāng)前節(jié)點(diǎn)的數(shù)據(jù)
*/
handleThreeNode(on, data) {
try {
// 1,如果勾選了炼杖,上級(jí)節(jié)點(diǎn)沒選灭返,則把上級(jí)節(jié)點(diǎn)和上上級(jí)改為半選
// 2,如果取消了坤邪,上級(jí)節(jié)點(diǎn)如果是勾選熙含,則把上級(jí)節(jié)點(diǎn)和上上級(jí)改為半選
const tree = this.$refs.tree
// 上級(jí)節(jié)點(diǎn)
console.log(data)
const parentNode = tree.getNode(data.parent_id)
const forefathersKey = parentNode.data.parent_id
// 祖先節(jié)點(diǎn)
console.log(parentNode)
console.log(forefathersKey)
const forefathersNode = tree.getNode(forefathersKey)
console.log(forefathersNode)
// 如果當(dāng)前節(jié)點(diǎn)被勾選
if (on) {
// 如果上級(jí)節(jié)點(diǎn)未被勾選,則讓他半選
if (!parentNode.checked) {
parentNode.indeterminate = true
}
// 如果祖先節(jié)點(diǎn)未被勾選艇纺,則讓他半選
if (!forefathersNode.checked) {
forefathersNode.indeterminate = true
}
// 如果當(dāng)前節(jié)點(diǎn)是被取消勾選
} else {
const parentArr = []
const forefathersArr = []
const parentData = parentNode.data
const forefathersData = forefathersNode.data
let parentBool = true
let forefathersBool = true
// 篩選出所有兄弟key怎静,如果有勾選的則代表上級(jí)不需要去除勾選
if (parentData.children.length > 0) {
parentData.children.forEach(parent => {
parentArr.push(parent.dept_id)
})
for (let i of parentArr) {
let thisNode = tree.getNode(i)
if (thisNode.checked) {
parentBool = false
}
}
}
// 為tree則代表沒有三級(jí)節(jié)點(diǎn)被勾選邮弹,此時(shí)上級(jí)去除勾選
if (parentBool) {
parentNode.checked = false
parentNode.indeterminate = false
} else {
parentNode.indeterminate = true
}
// 篩選出所有上級(jí)的兄弟key,如果有勾選的則代表上級(jí)不需要去除勾選
if (forefathersData.children.length > 0) {
forefathersData.children.forEach(parent => {
forefathersArr.push(parent.dept_id)
})
for (let i of forefathersArr) {
let thisNode = tree.getNode(i)
if (thisNode.checked || thisNode.indeterminate) {
forefathersBool = false
}
}
}
if (forefathersBool) {
forefathersNode.indeterminate = false
}
}
} catch (error) {
console.warn(error)
}
},
/**
* 樹被展開時(shí)
* @param {Object} data 該節(jié)點(diǎn)的數(shù)據(jù)
* @param {Object} node 節(jié)點(diǎn)對(duì)應(yīng)的Node
* @param {Object} ref 節(jié)點(diǎn)組件
*/
handleTreeOpen(data, node) {
// 如果節(jié)點(diǎn)被選中則不讓展開
if (node.checked) {
Tip.warn('當(dāng)前層級(jí)已被全選蚓聘,無法展開腌乡!')
node.expanded = false
}
},
// 拼接出需要的樹數(shù)據(jù)
handleJoinTree() {
try {
const tree = this.$refs.tree
const treeList = _.cloneDeep(this.treeList)
// 被選中的節(jié)點(diǎn)
const onItem = tree.getCheckedNodes()
// 半選中的節(jié)點(diǎn)
const halfItem = tree.getHalfCheckedNodes()
const oneArr = []
const twoArr = []
const threeArr = []
const oneArr_ = []
const twoArr_ = []
const threeArr_ = []
// 節(jié)點(diǎn)分層
if (onItem.length > 0) {
onItem.forEach(item => {
switch (item.depth) {
case 1:
oneArr.push(item.dept_id)
break
case 2:
twoArr.push(item.dept_id)
break
case 3:
threeArr.push(item.dept_id)
break
}
})
}
if (halfItem.length > 0) {
halfItem.forEach(item => {
switch (item.depth) {
case 1:
oneArr_.push(item.dept_id)
break
case 2:
twoArr_.push(item.dept_id)
break
case 3:
threeArr_.push(item.dept_id)
break
}
})
}
const oneList = this.handlejoinOne(treeList, oneArr, oneArr_)
const twoList = this.handlejoinTwo(treeList, twoArr, twoArr_)
const threeList = this.handlejoinThree(treeList, threeArr, threeArr_)
// 將第二層拼進(jìn)第一層
oneList.forEach(item => {
twoList.forEach(item2 => {
if (item2.parent_id === item.dept_id) {
if (!item.isOn) {
item.children.push(item2)
}
}
})
})
// 將第三層拼進(jìn)第二層
oneList.forEach(child1 => {
if (child1.children.length > 0) {
child1.children.forEach(child2 => {
threeList.forEach(child3 => {
if (child3.parent_id === child2.dept_id) {
if (!child2.isOn) {
child2.children.push(child3)
}
}
})
})
}
})
return oneList
} catch (error) {
console.warn(error)
}
},
// 返回第一層
handlejoinOne(treeList, oneArr, oneArr_) {
try {
// 找出第一層節(jié)點(diǎn)
const oneList = []
treeList.forEach(item => {
for (let i of oneArr) {
if (item.dept_id === i) {
oneList.push({
dept_id: item.dept_id,
children: [],
isOn: true,
name: item.dept_name
})
}
}
for (let i of oneArr_) {
if (item.dept_id === i) {
oneList.push({
dept_id: item.dept_id,
children: [],
isOn: false,
name: item.dept_name
})
}
}
})
return oneList
} catch (error) {
console.warn(error)
}
},
// 返回第二層
handlejoinTwo(treeList, twoArr, twoArr_) {
try {
const twoList = []
treeList.forEach(item => {
if (item.children.length > 0) {
item.children.forEach(item2 => {
for (let i of twoArr) {
if (item2.dept_id === i) {
twoList.push({
dept_id: item2.dept_id,
children: [],
isOn: true,
parent_id: item2.parent_id,
name: item2.dept_name
})
}
}
for (let i of twoArr_) {
if (item2.dept_id === i) {
twoList.push({
dept_id: item2.dept_id,
children: [],
isOn: false,
parent_id: item2.parent_id,
name: item2.dept_name
})
}
}
})
}
})
return twoList
} catch (error) {
console.warn(error)
}
},
// 返回第三層
handlejoinThree(treeList, threeArr, threeArr_) {
try {
const threeList = []
treeList.forEach(item => {
if (item.children.length > 0) {
item.children.forEach(item2 => {
if (item2.children.length > 0) {
item2.children.forEach(item3 => {
for (let i of threeArr) {
if (item3.dept_id === i) {
threeList.push({
dept_id: item3.dept_id,
isOn: true,
parent_id: item3.parent_id,
name: item3.dept_name
})
}
}
for (let i of threeArr_) {
if (item3.dept_id === i) {
threeList.push({
dept_id: item3.dept_id,
isOn: false,
parent_id: item3.parent_id,
name: item3.dept_name
})
}
}
})
}
})
}
})
return threeList
} catch (error) {
console.warn(error)
}
},
// 將數(shù)據(jù)的key恢復(fù)為原來的
handleRestoreKey() {
try {
const treeList = this.handleJoinTree()
// 去掉id后面的壹 貳 叁
treeList.forEach(item1 => {
item1.dept_id = item1.dept_id.replace('壹', '')
if (item1.children.length > 0) {
item1.children.forEach(item2 => {
item2.dept_id = item2.dept_id.replace('貳', '')
item2.parent_id = item2.parent_id.replace('壹', '')
if (item2.children.length > 0) {
item2.children.forEach(item3 => {
item3.dept_id = item3.dept_id.replace('叁', '')
item3.parent_id = item3.parent_id.replace('貳', '')
})
}
})
}
})
// 將dept_id字段轉(zhuǎn)為proj_id將dept_name字段轉(zhuǎn)為proj_name,將children轉(zhuǎn)為projs
treeList.forEach(child1 => {
if (child1.children.length > 0) {
const childObj = child1.children.map(item => {
let returnObj = {}
if (item.children.length > 0) {
const obj = item.children
obj.children = obj.map(child2 => {
return {
proj_id: child2.dept_id,
proj_name: child2.name
}
})
returnObj = {
dept_id: item.dept_id,
dept_name: item.name,
projs: obj.children
}
} else {
returnObj = {
projs: [],
dept_id: item.dept_id,
isOn: true,
name: item.name
}
}
return returnObj
})
child1.children = childObj
}
})
console.log(treeList)
return treeList
} catch (error) {
console.warn(error)
}
},
// 詳情設(shè)置樹勾選
handleSetTree(list) {
try {
console.log(list)
const one = []
const two = []
const three = []
if (list.length > 0) {
// 第一層
list.forEach(item => {
let child = item.children || ''
let obj = { id: item.dept_id + '壹', isOn: true }
if (child && child.length > 0) {
obj.isOn = false
}
one.push(obj)
})
// 第二層
list.forEach(item1 => {
let child1 = item1.children || ''
if (child1 && child1.length > 0) {
child1.forEach(item2 => {
let child2 = item2.projs || ''
let obj = { id: item2.dept_id + '貳', isOn: true }
if (child2 && child2.length > 0) {
obj.isOn = false
}
two.push(obj)
})
}
})
// 第二層
list.forEach(item1 => {
let child1 = item1.children || ''
if (child1 && child1.length > 0) {
child1.forEach(item2 => {
let child2 = item2.projs || ''
if (child2 && child2.length > 0) {
child2.forEach(item3 => {
let obj = { id: item3.proj_id + '叁', isOn: true }
three.push(obj)
})
}
})
}
})
const tree = this.$refs.tree
// 勾選第一層
if (one && one.length > 0) {
one.forEach(item => {
let node = tree.getNode(item.id)
if (item.isOn) {
node.checked = true
this.handleOneNode(true, node.data)
} else {
node.indeterminate = true
}
})
}
// 勾選第二層
if (two && two.length > 0) {
two.forEach(item => {
let node = tree.getNode(item.id)
if (item.isOn) {
node.checked = true
this.handleTwoNode(true, node.data)
} else {
node.indeterminate = true
}
})
}
// 勾選第三層
if (three && three.length > 0) {
three.forEach(item => {
let node = tree.getNode(item.id)
node.checked = true
})
}
}
} catch (error) {
console.warn(error)
}
}
}
獲取轉(zhuǎn)換后的結(jié)構(gòu):
this.treeList = this.handleChangeKey(data)
提交轉(zhuǎn)換后的結(jié)構(gòu):
const treeList = this.handleRestoreKey()
5夜牡,總結(jié)
如果你有用到Tree組件与纽,且產(chǎn)品出的需求不咋地,可以看看Tree常用這些方法技巧塘装;
獲取指定ID的節(jié)點(diǎn)
:this.$refs.tree.getNode(id)返回目前半選中的節(jié)點(diǎn)所組成的數(shù)組
:this.$refs.tree.getHalfCheckedNodes()返回目前被選中的節(jié)點(diǎn)所組成的數(shù)組
:this.$refs.tree.getCheckedNodes()通過 key / data 設(shè)置某個(gè)節(jié)點(diǎn)的勾選狀態(tài)
:this.$refs.tree.setChecked(id, true)
本次分享就到這兒啦急迂,我是@鵬多多,如果您看了覺得有幫助蹦肴,歡迎評(píng)論僚碎,關(guān)注,點(diǎn)贊冗尤,轉(zhuǎn)發(fā)听盖,我們下次見~
PS:在本頁按F12胀溺,在console中輸入document.querySelectorAll('._2VdqdF')[0].click()裂七,有驚喜哦
往期文章
- 超詳細(xì)!Vuex手把手教程
- 使用nvm管理node.js版本以及更換npm淘寶鏡像源
- 超詳細(xì)仓坞!Vue-Router手把手教程
- vue中利用.env文件存儲(chǔ)全局環(huán)境變量背零,以及配置vue啟動(dòng)和打包命令
- 微信小程序?qū)崿F(xiàn)搜索關(guān)鍵詞高亮
個(gè)人主頁