├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .github └── ISSUE_TEMPLATE │ ├── Bug_Report.md │ └── Feature_Request.md ├── .gitignore ├── .postcssrc.js ├── LICENSE ├── README.md ├── README_ZH.md ├── babel.config.js ├── build ├── build.dev.js ├── build.js ├── check-versions.js ├── utils.js ├── vue-loader.conf.js ├── webpack.base.conf.js ├── webpack.dev.conf.js └── webpack.prod.conf.js ├── doc ├── version-en.md └── version.md ├── package.json ├── src ├── DragNode.vue ├── VueDragTree.vue ├── index.js ├── mixins │ └── eventMixins.js └── util.js └── static └── ml-vue-drag-tree.gif /.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 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | build/*.js 2 | config/*.js 3 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | // http://eslint.org/docs/user-guide/configuring 2 | 3 | module.exports = { 4 | root: true, 5 | parser: 'babel-eslint', 6 | parserOptions: { 7 | sourceType: 'module' 8 | }, 9 | env: { 10 | browser: true, 11 | }, 12 | // https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style 13 | extends: 'standard', 14 | // required to lint *.vue files 15 | plugins: [ 16 | 'html' 17 | ], 18 | // add your custom rules here 19 | 'rules': { 20 | // allow paren-less arrow functions 21 | 'arrow-parens': 0, 22 | // allow async-await 23 | 'generator-star-spacing': 0, 24 | // allow debugger during development 25 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Bug_Report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug描述 3 | about: 🐞🐞🐞 遇到问题了 4 | --- 5 | 6 | ### 异常现象 7 | 8 | ### 重现链接 9 | 10 | ### 重现步骤 11 | 12 | ### 期望的效果 13 | 14 | ### 建议方案 15 | 16 | ### 环境 17 | - 模块: 18 | - 程序版本: 19 | - 浏览器: 20 | - node/npm 版本:[e.g. Node 10.15/npm 6.9]] 21 | 22 | ### 其他说明 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Feature_Request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: 🚀🚀🚀 需要新功能 4 | --- 5 | 6 | ### 描述你的业务场景 7 | 8 | ### 描述你的解决方案 9 | 10 | ### 进度 11 | 12 | ### 未完成 13 | 14 | ### 问题 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | test/e2e/reports 7 | selenium-debug.log 8 | dist/ 9 | 10 | package-lock.json 11 | 12 | # Editor directories and files 13 | .idea 14 | *.suo 15 | *.ntvs* 16 | *.njsproj 17 | *.sln 18 | -------------------------------------------------------------------------------- /.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | "plugins": { 5 | // to edit target browsers: use "browserlist" field in package.json 6 | "autoprefixer": {} 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 版权所有(c)2019 https://github.com/shuiRong 2 | 3 | 反996许可证版本1.0 4 | 5 | 在符合下列条件的情况下,特此免费向任何得到本授权作品的副本(包括源代码、文件和/或相关内容,以下统称为“授权作品”)的个人和法人实体授权:被授权个人或法人实体有权以任何目的处置授权作品,包括但不限于使用、复制,修改,衍生利用、散布,发布和再许可: 6 | 7 | 1. 个人或法人实体必须在许可作品的每个再散布或衍生副本上包含以上版权声明和本许可证,不得自行修改。 8 | 2. 个人或法人实体必须严格遵守与个人实际所在地或个人出生地或归化地、或法人实体注册地或经营地(以较严格者为准)的司法管辖区所有适用的与劳动和就业相关法律、法规、规则和标准。如果该司法管辖区没有此类法律、法规、规章和标准或其法律、法规、规章和标准不可执行,则个人或法人实体必须遵守国际劳工标准的核心公约。 9 | 3. 个人或法人不得以任何方式诱导或强迫其全职或兼职员工或其独立承包人以口头或书面形式同意直接或间接限制、削弱或放弃其所拥有的,受相关与劳动和就业有关的法律、法规、规则和标准保护的权利或补救措施,无论该等书面或口头协议是否被该司法管辖区的法律所承认,该等个人或法人实体也不得以任何方法限制其雇员或独立承包人向版权持有人或监督许可证合规情况的有关当局报告或投诉上述违反许可证的行为的权利。 10 | 11 | 该授权作品是"按原样"提供,不做任何明示或暗示的保证,包括但不限于对适销性、特定用途适用性和非侵权性的保证。在任何情况下,无论是在合同诉讼、侵权诉讼或其他诉讼中,版权持有人均不承担因本软件或本软件的使用或其他交易而产生、引起或与之相关的任何索赔、损害或其他责任。 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ml-vue-drag-tree 2 | 3 | > this pro is base on shuirong's [vue-drag-tree](https://github.com/shuirong/vue-drag-tree),thanks very much😘😘😘 4 | 5 | [](https://www.npmjs.com/package/ml-vue-drag-tree)[](https://www.npmjs.com/package/ml-vue-drag-tree)[](https://opensource.org/licenses/MIT) 6 | 7 | > It's a tree🎄 components(Vue2.x) that allow you to drag and drop the node to exchange their data .Support **nested and list** 8 | 9 | **Feature** 10 | 11 | - **Drag and Drop** the tree node, even between two different levels 12 | - **Controls** whether a particular node can **be dragged** and whether the node can be plugged into other nodes 13 | - Support **nested and list** 14 | - data and view **two-way-data-binding** 15 | 16 | **[中文](README_ZH.md)** || **Please Star! if it's helpful** 17 | **[Example Project](https://github.com/qq240814476/ml-vue-drag-tree-demo)** 18 | 19 | ### Preview 20 | 21 | --- 22 | 23 |  24 | 25 | ### Getting Start 26 | 27 | --- 28 | 29 | **Install** 30 | 31 | `npm install ml-vue-drag-tree --save` 32 | 33 | **Usage** 34 | 35 | [A Simple Project Using ml-vue-drag-tree](https://github.com/qq240814476/ml-vue-drag-tree-demo) 36 | 37 | 38 | 39 | main.js 40 | 41 | ```vue 42 | import Vue from 'vue' 43 | import VueDragTree from 'ml-vue-drag-tree' 44 | import 'ml-vue-drag-tree/dist/vue-drag-tree.min.css' 45 | 46 | Vue.use(VueDragTree) 47 | ``` 48 | 49 | ### 1.3.0 New Features 50 | 51 | --- 52 | 53 | - style support ie 54 | - css -> less 55 | - fixed mouse icon problems when dragover menu-item 56 | 57 | 58 | ### API 59 | 60 | --- 61 | 62 | **Attributes** 63 | 64 | | Name | Description | Type | Default | 65 | | :--------- | :------------------------------------------------- | :------- | :------- | 66 | | data | data of the tree | Array | -- | 67 | | allowDrag | Judging which node can be dragged | Function | ()=>true | 68 | | allowDrop | Judging which node can be plugged into other nodes | Function | ()=>true | 69 | | openNames | opened submenu names | Array | ()=>[] | 70 | | activeName | actived menuitem id | String | '' | 71 | | maxCharNum | text max show number in chinese char | Number | 6 | 72 | 73 | 74 | 75 | **Events** 76 | 77 | | Name | Description | arguments | 78 | | -------------------- | ---------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | 79 | | current-node-clicked | Tell you which node was clicked | (model,component) model: node data was clicked. component: VNode data for the node was clicked | 80 | | on-data-change | When Tree Data Change | (val) new **tree** data | 81 | | on-node-change | When Node Data Change,return **event's type** and changed **parent node's data** and **drag node's data** | (type, parentNode, node) type: 'remove'/'add' event's **type** parentNode: changed parentNode node: dragged **node's** data | 82 | | on-open-name-change | When openNames change, return new openNames Array | (arr) new **openNames** | 83 | | drag | The `drag` event is fired every few hundred milliseconds as an node is being dragged by the user | (model,component,e) model: node data was dragged. component: VNode data for the node was dragged; e: drag event | 84 | | drag-enter | The `drag-enter` event is fired when a dragged node enters a valid drop target | (model,component,e) model: data of the valid drop target; component: VNode of the valid drop target; e: drag event | 85 | | drag-leave | The `drag-leave` event is fired when a dragged node leaves a valid drop target | (model,component,e) model: data of the valid drop target; component: VNode of the valid drop target; e: drag event | 86 | | drag-over | The `drag-over` event is fired when an node is being dragged over a valid drop target | (model,component,e) model: data of the valid drop target; component: VNode of the valid drop target; e: drag event | 87 | | drag-end | The `drag-end` event is fired when a drag operation is being ended | (model,component,e) model: node data was dragged. component: VNode data for the node was dragged; e: drag event | 88 | | drop | The **drop** event is fired when an node is dropped on a valid drop target. | (model,component,e) model: data of the valid drop target; component: VNode of the valid drop target; e: drag event | 89 | 90 | 91 | 92 | 93 | 94 | **License** 95 | 96 | --- 97 | 98 | [The 996ICU License (996ICU)](LICENSE) 99 | -------------------------------------------------------------------------------- /README_ZH.md: -------------------------------------------------------------------------------- 1 | # ml-vue-drag-tree 2 | 3 | > 本项目基于shuirong的 [vue-drag-tree](https://github.com/shuirong/vue-drag-tree),感谢😘😘😘 4 | 5 | [](https://www.npmjs.com/package/ml-vue-drag-tree)[](https://www.npmjs.com/package/ml-vue-drag-tree)[](https://opensource.org/licenses/MIT) 6 | 7 | > 这是一个Vue2.x的树🎄组件。它允许你去拖拽任意节点,当然,“交换”会反映在data数据中。支持排序和嵌套 8 | 9 | **功能** 10 | 11 | - 对节点进行**任意拖拽** 12 | - **控制**特定节点**是否可拖**、**是否可放置**其他节点 13 | - 根据样式判断拖拽操作用于排序还是嵌套 14 | - 数据和视图双向绑定 15 | 16 | **[EN](README.md)** || **如果它对你有帮助的话,请Star支持!!!** 17 | **[示例项目](https://github.com/qq240814476/ml-vue-drag-tree-demo)** 18 | 19 | ### 预览 20 | 21 | ------ 22 | 23 |  24 | 25 | ### 快速开始 26 | 27 | ------ 28 | 29 | **Install** 30 | 31 | `npm install ml-vue-drag-tree --save` 32 | 33 | **Usage** 34 | 35 | [一个简单的项目,用了ml-vue-drag-tree](https://github.com/qq240814476/ml-vue-drag-tree-demo) 36 | 37 | main.js 38 | 39 | ```vue 40 | import Vue from 'vue' 41 | import VueDragTree from 'ml-vue-drag-tree' 42 | import 'ml-vue-drag-tree/dist/vue-drag-tree.min.css' 43 | 44 | Vue.use(VueDragTree) 45 | ``` 46 | 47 | ### 1.3.0 新特性 48 | 49 | --- 50 | 51 | - 样式支持ie 52 | - 调整css -> less 53 | - 修复鼠标在menu-item移动过程中出现 禁用图标 54 | 55 | 56 | ### 接口 57 | 58 | --- 59 | 60 | **属性** 61 | 62 | | 属性名 | 描述 | 类型 | 默认值 | 63 | | :--------- | :---------------------------------------------------- | :------- | :------- | 64 | | data | 节点树的数据 | Array | -- | 65 | | allowDrag | 判断哪些节点可以被拖拽(return true表示允许) | Function | ()=>true | 66 | | allowDrop | 判断哪些节点可以被塞入其他节点(return true表示允许) | Function | ()=>true | 67 | | openNames | 打开菜单的array | Array | ()=>[] | 68 | | activeName | 当前激活状态的id | String | '' | 69 | | maxCharNum | text最多显示中文字符长度 | Number | 6 | 70 | 71 | 72 | 73 | **事件** 74 | 75 | | 事件名 | 描述 | 参数 | 76 | | -------------------- | ------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------- | 77 | | current-node-clicked | 告诉你哪个节点被点击了,这个节点所在的组件是哪个 | (model,component) model: 当前被点击节点的数据. component: 当前节点所在的树组件 | 78 | | on-data-change | 树数据变化时触发 | (data) new **tree** data | 79 | | on-node-data-change | 节点数据改变时触发,返回**事件类型**,**受影响父节点**和**拖动的节点**数据 | (type, parentNode, node) type:'remove'/'add'事件类型 parentNode: 受影响父节点 node: 拖动的节点的数据 | 80 | | on-open-name-change | 子级菜单打开状态改变时触发,返回openNames数组 | (arr) new **openNames** | 81 | | drag | 节点被拖动时触发的 `drag` 事件 | (model,component,e) model: 当前被拖动节点的数据; component: 当前被拖动节点所在的树组件(VNode); e: 拖拽事件 | 82 | | drag-enter | 当被拖动节点进入有效的放置目标时, `dragenter` 事件被触发 | (model,component,e) model: 有效放置目标节点的数据; component: 有效放置目标节点所在的树组件(VNode); e: 拖拽事件 | 83 | | drag-leave | 当被拖动节点离开有效的放置目标时, `dragleave` 事件被触发 | (model,component,e) model: 有效放置目标节点的数据; component: 有效放置目标节点所在的树组件(VNode); e: 拖拽事件 | 84 | | drag-over | 当节点被拖拽到一个有效的放置目标上时,触发 `dragover `事件 | (model,component,e) model: 有效放置目标节点的数据; component: 有效放置目标节点所在的树组件(VNode); e: 拖拽事件 | 85 | | drag-end | 拖放事件在拖放操作结束时触发 | (model,component,e) model: 当前被拖动节点的数据; component: 当前被拖动节点所在的树组件(VNode); e: 拖拽事件 | 86 | | drop | 当节点被放置到一个有效的防止目标上时,`drop`被触发 | (model,component,e) model: 当前被拖动节点的数据; component: 当前被拖动节点所在的树组件(VNode); e: 拖拽事件 | 87 | 88 | 89 | 90 | 91 | **License** 92 | 93 | ------ 94 | 95 | [The 996ICU License (996ICU)](LICENSE) 96 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | '@babel/preset-env', 5 | { 6 | targets: { 7 | chrome: 5, 8 | ie: 9, 9 | }, 10 | }, 11 | ], 12 | ], 13 | } 14 | -------------------------------------------------------------------------------- /build/build.dev.js: -------------------------------------------------------------------------------- 1 | require('./check-versions')() 2 | 3 | var ora = require('ora') 4 | var rm = require('rimraf') 5 | var path = require('path') 6 | var chalk = require('chalk') 7 | var webpack = require('webpack') 8 | var webpackConfig = require('./webpack.dev.conf') 9 | 10 | var spinner = ora('building for production...') 11 | spinner.start() 12 | 13 | rm(path.join(path.resolve(__dirname, '../dist'), 'static'), err => { 14 | if (err) throw err 15 | webpack(webpackConfig, function (err, stats) { 16 | spinner.stop() 17 | if (err) throw err 18 | process.stdout.write(stats.toString({ 19 | colors: true, 20 | modules: false, 21 | children: false, 22 | chunks: false, 23 | chunkModules: false 24 | }) + '\n\n') 25 | 26 | console.log(chalk.cyan(' Build complete.\n')) 27 | console.log(chalk.yellow( 28 | ' Tip: built files are meant to be served over an HTTP server.\n' + 29 | ' Opening index.html over file:// won\'t work.\n' 30 | )) 31 | }) 32 | }) 33 | -------------------------------------------------------------------------------- /build/build.js: -------------------------------------------------------------------------------- 1 | require('./check-versions')() 2 | 3 | var ora = require('ora') 4 | var rm = require('rimraf') 5 | var path = require('path') 6 | var chalk = require('chalk') 7 | var webpack = require('webpack') 8 | var webpackConfig = require('./webpack.prod.conf') 9 | 10 | var spinner = ora('building for production...') 11 | spinner.start() 12 | 13 | rm(path.join(path.resolve(__dirname, '../dist'), 'static'), err => { 14 | if (err) throw err 15 | webpack(webpackConfig, function (err, stats) { 16 | spinner.stop() 17 | if (err) throw err 18 | process.stdout.write(stats.toString({ 19 | colors: true, 20 | modules: false, 21 | children: false, 22 | chunks: false, 23 | chunkModules: false 24 | }) + '\n\n') 25 | 26 | console.log(chalk.cyan(' Build complete.\n')) 27 | console.log(chalk.yellow( 28 | ' Tip: built files are meant to be served over an HTTP server.\n' + 29 | ' Opening index.html over file:// won\'t work.\n' 30 | )) 31 | }) 32 | }) 33 | -------------------------------------------------------------------------------- /build/check-versions.js: -------------------------------------------------------------------------------- 1 | var chalk = require('chalk') 2 | var semver = require('semver') 3 | var packageConfig = require('../package.json') 4 | var shell = require('shelljs') 5 | function exec (cmd) { 6 | return require('child_process').execSync(cmd).toString().trim() 7 | } 8 | 9 | var versionRequirements = [ 10 | { 11 | name: 'node', 12 | currentVersion: semver.clean(process.version), 13 | versionRequirement: packageConfig.engines.node 14 | }, 15 | ] 16 | 17 | if (shell.which('npm')) { 18 | versionRequirements.push({ 19 | name: 'npm', 20 | currentVersion: exec('npm --version'), 21 | versionRequirement: packageConfig.engines.npm 22 | }) 23 | } 24 | 25 | module.exports = function () { 26 | var warnings = [] 27 | for (var i = 0; i < versionRequirements.length; i++) { 28 | var mod = versionRequirements[i] 29 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { 30 | warnings.push(mod.name + ': ' + 31 | chalk.red(mod.currentVersion) + ' should be ' + 32 | chalk.green(mod.versionRequirement) 33 | ) 34 | } 35 | } 36 | 37 | if (warnings.length) { 38 | console.log('') 39 | console.log(chalk.yellow('To use this template, you must update following to modules:')) 40 | console.log() 41 | for (var i = 0; i < warnings.length; i++) { 42 | var warning = warnings[i] 43 | console.log(' ' + warning) 44 | } 45 | console.log() 46 | process.exit(1) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /build/utils.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var ExtractTextPlugin = require('extract-text-webpack-plugin') 3 | 4 | exports.assetsPath = function (_path) { 5 | var assetsSubDirectory = 'static' 6 | return path.posix.join(assetsSubDirectory, _path) 7 | } 8 | 9 | exports.cssLoaders = function (options) { 10 | options = options || {} 11 | 12 | var cssLoader = { 13 | loader: 'css-loader', 14 | options: { 15 | sourceMap: options.sourceMap 16 | } 17 | } 18 | 19 | // generate loader string to be used with extract text plugin 20 | function generateLoaders (loader, loaderOptions) { 21 | var loaders = [cssLoader] 22 | if (loader) { 23 | loaders.push({ 24 | loader: loader + '-loader', 25 | options: Object.assign({}, loaderOptions, { 26 | sourceMap: options.sourceMap 27 | }) 28 | }) 29 | } 30 | 31 | // Extract CSS when that option is specified 32 | // (which is the case during production build) 33 | if (options.extract) { 34 | return ExtractTextPlugin.extract({ 35 | use: loaders, 36 | fallback: 'vue-style-loader' 37 | }) 38 | } else { 39 | return ['vue-style-loader'].concat(loaders) 40 | } 41 | } 42 | 43 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html 44 | return { 45 | css: generateLoaders(), 46 | postcss: generateLoaders(), 47 | // less: generateLoaders('less'), 48 | sass: generateLoaders('sass', { indentedSyntax: true }), 49 | scss: generateLoaders('sass'), 50 | stylus: generateLoaders('stylus'), 51 | styl: generateLoaders('stylus') 52 | } 53 | } 54 | 55 | // Generate loaders for standalone style files (outside of .vue) 56 | exports.styleLoaders = function (options) { 57 | var output = [] 58 | var loaders = exports.cssLoaders(options) 59 | for (var extension in loaders) { 60 | var loader = loaders[extension] 61 | output.push({ 62 | test: new RegExp('\\.' + extension + '$'), 63 | use: loader 64 | }) 65 | } 66 | return output 67 | } 68 | -------------------------------------------------------------------------------- /build/vue-loader.conf.js: -------------------------------------------------------------------------------- 1 | var utils = require('./utils') 2 | 3 | module.exports = { 4 | loaders: utils.cssLoaders({ 5 | sourceMap: true, 6 | extract: true 7 | }) 8 | } 9 | -------------------------------------------------------------------------------- /build/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var utils = require('./utils') 3 | var ExtractTextPlugin = require('extract-text-webpack-plugin') 4 | var vueLoaderConfig = require('./vue-loader.conf') 5 | 6 | function resolve (dir) { 7 | return path.join(__dirname, '..', dir) 8 | } 9 | 10 | module.exports = { 11 | entry: { 12 | app: './src/index.js' 13 | }, 14 | resolve: { 15 | extensions: ['.js', '.vue', '.json'], 16 | alias: { 17 | 'vue$': 'vue/dist/vue.esm.js', 18 | '@': resolve('src') 19 | } 20 | }, 21 | module: { 22 | rules: [ 23 | /* config.module.rule('less') */ 24 | { 25 | test: /\.less$/, 26 | use: ExtractTextPlugin.extract({ 27 | use: [ 28 | 'css-loader', 29 | { 30 | loader: 'less-loader', 31 | options: { 32 | sourceMap: false 33 | } 34 | } 35 | ] 36 | }), 37 | }, 38 | { 39 | test: /\.vue$/, 40 | loader: 'vue-loader', 41 | options: vueLoaderConfig 42 | }, 43 | { 44 | test: /\.js$/, 45 | loader: 'babel-loader', 46 | include: [resolve('src'), resolve('test')] 47 | }, 48 | { 49 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 50 | loader: 'url-loader', 51 | options: { 52 | limit: 10000, 53 | name: utils.assetsPath('img/[name].[hash:7].[ext]') 54 | } 55 | }, 56 | { 57 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 58 | loader: 'url-loader', 59 | options: { 60 | limit: 10000, 61 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]') 62 | } 63 | } 64 | ] 65 | }, 66 | } 67 | -------------------------------------------------------------------------------- /build/webpack.dev.conf.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var utils = require('./utils') 3 | var merge = require('webpack-merge') 4 | var baseWebpackConfig = require('./webpack.base.conf') 5 | 6 | var webpackConfig = merge(baseWebpackConfig, { 7 | mode:'development', 8 | module: { 9 | rules: utils.styleLoaders({ 10 | sourceMap: true, 11 | extract: true 12 | }) 13 | }, 14 | devtool:'source-map', 15 | output: { 16 | path: path.resolve(__dirname, '../dist'), 17 | publicPath: '', 18 | filename: 'vue-drag-tree.min.js', 19 | library: 'VueDragTree', 20 | libraryTarget: 'umd', 21 | umdNamedDefine: true 22 | }, 23 | externals: { 24 | vue: { 25 | root: 'Vue', 26 | commonjs: 'vue', 27 | commonjs2: 'vue', 28 | amd: 'vue' 29 | }, 30 | loadsh: 'loadsh' 31 | }, 32 | }) 33 | 34 | module.exports = webpackConfig 35 | -------------------------------------------------------------------------------- /build/webpack.prod.conf.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var utils = require('./utils') 3 | var merge = require('webpack-merge') 4 | var baseWebpackConfig = require('./webpack.base.conf') 5 | var ExtractTextPlugin = require('extract-text-webpack-plugin') 6 | var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin') 7 | const VueLoaderPlugin = require('vue-loader/lib/plugin') 8 | 9 | var webpackConfig = merge(baseWebpackConfig, { 10 | mode:'production', 11 | module: { 12 | rules: utils.styleLoaders({ 13 | sourceMap: true, 14 | extract: true 15 | }) 16 | }, 17 | output: { 18 | path: path.resolve(__dirname, '../dist'), 19 | publicPath: '', 20 | filename: 'vue-drag-tree.min.js', 21 | library: 'VueDragTree', 22 | libraryTarget: 'umd', 23 | umdNamedDefine: true 24 | }, 25 | externals: { 26 | vue: { 27 | root: 'Vue', 28 | commonjs: 'vue', 29 | commonjs2: 'vue', 30 | amd: 'vue' 31 | }, 32 | // loadsh: 'loadsh' 33 | }, 34 | plugins: [ 35 | new VueLoaderPlugin(), 36 | // extract css into its own file 37 | new ExtractTextPlugin({ 38 | filename: 'vue-drag-tree.min.css' 39 | }), 40 | // Compress extracted CSS. We are using this plugin so that possible 41 | // duplicated CSS from different components can be deduped. 42 | new OptimizeCSSPlugin() 43 | ] 44 | }) 45 | 46 | module.exports = webpackConfig 47 | -------------------------------------------------------------------------------- /doc/version-en.md: -------------------------------------------------------------------------------- 1 | ## Update Doc 2 | --- 3 | 4 | ### 1.3.0 New Features 5 | 6 | --- 7 | 8 | - style support ie 9 | - css -> less 10 | - fixed mouse icon problems when dragover menu-item 11 | 12 | ### 1.3.1 13 | 14 | - fix some bugs 15 | 16 | -------------------------------------------------------------------------------- /doc/version.md: -------------------------------------------------------------------------------- 1 | ## 更新说明 2 | --- 3 | 4 | ### 1.3.0 新特性 5 | 6 | --- 7 | 8 | - 样式支持ie 9 | - 调整css -> less 10 | - 修复鼠标在menu-item移动过程中出现 禁用图标 11 | 12 | ### 1.3.1 13 | 14 | - 修复由于computed不触发导致的问题(无法删除二级节点) 15 | 16 | ### 1.3.2 17 | 18 | #### Fixed 19 | 20 | - `on-node-data-change` 传出 `parentNode` children错误 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ml-vue-drag-tree", 3 | "version": "1.3.3", 4 | "description": "🐛A vue tree🎄🏝 component which support drag & drop 👆👇 and nested", 5 | "author": "malin<240814476@qq.com>", 6 | "private": false, 7 | "main": "dist/vue-drag-tree.min.js", 8 | "scripts": { 9 | "dev": "node build/dev-server.js", 10 | "start": "node build/dev-server.js", 11 | "build": "node build/build.js", 12 | "build:dev": "node build/build.dev.js", 13 | "lint": "./node_modules/.bin/eslint --fix --ext .js,.vue src" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git@github.com:qq240814476/ml-vue-drag-tree.git" 18 | }, 19 | "publishConfig": { 20 | "registry": "https://registry.npmjs.org/" 21 | }, 22 | "bugs": { 23 | "url": "https://github.com/qq240814476/ml-vue-drag-tree/issues" 24 | }, 25 | "files": [ 26 | "dist", 27 | "src" 28 | ], 29 | "keywords": [ 30 | "vue-drag-tree", 31 | "vue-drag", 32 | "drag", 33 | "drop", 34 | "tree", 35 | "vue", 36 | "vue-components", 37 | "vuedragtree", 38 | "dragdrop", 39 | "drag and drop", 40 | "draganddrop" 41 | ], 42 | "license": "MIT", 43 | "homepage": "https://github.com/qq240814476/ml-vue-drag-tree#readme", 44 | "dependencies": { 45 | "iview": "^3.4.0", 46 | "lodash": "^3.10.1", 47 | "vue": "^2.6.10" 48 | }, 49 | "devDependencies": { 50 | "@babel/core": "^7.5.5", 51 | "@babel/preset-env": "^7.5.5", 52 | "autoprefixer": "^6.7.2", 53 | "babel-eslint": "^10.0.2", 54 | "babel-loader": "^8.0.6", 55 | "chalk": "^1.1.3", 56 | "chromedriver": "^2.27.2", 57 | "connect-history-api-fallback": "^1.3.0", 58 | "copy-webpack-plugin": "^4.0.1", 59 | "cross-spawn": "^5.0.1", 60 | "css-loader": "^3.2.0", 61 | "eslint": "^3.19.0", 62 | "eslint-config-standard": "^6.2.1", 63 | "eslint-friendly-formatter": "^2.0.7", 64 | "eslint-loader": "^1.7.1", 65 | "eslint-plugin-html": "^2.0.0", 66 | "eslint-plugin-promise": "^3.4.0", 67 | "eslint-plugin-standard": "^2.0.1", 68 | "eventsource-polyfill": "^0.9.6", 69 | "express": "^4.14.1", 70 | "extract-text-webpack-plugin": "^4.0.0-beta.0", 71 | "file-loader": "^4.2.0", 72 | "friendly-errors-webpack-plugin": "^1.1.3", 73 | "html-webpack-plugin": "^2.28.0", 74 | "http-proxy-middleware": "^0.17.3", 75 | "less": "^3.9.0", 76 | "less-loader": "^5.0.0", 77 | "nightwatch": "^0.9.12", 78 | "node-sass": "^4.5.3", 79 | "opn": "^4.0.2", 80 | "optimize-css-assets-webpack-plugin": "^5.0.3", 81 | "ora": "^1.2.0", 82 | "rimraf": "^2.6.0", 83 | "sass-loader": "^6.0.6", 84 | "selenium-server": "^3.0.1", 85 | "semver": "^5.3.0", 86 | "shelljs": "^0.7.6", 87 | "url-loader": "^2.1.0", 88 | "vue-loader": "^15.7.1", 89 | "vue-style-loader": "^4.1.2", 90 | "vue-template-compiler": "^2.3.3", 91 | "webpack": "^4.41.0", 92 | "webpack-bundle-analyzer": "^3.5.2", 93 | "webpack-dev-middleware": "^3.7.1", 94 | "webpack-hot-middleware": "^2.25.0", 95 | "webpack-merge": "^4.2.2" 96 | }, 97 | "engines": { 98 | "node": ">= 4.0.0", 99 | "npm": ">= 3.0.0" 100 | }, 101 | "browserslist": [ 102 | "> 1%", 103 | "last 2 versions", 104 | "not ie <= 8" 105 | ] 106 | } 107 | -------------------------------------------------------------------------------- /src/DragNode.vue: -------------------------------------------------------------------------------- 1 | 2 | 10 | 22 | 26 | 27 | 32 | 34 | 35 | 36 | 38 | 39 | 40 | 41 | 42 | 46 | 47 | 48 | 49 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 281 | 282 | 409 | 410 | -------------------------------------------------------------------------------- /src/VueDragTree.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 26 | {{formatText(data.title)}} 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 141 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import VueDragTree from './VueDragTree.vue' 2 | 3 | const VueDragTreeComponent = { 4 | install: function (Vue) { 5 | if (typeof window !== 'undefined' && window.Vue) { 6 | Vue = window.Vue 7 | } 8 | Vue.component('VueDragTree', VueDragTree) 9 | } 10 | } 11 | 12 | export default VueDragTreeComponent 13 | -------------------------------------------------------------------------------- /src/mixins/eventMixins.js: -------------------------------------------------------------------------------- 1 | export default { 2 | methods: { 3 | removeChild (parent, node) { 4 | this.$emit('on-node-data-change', 'remove', parent, node) 5 | }, 6 | addChild (parent, node) { 7 | this.$emit('on-node-data-change', 'add', parent, node) 8 | }, 9 | endChange () { 10 | this.$emit('on-data-change', this.data) 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/util.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | const lang = require('lodash/lang') 3 | 4 | // 返回最顶层那个vue-drag-tree.vue组件 5 | const findRoot = which => { 6 | let ok = false 7 | let that = which 8 | while (!ok) { 9 | // 根据组件name来判断 10 | if (that.$options._componentTag === 'vue-drag-tree') { 11 | ok = true 12 | // 交换两者的数据 13 | break 14 | } 15 | that = that.$parent 16 | } 17 | return that 18 | } 19 | 20 | /** 21 | * 是否两节点为包含关系 22 | * 即:直系父与子的关系 23 | * @param {*} from 拖拽节点 24 | * @param {*} to 放置区域元素 25 | */ 26 | const hasInclude = (from, to) => { 27 | return from.$parent._uid === to._uid 28 | } 29 | 30 | /** 31 | * 可以触发Vue响应式的数组删除节点方法 32 | * @param {*} arr 节点数组 33 | * @param {*} id 删除node的id 34 | * @param {*} isRoot 是否是根节点默认为false 35 | */ 36 | const deleteNodeInVueResponsive = (arr, id, isRoot = false) => { 37 | if (isRoot) { 38 | arr.data = arr.data.filter( 39 | item => item.id !== id 40 | ) 41 | if (arr.data.length !== 0) { 42 | Vue.set(arr, 'data', arr.data) 43 | } 44 | } else { 45 | arr.children = arr.children.filter( 46 | item => item.id !== id 47 | ) 48 | if (arr.children.length !== 0) { 49 | Vue.set(arr, 'children', arr.children) 50 | } else { 51 | Vue.delete(arr, 'children') 52 | } 53 | } 54 | } 55 | 56 | /** 57 | * 可以触发Vue响应式的数组增加节点方法 58 | * @param {*} arr 节点数组 59 | * @param {*} node 添加的节点 60 | */ 61 | const addNodeInVueResponsive = (arr, node) => { 62 | // 再from节点添加到to节点中最后一位。 63 | if (arr.data) { 64 | Vue.set(arr.data, arr.data.length, node) 65 | } else { 66 | if (!arr.children) { 67 | Vue.set(arr, 'children', [node]) 68 | } else { 69 | Vue.set(arr.children, arr.children.length, node) 70 | } 71 | } 72 | } 73 | 74 | /** 75 | * 可以触发Vue响应式的数组交换节点位置方法 76 | * @param {*} newFrom 换位置的元素 77 | * @param {*} to 目标元素 78 | * @param {*} behind 是否插在目标后面,默认为true 79 | */ 80 | const exchangePosInVueResponsive = (newFrom, to, behind = true) => { 81 | let toParentModel = to.$parent.model || { id: null, children: to.$parent.data } 82 | let index = toParentModel.children.indexOf(to.model) 83 | let newIndex = behind ? index + 1 : index 84 | toParentModel.children.splice(newIndex, 0, newFrom) 85 | to.$set(toParentModel.children, newIndex, newFrom) 86 | return toParentModel 87 | } 88 | 89 | /** 90 | * 交换两节点数据 91 | * @param from 被拖拽节点组件Vnode数据 92 | * @param to 拖拽节点组件Vnode数据 93 | */ 94 | const exchangeData = (from, to, e) => { 95 | // 没放对地方,什么都不做 96 | if (!to.isDragChildHover && !to.isDragTopHover && !to.isDragBottomHover) { 97 | return 98 | } 99 | 100 | // 如果拖动节点和被拖动节点相同,return; 101 | if (from._uid === to._uid) { 102 | return 103 | } 104 | 105 | // 如果两者是父子关系且from是父节点,to是子节点,什么都不做 106 | if (hasInclude(to, from)) { 107 | return 108 | } 109 | 110 | let newFrom = lang.cloneDeep(from.model) 111 | let fromParentModel = from.$parent.model 112 | let toModel = to.model 113 | 114 | let parentNode = {} 115 | let root = findRoot(to) 116 | 117 | // 先将from从其父节点信息移除; 118 | if (from.$parent.$options._componentTag === 'vue-drag-tree') { 119 | /** 120 | * 找到vue-drag-tree的父组件(数据源头),在这里修改数据。 121 | */ 122 | deleteNodeInVueResponsive(from.$parent, newFrom.id, true) 123 | parentNode = {id: null, children: from.$parent.data} 124 | } else { 125 | deleteNodeInVueResponsive(fromParentModel, newFrom.id) 126 | parentNode = fromParentModel 127 | } 128 | 129 | // 发送删除节点事件 130 | root.removeChild(parentNode, newFrom) 131 | 132 | const bottom = to.isDragBottomHover 133 | const top = to.isDragTopHover 134 | from.$nextTick(() => { 135 | if (bottom) { 136 | // 交换位置,插到后面 137 | parentNode = exchangePosInVueResponsive(newFrom, to) 138 | } else if (top) { 139 | // 交换位置,插到前面 140 | parentNode = exchangePosInVueResponsive(newFrom, to, false) 141 | } else { 142 | // 嵌套添加 143 | addNodeInVueResponsive(toModel, newFrom) 144 | parentNode = toModel 145 | } 146 | 147 | Vue.set(root.data, 0, lang.cloneDeep(root.data[0])) 148 | // 发送添加节点事件 149 | root.addChild(parentNode, newFrom) 150 | root.endChange() 151 | }) 152 | } 153 | 154 | const charWidthTable = { 155 | 1: ['.', '!', '|', ','], 156 | 2: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 157 | 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 158 | 'W', 'X', 'Y', 'Z', '@', '~', '#', '$', '%', '^', '&', '*', '(', ')', '-', '=', '+', '{', '}', '[', ']', '\\', '/', '<', '>', 159 | '1', '2', '3', '4', '5', '6', '7', '8', '9', '0' 160 | ] 161 | } 162 | 163 | const getCharWidth = (char) => { 164 | for (let i = 0; i < charWidthTable[1].length; i++) { 165 | if (char === charWidthTable[1][i]) { 166 | return 1 167 | } 168 | } 169 | for (let i = 0; i < charWidthTable[2].length; i++) { 170 | if (char === charWidthTable[2][i]) { 171 | return 1.5 172 | } 173 | } 174 | return 3 175 | } 176 | 177 | export const shortText = function (text, length) { 178 | text = text || '' 179 | let showtextShort = text 180 | let short = true 181 | let strArr = text.split('') 182 | let strWidth = [] 183 | let sum = 0 184 | for (let i = 0; i < strArr.length; i++) { 185 | sum += getCharWidth(strArr[i]) 186 | strWidth.push(sum) 187 | } 188 | let lengthFlag = length * 3 189 | if (sum > lengthFlag) { 190 | short = false 191 | let splitPoint = 0 192 | for (let j = 0; j < strWidth.length; j++) { 193 | if (strWidth[j] > lengthFlag) { 194 | splitPoint = j 195 | break 196 | } 197 | } 198 | showtextShort = text.substring(0, splitPoint) + '...' 199 | } 200 | return short ? false : showtextShort 201 | } 202 | export { findRoot, exchangeData } 203 | -------------------------------------------------------------------------------- /static/ml-vue-drag-tree.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamma1024/ml-vue-drag-tree/53546705ddafd2fd1fd2bb549351598f4c41e151/static/ml-vue-drag-tree.gif --------------------------------------------------------------------------------