init project

This commit is contained in:
ayou
2017-07-22 00:13:14 +08:00
commit 05ae84e77e
19 changed files with 3498 additions and 0 deletions

10
.babelrc Normal file
View File

@@ -0,0 +1,10 @@
{
"presets": ["es2015", "stage-2"],
"plugins": ["transform-runtime"],
"comments": false,
"env": {
"test": {
"plugins": [ "istanbul" ]
}
}
}

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
node_modules
npm-debug.log
.idea
coverage

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

2671
dist/img/fontawesome-webfont.912ec66.svg vendored Normal file

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 434 KiB

26
dist/vue-tree.min.js vendored Normal file

File diff suppressed because one or more lines are too long

134
examples/demo1.html Normal file
View File

@@ -0,0 +1,134 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="with=device-width,initial-scale=1.0,
maximum-scale=1.0,minimum-scale=1.0,uses-scalable=no">
<title>vue-date-range</title>
<link href="https://cdn.bootcss.com/bulma/0.4.2/css/bulma.min.css" rel="stylesheet">
<style>
html {
/*background: linear-gradient(to bottom right, #00ebcb, #dafffa)*/
}
.container {
max-width: 1024px !important;
background-color: #fff;
padding: 0 0.5rem;
background-color: #fff;
margin-top: 1rem;
/*box-shadow: ;*/
}
.date-container, .selected-container {
margin: 1rem auto;
padding: 0 !important;
min-width: 300px;
max-width: 400px !important;
}
.wrapper {
min-height: 100vh;
padding: 0 0.5rem;
}
.column {
padding-top: 0;
padding-bottom: 0.5rem;
}
@media screen and (min-width: 768px) {
.control:not(:last-child) {
margin-bottom: 0.2rem;
}
.is-full {
flex: 1;
}
.is-horizontal {
display: flex;
align-items: center;
}
.label {
margin-right: 1rem;
}
}
.date-picker {
position: relative;
}
.calendar-wrapper {
border: 1px solid #bbbbbb;
position: absolute;
min-width: 300px;
max-width: 400px;
z-index: 999;
}
@media all and (max-width: 375px) {
.calendar-wrapper {
width: 120%;
max-width: 120%;
left: 50%;
transform: translateX(-50%);
}
}
.mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 998;
}
</style>
</head>
<body>
<div id="app" class="wrapper is-fullwidth">
<vue-tree :model="data"></vue-tree>
</div>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="../dist/vue-tree.min.js"></script>
<script>
new Vue({
el: '#app',
components: {
'VueTree': vuetree.VueTree
},
data () {
return {
data: new vuetree.Tree([
{
name: 'Node 1-1',
id: 1,
pid: 0,
children: [
{
name: 'Node 2-1',
id: 2,
pid: 1
}
]
},
{
name: 'Node 1-2',
id: 3,
pid: 0
},
{
name: 'Node 1-3',
id: 4,
pid: 0
}
])
}
}
})
</script>
</body>
</html>

70
package.json Normal file
View File

@@ -0,0 +1,70 @@
{
"name": "vue-tree",
"version": "1.0.0",
"description": "A vue component for tree structure.",
"main": "dist/vue-tree.min.js",
"scripts": {
"test": "karma start --single-run",
"unit": "karma start --watch",
"coveralls": "npm run test -- --report lcovonly && cat ./coverage/lcov.info | coveralls",
"build": "webpack"
},
"repository": {
"type": "git",
"url": "git+https://github.com/ParadeTo/vue-date-range.git"
},
"keywords": [
"vue",
"datepicker",
"date",
"range",
"moment"
],
"author": "ayou",
"license": "ISC",
"bugs": {
"url": "https://github.com/ParadeTo/vue-date-range/issues"
},
"homepage": "https://github.com/ParadeTo/vue-date-range#readme",
"dependencies": {
"font-awesome": "4.7.0",
"jquery": "^3.2.1"
},
"devDependencies": {
"autoprefixer": "^6.4.0",
"babel-core": "^6.0.0",
"babel-eslint": "^7.0.0",
"babel-loader": "^6.0.0",
"babel-plugin-istanbul": "^3.0.0",
"babel-plugin-transform-runtime": "^6.0.0",
"babel-preset-es2015": "^6.0.0",
"babel-preset-stage-2": "^6.0.0",
"babel-register": "^6.0.0",
"chai": "^3.5.0",
"cross-env": "^5.0.1",
"css-loader": "^0.26.2",
"file-loader": "^0.10.1",
"isparta": "^4.0.0",
"isparta-loader": "^2.0.0",
"karma": "^1.7.0",
"karma-coverage": "^1.1.1",
"karma-coveralls": "^1.1.2",
"karma-mocha": "^1.3.0",
"karma-phantomjs-launcher": "^1.0.4",
"karma-sinon-chai": "^1.3.1",
"karma-webpack": "^2.0.3",
"less": "^2.7.2",
"less-loader": "^2.2.3",
"mocha": "^3.4.2",
"phantomjs-prebuilt": "^2.1.14",
"sinon": "^2.3.5",
"sinon-chai": "^2.11.0",
"sourcemap": "^0.1.0",
"url-loader": "^0.5.7",
"vue": "^2.3.4",
"vue-loader": "^11.1.3",
"vue-style-loader": "^2.0.3",
"vue-template-compiler": "^2.2.0",
"webpack": "^1.13.2"
}
}

3
readme.md Normal file
View File

@@ -0,0 +1,3 @@
# TODO
add test

195
src/Tree.js Normal file
View 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
View 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

Binary file not shown.

16
src/fonts/icomoon.svg Executable file
View 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="&#x20;" horiz-adv-x="512" d="" />
<glyph unicode="&#xe900;" 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="&#xe901;" 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="&#xe902;" 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="&#xe903;" 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="&#xe904;" 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="&#xe905;" 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

Binary file not shown.

BIN
src/fonts/icomoon.woff Executable file

Binary file not shown.

7
src/index.js Normal file
View 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

51
webpack.config.js Normal file
View File

@@ -0,0 +1,51 @@
var webpack = require('webpack');
var path = require('path');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'vue-tree.min.js',
library: 'vuetree',
libraryTarget: 'umd'
},
module: {
loaders: [
{
test: /\.vue$/,
loader: 'vue'
},
{
test: /\.js$/,
loader: 'babel',
include: [path.join(__dirname, 'src')],
exclude: /node_modules/
},
{
test: /\.(woff2?|eot|ttf|otf|svg)(\?.*)?$/,
loader: 'url-loader',
query: {
limit: 10000,
name: 'fonts/[name].[hash:7].[ext]'
}
}
]
},
vue: {
loaders: {
less: 'vue-style!css!less'
},
postcss: [
require('autoprefixer')({
browsers: ['iOS >= 7', 'Android >= 4.1']
})
]
},
plugins: [
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
})
]
}