init project
This commit is contained in:
195
src/Tree.js
Normal file
195
src/Tree.js
Normal file
@@ -0,0 +1,195 @@
|
||||
// used to record treenode's change
|
||||
let Record = {}
|
||||
// treenode's id
|
||||
let nodeId = 1
|
||||
|
||||
/**
|
||||
* Tree data struct
|
||||
* Created by ayou on 2017/7/20.
|
||||
* @param name: treenode's name
|
||||
* @param isLeaf: treenode is leaf node or not
|
||||
* @param id
|
||||
*/
|
||||
const TreeNode = function (name, isLeaf, id) {
|
||||
this.name = name
|
||||
this.id = (typeof id === 'undefined') ? ('new' + nodeId++) : id
|
||||
this.parent = null
|
||||
this.pid = null
|
||||
this.children = null
|
||||
this.isLeaf = !!isLeaf
|
||||
}
|
||||
|
||||
TreeNode.prototype.updateRecordProperty = function () {
|
||||
if (!Record[this.id]) {
|
||||
Record[this.id] = {}
|
||||
}
|
||||
|
||||
Record[this.id].name = this.name
|
||||
Record[this.id].id = this.id
|
||||
Record[this.id].pid = this.pid
|
||||
Record[this.id].isLeaf = this.isLeaf
|
||||
}
|
||||
|
||||
TreeNode.prototype.changeName = function (name) {
|
||||
this.name = name
|
||||
|
||||
this.updateRecordProperty()
|
||||
Record[this.id].modify = true
|
||||
}
|
||||
|
||||
TreeNode.prototype.addChildren = function (children, isNew) {
|
||||
if (!this.children) {
|
||||
this.children = []
|
||||
}
|
||||
|
||||
if (Array.isArray(children)) {
|
||||
for (let i = 0, len = children.length; i < len; i++) {
|
||||
const child = children[i]
|
||||
child.parent = this
|
||||
child.pid = this.id
|
||||
|
||||
if (isNew) {
|
||||
child.updateRecordProperty()
|
||||
Record[child.id].add = true
|
||||
}
|
||||
}
|
||||
this.children.concat(children)
|
||||
} else {
|
||||
const child = children
|
||||
child.parent = this
|
||||
child.pid = this.id
|
||||
this.children.push(child)
|
||||
|
||||
if (isNew) {
|
||||
child.updateRecordProperty()
|
||||
Record[child.id].add = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove self
|
||||
TreeNode.prototype.remove = function () {
|
||||
const parent = this.parent
|
||||
const index = parent.findChildIndex(this)
|
||||
parent.children.splice(index, 1)
|
||||
|
||||
this.updateRecordProperty()
|
||||
Record[this.id].remove = true
|
||||
}
|
||||
|
||||
// remove child
|
||||
TreeNode.prototype._removeChild = function (child) {
|
||||
for (var i = 0, len = this.children.length; i < len; i++) {
|
||||
if (this.children[i] === child) {
|
||||
this.children.splice(i, 1)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TreeNode.prototype.isTargetChild = function (target) {
|
||||
let parent = target.parent
|
||||
while (parent) {
|
||||
if (parent === this) {
|
||||
return true
|
||||
}
|
||||
parent = parent.parent
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
TreeNode.prototype.moveInto = function (target) {
|
||||
if (this.name === 'root' || this === target) {
|
||||
return
|
||||
}
|
||||
|
||||
// cannot move ancestor to child
|
||||
if (this.isTargetChild(target)) {
|
||||
return
|
||||
}
|
||||
|
||||
this.parent._removeChild(this)
|
||||
this.parent = target
|
||||
this.pid = target.id
|
||||
if (!target.children) {
|
||||
target.children = []
|
||||
}
|
||||
target.children.unshift(this)
|
||||
|
||||
this.updateRecordProperty()
|
||||
Record[this.id].targetId = target.id
|
||||
Record[this.id].move = true
|
||||
Record[this.id].moveType = 'inside'
|
||||
}
|
||||
|
||||
TreeNode.prototype.findChildIndex = function (child) {
|
||||
var index
|
||||
for (let i = 0, len = this.children.length; i < len; i++) {
|
||||
if (this.children[i] === child) {
|
||||
index = i
|
||||
break
|
||||
}
|
||||
}
|
||||
return index
|
||||
}
|
||||
|
||||
TreeNode.prototype._beforeInsert = function (target) {
|
||||
if (this.name === 'root' || this === target) {
|
||||
return
|
||||
}
|
||||
|
||||
// cannot move ancestor to child
|
||||
if (this.isTargetChild(target)) {
|
||||
return
|
||||
}
|
||||
|
||||
this.parent._removeChild(this)
|
||||
this.parent = target.parent
|
||||
this.pid = target.parent.id
|
||||
}
|
||||
|
||||
TreeNode.prototype.insertBefore = function (target) {
|
||||
this._beforeInsert(target)
|
||||
|
||||
const pos = target.parent.findChildIndex(target)
|
||||
target.parent.children.splice(pos, 0, this)
|
||||
|
||||
this.updateRecordProperty()
|
||||
Record[this.id].targetId = target.id
|
||||
Record[this.id].move = true
|
||||
Record[this.id].moveType = 'before'
|
||||
}
|
||||
|
||||
TreeNode.prototype.insertAfter = function (target) {
|
||||
this._beforeInsert(target)
|
||||
|
||||
const pos = target.parent.findChildIndex(target)
|
||||
target.parent.children.splice(pos + 1, 0, this)
|
||||
|
||||
this.updateRecordProperty()
|
||||
Record[this.id].targetId = target.id
|
||||
Record[this.id].move = true
|
||||
Record[this.id].moveType = 'after'
|
||||
}
|
||||
|
||||
function Tree(data) {
|
||||
this.root = new TreeNode('root', false, 0)
|
||||
this.initNode(this.root, data)
|
||||
return this.root
|
||||
}
|
||||
|
||||
Tree.prototype.initNode = function (node, data) {
|
||||
for (let i = 0, len = data.length; i < len; i++) {
|
||||
var _data = data[i]
|
||||
|
||||
var child = new TreeNode(_data.name, _data.attrs, _data.id)
|
||||
if (_data.children && _data.children.length > 0) {
|
||||
this.initNode(child, _data.children)
|
||||
}
|
||||
node.addChildren(child)
|
||||
}
|
||||
}
|
||||
|
||||
exports.Tree = Tree
|
||||
exports.TreeNode = TreeNode
|
||||
exports.Record = Record
|
311
src/VueTree.vue
Normal file
311
src/VueTree.vue
Normal file
@@ -0,0 +1,311 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="model.name !== 'root'">
|
||||
<div class="border up" :class="{'active': isDragEnterUp}"
|
||||
@drop="dropUp"
|
||||
@dragenter="dragEnterUp"
|
||||
@dragover='dragOverUp'
|
||||
@dragleave="dragLeaveUp"></div>
|
||||
<div class='tree-node' :id='model.id' :class="{'active': isDragEnterNode}"
|
||||
draggable="true"
|
||||
@click=""
|
||||
@dragstart='dragStart'
|
||||
@dragover='dragOver'
|
||||
@dragenter='dragEnter'
|
||||
@dragleave='dragLeave'
|
||||
@drop='drop'
|
||||
@dragend='dragEnd'
|
||||
@mouseover='mouseOver'
|
||||
@mouseout='mouseOut'>
|
||||
<span class="caret icon is-small" v-if="model.children && model.children.length > 0">
|
||||
<i class="vue-tree-icon" :class="caretClass" @click.prevent.stop="toggle"></i>
|
||||
</span>
|
||||
<div class="node-content" v-if="!editable">
|
||||
{{model.name}}
|
||||
</div>
|
||||
<input v-else class="input" type="text" ref="nodeInput" :value="model.name" @input="updateName" @blur="setUnEditable">
|
||||
<div class="operation" v-show="isHover">
|
||||
<span title="add tree node" @click.stop.prevent="addChild(false)" v-if="!model.isLeaf">
|
||||
<slot name="addTreeNode">
|
||||
<i class="vue-tree-icon icon-folder-plus-e"></i>
|
||||
</slot>
|
||||
</span>
|
||||
<span title="add tree node" @click.stop.prevent="addChild(true)" v-if="!model.isLeaf">
|
||||
<slot name="addLeafNode">
|
||||
<i class="vue-tree-icon icon-plus"></i>
|
||||
</slot>
|
||||
</span>
|
||||
<span title="edit" @click.stop.prevent="setEditable">
|
||||
<slot name="edit">
|
||||
<i class="vue-tree-icon icon-edit"></i>
|
||||
</slot>
|
||||
</span>
|
||||
<span title="delete" @click.stop.prevent="delNode">
|
||||
<slot name="edit">
|
||||
<i class="vue-tree-icon icon-trash"></i>
|
||||
</slot>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="model.children && model.children.length > 0 && expanded"
|
||||
class="border bottom"
|
||||
:class="{'active': isDragEnterBottom}"
|
||||
@drop="dropBottom"
|
||||
@dragenter="dragEnterBottom"
|
||||
@dragover='dragOverBottom'
|
||||
@dragleave="dragLeaveBottom"></div>
|
||||
</div>
|
||||
<div :class="{'tree-margin': model.name !== 'root'}" v-show="expanded" v-if="isFolder">
|
||||
<item v-for="model in model.children" :model="model" :key='model.id' :current-highlight='currentHighlight' :default-text='defaultText' :hover-color='hoverColor' :highlight-color='highlightColor'>
|
||||
</item>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Tree, TreeNode } from './Tree.js'
|
||||
import $ from 'jquery'
|
||||
|
||||
let fromComp = ''
|
||||
export default {
|
||||
data: function () {
|
||||
return {
|
||||
isHover: false,
|
||||
editable: false,
|
||||
isDragEnterUp: false,
|
||||
isDragEnterBottom: false,
|
||||
isDragEnterNode: false,
|
||||
expanded: true
|
||||
}
|
||||
},
|
||||
props: {
|
||||
model: Object
|
||||
},
|
||||
computed: {
|
||||
caretClass () {
|
||||
return this.expanded ? 'icon-caret-down' : 'icon-caret-right'
|
||||
},
|
||||
|
||||
isFolder() {
|
||||
return this.model.children &&
|
||||
this.model.children.length
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
const vm = this
|
||||
$(window).on('keyup', function (e) {
|
||||
// click enter
|
||||
if (e.keyCode === 13 && vm.editable) {
|
||||
vm.editable = false
|
||||
}
|
||||
})
|
||||
},
|
||||
beforeDestroy () {
|
||||
$(window).off('keyup')
|
||||
},
|
||||
methods: {
|
||||
updateName (e) {
|
||||
this.model.changeName(e.target.value)
|
||||
},
|
||||
|
||||
delNode () {
|
||||
const vm = this
|
||||
if (window.confirm('Are you sure?')) {
|
||||
vm.model.remove()
|
||||
}
|
||||
},
|
||||
|
||||
setEditable () {
|
||||
this.editable = true
|
||||
this.$nextTick(() => {
|
||||
$(this.$refs.nodeInput).trigger('focus')
|
||||
})
|
||||
},
|
||||
|
||||
setUnEditable () {
|
||||
this.editable = false
|
||||
},
|
||||
|
||||
toggle() {
|
||||
if (this.isFolder) {
|
||||
this.expanded = !this.expanded
|
||||
}
|
||||
},
|
||||
|
||||
mouseOver(e) {
|
||||
this.isHover = true
|
||||
},
|
||||
|
||||
mouseOut(e) {
|
||||
this.isHover = false
|
||||
},
|
||||
|
||||
addChild(isLeaf) {
|
||||
this.expanded = true
|
||||
var node = new TreeNode(name, isLeaf)
|
||||
this.model.addChildren(node, true)
|
||||
},
|
||||
|
||||
dragStart(e) {
|
||||
fromComp = this
|
||||
|
||||
e.dataTransfer.effectAllowed = 'move'
|
||||
return true
|
||||
},
|
||||
dragEnd(e) {
|
||||
fromComp = null
|
||||
},
|
||||
dragOver(e) {
|
||||
e.preventDefault()
|
||||
return true
|
||||
},
|
||||
dragEnter(e) {
|
||||
this.isDragEnterNode = true
|
||||
},
|
||||
dragLeave(e) {
|
||||
this.isDragEnterNode = false
|
||||
},
|
||||
drop(e) {
|
||||
fromComp.model.moveInto(this.model)
|
||||
this.isDragEnterNode = false
|
||||
},
|
||||
|
||||
dragEnterUp () {
|
||||
this.isDragEnterUp = true
|
||||
},
|
||||
dragOverUp (e) {
|
||||
e.preventDefault()
|
||||
return true
|
||||
},
|
||||
dragLeaveUp () {
|
||||
this.isDragEnterUp = false
|
||||
},
|
||||
dropUp () {
|
||||
fromComp.model.insertBefore(this.model)
|
||||
this.isDragEnterUp = false
|
||||
},
|
||||
|
||||
dragEnterBottom () {
|
||||
this.isDragEnterBottom = true
|
||||
},
|
||||
dragOverBottom (e) {
|
||||
e.preventDefault()
|
||||
return true
|
||||
},
|
||||
dragLeaveBottom () {
|
||||
this.isDragEnterBottom = false
|
||||
},
|
||||
dropBottom () {
|
||||
fromComp.model.insertAfter(this.model)
|
||||
this.isDragEnterBottom = false
|
||||
}
|
||||
},
|
||||
beforeCreate () {
|
||||
this.$options.components.item = require('./VueTree.vue')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" rel="stylesheet/less" scoped>
|
||||
@font-face {
|
||||
font-family: 'icomoon';
|
||||
src: url('fonts/icomoon.eot?ui1hbx');
|
||||
src: url('fonts/icomoon.eot?ui1hbx#iefix') format('embedded-opentype'),
|
||||
url('fonts/icomoon.ttf?ui1hbx') format('truetype'),
|
||||
url('fonts/icomoon.woff?ui1hbx') format('woff'),
|
||||
url('fonts/icomoon.svg?ui1hbx#icomoon') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.vue-tree-icon {
|
||||
/* use !important to prevent issues with browser extensions that change fonts */
|
||||
font-family: 'icomoon' !important;
|
||||
speak: none;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
font-variant: normal;
|
||||
text-transform: none;
|
||||
line-height: 1;
|
||||
|
||||
/* Better Font Rendering =========== */
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
&:hover {
|
||||
color: blue;
|
||||
}
|
||||
}
|
||||
|
||||
.icon-caret-down:before {
|
||||
content: "\e900";
|
||||
}
|
||||
.icon-caret-right:before {
|
||||
content: "\e901";
|
||||
}
|
||||
.icon-edit:before {
|
||||
content: "\e902";
|
||||
}
|
||||
.icon-folder-plus-e:before {
|
||||
content: "\e903";
|
||||
}
|
||||
.icon-plus:before {
|
||||
content: "\e904";
|
||||
}
|
||||
.icon-trash:before {
|
||||
content: "\e905";
|
||||
}
|
||||
|
||||
|
||||
.border {
|
||||
height: 5px;
|
||||
&.up {
|
||||
margin-top: -5px;
|
||||
background-color: transparent;
|
||||
}
|
||||
&.bottom {
|
||||
background-color: transparent;
|
||||
}
|
||||
&.active {
|
||||
border-bottom: 3px dashed blue;
|
||||
/*background-color: blue;*/
|
||||
}
|
||||
}
|
||||
|
||||
.tree-node {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 5px 0 5px 1rem;
|
||||
.input {
|
||||
border: none;
|
||||
max-width: 150px;
|
||||
border-bottom: 1px solid blue;
|
||||
}
|
||||
&:hover {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
&.active {
|
||||
outline: 2px dashed pink;
|
||||
}
|
||||
.caret {
|
||||
margin-left: -1rem;
|
||||
}
|
||||
/*.fa {*/
|
||||
/*color: #ddd;*/
|
||||
/*&:hover {*/
|
||||
/*color: blue;*/
|
||||
/*}*/
|
||||
/*}*/
|
||||
.operation {
|
||||
margin-left: 2rem;
|
||||
letter-spacing: 1.2px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.item {
|
||||
cursor: pointer;
|
||||
}
|
||||
.tree-margin {
|
||||
margin-left: 2em;
|
||||
}
|
||||
</style>
|
BIN
src/fonts/icomoon.eot
Executable file
BIN
src/fonts/icomoon.eot
Executable file
Binary file not shown.
16
src/fonts/icomoon.svg
Executable file
16
src/fonts/icomoon.svg
Executable file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
|
||||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<metadata>Generated by IcoMoon</metadata>
|
||||
<defs>
|
||||
<font id="icomoon" horiz-adv-x="1024">
|
||||
<font-face units-per-em="1024" ascent="960" descent="-64" />
|
||||
<missing-glyph horiz-adv-x="1024" />
|
||||
<glyph unicode=" " horiz-adv-x="512" d="" />
|
||||
<glyph unicode="" glyph-name="caret-down" d="M635.311 448q0-14.848-10.848-25.728l-256-256q-10.848-10.848-25.728-10.848t-25.728 10.848-10.848 25.728v512q0 14.848 10.848 25.728t25.728 10.848 25.728-10.848l256-256q10.848-10.848 10.848-25.728z" />
|
||||
<glyph unicode="" glyph-name="caret-right" d="M512 301.714q-14.848 0-25.746 10.825l-256 256q-10.825 10.825-10.825 25.746t10.825 25.746 25.746 10.825h512q14.848 0 25.746-10.825t10.825-25.746-10.825-25.746l-256-256q-10.825-10.825-25.746-10.825z" />
|
||||
<glyph unicode="" glyph-name="edit" d="M816.001 594.256c-11.151 0-20.203-9.053-20.203-20.201v-363.665c0-22.31-18.089-40.402-40.401-40.402h-505.083c-22.318 0-40.405 18.093-40.405 40.402v505.086c0 22.317 18.094 40.401 40.405 40.401h343.46c11.168 0 20.2 9.038 20.2 20.204 0 11.161-9.036 20.201-20.2 20.201h-363.675c-33.483 0-60.612-27.136-60.612-60.61v-545.494c0-33.481 27.13-60.612 60.612-60.612h545.494c33.482 0 60.613 27.133 60.613 60.612v383.877c-0.004 11.148-9.040 20.201-20.205 20.201zM417.158 397.194c7.893-7.868 20.674-7.868 28.572 0l363.068 365.049c7.889 7.894 7.889 20.689 0 28.572-7.89 7.893-20.678 7.893-28.563 0l-363.077-365.035c-7.897-7.891-7.897-20.692 0-28.586z" />
|
||||
<glyph unicode="" glyph-name="folder-plus-e" d="M946.471 884.169h-548.426l-42.856 51.918c-17.498 21.394-39.525 25.1-46.676 23.629h-230.922c-42.788 0-77.591-34.803-77.591-77.525v-688.284l6.327-39.784h19.857c10.467-7.603 23.121-11.751 36.469-11.751h509.37v65.392h-494.433l-12.194-1.020v675.448c0 6.704 5.486 12.133 12.194 12.133l227.025 0.256 43.551-52.108c6.96-11.43 22.799-23.693 46.424-23.693h551.88c6.709 0 12.133-5.49 12.133-12.198v-202.19h65.397v202.189c0 42.787-34.803 77.589-77.529 77.589zM988.451 391.215h-142.197v142.197c0 19.65-15.9 35.549-35.549 35.549s-35.549-15.9-35.549-35.549v-142.197h-142.197c-19.65 0-35.549-15.9-35.549-35.549s15.9-35.549 35.549-35.549h142.197v-142.197c0-19.65 15.9-35.549 35.549-35.549s35.549 15.9 35.549 35.549v142.197h142.197c19.65 0 35.549 15.9 35.549 35.549s-15.9 35.549-35.549 35.549z" />
|
||||
<glyph unicode="" glyph-name="plus" d="M863.328 478.66l-317.344-0.1v318.622c0 17.665-14.336 32.001-32 32.001s-32-14.336-32-32v-318.401l-322.465 0.177c-17.632 0-31.935-14.24-32-31.904-0.097-17.665 14.208-32.032 31.871-32.096l322.593-0.177v-319.167c0-17.696 14.336-32.001 31.999-32.001s32 14.303 32 32v318.946l317.216 0.1c17.632 0 31.935 14.24 32 31.905s-14.238 32.031-31.87 32.095z" />
|
||||
<glyph unicode="" glyph-name="trash" d="M607.904 191.952c-17.712 0-32 14.288-32 32v352.112c0 17.712 14.288 32 32 32s31.984-14.288 31.984-32v-351.936c0-0.033 0-0.073 0-0.112 0-17.68-14.31-32.019-31.98-32.064h-0.004zM415.936 191.952c-17.712 0-32 14.288-32 32v352.112c0 17.712 14.272 32 32 32s32-14.288 32-32v-351.936c0-0.024 0-0.052 0-0.080 0-17.692-14.315-32.041-31.995-32.096h-0.005zM928.016 736.032h-159.968v63.984c0 52.992-42.672 95.984-95.296 95.984h-320.816c-52.999-0.027-95.957-42.984-95.984-95.981v-63.987h-159.968c-17.712 0-32-14.288-32-32s14.272-32 32-32h832.032c0.014 0 0.031 0 0.048 0 17.638 0 31.936 14.298 31.936 31.936 0 0.023 0 0.045 0 0.068v-0.004c0 17.728-14.272 32-31.984 32zM319.952 800.016c0 17.552 14.448 32 32 32h320.816c17.536 0 31.312-14.112 31.312-32v-63.984h-384.128v63.984zM736.048 0h-447.92c-53.001 0.009-95.966 42.968-95.984 95.966v480.434c0 17.712 14.272 32 32 32s32-14.288 32-32v-480.432c0-17.712 14.448-32 32-32h448.096c17.712 0 32 14.288 32 32v479.232c0 17.712 14.288 32 32 32s31.984-14.288 31.984-32v-479.232c-0.192-52.816-43.2-95.968-96.176-95.968z" />
|
||||
</font></defs></svg>
|
After Width: | Height: | Size: 3.9 KiB |
BIN
src/fonts/icomoon.ttf
Executable file
BIN
src/fonts/icomoon.ttf
Executable file
Binary file not shown.
BIN
src/fonts/icomoon.woff
Executable file
BIN
src/fonts/icomoon.woff
Executable file
Binary file not shown.
7
src/index.js
Normal file
7
src/index.js
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Created by ayou on 17/7/21.
|
||||
*/
|
||||
exports.VueTree = require('./VueTree.vue')
|
||||
exports.TreeNode = require('./Tree.js').TreeNode
|
||||
exports.Tree = require('./Tree.js').Tree
|
||||
exports.Record = require('./Tree.js').Record
|
Reference in New Issue
Block a user