Compare commits
30 Commits
feature-ad
...
v1.4.6
Author | SHA1 | Date | |
---|---|---|---|
![]() |
5500a36492 | ||
![]() |
66bc19baaa | ||
![]() |
97daf67f9a | ||
![]() |
d1e12aad85 | ||
![]() |
d5dd9b88c9 | ||
![]() |
ccb1067713 | ||
![]() |
d5705f92e8 | ||
![]() |
ae494ce25f | ||
![]() |
c59015fdf7 | ||
![]() |
5f94ebf51b | ||
![]() |
53320cc235 | ||
![]() |
d73b4c1829 | ||
![]() |
15f33d187d | ||
![]() |
a67e39ce31 | ||
![]() |
69ffc1da0f | ||
![]() |
31c9225441 | ||
![]() |
64c56af961 | ||
![]() |
a792ee3910 | ||
![]() |
aa3359155f | ||
![]() |
c1270b880b | ||
![]() |
d8a5da1e0e | ||
![]() |
780d42c6ea | ||
![]() |
9c2d25e313 | ||
![]() |
fbd370e9e5 | ||
![]() |
332402dee6 | ||
![]() |
61ae848898 | ||
![]() |
82e87e493a | ||
![]() |
2427f47201 | ||
![]() |
fab494cedf | ||
![]() |
ad6ad1b255 |
29
.eslintrc
Normal file
29
.eslintrc
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"root": true,
|
||||||
|
"env": {
|
||||||
|
"node": true
|
||||||
|
},
|
||||||
|
"extends": [
|
||||||
|
"plugin:vue/essential",
|
||||||
|
"plugin:prettier/recommended",
|
||||||
|
"eslint:recommended"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"prettier/prettier": "error",
|
||||||
|
"no-console": "warn"
|
||||||
|
},
|
||||||
|
"parserOptions": {
|
||||||
|
"parser": "babel-eslint"
|
||||||
|
},
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": [
|
||||||
|
"**/__tests__/*.{j,t}s?(x)",
|
||||||
|
"**/tests/unit/**/*.spec.{j,t}s?(x)"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"jest": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@@ -1,4 +1,3 @@
|
|||||||
hooks:
|
hooks:
|
||||||
pre-commit:
|
pre-commit: npm run lint-staged
|
||||||
- npm run standard
|
|
||||||
commit-msg: commitlint -E HUSKY_GIT_PARAMS
|
commit-msg: commitlint -E HUSKY_GIT_PARAMS
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2019 ayou
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
@@ -1,5 +1,3 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
presets: [
|
presets: ['@vue/cli-plugin-babel/preset']
|
||||||
'@vue/cli-plugin-babel/preset'
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
81
dev/App.vue
81
dev/App.vue
@@ -12,27 +12,51 @@
|
|||||||
:model="data"
|
:model="data"
|
||||||
default-tree-node-name="new node"
|
default-tree-node-name="new node"
|
||||||
default-leaf-node-name="new leaf"
|
default-leaf-node-name="new leaf"
|
||||||
v-bind:default-expanded="false">
|
v-bind:default-expanded="false"
|
||||||
<span class="icon" slot="addTreeNodeIcon">📂</span>
|
>
|
||||||
<span class="icon" slot="addLeafNodeIcon">+</span>
|
<template v-slot:leafNameDisplay="slotProps">
|
||||||
<span class="icon" slot="editNodeIcon">📃</span>
|
<span>
|
||||||
<span class="icon" slot="delNodeIcon">✂️</span>
|
{{ slotProps.model.name }} <span class="muted">#{{ slotProps.model.id }}</span>
|
||||||
<span class="icon" slot="leafNodeIcon">🍃</span>
|
</span>
|
||||||
<span class="icon" slot="treeNodeIcon">🌲</span>
|
</template>
|
||||||
|
<template v-slot:addTreeNodeIcon="slotProps">
|
||||||
|
<span class="icon">📂</span>
|
||||||
|
</template>
|
||||||
|
<template v-slot:addLeafNodeIcon="slotProps">
|
||||||
|
<span class="icon">+</span>
|
||||||
|
</template>
|
||||||
|
<template v-slot:editNodeIcon="slotProps">
|
||||||
|
<span class="icon">📃</span>
|
||||||
|
</template>
|
||||||
|
<template v-slot:delNodeIcon="slotProps">
|
||||||
|
<span class="icon">✂️</span>
|
||||||
|
</template>
|
||||||
|
<template v-slot:leafNodeIcon="slotProps">
|
||||||
|
<span class="icon">🍃</span>
|
||||||
|
</template>
|
||||||
|
<template v-slot:treeNodeIcon="slotProps">
|
||||||
|
<span class="icon">
|
||||||
|
{{
|
||||||
|
slotProps.model.children && slotProps.model.children.length > 0 && !slotProps.expanded
|
||||||
|
? '🌲'
|
||||||
|
: ''
|
||||||
|
}}</span
|
||||||
|
>
|
||||||
|
</template>
|
||||||
</vue-tree-list>
|
</vue-tree-list>
|
||||||
<button @click="getNewTree">Get new tree</button>
|
<button @click="getNewTree">Get new tree</button>
|
||||||
<pre>
|
<pre>
|
||||||
{{newTree}}
|
{{ newTree }}
|
||||||
</pre>
|
</pre>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import { VueTreeList, Tree, TreeNode } from '../src'
|
import { VueTreeList, Tree, TreeNode } from '../src'
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
VueTreeList
|
VueTreeList
|
||||||
},
|
},
|
||||||
data () {
|
data() {
|
||||||
return {
|
return {
|
||||||
newTree: {},
|
newTree: {},
|
||||||
data: new Tree([
|
data: new Tree([
|
||||||
@@ -69,51 +93,51 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onDel (node) {
|
onDel(node) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(node)
|
console.log(node)
|
||||||
node.remove()
|
node.remove()
|
||||||
},
|
},
|
||||||
|
|
||||||
onChangeName (params) {
|
onChangeName(params) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(params)
|
console.log(params)
|
||||||
},
|
},
|
||||||
|
|
||||||
onAddNode (params) {
|
onAddNode(params) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(params)
|
console.log(params)
|
||||||
},
|
},
|
||||||
|
|
||||||
onClick (params) {
|
onClick(params) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(params)
|
console.log(params)
|
||||||
},
|
},
|
||||||
|
|
||||||
drop: function ({node, src, target}) {
|
drop: function({ node, src, target }) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log('drop', node, src, target)
|
console.log('drop', node, src, target)
|
||||||
},
|
},
|
||||||
|
|
||||||
dropBefore: function ({node, src, target}) {
|
dropBefore: function({ node, src, target }) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log('drop-before', node, src, target)
|
console.log('drop-before', node, src, target)
|
||||||
},
|
},
|
||||||
|
|
||||||
dropAfter: function ({node, src, target}) {
|
dropAfter: function({ node, src, target }) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log('drop-after', node, src, target)
|
console.log('drop-after', node, src, target)
|
||||||
},
|
},
|
||||||
|
|
||||||
addNode () {
|
addNode() {
|
||||||
var node = new TreeNode({ name: 'new node', isLeaf: false })
|
var node = new TreeNode({ name: 'new node', isLeaf: false })
|
||||||
if (!this.data.children) this.data.children = []
|
if (!this.data.children) this.data.children = []
|
||||||
this.data.addChildren(node)
|
this.data.addChildren(node)
|
||||||
},
|
},
|
||||||
|
|
||||||
getNewTree () {
|
getNewTree() {
|
||||||
var vm = this
|
var vm = this
|
||||||
function _dfs (oldNode) {
|
function _dfs(oldNode) {
|
||||||
var newNode = {}
|
var newNode = {}
|
||||||
|
|
||||||
for (var k in oldNode) {
|
for (var k in oldNode) {
|
||||||
@@ -134,10 +158,10 @@
|
|||||||
vm.newTree = _dfs(vm.data)
|
vm.newTree = _dfs(vm.data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="less" rel="stylesheet/less">
|
<style lang="less" rel="stylesheet/less">
|
||||||
.vtl {
|
.vtl {
|
||||||
.vtl-drag-disabled {
|
.vtl-drag-disabled {
|
||||||
background-color: #d0cfcf;
|
background-color: #d0cfcf;
|
||||||
&:hover {
|
&:hover {
|
||||||
@@ -147,13 +171,18 @@
|
|||||||
.vtl-disabled {
|
.vtl-disabled {
|
||||||
background-color: #d0cfcf;
|
background-color: #d0cfcf;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style lang="less" rel="stylesheet/less" scoped>
|
<style lang="less" rel="stylesheet/less" scoped>
|
||||||
.icon {
|
.icon {
|
||||||
&:hover {
|
&:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.muted {
|
||||||
|
color: gray;
|
||||||
|
font-size: 80%;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
1338
package-lock.json
generated
1338
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
44
package.json
44
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "vue-tree-list",
|
"name": "vue-tree-list",
|
||||||
"version": "1.4.1",
|
"version": "1.4.6",
|
||||||
"description": "A vue component for tree structure. Support adding treenode/leafnode, editing node's name and dragging.",
|
"description": "A vue component for tree structure. Support adding treenode/leafnode, editing node's name and dragging.",
|
||||||
"author": "ayou",
|
"author": "ayou",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -9,14 +9,12 @@
|
|||||||
"test:unit": "vue-cli-service test:unit --watch",
|
"test:unit": "vue-cli-service test:unit --watch",
|
||||||
"test:coverage": "vue-cli-service test:unit --coverage",
|
"test:coverage": "vue-cli-service test:unit --coverage",
|
||||||
"lint": "vue-cli-service lint",
|
"lint": "vue-cli-service lint",
|
||||||
|
"lint-staged": "lint-staged",
|
||||||
"commit": "npx git-cz",
|
"commit": "npx git-cz",
|
||||||
"prepublish": "npm run build"
|
"prepublish": "npm run build"
|
||||||
},
|
},
|
||||||
"main": "dist/vue-tree-list.umd.min.js",
|
"main": "dist/vue-tree-list.umd.min.js",
|
||||||
"dependencies": {
|
"dependencies": {},
|
||||||
"core-js": "^3.4.3",
|
|
||||||
"vue": "^2.6.10"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vue/cli-plugin-babel": "^4.1.0",
|
"@vue/cli-plugin-babel": "^4.1.0",
|
||||||
"@vue/cli-plugin-eslint": "^4.1.0",
|
"@vue/cli-plugin-eslint": "^4.1.0",
|
||||||
@@ -24,38 +22,24 @@
|
|||||||
"@vue/cli-service": "^4.1.0",
|
"@vue/cli-service": "^4.1.0",
|
||||||
"@vue/test-utils": "1.0.0-beta.29",
|
"@vue/test-utils": "1.0.0-beta.29",
|
||||||
"babel-eslint": "^10.0.3",
|
"babel-eslint": "^10.0.3",
|
||||||
|
"core-js": "^3.4.3",
|
||||||
"eslint": "^5.16.0",
|
"eslint": "^5.16.0",
|
||||||
|
"eslint-config-prettier": "^6.10.0",
|
||||||
|
"eslint-plugin-prettier": "^3.1.2",
|
||||||
"eslint-plugin-vue": "^5.0.0",
|
"eslint-plugin-vue": "^5.0.0",
|
||||||
|
"husky": "^4.2.1",
|
||||||
"jest-serializer-vue": "^2.0.2",
|
"jest-serializer-vue": "^2.0.2",
|
||||||
"less": "^3.10.3",
|
"less": "^3.10.3",
|
||||||
"less-loader": "^5.0.0",
|
"less-loader": "^5.0.0",
|
||||||
|
"lint-staged": "^10.0.4",
|
||||||
|
"prettier": "^1.19.1",
|
||||||
|
"prettier-eslint-cli": "^5.0.0",
|
||||||
|
"vue": "^2.6.10",
|
||||||
"vue-template-compiler": "^2.6.10"
|
"vue-template-compiler": "^2.6.10"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"lint-staged": {
|
||||||
"root": true,
|
"**/*.{js,json,md,vue}": [
|
||||||
"env": {
|
"prettier --write"
|
||||||
"node": true
|
|
||||||
},
|
|
||||||
"extends": [
|
|
||||||
"plugin:vue/essential",
|
|
||||||
"eslint:recommended"
|
|
||||||
],
|
|
||||||
"rules": {
|
|
||||||
"no-console": "warn"
|
|
||||||
},
|
|
||||||
"parserOptions": {
|
|
||||||
"parser": "babel-eslint"
|
|
||||||
},
|
|
||||||
"overrides": [
|
|
||||||
{
|
|
||||||
"files": [
|
|
||||||
"**/__tests__/*.{j,t}s?(x)",
|
|
||||||
"**/tests/unit/**/*.spec.{j,t}s?(x)"
|
|
||||||
],
|
|
||||||
"env": {
|
|
||||||
"jest": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
|
15
prettier.config.js
Normal file
15
prettier.config.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
module.exports = {
|
||||||
|
printWidth: 100,
|
||||||
|
tabWidth: 2,
|
||||||
|
useTabs: false,
|
||||||
|
semi: false,
|
||||||
|
singleQuote: true,
|
||||||
|
jsxSingleQuote: true,
|
||||||
|
bracketSpacing: true,
|
||||||
|
jsxBracketSameLine: false,
|
||||||
|
rangeStart: 0,
|
||||||
|
rangeEnd: Infinity,
|
||||||
|
requirePragma: false,
|
||||||
|
insertPragma: false,
|
||||||
|
htmlWhitespaceSensitivity: 'css'
|
||||||
|
}
|
150
readme.md
150
readme.md
@@ -1,14 +1,29 @@
|
|||||||
[](https://github.com/ParadeTo/vue-tree-list/actions)
|
[](https://github.com/ParadeTo/vue-tree-list/actions)
|
||||||
|
|
||||||
# vue-tree-list
|
# vue-tree-list
|
||||||
|
|
||||||
A vue component for tree structure. Support adding treenode/leafnode, editing node's name and dragging.
|
A vue component for tree structure. Support adding treenode/leafnode, editing node's name and dragging.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
[Live Demo](http://paradeto.com/vue-tree-list/)
|
[Live Demo](http://paradeto.com/vue-tree-list/)
|
||||||
|
|
||||||
|
# install
|
||||||
|
|
||||||
|
Install the plugin then you can use the component globally.
|
||||||
|
|
||||||
|
```js
|
||||||
|
import Vue from 'vue'
|
||||||
|
import VueTreeList from 'vue-tree-list'
|
||||||
|
|
||||||
|
Vue.use(VueTreeList)
|
||||||
|
```
|
||||||
|
|
||||||
|
Or just register locally like the example below.
|
||||||
|
|
||||||
# use
|
# use
|
||||||
``npm install vue-tree-list``
|
|
||||||
|
`npm install vue-tree-list`
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<template>
|
<template>
|
||||||
@@ -22,7 +37,13 @@ A vue component for tree structure. Support adding treenode/leafnode, editing no
|
|||||||
:model="data"
|
:model="data"
|
||||||
default-tree-node-name="new node"
|
default-tree-node-name="new node"
|
||||||
default-leaf-node-name="new leaf"
|
default-leaf-node-name="new leaf"
|
||||||
v-bind:default-expanded="false">
|
v-bind:default-expanded="false"
|
||||||
|
>
|
||||||
|
<template v-slot:leafNameDisplay="slotProps">
|
||||||
|
<span>
|
||||||
|
{{ slotProps.model.name }} <span class="muted">#{{ slotProps.model.id }}</span>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
<span class="icon" slot="addTreeNodeIcon">📂</span>
|
<span class="icon" slot="addTreeNodeIcon">📂</span>
|
||||||
<span class="icon" slot="addLeafNodeIcon">+</span>
|
<span class="icon" slot="addLeafNodeIcon">+</span>
|
||||||
<span class="icon" slot="editNodeIcon">📃</span>
|
<span class="icon" slot="editNodeIcon">📃</span>
|
||||||
@@ -43,7 +64,7 @@ A vue component for tree structure. Support adding treenode/leafnode, editing no
|
|||||||
components: {
|
components: {
|
||||||
VueTreeList
|
VueTreeList
|
||||||
},
|
},
|
||||||
data () {
|
data() {
|
||||||
return {
|
return {
|
||||||
newTree: {},
|
newTree: {},
|
||||||
data: new Tree([
|
data: new Tree([
|
||||||
@@ -80,32 +101,32 @@ A vue component for tree structure. Support adding treenode/leafnode, editing no
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onDel (node) {
|
onDel(node) {
|
||||||
console.log(node)
|
console.log(node)
|
||||||
node.remove()
|
node.remove()
|
||||||
},
|
},
|
||||||
|
|
||||||
onChangeName (params) {
|
onChangeName(params) {
|
||||||
console.log(params)
|
console.log(params)
|
||||||
},
|
},
|
||||||
|
|
||||||
onAddNode (params) {
|
onAddNode(params) {
|
||||||
console.log(params)
|
console.log(params)
|
||||||
},
|
},
|
||||||
|
|
||||||
onClick (params) {
|
onClick(params) {
|
||||||
console.log(params)
|
console.log(params)
|
||||||
},
|
},
|
||||||
|
|
||||||
addNode () {
|
addNode() {
|
||||||
var node = new TreeNode({ name: 'new node', isLeaf: false })
|
var node = new TreeNode({ name: 'new node', isLeaf: false })
|
||||||
if (!this.data.children) this.data.children = []
|
if (!this.data.children) this.data.children = []
|
||||||
this.data.addChildren(node)
|
this.data.addChildren(node)
|
||||||
},
|
},
|
||||||
|
|
||||||
getNewTree () {
|
getNewTree() {
|
||||||
var vm = this
|
var vm = this
|
||||||
function _dfs (oldNode) {
|
function _dfs(oldNode) {
|
||||||
var newNode = {}
|
var newNode = {}
|
||||||
|
|
||||||
for (var k in oldNode) {
|
for (var k in oldNode) {
|
||||||
@@ -124,8 +145,7 @@ A vue component for tree structure. Support adding treenode/leafnode, editing no
|
|||||||
}
|
}
|
||||||
|
|
||||||
vm.newTree = _dfs(vm.data)
|
vm.newTree = _dfs(vm.data)
|
||||||
},
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -150,62 +170,90 @@ A vue component for tree structure. Support adding treenode/leafnode, editing no
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
|
||||||
|
|
||||||
|
.muted {
|
||||||
|
color: gray;
|
||||||
|
font-size: 80%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
```
|
```
|
||||||
|
|
||||||
# props
|
# props
|
||||||
## props of vue-tree-list
|
|
||||||
| name | type | default | description |
|
|
||||||
|:-----:|:-------:|:------------:|:----:|
|
|
||||||
model | TreeNode | - | You can use `const head = new Tree([])` to generate a tree with the head of `TreeNode` type
|
|
||||||
default-tree-node-name | string | New node node | Default name for new treenode
|
|
||||||
default-leaf-node-name | string | New leaf node | Default name for new leafnode
|
|
||||||
default-expanded | boolean | true | Tree is expanded or not
|
|
||||||
|
|
||||||
|
## props of vue-tree-list
|
||||||
|
|
||||||
|
| name | type | default | description |
|
||||||
|
| :--------------------: | :------: | :-----------: | :-----------------------------------------------------------------------------------------: |
|
||||||
|
| model | TreeNode | - | You can use `const head = new Tree([])` to generate a tree with the head of `TreeNode` type |
|
||||||
|
| default-tree-node-name | string | New node node | Default name for new treenode |
|
||||||
|
| default-leaf-node-name | string | New leaf node | Default name for new leafnode |
|
||||||
|
| default-expanded | boolean | true | Tree is expanded or not |
|
||||||
|
|
||||||
## props of TreeNode
|
## props of TreeNode
|
||||||
|
|
||||||
### attributes
|
### attributes
|
||||||
|
|
||||||
| name | type | default | description |
|
| name | type | default | description |
|
||||||
|:-----:|:-------:|:------------:|:----:|
|
| :-----------------: | :------------: | :---------------: | :------------------------------: |
|
||||||
id | string, number | current timestamp | The node's id
|
| id | string, number | current timestamp | The node's id |
|
||||||
isLeaf | boolean | false | The node is leaf or not
|
| isLeaf | boolean | false | The node is leaf or not |
|
||||||
dragDisabled | boolean | false | Forbid dragging tree node
|
| dragDisabled | boolean | false | Forbid dragging tree node |
|
||||||
addTreeNodeDisabled | boolean | false | Show `addTreeNode` button or not
|
| addTreeNodeDisabled | boolean | false | Show `addTreeNode` button or not |
|
||||||
addLeafNodeDisabled | boolean | false | Show `addLeafNode` button or not
|
| addLeafNodeDisabled | boolean | false | Show `addLeafNode` button or not |
|
||||||
editNodeDisabled | boolean | false | Show `editNode` button or not
|
| editNodeDisabled | boolean | false | Show `editNode` button or not |
|
||||||
delNodeDisabled | boolean | false | Show `delNode` button or not
|
| delNodeDisabled | boolean | false | Show `delNode` button or not |
|
||||||
children | array | null | The children of node
|
| children | array | null | The children of node |
|
||||||
|
|
||||||
### methods
|
### methods
|
||||||
|
|
||||||
| name | params | description |
|
| name | params | description |
|
||||||
|:-----:|:-------:|:----:|
|
| :----------: | :---------------------: | :---------------------------: |
|
||||||
changeName | name | Change node's name
|
| changeName | name | Change node's name |
|
||||||
addChildren | children: object, array | Add children to node
|
| addChildren | children: object, array | Add children to node |
|
||||||
remove | - | Remove node from the tree
|
| remove | - | Remove node from the tree |
|
||||||
moveInto | target: TreeNode | Move node into another node
|
| moveInto | target: TreeNode | Move node into another node |
|
||||||
insertBefore | target: TreeNode | Move node before another node
|
| insertBefore | target: TreeNode | Move node before another node |
|
||||||
insertAfter | target: TreeNode | Move node after another node
|
| insertAfter | target: TreeNode | Move node after another node |
|
||||||
|
|
||||||
# events
|
# events
|
||||||
|
|
||||||
| name | params | description |
|
| name | params | description |
|
||||||
|:-----:|:-------:|:----:|
|
| :---------: | :--------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------: |
|
||||||
click | TreeNode | Trigger when clicking a tree node
|
| click | TreeNode | Trigger when clicking a tree node |
|
||||||
change-name | {'id', 'oldName', 'newName'} | Trigger after changing a node's name
|
| change-name | {'id', 'oldName', 'newName'} | Trigger after changing a node's name |
|
||||||
delete-node | TreeNode | Trigger when clicking `delNode` button. You can call `remove` of `TreeNode` to remove the node.
|
| delete-node | TreeNode | Trigger when clicking `delNode` button. You can call `remove` of `TreeNode` to remove the node. |
|
||||||
add-node | TreeNode | Trigger after adding a new node
|
| add-node | TreeNode | Trigger after adding a new node |
|
||||||
drop | {node, src, target} | Trigger after dropping a node into another. node: the draggable node, src: the draggable node's parent, target: the node that draggable node will drop into
|
| drop | {node, src, target} | Trigger after dropping a node into another. node: the draggable node, src: the draggable node's parent, target: the node that draggable node will drop into |
|
||||||
drop-before | {node, src, target} | Trigger after dropping a node before another. node: the draggable node, src: the draggable node's parent, target: the node that draggable node will drop before
|
| drop-before | {node, src, target} | Trigger after dropping a node before another. node: the draggable node, src: the draggable node's parent, target: the node that draggable node will drop before |
|
||||||
drop-after | {node, src, target} | Trigger after dropping a node after another. node: the draggable node, src: the draggable node's parent, target: the node that draggable node will drop after
|
| drop-after | {node, src, target} | Trigger after dropping a node after another. node: the draggable node, src: the draggable node's parent, target: the node that draggable node will drop after |
|
||||||
|
|
||||||
# customize operation icons
|
# customize operation icons
|
||||||
The component has default icons for `addTreeNodeIcon`, `addLeafNodeIcon`, `editNodeIcon`, `delNodeIcon`, `leafNodeIcon`, `treeNodeIcon` button, but you can also customize them:
|
|
||||||
|
The component has default icons for `addTreeNodeIcon`, `addLeafNodeIcon`, `editNodeIcon`, `delNodeIcon`, `leafNodeIcon`, `treeNodeIcon` button, but you can also customize them and can access `model`, `root`, `expanded` as below:
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<span class="icon" slot="addTreeNodeIcon">📂</span>
|
<template v-slot:leafNameDisplay="slotProps">
|
||||||
<span class="icon" slot="addLeafNodeIcon">+</span>
|
<span>{{ slotProps.model.name }} #{{ slotProps.model.id }}</span>
|
||||||
<span class="icon" slot="editNodeIcon">📃</span>
|
</template>
|
||||||
<span class="icon" slot="delNodeIcon">✂️</span>
|
<template v-slot:addTreeNodeIcon="slotProps">
|
||||||
<span class="icon" slot="leafNodeIcon">🍃</span>
|
<span class="icon">📂</span>
|
||||||
<span class="icon" slot="treeNodeIcon">🌲</span>
|
</template>
|
||||||
|
<template v-slot:addLeafNodeIcon="slotProps">
|
||||||
|
<span class="icon">+</span>
|
||||||
|
</template>
|
||||||
|
<template v-slot:editNodeIcon="slotProps">
|
||||||
|
<span class="icon">📃</span>
|
||||||
|
</template>
|
||||||
|
<template v-slot:delNodeIcon="slotProps">
|
||||||
|
<span class="icon">✂️</span>
|
||||||
|
</template>
|
||||||
|
<template v-slot:leafNodeIcon="slotProps">
|
||||||
|
<span class="icon">🍃</span>
|
||||||
|
</template>
|
||||||
|
<template v-slot:treeNodeIcon="slotProps">
|
||||||
|
<span class="icon">
|
||||||
|
{{ (slotProps.model.children && slotProps.model.children.length > 0 && !slotProps.expanded) ?
|
||||||
|
'🌲' : '' }}</span
|
||||||
|
>
|
||||||
|
</template>
|
||||||
```
|
```
|
||||||
|
70
src/Tree.js
70
src/Tree.js
@@ -1,3 +1,4 @@
|
|||||||
|
import { traverseTree } from './tools'
|
||||||
/**
|
/**
|
||||||
* Tree data struct
|
* Tree data struct
|
||||||
* Created by ayou on 2017/7/20.
|
* Created by ayou on 2017/7/20.
|
||||||
@@ -8,9 +9,10 @@
|
|||||||
* dragDisabled: decide if it can be dragged
|
* dragDisabled: decide if it can be dragged
|
||||||
* disabled: desabled all operation
|
* disabled: desabled all operation
|
||||||
*/
|
*/
|
||||||
const TreeNode = function (data) {
|
export class TreeNode {
|
||||||
|
constructor(data) {
|
||||||
const { id, isLeaf } = data
|
const { id, isLeaf } = data
|
||||||
this.id = (typeof id === 'undefined') ? new Date().valueOf() : id
|
this.id = typeof id === 'undefined' ? new Date().valueOf() : id
|
||||||
this.parent = null
|
this.parent = null
|
||||||
this.children = null
|
this.children = null
|
||||||
this.isLeaf = !!isLeaf
|
this.isLeaf = !!isLeaf
|
||||||
@@ -21,13 +23,13 @@ const TreeNode = function (data) {
|
|||||||
this[k] = data[k]
|
this[k] = data[k]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TreeNode.prototype.changeName = function (name) {
|
changeName(name) {
|
||||||
this.name = name
|
this.name = name
|
||||||
}
|
}
|
||||||
|
|
||||||
TreeNode.prototype.addChildren = function (children) {
|
addChildren(children) {
|
||||||
if (!this.children) {
|
if (!this.children) {
|
||||||
this.children = []
|
this.children = []
|
||||||
}
|
}
|
||||||
@@ -45,26 +47,26 @@ TreeNode.prototype.addChildren = function (children) {
|
|||||||
child.pid = this.id
|
child.pid = this.id
|
||||||
this.children.push(child)
|
this.children.push(child)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove self
|
// remove self
|
||||||
TreeNode.prototype.remove = function () {
|
remove() {
|
||||||
const parent = this.parent
|
const parent = this.parent
|
||||||
const index = parent.findChildIndex(this)
|
const index = parent.findChildIndex(this)
|
||||||
parent.children.splice(index, 1)
|
parent.children.splice(index, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove child
|
// remove child
|
||||||
TreeNode.prototype._removeChild = function (child) {
|
_removeChild(child) {
|
||||||
for (var i = 0, len = this.children.length; i < len; i++) {
|
for (var i = 0, len = this.children.length; i < len; i++) {
|
||||||
if (this.children[i] === child) {
|
if (this.children[i] === child) {
|
||||||
this.children.splice(i, 1)
|
this.children.splice(i, 1)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TreeNode.prototype.isTargetChild = function (target) {
|
isTargetChild(target) {
|
||||||
let parent = target.parent
|
let parent = target.parent
|
||||||
while (parent) {
|
while (parent) {
|
||||||
if (parent === this) {
|
if (parent === this) {
|
||||||
@@ -73,9 +75,9 @@ TreeNode.prototype.isTargetChild = function (target) {
|
|||||||
parent = parent.parent
|
parent = parent.parent
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
TreeNode.prototype.moveInto = function (target) {
|
moveInto(target) {
|
||||||
if (this.name === 'root' || this === target) {
|
if (this.name === 'root' || this === target) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -97,9 +99,9 @@ TreeNode.prototype.moveInto = function (target) {
|
|||||||
target.children = []
|
target.children = []
|
||||||
}
|
}
|
||||||
target.children.unshift(this)
|
target.children.unshift(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
TreeNode.prototype.findChildIndex = function (child) {
|
findChildIndex(child) {
|
||||||
var index
|
var index
|
||||||
for (let i = 0, len = this.children.length; i < len; i++) {
|
for (let i = 0, len = this.children.length; i < len; i++) {
|
||||||
if (this.children[i] === child) {
|
if (this.children[i] === child) {
|
||||||
@@ -108,9 +110,9 @@ TreeNode.prototype.findChildIndex = function (child) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return index
|
return index
|
||||||
}
|
}
|
||||||
|
|
||||||
TreeNode.prototype._beforeInsert = function (target) {
|
_canInsert(target) {
|
||||||
if (this.name === 'root' || this === target) {
|
if (this.name === 'root' || this === target) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -124,29 +126,35 @@ TreeNode.prototype._beforeInsert = function (target) {
|
|||||||
this.parent = target.parent
|
this.parent = target.parent
|
||||||
this.pid = target.parent.id
|
this.pid = target.parent.id
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
TreeNode.prototype.insertBefore = function (target) {
|
insertBefore(target) {
|
||||||
if (!this._beforeInsert(target)) return
|
if (!this._canInsert(target)) return
|
||||||
|
|
||||||
const pos = target.parent.findChildIndex(target)
|
const pos = target.parent.findChildIndex(target)
|
||||||
target.parent.children.splice(pos, 0, this)
|
target.parent.children.splice(pos, 0, this)
|
||||||
}
|
}
|
||||||
|
|
||||||
TreeNode.prototype.insertAfter = function (target) {
|
insertAfter(target) {
|
||||||
if (!this._beforeInsert(target)) return
|
if (!this._canInsert(target)) return
|
||||||
|
|
||||||
const pos = target.parent.findChildIndex(target)
|
const pos = target.parent.findChildIndex(target)
|
||||||
target.parent.children.splice(pos + 1, 0, this)
|
target.parent.children.splice(pos + 1, 0, this)
|
||||||
|
}
|
||||||
|
|
||||||
|
toString() {
|
||||||
|
return JSON.stringify(traverseTree(this))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function Tree (data) {
|
export class Tree {
|
||||||
|
constructor(data) {
|
||||||
this.root = new TreeNode({ name: 'root', isLeaf: false, id: 0 })
|
this.root = new TreeNode({ name: 'root', isLeaf: false, id: 0 })
|
||||||
this.initNode(this.root, data)
|
this.initNode(this.root, data)
|
||||||
return this.root
|
return this.root
|
||||||
}
|
}
|
||||||
|
|
||||||
Tree.prototype.initNode = function (node, data) {
|
initNode(node, data) {
|
||||||
for (let i = 0, len = data.length; i < len; i++) {
|
for (let i = 0, len = data.length; i < len; i++) {
|
||||||
var _data = data[i]
|
var _data = data[i]
|
||||||
|
|
||||||
@@ -156,7 +164,5 @@ Tree.prototype.initNode = function (node, data) {
|
|||||||
}
|
}
|
||||||
node.addChildren(child)
|
node.addChildren(child)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.Tree = Tree
|
|
||||||
exports.TreeNode = TreeNode
|
|
||||||
|
@@ -1,105 +1,152 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class='vtl'>
|
<div class="vtl">
|
||||||
<div
|
<div
|
||||||
v-if="model.name !== 'root'"
|
v-if="model.name !== 'root'"
|
||||||
:id='model.id'
|
:id="model.id"
|
||||||
class="vtl-node"
|
class="vtl-node"
|
||||||
:class="{'vtl-leaf-node': model.isLeaf, 'vtl-tree-node': ! model.isLeaf}"
|
:class="{ 'vtl-leaf-node': model.isLeaf, 'vtl-tree-node': !model.isLeaf }"
|
||||||
>
|
>
|
||||||
<div class="vtl-border vtl-up" :class="{'vtl-active': isDragEnterUp}"
|
<div
|
||||||
|
class="vtl-border vtl-up"
|
||||||
|
:class="{ 'vtl-active': isDragEnterUp }"
|
||||||
@drop="dropBefore"
|
@drop="dropBefore"
|
||||||
@dragenter="dragEnterUp"
|
@dragenter="dragEnterUp"
|
||||||
@dragover='dragOverUp'
|
@dragover="dragOverUp"
|
||||||
@dragleave="dragLeaveUp" />
|
@dragleave="dragLeaveUp"
|
||||||
<div :class="treeNodeClass"
|
/>
|
||||||
|
<div
|
||||||
|
:class="treeNodeClass"
|
||||||
:draggable="!model.dragDisabled"
|
:draggable="!model.dragDisabled"
|
||||||
@dragstart='dragStart'
|
@dragstart="dragStart"
|
||||||
@dragover='dragOver'
|
@dragover="dragOver"
|
||||||
@dragenter='dragEnter'
|
@dragenter="dragEnter"
|
||||||
@dragleave='dragLeave'
|
@dragleave="dragLeave"
|
||||||
@drop='drop'
|
@drop="drop"
|
||||||
@dragend='dragEnd'
|
@dragend="dragEnd"
|
||||||
@mouseover='mouseOver'
|
@mouseover="mouseOver"
|
||||||
@mouseout='mouseOut'
|
@mouseout="mouseOut"
|
||||||
@click.stop='click'>
|
@click.stop="click"
|
||||||
|
>
|
||||||
<span class="vtl-caret vtl-is-small" v-if="model.children && model.children.length > 0">
|
<span class="vtl-caret vtl-is-small" v-if="model.children && model.children.length > 0">
|
||||||
<i class="vtl-icon" :class="caretClass" @click.prevent.stop="toggle"></i>
|
<i class="vtl-icon" :class="caretClass" @click.prevent.stop="toggle"></i>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span v-if="model.isLeaf">
|
<span v-if="model.isLeaf">
|
||||||
<slot name="leafNodeIcon">
|
<slot name="leafNodeIcon" :expanded="expanded" :model="model" :root="rootNode">
|
||||||
<i class="vtl-icon vtl-menu-icon vtl-icon-file"></i>
|
<i class="vtl-icon vtl-menu-icon vtl-icon-file"></i>
|
||||||
</slot>
|
</slot>
|
||||||
</span>
|
</span>
|
||||||
<span v-else>
|
<span v-else>
|
||||||
<slot name="treeNodeIcon">
|
<slot name="treeNodeIcon" :expanded="expanded" :model="model" :root="rootNode">
|
||||||
<i class="vtl-icon vtl-menu-icon vtl-icon-folder"></i>
|
<i class="vtl-icon vtl-menu-icon vtl-icon-folder"></i>
|
||||||
</slot>
|
</slot>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<div class="vtl-node-content" v-if="!editable">
|
<div class="vtl-node-content" v-if="!editable">
|
||||||
{{model.name}}
|
<slot name="leafNameDisplay" :expanded="expanded" :model="model" :root="rootNode">
|
||||||
|
{{ model.name }}
|
||||||
|
</slot>
|
||||||
</div>
|
</div>
|
||||||
<input v-else class="vtl-input" type="text" ref="nodeInput" :value="model.name" @input="updateName" @blur="setUnEditable">
|
<input
|
||||||
|
v-else
|
||||||
|
class="vtl-input"
|
||||||
|
type="text"
|
||||||
|
ref="nodeInput"
|
||||||
|
:value="model.name"
|
||||||
|
@input="updateName"
|
||||||
|
@blur="setUnEditable"
|
||||||
|
/>
|
||||||
<div class="vtl-operation" v-show="isHover">
|
<div class="vtl-operation" v-show="isHover">
|
||||||
<span title="add tree node" @click.stop.prevent="addChild(false)" v-if="!model.isLeaf && !model.addTreeNodeDisabled">
|
<span
|
||||||
<slot name="addTreeNodeIcon">
|
title="add tree node"
|
||||||
|
@click.stop.prevent="addChild(false)"
|
||||||
|
v-if="!model.isLeaf && !model.addTreeNodeDisabled"
|
||||||
|
>
|
||||||
|
<slot name="addTreeNodeIcon" :expanded="expanded" :model="model" :root="rootNode">
|
||||||
<i class="vtl-icon vtl-icon-folder-plus-e"></i>
|
<i class="vtl-icon vtl-icon-folder-plus-e"></i>
|
||||||
</slot>
|
</slot>
|
||||||
</span>
|
</span>
|
||||||
<span title="add leaf node" @click.stop.prevent="addChild(true)" v-if="!model.isLeaf && !model.addLeafNodeDisabled">
|
<span
|
||||||
<slot name="addLeafNodeIcon">
|
title="add leaf node"
|
||||||
|
@click.stop.prevent="addChild(true)"
|
||||||
|
v-if="!model.isLeaf && !model.addLeafNodeDisabled"
|
||||||
|
>
|
||||||
|
<slot name="addLeafNodeIcon" :expanded="expanded" :model="model" :root="rootNode">
|
||||||
<i class="vtl-icon vtl-icon-plus"></i>
|
<i class="vtl-icon vtl-icon-plus"></i>
|
||||||
</slot>
|
</slot>
|
||||||
</span>
|
</span>
|
||||||
<span title="edit" @click.stop.prevent="setEditable" v-if="!model.editNodeDisabled">
|
<span title="edit" @click.stop.prevent="setEditable" v-if="!model.editNodeDisabled">
|
||||||
<slot name="editNodeIcon">
|
<slot name="editNodeIcon" :expanded="expanded" :model="model" :root="rootNode">
|
||||||
<i class="vtl-icon vtl-icon-edit"></i>
|
<i class="vtl-icon vtl-icon-edit"></i>
|
||||||
</slot>
|
</slot>
|
||||||
</span>
|
</span>
|
||||||
<span title="delete" @click.stop.prevent="delNode" v-if="!model.delNodeDisabled">
|
<span title="delete" @click.stop.prevent="delNode" v-if="!model.delNodeDisabled">
|
||||||
<slot name="delNodeIcon">
|
<slot name="delNodeIcon" :expanded="expanded" :model="model" :root="rootNode">
|
||||||
<i class="vtl-icon vtl-icon-trash"></i>
|
<i class="vtl-icon vtl-icon-trash"></i>
|
||||||
</slot>
|
</slot>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="model.children && model.children.length > 0 && expanded"
|
<div
|
||||||
|
v-if="model.children && model.children.length > 0 && expanded"
|
||||||
class="vtl-border vtl-bottom"
|
class="vtl-border vtl-bottom"
|
||||||
:class="{'vtl-active': isDragEnterBottom}"
|
:class="{ 'vtl-active': isDragEnterBottom }"
|
||||||
@drop="dropAfter"
|
@drop="dropAfter"
|
||||||
@dragenter="dragEnterBottom"
|
@dragenter="dragEnterBottom"
|
||||||
@dragover='dragOverBottom'
|
@dragover="dragOverBottom"
|
||||||
@dragleave="dragLeaveBottom"></div>
|
@dragleave="dragLeaveBottom"
|
||||||
|
></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div :class="{'vtl-tree-margin': model.name !== 'root'}" v-show="model.name === 'root' || expanded" v-if="isFolder">
|
<div
|
||||||
<item v-for="model in model.children"
|
:class="{ 'vtl-tree-margin': model.name !== 'root' }"
|
||||||
|
v-show="model.name === 'root' || expanded"
|
||||||
|
v-if="isFolder"
|
||||||
|
>
|
||||||
|
<item
|
||||||
|
v-for="model in model.children"
|
||||||
:default-tree-node-name="defaultTreeNodeName"
|
:default-tree-node-name="defaultTreeNodeName"
|
||||||
:default-leaf-node-name="defaultLeafNodeName"
|
:default-leaf-node-name="defaultLeafNodeName"
|
||||||
:default-expanded="defaultExpanded"
|
:default-expanded="defaultExpanded"
|
||||||
:model="model"
|
:model="model"
|
||||||
:key='model.id'>
|
:key="model.id"
|
||||||
<slot name="addTreeNodeIcon" slot="addTreeNodeIcon" />
|
>
|
||||||
<slot name="addLeafNodeIcon" slot="addLeafNodeIcon" />
|
<template v-slot:leafNameDisplay="slotProps">
|
||||||
<slot name="editNodeIcon" slot="editNodeIcon" />
|
<slot name="leafNameDisplay" v-bind="slotProps" />
|
||||||
<slot name="delNodeIcon" slot="delNodeIcon" />
|
</template>
|
||||||
<slot name="leafNodeIcon" slot="leafNodeIcon" />
|
<template v-slot:addTreeNodeIcon="slotProps">
|
||||||
<slot name="treeNodeIcon" slot="treeNodeIcon" />
|
<slot name="addTreeNodeIcon" v-bind="slotProps" />
|
||||||
|
</template>
|
||||||
|
<template v-slot:addLeafNodeIcon="slotProps">
|
||||||
|
<slot name="addLeafNodeIcon" v-bind="slotProps" />
|
||||||
|
</template>
|
||||||
|
<template v-slot:editNodeIcon="slotProps">
|
||||||
|
<slot name="editNodeIcon" v-bind="slotProps" />
|
||||||
|
</template>
|
||||||
|
<template v-slot:delNodeIcon="slotProps">
|
||||||
|
<slot name="delNodeIcon" v-bind="slotProps" />
|
||||||
|
</template>
|
||||||
|
<template v-slot:leafNodeIcon="slotProps">
|
||||||
|
<slot name="leafNodeIcon" v-bind="slotProps" />
|
||||||
|
</template>
|
||||||
|
<template v-slot:treeNodeIcon="slotProps">
|
||||||
|
<slot name="treeNodeIcon" v-bind="slotProps" />
|
||||||
|
</template>
|
||||||
</item>
|
</item>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { TreeNode } from './Tree.js'
|
import { TreeNode } from './Tree.js'
|
||||||
import { addHandler, removeHandler } from './tools.js'
|
import { addHandler, removeHandler } from './tools.js'
|
||||||
|
|
||||||
let compInOperation = null
|
let compInOperation = null
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data: function () {
|
name: 'vue-tree-list',
|
||||||
|
data: function() {
|
||||||
return {
|
return {
|
||||||
isHover: false,
|
isHover: false,
|
||||||
editable: false,
|
editable: false,
|
||||||
@@ -127,21 +174,25 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
caretClass () {
|
rootNode() {
|
||||||
|
var node = this.$parent
|
||||||
|
while (node._props.model.name !== 'root') {
|
||||||
|
node = node.$parent
|
||||||
|
}
|
||||||
|
return node
|
||||||
|
},
|
||||||
|
|
||||||
|
caretClass() {
|
||||||
return this.expanded ? 'vtl-icon-caret-down' : 'vtl-icon-caret-right'
|
return this.expanded ? 'vtl-icon-caret-down' : 'vtl-icon-caret-right'
|
||||||
},
|
},
|
||||||
|
|
||||||
isFolder () {
|
isFolder() {
|
||||||
return this.model.children &&
|
return this.model.children && this.model.children.length
|
||||||
this.model.children.length
|
|
||||||
},
|
},
|
||||||
|
|
||||||
treeNodeClass () {
|
treeNodeClass() {
|
||||||
const {
|
const {
|
||||||
model: {
|
model: { dragDisabled, disabled },
|
||||||
dragDisabled,
|
|
||||||
disabled
|
|
||||||
},
|
|
||||||
isDragEnterNode
|
isDragEnterNode
|
||||||
} = this
|
} = this
|
||||||
|
|
||||||
@@ -153,35 +204,38 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
beforeCreate () {
|
beforeCreate() {
|
||||||
this.$options.components.item = require('./VueTreeList').default
|
this.$options.components.item = require('./VueTreeList').default
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted() {
|
||||||
const vm = this
|
const vm = this
|
||||||
addHandler(window, 'keyup', function (e) {
|
addHandler(window, 'keyup', function(e) {
|
||||||
// click enter
|
// click enter
|
||||||
if (e.keyCode === 13 && vm.editable) {
|
if (e.keyCode === 13 && vm.editable) {
|
||||||
vm.editable = false
|
vm.editable = false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
beforeDestroy () {
|
beforeDestroy() {
|
||||||
removeHandler(window, 'keyup')
|
removeHandler(window, 'keyup')
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
updateName (e) {
|
updateName(e) {
|
||||||
var oldName = this.model.name;
|
var oldName = this.model.name
|
||||||
this.model.changeName(e.target.value)
|
this.model.changeName(e.target.value)
|
||||||
var node = this.getRootNode();
|
this.rootNode.$emit('change-name', {
|
||||||
node.$emit('change-name', {'id': this.model.id, 'oldName': oldName, 'newName': e.target.value})
|
id: this.model.id,
|
||||||
|
oldName: oldName,
|
||||||
|
newName: e.target.value,
|
||||||
|
node: this.model
|
||||||
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
delNode () {
|
delNode() {
|
||||||
var node = this.getRootNode()
|
this.rootNode.$emit('delete-node', this.model)
|
||||||
node.$emit('delete-node', this.model)
|
|
||||||
},
|
},
|
||||||
|
|
||||||
setEditable () {
|
setEditable() {
|
||||||
this.editable = true
|
this.editable = true
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
const $input = this.$refs.nodeInput
|
const $input = this.$refs.nodeInput
|
||||||
@@ -190,8 +244,16 @@
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
setUnEditable () {
|
setUnEditable(e) {
|
||||||
this.editable = false
|
this.editable = false
|
||||||
|
var oldName = this.model.name
|
||||||
|
this.model.changeName(e.target.value)
|
||||||
|
this.rootNode.$emit('change-name', {
|
||||||
|
id: this.model.id,
|
||||||
|
oldName: oldName,
|
||||||
|
newName: e.target.value,
|
||||||
|
eventType: 'blur'
|
||||||
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
toggle() {
|
toggle() {
|
||||||
@@ -210,8 +272,7 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
click() {
|
click() {
|
||||||
var node = this.getRootNode()
|
this.rootNode.$emit('click', this.model)
|
||||||
node.$emit('click', this.model);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
addChild(isLeaf) {
|
addChild(isLeaf) {
|
||||||
@@ -219,15 +280,14 @@
|
|||||||
this.expanded = true
|
this.expanded = true
|
||||||
var node = new TreeNode({ name, isLeaf })
|
var node = new TreeNode({ name, isLeaf })
|
||||||
this.model.addChildren(node, true)
|
this.model.addChildren(node, true)
|
||||||
var root = this.getRootNode();
|
this.rootNode.$emit('add-node', node)
|
||||||
root.$emit('add-node', node)
|
|
||||||
},
|
},
|
||||||
|
|
||||||
dragStart(e) {
|
dragStart(e) {
|
||||||
if (!(this.model.dragDisabled || this.model.disabled)) {
|
if (!(this.model.dragDisabled || this.model.disabled)) {
|
||||||
compInOperation = this
|
compInOperation = this
|
||||||
// for firefox
|
// for firefox
|
||||||
e.dataTransfer.setData("data","data");
|
e.dataTransfer.setData('data', 'data')
|
||||||
e.dataTransfer.effectAllowed = 'move'
|
e.dataTransfer.effectAllowed = 'move'
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -242,7 +302,8 @@
|
|||||||
},
|
},
|
||||||
dragEnter() {
|
dragEnter() {
|
||||||
if (!compInOperation) return
|
if (!compInOperation) return
|
||||||
if (this.model.isLeaf) return
|
if (compInOperation.model.id === this.model.id || !compInOperation || this.model.isLeaf)
|
||||||
|
return
|
||||||
this.isDragEnterNode = true
|
this.isDragEnterNode = true
|
||||||
},
|
},
|
||||||
dragLeave() {
|
dragLeave() {
|
||||||
@@ -250,67 +311,69 @@
|
|||||||
},
|
},
|
||||||
drop() {
|
drop() {
|
||||||
if (!compInOperation) return
|
if (!compInOperation) return
|
||||||
const oldParent = compInOperation.model.parent;
|
const oldParent = compInOperation.model.parent
|
||||||
compInOperation.model.moveInto(this.model)
|
compInOperation.model.moveInto(this.model)
|
||||||
this.isDragEnterNode = false
|
this.isDragEnterNode = false
|
||||||
var node = this.getRootNode();
|
this.rootNode.$emit('drop', {
|
||||||
node.$emit('drop', {target: this.model, node: compInOperation.model, src: oldParent})
|
target: this.model,
|
||||||
|
node: compInOperation.model,
|
||||||
|
src: oldParent
|
||||||
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
dragEnterUp () {
|
dragEnterUp() {
|
||||||
if (!compInOperation) return
|
if (!compInOperation) return
|
||||||
this.isDragEnterUp = true
|
this.isDragEnterUp = true
|
||||||
},
|
},
|
||||||
dragOverUp (e) {
|
dragOverUp(e) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
dragLeaveUp () {
|
dragLeaveUp() {
|
||||||
if (!compInOperation) return
|
if (!compInOperation) return
|
||||||
this.isDragEnterUp = false
|
this.isDragEnterUp = false
|
||||||
},
|
},
|
||||||
dropBefore () {
|
dropBefore() {
|
||||||
if (!compInOperation) return
|
if (!compInOperation) return
|
||||||
const oldParent = compInOperation.model.parent;
|
const oldParent = compInOperation.model.parent
|
||||||
compInOperation.model.insertBefore(this.model)
|
compInOperation.model.insertBefore(this.model)
|
||||||
this.isDragEnterUp = false
|
this.isDragEnterUp = false
|
||||||
var node = this.getRootNode();
|
this.rootNode.$emit('drop-before', {
|
||||||
node.$emit('drop-before', {target: this.model, node: compInOperation.model, src: oldParent})
|
target: this.model,
|
||||||
|
node: compInOperation.model,
|
||||||
|
src: oldParent
|
||||||
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
dragEnterBottom () {
|
dragEnterBottom() {
|
||||||
if (!compInOperation) return
|
if (!compInOperation) return
|
||||||
this.isDragEnterBottom = true
|
this.isDragEnterBottom = true
|
||||||
},
|
},
|
||||||
dragOverBottom (e) {
|
dragOverBottom(e) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
dragLeaveBottom () {
|
dragLeaveBottom() {
|
||||||
if (!compInOperation) return
|
if (!compInOperation) return
|
||||||
this.isDragEnterBottom = false
|
this.isDragEnterBottom = false
|
||||||
},
|
},
|
||||||
dropAfter () {
|
dropAfter() {
|
||||||
if (!compInOperation) return
|
if (!compInOperation) return
|
||||||
const oldParent = compInOperation.model.parent;
|
const oldParent = compInOperation.model.parent
|
||||||
compInOperation.model.insertAfter(this.model)
|
compInOperation.model.insertAfter(this.model)
|
||||||
this.isDragEnterBottom = false
|
this.isDragEnterBottom = false
|
||||||
var node = this.getRootNode();
|
this.rootNode.$emit('drop-after', {
|
||||||
node.$emit('drop-after', {target: this.model, node: compInOperation.model, src: oldParent})
|
target: this.model,
|
||||||
},
|
node: compInOperation.model,
|
||||||
getRootNode() {
|
src: oldParent
|
||||||
var node = this.$parent
|
})
|
||||||
while (node._props.model.name !== 'root') {
|
|
||||||
node = node.$parent
|
|
||||||
}
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" rel="stylesheet/less">
|
<style lang="less" rel="stylesheet/less">
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'icomoon';
|
font-family: 'icomoon';
|
||||||
src: url('fonts/icomoon.eot?ui1hbx');
|
src: url('fonts/icomoon.eot?ui1hbx');
|
||||||
src: url('fonts/icomoon.eot?ui1hbx#iefix') format('embedded-opentype'),
|
src: url('fonts/icomoon.eot?ui1hbx#iefix') format('embedded-opentype'),
|
||||||
@@ -319,9 +382,9 @@
|
|||||||
url('fonts/icomoon.svg?ui1hbx#icomoon') format('svg');
|
url('fonts/icomoon.svg?ui1hbx#icomoon') format('svg');
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vtl-icon {
|
.vtl-icon {
|
||||||
/* use !important to prevent issues with browser extensions that change fonts */
|
/* use !important to prevent issues with browser extensions that change fonts */
|
||||||
font-family: 'icomoon' !important;
|
font-family: 'icomoon' !important;
|
||||||
speak: none;
|
speak: none;
|
||||||
@@ -343,35 +406,34 @@
|
|||||||
&:hover {
|
&:hover {
|
||||||
color: blue;
|
color: blue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.vtl-icon-file:before {
|
.vtl-icon-file:before {
|
||||||
content: "\e906";
|
content: '\e906';
|
||||||
}
|
}
|
||||||
.vtl-icon-folder:before {
|
.vtl-icon-folder:before {
|
||||||
content: "\e907";
|
content: '\e907';
|
||||||
}
|
}
|
||||||
.vtl-icon-caret-down:before {
|
.vtl-icon-caret-down:before {
|
||||||
content: "\e901";
|
content: '\e901';
|
||||||
}
|
}
|
||||||
.vtl-icon-caret-right:before {
|
.vtl-icon-caret-right:before {
|
||||||
content: "\e900";
|
content: '\e900';
|
||||||
}
|
}
|
||||||
.vtl-icon-edit:before {
|
.vtl-icon-edit:before {
|
||||||
content: "\e902";
|
content: '\e902';
|
||||||
}
|
}
|
||||||
.vtl-icon-folder-plus-e:before {
|
.vtl-icon-folder-plus-e:before {
|
||||||
content: "\e903";
|
content: '\e903';
|
||||||
}
|
}
|
||||||
.vtl-icon-plus:before {
|
.vtl-icon-plus:before {
|
||||||
content: "\e904";
|
content: '\e904';
|
||||||
}
|
}
|
||||||
.vtl-icon-trash:before {
|
.vtl-icon-trash:before {
|
||||||
content: "\e905";
|
content: '\e905';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vtl-border {
|
||||||
.vtl-border {
|
|
||||||
height: 5px;
|
height: 5px;
|
||||||
&.vtl-up {
|
&.vtl-up {
|
||||||
margin-top: -5px;
|
margin-top: -5px;
|
||||||
@@ -384,9 +446,9 @@
|
|||||||
border-bottom: 3px dashed blue;
|
border-bottom: 3px dashed blue;
|
||||||
/*background-color: blue;*/
|
/*background-color: blue;*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.vtl-node-main {
|
.vtl-node-main {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 5px 0 5px 1rem;
|
padding: 5px 0 5px 1rem;
|
||||||
@@ -408,13 +470,12 @@
|
|||||||
margin-left: 2rem;
|
margin-left: 2rem;
|
||||||
letter-spacing: 1px;
|
letter-spacing: 1px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vtl-item {
|
||||||
.vtl-item {
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.vtl-tree-margin {
|
.vtl-tree-margin {
|
||||||
margin-left: 2em;
|
margin-left: 2em;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
11
src/index.js
11
src/index.js
@@ -2,7 +2,12 @@
|
|||||||
* Created by ayou on 17/7/21.
|
* Created by ayou on 17/7/21.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import VueTreeList from "./VueTreeList";
|
import VueTreeList from './VueTreeList'
|
||||||
import { Tree, TreeNode } from "./Tree";
|
import { Tree, TreeNode } from './Tree'
|
||||||
|
|
||||||
export { Tree, TreeNode, VueTreeList };
|
VueTreeList.install = Vue => {
|
||||||
|
Vue.component(VueTreeList.name, VueTreeList)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default VueTreeList
|
||||||
|
export { Tree, TreeNode, VueTreeList }
|
||||||
|
26
src/tools.js
26
src/tools.js
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
var handlerCache
|
var handlerCache
|
||||||
|
|
||||||
exports.addHandler = function (element, type, handler) {
|
export const addHandler = function(element, type, handler) {
|
||||||
handlerCache = handler
|
handlerCache = handler
|
||||||
if (element.addEventListener) {
|
if (element.addEventListener) {
|
||||||
element.addEventListener(type, handler, false)
|
element.addEventListener(type, handler, false)
|
||||||
@@ -15,7 +15,7 @@ exports.addHandler = function (element, type, handler) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.removeHandler = function (element, type) {
|
export const removeHandler = function(element, type) {
|
||||||
if (element.removeEventListener) {
|
if (element.removeEventListener) {
|
||||||
element.removeEventListener(type, handlerCache, false)
|
element.removeEventListener(type, handlerCache, false)
|
||||||
} else if (element.detachEvent) {
|
} else if (element.detachEvent) {
|
||||||
@@ -25,7 +25,21 @@ exports.removeHandler = function (element, type) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// exports.fireFocusEvent = function (ele) {
|
// depth first search
|
||||||
// var event = new FocusEvent()
|
export const traverseTree = root => {
|
||||||
// ele.dispatch(event)
|
var newRoot = {}
|
||||||
// }
|
|
||||||
|
for (var k in root) {
|
||||||
|
if (k !== 'children' && k !== 'parent') {
|
||||||
|
newRoot[k] = root[k]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (root.children && root.children.length > 0) {
|
||||||
|
newRoot.children = []
|
||||||
|
for (var i = 0, len = root.children.length; i < len; i++) {
|
||||||
|
newRoot.children.push(traverseTree(root.children[i]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newRoot
|
||||||
|
}
|
||||||
|
53
tests/unit/__snapshots__/slot.spec.js.snap
Normal file
53
tests/unit/__snapshots__/slot.spec.js.snap
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`Slot render slot correctly 1`] = `
|
||||||
|
<div class="vtl">
|
||||||
|
<!---->
|
||||||
|
<div class="">
|
||||||
|
<div class="vtl">
|
||||||
|
<div id="t1" class="vtl-node vtl-tree-node">
|
||||||
|
<div class="vtl-border vtl-up"></div>
|
||||||
|
<div draggable="true" class="vtl-node-main"><span class="vtl-caret vtl-is-small"><i class="vtl-icon vtl-icon-caret-down"></i></span> <span><span class="tree-node-icon icon">❀</span></span>
|
||||||
|
<div class="vtl-node-content">
|
||||||
|
Node 1
|
||||||
|
</div>
|
||||||
|
<div class="vtl-operation" style="display: none;"><span title="add tree node"><span class="add-tree-node-icon">📂</span></span> <span title="add leaf node"><span class="icon">+</span></span> <span title="edit"><span class="icon">📃</span></span> <span title="delete"><span></span></span></div>
|
||||||
|
</div>
|
||||||
|
<div class="vtl-border vtl-bottom"></div>
|
||||||
|
</div>
|
||||||
|
<div class="vtl-tree-margin">
|
||||||
|
<div class="vtl">
|
||||||
|
<div id="t11" class="vtl-node vtl-leaf-node">
|
||||||
|
<div class="vtl-border vtl-up"></div>
|
||||||
|
<div draggable="true" class="vtl-node-main">
|
||||||
|
<!----> <span><span class="icon">🍃</span></span>
|
||||||
|
<div class="vtl-node-content">
|
||||||
|
Node 1-1
|
||||||
|
</div>
|
||||||
|
<div class="vtl-operation" style="display: none;">
|
||||||
|
<!---->
|
||||||
|
<!----> <span title="edit"><span class="icon">📃</span></span> <span title="delete"><span class="del-node-icon">✂️</span></span></div>
|
||||||
|
</div>
|
||||||
|
<!---->
|
||||||
|
</div>
|
||||||
|
<!---->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="vtl">
|
||||||
|
<div id="t2" class="vtl-node vtl-tree-node">
|
||||||
|
<div class="vtl-border vtl-up"></div>
|
||||||
|
<div draggable="true" class="vtl-node-main">
|
||||||
|
<!----> <span><span class="tree-node-icon icon">❀</span></span>
|
||||||
|
<div class="vtl-node-content">
|
||||||
|
Node 2
|
||||||
|
</div>
|
||||||
|
<div class="vtl-operation" style="display: none;"><span title="add tree node"><span class="add-tree-node-icon">📂</span></span> <span title="add leaf node"><span class="icon">+</span></span> <span title="edit"><span class="icon">📃</span></span> <span title="delete"><span class="del-node-icon">✂️</span></span></div>
|
||||||
|
</div>
|
||||||
|
<!---->
|
||||||
|
</div>
|
||||||
|
<!---->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
@@ -1,6 +1,6 @@
|
|||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import {mount} from '@vue/test-utils'
|
import { mount } from '@vue/test-utils'
|
||||||
import {Tree, VueTreeList} from '@/index'
|
import { Tree, VueTreeList } from '@/index'
|
||||||
|
|
||||||
describe('Drag', () => {
|
describe('Drag', () => {
|
||||||
let wrapper
|
let wrapper
|
||||||
@@ -36,14 +36,14 @@ describe('Drag', () => {
|
|||||||
pid: 0
|
pid: 0
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
wrapper = mount(VueTreeList, {propsData: {model: new Tree([])}})
|
wrapper = mount(VueTreeList, { propsData: { model: new Tree([]) } })
|
||||||
wrapper.setProps({model: tree})
|
wrapper.setProps({ model: tree })
|
||||||
})
|
})
|
||||||
|
|
||||||
it('drag before', done => {
|
it('drag before', done => {
|
||||||
const $tree2 = wrapper.find('#t2 .vtl-node-main')
|
const $tree2 = wrapper.find('#t2 .vtl-node-main')
|
||||||
const $tree1Up = wrapper.find('#t1 .vtl-up')
|
const $tree1Up = wrapper.find('#t1 .vtl-up')
|
||||||
$tree2.trigger('dragstart', { dataTransfer: { setData: () => {} }})
|
$tree2.trigger('dragstart', { dataTransfer: { setData: () => {} } })
|
||||||
$tree1Up.trigger('drop')
|
$tree1Up.trigger('drop')
|
||||||
Vue.nextTick(() => {
|
Vue.nextTick(() => {
|
||||||
expect(wrapper.find('.vtl-node').attributes('id')).toBe('t2')
|
expect(wrapper.find('.vtl-node').attributes('id')).toBe('t2')
|
||||||
@@ -54,10 +54,15 @@ describe('Drag', () => {
|
|||||||
it('drag after', done => {
|
it('drag after', done => {
|
||||||
const $tree3 = wrapper.find('#t3 .vtl-node-main')
|
const $tree3 = wrapper.find('#t3 .vtl-node-main')
|
||||||
const $tree1Bottom = wrapper.find('#t1 .vtl-bottom')
|
const $tree1Bottom = wrapper.find('#t1 .vtl-bottom')
|
||||||
$tree3.trigger('dragstart', { dataTransfer: { setData: () => {} }})
|
$tree3.trigger('dragstart', { dataTransfer: { setData: () => {} } })
|
||||||
$tree1Bottom.trigger('drop')
|
$tree1Bottom.trigger('drop')
|
||||||
Vue.nextTick(() => {
|
Vue.nextTick(() => {
|
||||||
expect(wrapper.findAll('.vtl-tree-node').at(2).attributes('id')).toBe('t3')
|
expect(
|
||||||
|
wrapper
|
||||||
|
.findAll('.vtl-tree-node')
|
||||||
|
.at(2)
|
||||||
|
.attributes('id')
|
||||||
|
).toBe('t3')
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -65,7 +70,7 @@ describe('Drag', () => {
|
|||||||
it('drag into', done => {
|
it('drag into', done => {
|
||||||
const $tree3 = wrapper.find('#t3 .vtl-node-main')
|
const $tree3 = wrapper.find('#t3 .vtl-node-main')
|
||||||
const $tree1 = wrapper.find('#t1 .vtl-node-main')
|
const $tree1 = wrapper.find('#t1 .vtl-node-main')
|
||||||
$tree3.trigger('dragstart', { dataTransfer: { setData: () => {} }})
|
$tree3.trigger('dragstart', { dataTransfer: { setData: () => {} } })
|
||||||
$tree1.trigger('drop')
|
$tree1.trigger('drop')
|
||||||
Vue.nextTick(() => {
|
Vue.nextTick(() => {
|
||||||
expect(wrapper.find('#t1 + .vtl-tree-margin .vtl-node').attributes('id')).toBe('t3')
|
expect(wrapper.find('#t1 + .vtl-tree-margin .vtl-node').attributes('id')).toBe('t3')
|
||||||
@@ -77,7 +82,7 @@ describe('Drag', () => {
|
|||||||
const snapshot = wrapper.html()
|
const snapshot = wrapper.html()
|
||||||
const $tree1 = wrapper.find('#t1 .vtl-node-main')
|
const $tree1 = wrapper.find('#t1 .vtl-node-main')
|
||||||
const $tree1Child = wrapper.find('#t12 .vtl-node-main')
|
const $tree1Child = wrapper.find('#t12 .vtl-node-main')
|
||||||
$tree1.trigger('dragstart', { dataTransfer: { setData: () => {} }})
|
$tree1.trigger('dragstart', { dataTransfer: { setData: () => {} } })
|
||||||
$tree1Child.trigger('drop')
|
$tree1Child.trigger('drop')
|
||||||
Vue.nextTick(() => {
|
Vue.nextTick(() => {
|
||||||
expect(wrapper.html()).toBe(snapshot)
|
expect(wrapper.html()).toBe(snapshot)
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import {mount} from '@vue/test-utils'
|
import { mount } from '@vue/test-utils'
|
||||||
import {Tree, VueTreeList} from '@/index'
|
import { Tree, VueTreeList } from '@/index'
|
||||||
|
|
||||||
describe('Operation', () => {
|
describe('Operation', () => {
|
||||||
let wrapper
|
let wrapper
|
||||||
@@ -26,8 +26,8 @@ describe('Operation', () => {
|
|||||||
pid: 0
|
pid: 0
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
wrapper = mount(VueTreeList, {propsData: {model: new Tree([])}})
|
wrapper = mount(VueTreeList, { propsData: { model: new Tree([]) } })
|
||||||
wrapper.setProps({model: tree})
|
wrapper.setProps({ model: tree })
|
||||||
})
|
})
|
||||||
|
|
||||||
it('delete leaf node', done => {
|
it('delete leaf node', done => {
|
||||||
@@ -75,7 +75,7 @@ describe('Operation', () => {
|
|||||||
const $input = wrapper.find('#t1 .vtl-input')
|
const $input = wrapper.find('#t1 .vtl-input')
|
||||||
$input.element.value = 'New Node 1'
|
$input.element.value = 'New Node 1'
|
||||||
$input.trigger('input')
|
$input.trigger('input')
|
||||||
var event = new KeyboardEvent('keyup', {keyCode: 13})
|
var event = new KeyboardEvent('keyup', { keyCode: 13 })
|
||||||
window.dispatchEvent(event)
|
window.dispatchEvent(event)
|
||||||
Vue.nextTick(() => {
|
Vue.nextTick(() => {
|
||||||
expect(wrapper.find('#t1').text()).toBe('New Node 1')
|
expect(wrapper.find('#t1').text()).toBe('New Node 1')
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import {mount} from '@vue/test-utils'
|
import { mount } from '@vue/test-utils'
|
||||||
import {Tree, VueTreeList} from '@/index'
|
import { Tree, VueTreeList } from '@/index'
|
||||||
|
|
||||||
describe('Render', () => {
|
describe('Render', () => {
|
||||||
it('render correctly', () => {
|
it('render correctly', () => {
|
||||||
|
91
tests/unit/slot.spec.js
Normal file
91
tests/unit/slot.spec.js
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
import Vue from 'vue'
|
||||||
|
import { mount } from '@vue/test-utils'
|
||||||
|
import { Tree, VueTreeList } from '@/index'
|
||||||
|
|
||||||
|
describe('Slot', () => {
|
||||||
|
let wrapper
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
const tree = new Tree([
|
||||||
|
{
|
||||||
|
name: 'Node 1',
|
||||||
|
id: 't1',
|
||||||
|
pid: 0,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: 'Node 1-1',
|
||||||
|
id: 't11',
|
||||||
|
isLeaf: true,
|
||||||
|
pid: 't1'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Node 2',
|
||||||
|
id: 't2',
|
||||||
|
pid: 0
|
||||||
|
}
|
||||||
|
])
|
||||||
|
wrapper = mount(VueTreeList, {
|
||||||
|
propsData: { model: new Tree([]) },
|
||||||
|
scopedSlots: {
|
||||||
|
addTreeNodeIcon() {
|
||||||
|
return <span class='add-tree-node-icon'>📂</span>
|
||||||
|
},
|
||||||
|
addLeafNodeIcon() {
|
||||||
|
return <span class='icon'>+</span>
|
||||||
|
},
|
||||||
|
editNodeIcon() {
|
||||||
|
return <span class='icon'>📃</span>
|
||||||
|
},
|
||||||
|
delNodeIcon(slotProps) {
|
||||||
|
return slotProps.model.isLeaf || !slotProps.model.children ? (
|
||||||
|
<span class='del-node-icon'>✂️</span>
|
||||||
|
) : (
|
||||||
|
<span />
|
||||||
|
)
|
||||||
|
},
|
||||||
|
leafNodeIcon() {
|
||||||
|
return <span class='icon'>🍃</span>
|
||||||
|
},
|
||||||
|
treeNodeIcon(slotProps) {
|
||||||
|
return (
|
||||||
|
<span class='tree-node-icon icon'>
|
||||||
|
{slotProps.model.children &&
|
||||||
|
slotProps.model.children.length > 0 &&
|
||||||
|
!slotProps.expanded
|
||||||
|
? '🌲'
|
||||||
|
: '❀'}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
wrapper.setProps({ model: tree })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('render slot correctly', () => {
|
||||||
|
expect(wrapper).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('toggle tree node show different icon', done => {
|
||||||
|
const $caretDown = wrapper.find('.vtl-icon-caret-down')
|
||||||
|
expect(wrapper.find('#t1 .tree-node-icon').text()).toBe('❀')
|
||||||
|
$caretDown.trigger('click')
|
||||||
|
Vue.nextTick(() => {
|
||||||
|
expect(wrapper.exists('.vtl-icon-caret-right')).toBe(true)
|
||||||
|
expect(wrapper.find('#t1 .tree-node-icon').text()).toBe('🌲')
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('dont show ✂️ after add child ', done => {
|
||||||
|
const $addTreeNodeIcon = wrapper.find('#t2 .add-tree-node-icon')
|
||||||
|
expect(wrapper.find('#t2 .del-node-icon').exists()).toBe(true)
|
||||||
|
$addTreeNodeIcon.trigger('click')
|
||||||
|
Vue.nextTick(() => {
|
||||||
|
expect(wrapper.find('#t2 .del-node-icon').exists()).toBe(false)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
5
vue.config.js
Normal file
5
vue.config.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
module.exports = {
|
||||||
|
css: {
|
||||||
|
extract: false
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user