1,前言


最近这段时间在做一个新的模块,其中有一个三层的树结构,产品经理提出了一个很古怪的需求,整的我只能自己控制树的交互,写完之后,感觉对这个组件的用法有了不一样的了解,故而写下来。

2,需求


  • 如果上级节点勾选了,则底下所有节点也勾选
  • 如果是一个个勾选子级节点,直至勾选满所有子级,则该父级节点不能勾选,只能算选中状态
  • 已勾选的节点不能展开,如果是展开了再勾选的,要自动收缩回去

遇见问题:

问题1:后端数据不友好,无唯一key值(有重复key),导致Tree组件无唯一的key

问题2:后端数据不友好,第一层第二层的字段和第三层的字段不一致(第一层字段是dept_id,子集字段是children,第二层子集字段是porjs,第三层字段又是porj_id

问题3:不能使用check-strictly,也就是Tree组件自带的父子关联,只能手动控制checkbox的选中状态

问题4:提交给后端的数据,如果一级二级节点被勾选,则不用传递其下层结构,如果不是被勾选,则需要传递其下层结构

如图:



不过还好这个树结构只有三层,办法还是有的。(如果是未知层级就难了)

3,解决思路


问题1:无唯一key

这个好办,接口请求到数据之后,深拷贝一份,遍历一下,给id手动添加字符来使它们变成唯一的,最后提交的时候去掉前面添加的字符

// 将所有id根据层级加上壹,贰,叁
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)
}
}
// 将数据的key恢复为原来的
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:第一层第二层的字段和第三层的字段不一致

这个也好办,最好的办法是后端调整成一样的,但是如果碰见博主这样的无法沟通的后端,只能前端自己转换字段了,这里采用的是forEach遍历,然后使用map替换对象键名。

// 将树数据的projs字段和proj_id和proj_name改名
handleChangeKey(data) {
try {
const tree = data
tree.forEach(item => {
if (item.children) {
const arr = item.children
// 将projs字段转为children
item.children = arr.map(item1 => {
if (item1.projs.length > 0) {
const obj = item1.projs
const parent_id = item1.dept_id
// 将proj_id字段转为dept_id 将proj_name字段转为dept_name
// 并添加depth深度和父节点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

这个就比较繁琐了,不能使用Tree自带的勾选父子关联(原因看需求2),只能自己手写一二三级节点的勾选逻辑。这样的话,二级和三级节点需要有个parent_id字段,也就是其父级的id,且有一个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属性,设置check-strictlytrue,利用@check-change监听节点勾选,利用@node-expand监听节点展开收起,设置node-key为每个节点的id

思路是:通过@check-change的时间回调,拿到第一个参数data,这个data里包含该节点的数据,通过这个数据可以拿到depth判断他是第几层节点,还可以拿到parent_id找到它的上级节点。根据这个区分一二三级节点,然后通过获取到的id,使用this.$refs.tree.getNode(id)可以获取到节点Node。设置节点Nodecheckedtrue,则该节点会变成勾选状态。设置它的indeterminatetrue,则会变成选中状态,设置expandedtrue,则是展开状态。也可以通过this.$refs.tree.setChecked(id, true)来设置选中。

问题4:提交给后端的数据

这个就是坑了,需要先把之前改变的key变回去,还有子级的键名改回去,然后根据是勾选还是只是单纯的选中来拼接数据。在这里用到了getCheckedNodes来获取目前被选中的节点所组成的数组,也用到了getHalfCheckedNodes获取半选中的节点所组成的数组。

4,完整代码


export default {
// 将树数据的projs字段和proj_id和proj_name改名
handleChangeKey(data) {
try {
const tree = data
tree.forEach(item => {
if (item.children) {
const arr = item.children
// 将projs字段转为children
item.children = arr.map(item1 => {
if (item1.projs.length > 0) {
const obj = item1.projs
const parent_id = item1.dept_id
// 将proj_id字段转为dept_id 将proj_name字段转为dept_name
// 并添加depth深度和父节点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根据层级加上壹,贰,叁
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)
}
},
/**
* 树的选中状态发生变化时
* @param {Object} data 该节点的数据
* @param {Object} on 节点本身是否被选中
* @param {Object} child 节点的子树中是否有被选中的节点
*/
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)
}
},
/**
* 一级节点处理
* @param {Boolean} on 是否被选中
* @param {Object} data 当前节点的数据
*/
handleOneNode(on, data) {
try {
const tree = this.$refs.tree
// 如果当前节点未被选中且为半选状态
const node = tree.getNode(data.dept_id)
if (node.indeterminate && !node.checked) return
// 如果当前节点被选中则不能展开
if (node.checked && node.expanded) node.expanded = false
// 勾选所有下级
let arr = []
if (data.children.length > 0) {
data.children.forEach(item => {
// 筛选出所有的下级key
arr.push(item.dept_id)
if (item.children.length > 0) {
item.children.forEach(child => {
// 筛选出所有的下下级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)
}
},
/**
* 二级节点处理
* @param {Boolean} on 是否被选中
* @param {Object} data 当前节点的数据
*/
handleTwoNode(on, data) {
try {
const tree = this.$refs.tree
const node = tree.getNode(data.dept_id)
// 如果当前是半选
if (node.indeterminate && !node.checked) return
// 如果当前节点被选中则不能展开
if (node.checked && node.expanded) node.expanded = false
// 上级节点
const parentNode = tree.getNode(data.parent_id)
// 勾选所有下级
let arr = []
if (data.children.length > 0) {
data.children.forEach(item => {
// 筛选出所有的下级key
arr.push(item.dept_id)
})
}
// 选中or取消
if (on) {
arr.forEach(dept => {
tree.setChecked(dept, true)
})
// 如果上级节点不是被勾选则让上级节点半勾选
if (!parentNode.checked) {
parentNode.indeterminate = true
}
} else {
// 先取消所有下级勾选
arr.forEach(dept => {
tree.setChecked(dept, false)
})
// 如果上级节点被勾选则让上级节点半勾选
if (parentNode.checked) {
parentNode.indeterminate = true
// 如果上级是半选,则循环判断下级是否还存在勾选的,来决定上级是否需要去掉半选
} else if (parentNode.indeterminate) {
const parentData = parentNode.data || []
let bool = true
const children = parentData.children
const childArr = []
// 筛选出所有兄弟节点的key
if (children && children.length > 0) {
children.forEach(childItem => {
childArr.push(childItem.dept_id)
})
}
// 循环判断
if (childArr.length > 0) {
for (let i of childArr) {
let thisNode = tree.getNode(i)
// 如果有一个是勾选或者半选
if (thisNode.checked || thisNode.indeterminate) {
bool = false
}
}
}
if (bool) {
parentNode.indeterminate = false
}
}
}
} catch (error) {
console.warn(error)
}
},
/**
* 三级节点处理
* @param {Boolean} on 是否被选中
* @param {Object} data 当前节点的数据
*/
handleThreeNode(on, data) {
try {
// 1,如果勾选了,上级节点没选,则把上级节点和上上级改为半选
// 2,如果取消了,上级节点如果是勾选,则把上级节点和上上级改为半选
const tree = this.$refs.tree
// 上级节点
console.log(data)
const parentNode = tree.getNode(data.parent_id)
const forefathersKey = parentNode.data.parent_id
// 祖先节点
console.log(parentNode)
console.log(forefathersKey)
const forefathersNode = tree.getNode(forefathersKey)
console.log(forefathersNode)
// 如果当前节点被勾选
if (on) {
// 如果上级节点未被勾选,则让他半选
if (!parentNode.checked) {
parentNode.indeterminate = true
}
// 如果祖先节点未被勾选,则让他半选
if (!forefathersNode.checked) {
forefathersNode.indeterminate = true
}
// 如果当前节点是被取消勾选
} else {
const parentArr = []
const forefathersArr = []
const parentData = parentNode.data
const forefathersData = forefathersNode.data
let parentBool = true
let forefathersBool = true
// 筛选出所有兄弟key,如果有勾选的则代表上级不需要去除勾选
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则代表没有三级节点被勾选,此时上级去除勾选
if (parentBool) {
parentNode.checked = false
parentNode.indeterminate = false
} else {
parentNode.indeterminate = true
}
// 筛选出所有上级的兄弟key,如果有勾选的则代表上级不需要去除勾选
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)
}
},
/**
* 树被展开时
* @param {Object} data 该节点的数据
* @param {Object} node 节点对应的Node
* @param {Object} ref 节点组件
*/
handleTreeOpen(data, node) {
// 如果节点被选中则不让展开
if (node.checked) {
Tip.warn('当前层级已被全选,无法展开!')
node.expanded = false
}
},
// 拼接出需要的树数据
handleJoinTree() {
try {
const tree = this.$refs.tree
const treeList = _.cloneDeep(this.treeList)
// 被选中的节点
const onItem = tree.getCheckedNodes()
// 半选中的节点
const halfItem = tree.getHalfCheckedNodes()
const oneArr = []
const twoArr = []
const threeArr = []
const oneArr_ = []
const twoArr_ = []
const threeArr_ = []
// 节点分层
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_)
// 将第二层拼进第一层
oneList.forEach(item => {
twoList.forEach(item2 => {
if (item2.parent_id === item.dept_id) {
if (!item.isOn) {
item.children.push(item2)
}
}
})
})
// 将第三层拼进第二层
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 {
// 找出第一层节点
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)
}
},
// 将数据的key恢复为原来的
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字段转为proj_id将dept_name字段转为proj_name,将children转为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)
}
},
// 详情设置树勾选
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)
}
}
}

获取转换后的结构:

this.treeList = this.handleChangeKey(data)

提交转换后的结构:

const treeList = this.handleRestoreKey()

5,总结


如果你有用到Tree组件,且产品出的需求不咋地,可以看看Tree常用这些方法技巧;

  • 获取指定ID的节点:this.$refs.tree.getNode(id)

  • 返回目前半选中的节点所组成的数组:this.$refs.tree.getHalfCheckedNodes()

  • 返回目前被选中的节点所组成的数组:this.$refs.tree.getCheckedNodes()

  • 通过 key / data 设置某个节点的勾选状态:this.$refs.tree.setChecked(id, true)


如果看了觉得有帮助的,我是@鹏多多,欢迎 点赞 关注 评论;END


PS:在本页按F12,在console中输入document.querySelectorAll('.diggit')[0].click(),有惊喜哦


公众号

往期文章

个人主页

element-ui的Tree树组件使用技巧的更多相关文章

  1. Tree( 树) 组件[4]

    本节课重点了解 EasyUI 中 Tree(树)组件的使用方法, 这个组件依赖于 Draggable(拖动)和 Droppable(放置)组件.一.方法列表 //部分方法onClick : funct ...

  2. Tree( 树) 组件[3]

    本节课重点了解 EasyUI 中 Tree(树)组件的使用方法, 这个组件依赖于 Draggable(拖动)和 Droppable(放置)组件.一. 事件列表很多事件的回调函数都包含'node'参数, ...

  3. Tree( 树) 组件[2]

    本节课重点了解 EasyUI 中 Tree(树)组件的使用方法, 这个组件依赖于 Draggable(拖动)和 Droppable(放置)组件.一. 异步加载如果想从数据库里获取导航内容, 那么就必须 ...

  4. Tree( 树) 组件[1]

    本节课重点了解 EasyUI 中 Tree(树)组件的使用方法, 这个组件依赖于 Draggable(拖动)和 Droppable(放置)组件. 一. 加载方式//class 加载方式<ul c ...

  5. 第二百二十六节,jQuery EasyUI,Tree(树)组件

    jQuery EasyUI,Tree(树)组件 本节课重点了解 EasyUI 中 Tree(树)组件的使用方法,这个组件依赖于 Draggable(拖 动)和 Droppable(放置)组件. 一.加 ...

  6. JQuery Easy Ui (Tree树)详解(转)

    第一讲:JQuery Easy Ui到底是什么呢? 首先咱们知道JQuery是对Java Script的封装,是一个js库,主要提供的功能是选择器,属性修改和事件绑定等等.. JQuery ui是在j ...

  7. Element ui 上传文件组件(单文件上传) 点击提交 没反应

    element ui 第一次上传文件后 上传其他文件再次点击不再次提交 需要使用 clearFiles 清空已上传文件列表 这时候在次点击 上传按钮 就会惊喜的发现 可以上传了使用方法 this.$r ...

  8. EasyUI - Tree 树组件

    效果: 数据库设计: 使用的数据: 其中的字段,是跟据要生成的树节点的属性定义的. text:代表要显示的字段名称. state:是否是目录节点. iconCls:节点的图标是什么. url:跳转的链 ...

  9. 基于element ui的级联选择器组件实现的分类后台接口

    今天在做资产管理系统的时候遇到一个分类的级联选择器,前端是用的element的组件,需要后台提供接口支持.     这个组件需要传入的数据结构大概是这样的,详细的可参考官方案例: [{ value: ...

随机推荐

  1. 30 个极大提高开发效率超级实用的 VSCode 插件

    Visual Studio Code 的插件对于在提升编程效率和加快工作速度非常重要.这里有 30 个最受欢迎的 VSCode 插件,它们将使你成为更高效的搬砖摸鱼大师.这些插件主要适用于前端开发人员 ...

  2. Are You OK?主键、聚集索引、辅助索引

    每张表都一定存在主键吗? 关于这个问题,各位小伙伴们不妨先自己想一想,再往下寻找答案. 首先公布结论:对于 InnoDB 存储引擎来说,每张表都一定有个主键(Primary Key)! 让人非常遗憾的 ...

  3. NOIP 模拟 $18\; \rm 导弹袭击$

    题解 \(by\;zj\varphi\) 一道凸包题 对于每个导弹,它的飞行时间就是 \(tim=\frac{A}{a_i}+\frac{B}{b_i}\) 我们设 \(x=\frac{1}{a_i} ...

  4. Android系统编程入门系列之广播接收者BroadcastReceiver实现进程间通信

    在前边几篇关于Android系统两个重要组件的介绍中,界面Activity负责应用程序与用户的交互,服务Service负责应用程序内部线程间的交互或两个应用程序进程之间的数据交互.看上去这两大组件就能 ...

  5. kubebuilder实战之四:operator需求说明和设计

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  6. 刷题-力扣-剑指 Offer II 055. 二叉搜索树迭代器

    剑指 Offer II 055. 二叉搜索树迭代器 题目链接 来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/kTOapQ 著作权归领扣网络所有 ...

  7. jdbc操作mysql(三):利用注解封装

    案例五:利用注解封装 重复步骤 我们使用jdbc操作mysql时发现,操作不同表中数据,所写的方法基本相同:比如我们根据id向用户表添加数据,根据id删除商品表的数据,或者查询所有数据并用list集合 ...

  8. vue中的v-cloak指令

    v-cloak不需要表达式,它会在vue实例结束编译时从绑定的html元素上移除,经常和display:none;配合使用: <div id="app" v-cloak> ...

  9. 常用cron表达式

    0 0 10,14,16 * * ? 每天上午10点,下午2点,4点 0 0/30 9-17 * * ? 朝九晚五工作时间内每半小时 0 0 12 ? * WED 表示每个星期三中午12点 " ...

  10. 剑指 Offer 32 - I. 从上到下打印二叉树

    剑指 Offer 32 - I. 从上到下打印二叉树 从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印. 例如: 给定二叉树: [3,9,20,null,null,15,7], 3 ...