├── .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 | 
19 |
20 | #### 2.编辑
21 | 
22 |
23 | #### 3.新增
24 |
25 | 
26 |
27 | #### 4.删除
28 |
29 | 
30 |
31 | #### 5.提示
32 |
33 | 
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 |
2 |
3 |
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 |
2 |
3 |
8 |
9 |
10 |
13 | 取 消
14 |
15 |
19 | 确 定
20 |
21 |
22 |
23 |
24 |
25 |
26 |
32 |
33 |
34 |
35 |
36 |
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 |
--------------------------------------------------------------------------------