├── .babelrc ├── .editorconfig ├── .gitignore ├── README.md ├── index.html ├── karma.conf.js ├── package.json ├── src ├── App.vue ├── assets │ └── logo.png ├── components │ ├── index.js │ └── tree │ │ ├── index.js │ │ └── src │ │ ├── tree.scss │ │ ├── tree.utils.js │ │ └── tree.vue ├── main.js └── mock │ └── tree.api.js ├── test ├── tree.spec.js └── tree.utils.spec.js ├── webpack.config.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { "modules": false }], 4 | "stage-3" 5 | ], 6 | "plugins": ["transform-vue-jsx"] 7 | } 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | npm-debug.log 5 | yarn-error.log 6 | package-lock.json 7 | 8 | # Editor directories and files 9 | .idea 10 | *.suo 11 | *.ntvs* 12 | *.njsproj 13 | *.sln 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-tree 2 | 3 | > A Vue.js project 4 | 5 | ## Build Setup 6 | 7 | ``` bash 8 | # install dependencies 9 | npm install 10 | 11 | # serve with hot reload at localhost:8080 12 | npm run dev 13 | 14 | # build for production with minification 15 | npm run build 16 | ``` 17 | #### 1.总体预览 18 | ![](https://user-gold-cdn.xitu.io/2018/7/10/164821089b645f33?w=327&h=188&f=png&s=4708) 19 | 20 | #### 2.编辑 21 | ![](https://user-gold-cdn.xitu.io/2018/7/10/16482123006c40ec?w=431&h=168&f=png&s=4802) 22 | 23 | #### 3.新增 24 | 25 | ![](https://user-gold-cdn.xitu.io/2018/7/10/1648213e54241c98?w=443&h=197&f=png&s=5921) 26 | 27 | #### 4.删除 28 | 29 | ![](https://user-gold-cdn.xitu.io/2018/7/10/1648214c5f0c2d1c?w=900&h=268&f=png&s=12175) 30 | 31 | #### 5.提示 32 | 33 | ![](https://user-gold-cdn.xitu.io/2018/7/10/164821a53a3c97e2?w=374&h=124&f=png&s=4465) 34 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | vue-tree 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | var webpackConfig = require('./webpack.config') 2 | 3 | module.exports = function (config) { 4 | config.set({ 5 | frameworks: ['mocha', 'chai'], 6 | 7 | files: [ 8 | 'test/**/**.spec.js' 9 | ], 10 | 11 | preprocessors: { 12 | '**/*.spec.js': ['webpack', 'sourcemap'] 13 | }, 14 | 15 | webpack: webpackConfig, 16 | 17 | reporters: ['spec'], 18 | 19 | browsers: ['Chrome'] 20 | }) 21 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-tree", 3 | "description": "A Vue.js project", 4 | "version": "1.0.0", 5 | "author": "luoyide ", 6 | "license": "MIT", 7 | "private": true, 8 | "scripts": { 9 | "dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot", 10 | "build": "cross-env NODE_ENV=production webpack --progress --hide-modules", 11 | "test": "karma start --single-run" 12 | }, 13 | "dependencies": { 14 | "element-ui": "^2.4.3", 15 | "prettier": "^1.12.1", 16 | "vue": "^2.5.11" 17 | }, 18 | "browserslist": [ 19 | "> 1%", 20 | "last 2 versions", 21 | "not ie <= 8" 22 | ], 23 | "devDependencies": { 24 | "@vue/test-utils": "^1.0.0-beta.16", 25 | "babel-core": "^6.26.0", 26 | "babel-helper-vue-jsx-merge-props": "^2.0.3", 27 | "babel-loader": "^7.1.2", 28 | "babel-plugin-syntax-jsx": "^6.18.0", 29 | "babel-plugin-transform-vue-jsx": "^3.7.0", 30 | "babel-preset-env": "^1.6.0", 31 | "babel-preset-stage-3": "^6.24.1", 32 | "chai": "^4.1.2", 33 | "cross-env": "^5.0.5", 34 | "css-loader": "^0.28.7", 35 | "file-loader": "^1.1.4", 36 | "karma": "^2.0.2", 37 | "karma-chai": "^0.1.0", 38 | "karma-chrome-launcher": "^2.2.0", 39 | "karma-mocha": "^1.3.0", 40 | "karma-sourcemap-loader": "^0.3.7", 41 | "karma-spec-reporter": "0.0.32", 42 | "karma-webpack": "^3.0.0", 43 | "mocha": "^5.2.0", 44 | "node-sass": "^4.9.2", 45 | "punycode": "^2.1.1", 46 | "sass-loader": "^7.0.3", 47 | "vue-loader": "^13.0.5", 48 | "vue-template-compiler": "^2.4.4", 49 | "webpack": "^3.6.0", 50 | "webpack-dev-server": "^2.9.1" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lyd9607/vue-tree/c4a736d21be103bf216b5c7a475cb8d04bca0e77/src/assets/logo.png -------------------------------------------------------------------------------- /src/components/index.js: -------------------------------------------------------------------------------- 1 | import Tree from './tree' 2 | 3 | const components = [ 4 | Tree 5 | ] 6 | 7 | const install = (Vue) => { 8 | if (install.installed) { 9 | return 10 | } 11 | 12 | components.forEach(component => { 13 | Vue.use(component) 14 | }) 15 | 16 | // todo 17 | } 18 | export default { 19 | install 20 | } -------------------------------------------------------------------------------- /src/components/tree/index.js: -------------------------------------------------------------------------------- 1 | import Tree from './src/tree.vue' 2 | 3 | Tree.install = function(Vue) { 4 | Vue.component(Tree.name, Tree) 5 | } 6 | 7 | export default Tree -------------------------------------------------------------------------------- /src/components/tree/src/tree.scss: -------------------------------------------------------------------------------- 1 | 2 | .ly-tree-container { 3 | margin: 20px 0 20px 20px; 4 | width: 60%; 5 | padding: 20px; 6 | 7 | span { 8 | font-size: 14px; 9 | } 10 | 11 | .el-tree > .el-tree-node > .el-tree-node__content:first-child { 12 | &::before, 13 | &::after { 14 | border: none; 15 | } 16 | } 17 | 18 | .ly-visible { 19 | margin-left: 50px; 20 | visibility: hidden; 21 | } 22 | 23 | .ly-edit__text { 24 | width: 25%; 25 | height: 25px; 26 | border: 1px solid #e6e6e6; 27 | border-radius: 3px; 28 | color: #666; 29 | text-indent: 10px; 30 | } 31 | 32 | .ly-tree__loading { 33 | color: #666; 34 | font-weight: bold; 35 | } 36 | 37 | .ly-tree-node { 38 | flex: 1; 39 | display: flex; 40 | align-items: center; 41 | // justify-content: space-between; 42 | justify-content: flex-start; 43 | font-size: 14px; 44 | padding-right: 8px; 45 | } 46 | 47 | .ly-tree-node > div > span:last-child { 48 | display: inline-block; 49 | width: 110px; 50 | text-align: left; 51 | } 52 | 53 | .ly-tree-node > span:last-child { 54 | display: inline-block; 55 | width: 110px; 56 | text-align: left; 57 | } 58 | 59 | .el-tree-node .el-tree-node__content { 60 | height: 30px; 61 | 62 | &:hover .ly-visible { 63 | visibility: visible; 64 | } 65 | 66 | &::before, 67 | &::after { 68 | content: ''; 69 | position: absolute; 70 | right: auto; 71 | } 72 | 73 | &::before { 74 | border-left: 1px solid #e6e6e6; 75 | bottom: 50px; 76 | height: 100%; 77 | top: 0; 78 | width: 1px; 79 | margin-left: -5px; 80 | margin-top: -15px; 81 | } 82 | 83 | &::after { 84 | border-top: 1px solid #e6e6e6; 85 | height: 20px; 86 | top: 14px; 87 | width: 10px; 88 | margin-left: -5px; 89 | } 90 | } 91 | 92 | .el-tree .el-tree-node { 93 | position: relative; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/components/tree/src/tree.utils.js: -------------------------------------------------------------------------------- 1 | export function getEditContent(h, data, node) { 2 | let self = this 3 | return ( 4 | 5 | self.close(data, node) } 9 | > 10 | 取消 11 | 12 | self.editMsg(data, node) } 16 | > 17 | 确认 18 | 19 | 20 | ) 21 | } 22 | 23 | export function getDefaultContent(h, data, node) { 24 | let self = this 25 | return ( 26 |
27 | { 28 | self.is_superuser && 29 | ( 30 | self.update(node, data) } 34 | > 35 | 编辑 36 | 37 | 38 | { 39 | data.level !== 6 && 40 | self.append(node, data) } 44 | > 45 | 添加 46 | 47 | } 48 | 49 | { 50 | data.level !== 1 && 51 | self.remove(node, data) } 55 | > 56 | 删除 57 | 58 | } 59 | ) 60 | } 61 |
62 | ) 63 | } 64 | -------------------------------------------------------------------------------- /src/components/tree/src/tree.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 248 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Elements from 'element-ui' 3 | import Components from './components' 4 | import App from './App.vue' 5 | 6 | Vue.use(Elements) 7 | Vue.use(Components) 8 | 9 | new Vue({ 10 | el: '#app', 11 | render: h => h(App) 12 | }) 13 | -------------------------------------------------------------------------------- /src/mock/tree.api.js: -------------------------------------------------------------------------------- 1 | let id = 1000 2 | 3 | export let data = [ 4 | { 5 | "id": 1, 6 | "name": "技术部", 7 | "level": 1, 8 | "child": [ 9 | { 10 | "id": 2, 11 | "name": "运维组", 12 | "level": 2, 13 | "child": [ 14 | { 15 | "id": 3, 16 | "name": "godo", 17 | "level": 3, 18 | "child": [] 19 | } 20 | ] 21 | }, 22 | { 23 | "id": 4, 24 | "name": "测试组", 25 | "level": 2, 26 | "child": [] 27 | } 28 | ] 29 | } 30 | ] 31 | 32 | export let getServiceTree = () => { 33 | return { 34 | "code": 200, 35 | "message": 'ok', 36 | "is_superuser": true, //是否管理员,管理员可操作,非管理员看不见操作按钮 37 | "data": data 38 | } 39 | } 40 | 41 | export let delItem = (data, payload) => { 42 | for(let i = 0; i < data.length; i++) { 43 | if (data[i].id === payload.id) { 44 | data.splice(i, 1) 45 | break 46 | } 47 | if (data[i].child && data[i].child.length) { 48 | delItem(data[i].child, payload) 49 | } 50 | } 51 | } 52 | 53 | export let addItem = (data, payload) => { 54 | let addObj 55 | for(let i = 0; i < data.length; i++) { 56 | if (data[i].id === payload.id) { 57 | addObj = { 58 | id: id++, 59 | name: payload.name, 60 | level: data[i].level + 1, 61 | child: [] 62 | } 63 | data[i].child.unshift(addObj) 64 | break 65 | } 66 | 67 | if (data[i].child && data[i].child.length) { 68 | addItem(data[i].child, payload) 69 | } 70 | } 71 | } 72 | 73 | export let updateItem = (data, payload) => { 74 | for(let i = 0; i < data.length; i++) { 75 | if (data[i].id === payload.id) { 76 | data[i].name = payload.name 77 | break 78 | } 79 | 80 | if (data[i].child && data[i].child.length) { 81 | updateItem(data[i].child, payload) 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /test/tree.spec.js: -------------------------------------------------------------------------------- 1 | // import { expect } from 'chai' 2 | // import { shallowMount } from '@vue/test-utils' 3 | // import Tree from '../src/components/tree/src/tree.vue' 4 | 5 | // describe('tree.vue', () => { 6 | // it('increments count when button is clicked', () => { 7 | // const wrapper = shallowMount(Counter) 8 | // wrapper.find('button').trigger('click') 9 | // expect(wrapper.find('div').text()).contains('1') 10 | // }) 11 | // }) 12 | -------------------------------------------------------------------------------- /test/tree.utils.spec.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lyd9607/vue-tree/c4a736d21be103bf216b5c7a475cb8d04bca0e77/test/tree.utils.spec.js -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var webpack = require('webpack') 3 | 4 | module.exports = { 5 | entry: './src/main.js', 6 | output: { 7 | path: path.resolve(__dirname, './dist'), 8 | publicPath: '/dist/', 9 | filename: 'build.js' 10 | }, 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.css$/, 15 | use: [ 16 | 'vue-style-loader', 17 | 'css-loader' 18 | ], 19 | }, { 20 | test: /\.scss$/, 21 | use: [ 22 | 'vue-style-loader', 23 | 'css-loader', 24 | 'sass-loader' 25 | ] 26 | }, { 27 | test: /\.vue$/, 28 | loader: 'vue-loader', 29 | options: { 30 | loaders: { 31 | } 32 | // other vue-loader options go here 33 | } 34 | }, 35 | { 36 | test: /\.js$/, 37 | loader: 'babel-loader', 38 | exclude: /node_modules/ 39 | }, 40 | { 41 | test: /\.(png|jpg|gif|svg)$/, 42 | loader: 'file-loader', 43 | options: { 44 | name: '[name].[ext]?[hash]' 45 | } 46 | } 47 | ] 48 | }, 49 | resolve: { 50 | alias: { 51 | 'vue$': 'vue/dist/vue.esm.js' 52 | }, 53 | extensions: ['*', '.js', '.vue', '.json'] 54 | }, 55 | devServer: { 56 | historyApiFallback: true, 57 | noInfo: true, 58 | overlay: true 59 | }, 60 | performance: { 61 | hints: false 62 | }, 63 | devtool: '#eval-source-map' 64 | } 65 | 66 | if (process.env.NODE_ENV === 'production') { 67 | module.exports.devtool = '#source-map' 68 | // http://vue-loader.vuejs.org/en/workflow/production.html 69 | module.exports.plugins = (module.exports.plugins || []).concat([ 70 | new webpack.DefinePlugin({ 71 | 'process.env': { 72 | NODE_ENV: '"production"' 73 | } 74 | }), 75 | new webpack.optimize.UglifyJsPlugin({ 76 | sourceMap: true, 77 | compress: { 78 | warnings: false 79 | } 80 | }), 81 | new webpack.LoaderOptionsPlugin({ 82 | minimize: true 83 | }) 84 | ]) 85 | } 86 | --------------------------------------------------------------------------------