├── static └── .gitkeep ├── src ├── components │ ├── dragItem │ │ ├── detailSublist.vue │ │ ├── segmentingLine.vue │ │ ├── threeColumn.vue │ │ ├── z_readme.text │ │ ├── showDetails.vue │ │ ├── twoColumns.vue │ │ ├── tableLayout.vue │ │ ├── single.备份.vue │ │ ├── children.vue │ │ └── single.vue │ ├── dragList │ │ ├── z_readme.text │ │ └── dragCompent.vue │ ├── vue-slicksort │ │ ├── HandleDirective.js │ │ ├── index.js │ │ ├── components.js │ │ ├── Manager.js │ │ ├── ElementMixin.js │ │ ├── utils.js │ │ └── ContainerMixin.js │ └── HelloWorld.vue ├── assets │ ├── logo.png │ └── show.gif ├── store │ ├── index.js │ └── modules │ │ ├── base.js │ │ ├── carPicker.js │ │ └── dragItemDate.js ├── main.js ├── view │ ├── testDemo.vue │ ├── vueSlicksortPractice.vue │ ├── canBack.vue │ └── dragDemo.vue ├── router │ └── index.js └── App.vue ├── config ├── prod.env.js ├── dev.env.js └── index.js ├── .editorconfig ├── .gitignore ├── .babelrc ├── .postcssrc.js ├── index.html ├── README.md └── package.json /static/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/dragItem/detailSublist.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/dragItem/segmentingLine.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/dragItem/threeColumn.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/dragList/z_readme.text: -------------------------------------------------------------------------------- 1 | 这是设计器左边的 组件列表 -------------------------------------------------------------------------------- /src/components/dragItem/z_readme.text: -------------------------------------------------------------------------------- 1 | 这是设计器 中间的拖动组件 2 | 分为5种: 3 | 一行两列 一行三列 表格 明细子表 分割线。 -------------------------------------------------------------------------------- /config/prod.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | NODE_ENV: '"production"' 4 | } 5 | -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YalongYan/vue-drag-layout/HEAD/src/assets/logo.png -------------------------------------------------------------------------------- /src/assets/show.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YalongYan/vue-drag-layout/HEAD/src/assets/show.gif -------------------------------------------------------------------------------- /config/dev.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const merge = require('webpack-merge') 3 | const prodEnv = require('./prod.env') 4 | 5 | module.exports = merge(prodEnv, { 6 | NODE_ENV: '"development"' 7 | }) 8 | -------------------------------------------------------------------------------- /src/components/vue-slicksort/HandleDirective.js: -------------------------------------------------------------------------------- 1 | // Export Sortable Element Handle Directive 2 | export const HandleDirective = { 3 | bind(el) { 4 | el.sortableHandle = true; 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /.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-debug.log* 6 | yarn-error.log* 7 | 8 | # Editor directories and files 9 | .idea 10 | .vscode 11 | *.suo 12 | *.ntvs* 13 | *.njsproj 14 | *.sln 15 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "modules": false, 5 | "targets": { 6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] 7 | } 8 | }], 9 | "stage-2" 10 | ], 11 | "plugins": ["transform-vue-jsx", "transform-runtime"] 12 | } 13 | -------------------------------------------------------------------------------- /src/components/vue-slicksort/index.js: -------------------------------------------------------------------------------- 1 | export { ElementMixin } from './ElementMixin'; 2 | export { ContainerMixin } from './ContainerMixin'; 3 | export { HandleDirective } from './HandleDirective'; 4 | export { SlickList, SlickItem } from './components'; 5 | 6 | export { arrayMove } from './utils'; 7 | -------------------------------------------------------------------------------- /.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | "plugins": { 5 | "postcss-import": {}, 6 | "postcss-url": {}, 7 | // to edit target browsers: use "browserslist" field in package.json 8 | "autoprefixer": {} 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | vue-drag2 7 | 8 | 9 |
10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Vuex from 'vuex'; 3 | 4 | // import base from './modules/base'; 5 | // import user from './modules/dragItemDate'; 6 | // import carPicker from './modules/carPicker'; 7 | import dragItemDate from './modules/dragItemDate'; 8 | 9 | Vue.use(Vuex); 10 | 11 | export default new Vuex.Store({ 12 | modules: { 13 | dragItemDate 14 | // base, 15 | // user, 16 | // carPicker, 17 | }, 18 | }); 19 | -------------------------------------------------------------------------------- /src/components/vue-slicksort/components.js: -------------------------------------------------------------------------------- 1 | import { ElementMixin } from './ElementMixin'; 2 | import { ContainerMixin } from './ContainerMixin'; 3 | 4 | export const SlickList = { 5 | name: 'slick-list', 6 | mixins: [ ContainerMixin ], 7 | render (h) { 8 | return h('div', this.$slots.default); 9 | }, 10 | }; 11 | 12 | export const SlickItem = { 13 | name: 'slick-item', 14 | mixins: [ ElementMixin ], 15 | render (h) { 16 | return h('div', this.$slots.default); 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | // The Vue build version to load with the `import` command 2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias. 3 | import 'babel-polyfill' 4 | import Vue from 'vue' 5 | // import App from './App' 6 | import store from './store' 7 | import router from '@/router'; 8 | Vue.config.productionTip = false 9 | 10 | /* eslint-disable no-new */ 11 | new Vue({ 12 | el: '#app', 13 | router, 14 | store 15 | // components: { App }, 16 | // template: '' 17 | }) 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-slicksort-practice 2 | ### 目前的效果如下 3 | ![https://raw.githubusercontent.com/YalongYan/vue-drag-layout/master/src/assets/show.gif](https://raw.githubusercontent.com/YalongYan/vue-drag-layout/master/src/assets/show.gif) 4 | 5 | > A Vue.js project 6 | 7 | ## Build Setup 8 | 9 | ``` bash 10 | # install dependencies 11 | npm install 12 | 13 | # serve with hot reload at localhost:8080 14 | npm run dev 15 | 访问网址: http://localhost:8080/#/dragDemo 16 | 17 | # build for production with minification 18 | npm run build 19 | 20 | # build for production and view the bundle analyzer report 21 | npm run build --report 22 | ``` 23 | 24 | For a detailed explanation on how things work, check out the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader). 25 | -------------------------------------------------------------------------------- /src/view/testDemo.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 38 | 41 | -------------------------------------------------------------------------------- /src/view/vueSlicksortPractice.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 37 | 40 | -------------------------------------------------------------------------------- /src/components/vue-slicksort/Manager.js: -------------------------------------------------------------------------------- 1 | export default class Manager { 2 | constructor() { 3 | this.refs = {}; 4 | } 5 | 6 | add(collection, ref) { 7 | if (!this.refs[collection]) { 8 | this.refs[collection] = []; 9 | } 10 | 11 | this.refs[collection].push(ref); 12 | } 13 | 14 | remove(collection, ref) { 15 | const index = this.getIndex(collection, ref); 16 | 17 | if (index !== -1) { 18 | this.refs[collection].splice(index, 1); 19 | } 20 | } 21 | 22 | isActive() { 23 | return this.active; 24 | } 25 | 26 | getActive() { 27 | return this.refs[this.active.collection].find(({node}) => node.sortableInfo.index == this.active.index); 28 | } 29 | 30 | getIndex(collection, ref) { 31 | return this.refs[collection].indexOf(ref); 32 | } 33 | 34 | getOrderedRefs(collection = this.active.collection) { 35 | return this.refs[collection].sort((a, b) => { 36 | return a.node.sortableInfo.index - b.node.sortableInfo.index; 37 | }); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import App from '@/App' 4 | import dragDemo from '@/view/dragDemo' 5 | import vueSlicksortPractice from '@/view/vueSlicksortPractice' 6 | import testDemo from '@/view/testDemo' 7 | import canBack from '@/view/canBack' 8 | 9 | Vue.use(Router) 10 | 11 | export default new Router({ 12 | routes: [ 13 | // { 14 | // path: '/', 15 | // redirect: { 16 | // path: '/index' 17 | // } 18 | // }, 19 | { 20 | path: '/', 21 | component: App 22 | }, 23 | { 24 | path: '/dragDemo', 25 | name: 'dragDemo', 26 | component: dragDemo 27 | }, 28 | { 29 | path: '/canBack', 30 | name: 'canBack', 31 | component: canBack 32 | }, 33 | { 34 | path: '/vueSlicksortPractice', 35 | name: 'vueSlicksortPractice', 36 | component: vueSlicksortPractice 37 | }, 38 | { 39 | path: '/testDemo', 40 | name: 'testDemo', 41 | component: testDemo 42 | } 43 | ] 44 | }) 45 | -------------------------------------------------------------------------------- /src/view/canBack.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 48 | 51 | -------------------------------------------------------------------------------- /src/store/modules/base.js: -------------------------------------------------------------------------------- 1 | import types from '../mutation-types'; 2 | 3 | const state = { 4 | isShowHeader: false, 5 | currentPageTitle: '', 6 | isReqError: false, 7 | fetchLoading: false, 8 | }; 9 | 10 | const actions = { 11 | setShowHeader({ commit }, status) { 12 | commit(types.SET_SHOW_HEADER, status); 13 | }, 14 | setCurrentPageTitle({ commit }, title) { 15 | commit(types.SET_CURRENT_PAGE_TITLE, title); 16 | }, 17 | setNetworkError({ commit }, status) { 18 | commit(types.SET_NETWORK_ERROR, status); 19 | }, 20 | setFetchLoading({ commit }, status) { 21 | commit(types.FETCH_LOADING, status); 22 | }, 23 | }; 24 | 25 | const getters = { 26 | isShowHeader: state => state.isShowHeader, 27 | fetchLoading: state => state.fetchLoading, 28 | currentPageTitle: state => state.currentPageTitle, 29 | isReqError: state => state.isReqError, 30 | }; 31 | 32 | /** 33 | * 提交同步请求 34 | * */ 35 | const mutations = { 36 | [types.SET_SHOW_HEADER](state, isShowHeader) { 37 | state.isShowHeader = isShowHeader; 38 | }, 39 | [types.FETCH_LOADING](state, fetchLoading) { 40 | state.fetchLoading = fetchLoading; 41 | }, 42 | [types.SET_CURRENT_PAGE_TITLE](state, title) { 43 | state.currentPageTitle = title; 44 | }, 45 | [types.SET_NETWORK_ERROR](state, isReqError) { 46 | state.isReqError = isReqError; 47 | }, 48 | }; 49 | 50 | export default { 51 | state, 52 | actions, 53 | getters, 54 | mutations, 55 | }; 56 | -------------------------------------------------------------------------------- /src/store/modules/carPicker.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 车辆品牌选择 3 | */ 4 | import types from '../mutation-types'; 5 | 6 | const state = { 7 | carData: { 8 | brand: { 9 | name: '不限品牌', 10 | value: '', 11 | }, 12 | series: { 13 | name: '不限车系', 14 | value: '', 15 | }, 16 | type: { 17 | name: '不限车型', 18 | value: '', 19 | }, 20 | }, 21 | }; 22 | 23 | const getters = { 24 | getCarBrand: state => state.carData.brand, 25 | getCarSeries: state => state.carData.series, 26 | getCarType: state => state.carData.type, 27 | }; 28 | 29 | const mutations = { 30 | [types.SET_CAR_BRAND](state, data) { 31 | state.carData.brand = data; 32 | state.carData.series = { 33 | name: '不限车系', 34 | value: '', 35 | }; 36 | state.carData.type = { 37 | name: '不限车型', 38 | value: '', 39 | }; 40 | }, 41 | [types.SET_CAR_SERIES](state, data) { 42 | state.carData.series = data; 43 | state.carData.type = { 44 | name: '不限车型', 45 | value: '', 46 | }; 47 | }, 48 | [types.SET_CAR_TYPE](state, data) { 49 | state.carData.type = data; 50 | }, 51 | }; 52 | 53 | const actions = { 54 | setCarBrand({ commit }, data) { 55 | commit(types.SET_CAR_BRAND, data); 56 | }, 57 | setCarSeries({ commit }, data) { 58 | commit(types.SET_CAR_SERIES, data); 59 | }, 60 | setCarType({ commit }, data) { 61 | commit(types.SET_CAR_TYPE, data); 62 | }, 63 | }; 64 | 65 | export default { 66 | state, 67 | getters, 68 | mutations, 69 | actions, 70 | }; 71 | -------------------------------------------------------------------------------- /src/components/vue-slicksort/ElementMixin.js: -------------------------------------------------------------------------------- 1 | // Export Sortable Element Component Mixin 2 | export const ElementMixin = { 3 | inject: ['manager'], 4 | props: { 5 | index: { 6 | type: Number, 7 | required: true, 8 | }, 9 | collection: { 10 | type: [String, Number], 11 | default: 'default', 12 | }, 13 | disabled: { 14 | type: Boolean, 15 | default: false, 16 | }, 17 | }, 18 | 19 | mounted() { 20 | const {collection, disabled, index} = this.$props; 21 | 22 | if (!disabled) { 23 | this.setDraggable(collection, index); 24 | } 25 | }, 26 | 27 | watch: { 28 | index(newIndex) { 29 | if (this.$el && this.$el.sortableInfo) { 30 | this.$el.sortableInfo.index = newIndex; 31 | } 32 | }, 33 | disabled(isDisabled) { 34 | if (isDisabled) { 35 | this.removeDraggable(this.collection); 36 | } else { 37 | this.setDraggable(this.collection, this.index); 38 | } 39 | }, 40 | collection(newCollection, oldCollection) { 41 | this.removeDraggable(oldCollection); 42 | this.setDraggable(newCollection, this.index); 43 | }, 44 | }, 45 | 46 | beforeDestroy() { 47 | const {collection, disabled} = this; 48 | 49 | if (!disabled) this.removeDraggable(collection); 50 | }, 51 | methods: { 52 | setDraggable(collection, index) { 53 | const node = this.$el; 54 | 55 | node.sortableInfo = { 56 | index, 57 | collection, 58 | manager: this.manager, 59 | }; 60 | 61 | this.ref = {node}; 62 | this.manager.add(collection, this.ref); 63 | }, 64 | 65 | removeDraggable(collection) { 66 | this.manager.remove(collection, this.ref); 67 | }, 68 | }, 69 | }; 70 | -------------------------------------------------------------------------------- /src/components/vue-slicksort/utils.js: -------------------------------------------------------------------------------- 1 | export function arrayMove(arr, previousIndex, newIndex) { 2 | const array = arr.slice(0); 3 | if (newIndex >= array.length) { 4 | let k = newIndex - array.length; 5 | while (k-- + 1) { 6 | array.push(undefined); 7 | } 8 | } 9 | array.splice(newIndex, 0, array.splice(previousIndex, 1)[0]); 10 | return array; 11 | } 12 | 13 | export const events = { 14 | start: ['touchstart', 'mousedown'], 15 | move: ['touchmove', 'mousemove'], 16 | end: ['touchend', 'touchcancel', 'mouseup'], 17 | }; 18 | 19 | export const vendorPrefix = (function() { 20 | if (typeof window === 'undefined' || typeof document === 'undefined') return ''; // server environment 21 | // fix for: 22 | // https://bugzilla.mozilla.org/show_bug.cgi?id=548397 23 | // window.getComputedStyle() returns null inside an iframe with display: none 24 | // in this case return an array with a fake mozilla style in it. 25 | const styles = window.getComputedStyle(document.documentElement, '') || ['-moz-hidden-iframe']; 26 | const pre = (Array.prototype.slice.call(styles).join('').match(/-(moz|webkit|ms)-/) || (styles.OLink === '' && ['', 'o']))[1]; 27 | 28 | switch (pre) { 29 | case 'ms': 30 | return 'ms'; 31 | default: 32 | return pre && pre.length ? pre[0].toUpperCase() + pre.substr(1) : ''; 33 | } 34 | })(); 35 | 36 | export function closest(el, fn) { 37 | while (el) { 38 | if (fn(el)) return el; 39 | el = el.parentNode; 40 | } 41 | } 42 | 43 | export function limit(min, max, value) { 44 | if (value < min) { 45 | return min; 46 | } 47 | if (value > max) { 48 | return max; 49 | } 50 | return value; 51 | } 52 | 53 | function getCSSPixelValue(stringValue) { 54 | if (stringValue.substr(-2) === 'px') { 55 | return parseFloat(stringValue); 56 | } 57 | return 0; 58 | } 59 | 60 | export function getElementMargin(element) { 61 | const style = window.getComputedStyle(element); 62 | 63 | return { 64 | top: getCSSPixelValue(style.marginTop), 65 | right: getCSSPixelValue(style.marginRight), 66 | bottom: getCSSPixelValue(style.marginBottom), 67 | left: getCSSPixelValue(style.marginLeft), 68 | }; 69 | } 70 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-drag2", 3 | "version": "1.0.0", 4 | "description": "A Vue.js project", 5 | "author": "yanyalong", 6 | "private": true, 7 | "scripts": { 8 | "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", 9 | "start": "npm run dev", 10 | "build": "node build/build.js", 11 | "lint": "eslint --ext .js,.vue src" 12 | }, 13 | "dependencies": { 14 | "ajv": "^6.0.0", 15 | "vue": "^2.5.2", 16 | "vue-router": "^3.0.1", 17 | "vue-slicksort": "^0.1.10", 18 | "vuex": "^3.0.1" 19 | }, 20 | "devDependencies": { 21 | "autoprefixer": "^7.1.2", 22 | "babel-core": "^6.22.1", 23 | "babel-helper-vue-jsx-merge-props": "^2.0.3", 24 | "babel-loader": "^7.1.1", 25 | "babel-plugin-syntax-jsx": "^6.18.0", 26 | "babel-plugin-transform-runtime": "^6.22.0", 27 | "babel-plugin-transform-vue-jsx": "^3.5.0", 28 | "babel-polyfill": "^6.26.0", 29 | "babel-preset-env": "^1.3.2", 30 | "babel-preset-stage-2": "^6.22.0", 31 | "chalk": "^2.0.1", 32 | "copy-webpack-plugin": "^4.0.1", 33 | "css-loader": "^0.28.0", 34 | "extract-text-webpack-plugin": "^3.0.0", 35 | "file-loader": "^1.1.4", 36 | "friendly-errors-webpack-plugin": "^1.6.1", 37 | "html-webpack-plugin": "^2.30.1", 38 | "node-notifier": "^5.1.2", 39 | "node-sass": "^4.9.3", 40 | "optimize-css-assets-webpack-plugin": "^3.2.0", 41 | "ora": "^1.2.0", 42 | "portfinder": "^1.0.13", 43 | "postcss-import": "^11.0.0", 44 | "postcss-loader": "^2.0.8", 45 | "postcss-url": "^7.2.1", 46 | "rimraf": "^2.6.0", 47 | "sass-loader": "^7.1.0", 48 | "semver": "^5.3.0", 49 | "shelljs": "^0.7.6", 50 | "uglifyjs-webpack-plugin": "^1.1.1", 51 | "url-loader": "^1.1.1", 52 | "vue-loader": "^13.3.0", 53 | "vue-style-loader": "^3.0.1", 54 | "vue-template-compiler": "^2.5.2", 55 | "webpack": "^3.6.0", 56 | "webpack-bundle-analyzer": "^2.9.0", 57 | "webpack-dev-server": "^2.9.1", 58 | "webpack-merge": "^4.1.0" 59 | }, 60 | "engines": { 61 | "node": ">= 6.0.0", 62 | "npm": ">= 3.0.0" 63 | }, 64 | "browserslist": [ 65 | "> 1%", 66 | "last 2 versions", 67 | "not ie <= 8" 68 | ] 69 | } 70 | -------------------------------------------------------------------------------- /src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 71 | 72 | 73 | 100 | -------------------------------------------------------------------------------- /src/components/dragItem/showDetails.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 56 | 57 | 58 | 73 | -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // Template version: 1.3.1 3 | // see http://vuejs-templates.github.io/webpack for documentation. 4 | 5 | const path = require('path') 6 | 7 | module.exports = { 8 | dev: { 9 | 10 | // Paths 11 | assetsSubDirectory: 'static', 12 | assetsPublicPath: '/', 13 | proxyTable: {}, 14 | 15 | // Various Dev Server settings 16 | host: 'localhost', // can be overwritten by process.env.HOST 17 | port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined 18 | autoOpenBrowser: false, 19 | errorOverlay: true, 20 | notifyOnErrors: true, 21 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- 22 | 23 | // Use Eslint Loader? 24 | // If true, your code will be linted during bundling and 25 | // linting errors and warnings will be shown in the console. 26 | useEslint: true, 27 | // If true, eslint errors and warnings will also be shown in the error overlay 28 | // in the browser. 29 | showEslintErrorsInOverlay: false, 30 | 31 | /** 32 | * Source Maps 33 | */ 34 | 35 | // https://webpack.js.org/configuration/devtool/#development 36 | devtool: 'cheap-module-eval-source-map', 37 | 38 | // If you have problems debugging vue-files in devtools, 39 | // set this to false - it *may* help 40 | // https://vue-loader.vuejs.org/en/options.html#cachebusting 41 | cacheBusting: true, 42 | 43 | cssSourceMap: true 44 | }, 45 | 46 | build: { 47 | // Template for index.html 48 | index: path.resolve(__dirname, '../dist/index.html'), 49 | 50 | // Paths 51 | assetsRoot: path.resolve(__dirname, '../dist'), 52 | assetsSubDirectory: 'static', 53 | assetsPublicPath: '/', 54 | 55 | /** 56 | * Source Maps 57 | */ 58 | 59 | productionSourceMap: true, 60 | // https://webpack.js.org/configuration/devtool/#production 61 | devtool: '#source-map', 62 | 63 | // Gzip off by default as many popular static hosts such as 64 | // Surge or Netlify already gzip all static assets for you. 65 | // Before setting to `true`, make sure to: 66 | // npm install --save-dev compression-webpack-plugin 67 | productionGzip: false, 68 | productionGzipExtensions: ['js', 'css'], 69 | 70 | // Run the build command with an extra argument to 71 | // View the bundle analyzer report after build finishes: 72 | // `npm run build --report` 73 | // Set to `true` or `false` to always turn it on or off 74 | bundleAnalyzerReport: process.env.npm_config_report 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 33 | 34 | 122 | -------------------------------------------------------------------------------- /src/components/dragItem/twoColumns.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 74 | 75 | 76 | 107 | -------------------------------------------------------------------------------- /src/components/dragItem/tableLayout.vue: -------------------------------------------------------------------------------- 1 | // 这个是为了备份用的 2 | // 这个是为了备份用的 3 | // 这个是为了备份用的 4 | // 这个是为了备份用的 5 | // 这个是为了备份用的 6 | // 这个是为了备份用的 7 | // 这个是为了备份用的 8 | // 这个是为了备份用的 9 | // 这个是为了备份用的 10 | // 这个是为了备份用的 11 | 17 | 18 | 124 | 125 | 126 | 134 | -------------------------------------------------------------------------------- /src/components/dragItem/single.备份.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 145 | 146 | 147 | 155 | -------------------------------------------------------------------------------- /src/components/dragList/dragCompent.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 144 | 145 | 146 | 158 | -------------------------------------------------------------------------------- /src/view/dragDemo.vue: -------------------------------------------------------------------------------- 1 | 65 | 66 | 139 | 140 | 201 | -------------------------------------------------------------------------------- /src/components/dragItem/children.vue: -------------------------------------------------------------------------------- 1 | 61 | 62 | 305 | 306 | 307 | 330 | -------------------------------------------------------------------------------- /src/components/dragItem/single.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 267 | 268 | 269 | 354 | -------------------------------------------------------------------------------- /src/store/modules/dragItemDate.js: -------------------------------------------------------------------------------- 1 | import { stat } from "fs"; 2 | 3 | const state = { 4 | // 只用了原数据的数组 对象里增加了 'upActive': false 'downActive': false 是用来控制红色边框的出现 5 | layoutContentItem: [{ 6 | "componentKey": "Text", 7 | "title": "文本000", 8 | "fieldId": "20181108195717mCmp5TOBfA", 9 | "inLeft": false, 10 | "required": false, 11 | "crux": true, 12 | "isTextArea": false, 13 | "size": "large", 14 | "borderColor": "", 15 | "bacColor": "", 16 | "titleFontColor": "", 17 | "titleFontSize": 13, 18 | "titleBold": false, 19 | "titleUnderline": false, 20 | "bacImg": null, 21 | "tips": "", 22 | "hideTitle": false, 23 | "uniqueCheck": false, 24 | "visible": false, 25 | "calculate": false, 26 | "calType": "2", 27 | "complexCal": "", 28 | "complexTrueVal": "", 29 | "numberFields": "", 30 | "numberFieldArr": [], 31 | "columncode": "wb_1541678285948866", 32 | 'upActive': false, 33 | 'downActive': false 34 | }, 35 | { 36 | "componentKey": "Text", 37 | "title": "文本222", 38 | "fieldId": "20181109101800TWTeNtI5xk", 39 | "inLeft": true, 40 | "required": true, 41 | "crux": false, 42 | "isTextArea": false, 43 | "size": "large", 44 | "borderColor": "", 45 | "bacColor": "", 46 | "titleFontColor": "", 47 | "titleFontSize": 13, 48 | "titleBold": false, 49 | "titleUnderline": false, 50 | "bacImg": null, 51 | "tips": "", 52 | "hideTitle": true, 53 | "uniqueCheck": false, 54 | "visible": false, 55 | "calculate": false, 56 | "calType": "2", 57 | "complexCal": "", 58 | "complexTrueVal": "", 59 | "numberFields": "", 60 | "numberFieldArr": [], 61 | "columncode": "wb222_1541729905916187", 62 | 'upActive': false, 63 | 'downActive': false 64 | }, 65 | { 66 | "componentKey": "Text", 67 | "title": "文本3333", 68 | "fieldId": "20181109101800TWTeNtI5xk", 69 | "inLeft": false, 70 | "required": false, 71 | "crux": false, 72 | "isTextArea": false, 73 | "size": "large", 74 | "borderColor": "", 75 | "bacColor": "", 76 | "titleFontColor": "", 77 | "titleFontSize": 13, 78 | "titleBold": false, 79 | "titleUnderline": false, 80 | "bacImg": null, 81 | "tips": "", 82 | "hideTitle": false, 83 | "uniqueCheck": false, 84 | "visible": false, 85 | "calculate": false, 86 | "calType": "2", 87 | "complexCal": "", 88 | "complexTrueVal": "", 89 | "numberFields": "", 90 | "numberFieldArr": [], 91 | "columncode": "wb222_1541729905916187", 92 | 'upActive': false, 93 | 'downActive': false 94 | }, 95 | // { 96 | // "fieldId": "20181108201139GBJRyIJAt2", 97 | // "componentKey": "ColumnPanel", 98 | // "title": "一行两列", 99 | // "size": "2", 100 | // "available": true, 101 | // "bacColor": "", 102 | // "borderWidth": 1, 103 | // "layoutDetail": [{ 104 | // "componentKey": "ColumnPanel", 105 | // "layoutDetail": [], 106 | // "size": "1" 107 | // }, { 108 | // "componentKey": "ColumnPanel", 109 | // "layoutDetail": [{ 110 | // "componentKey": "Text", 111 | // "title": "文本111", 112 | // "fieldId": "20181108201815UbNRaXUIfB", 113 | // "inLeft": false, 114 | // "required": false, 115 | // "crux": false, 116 | // "isTextArea": false, 117 | // "size": "large", 118 | // "borderColor": "", 119 | // "bacColor": "", 120 | // "titleFontColor": "", 121 | // "titleFontSize": 13, 122 | // "titleBold": false, 123 | // "titleUnderline": false, 124 | // "bacImg": null, 125 | // "tips": "", 126 | // "hideTitle": false, 127 | // "uniqueCheck": false, 128 | // "visible": false, 129 | // "calculate": false, 130 | // "calType": "2", 131 | // "complexCal": "", 132 | // "complexTrueVal": "", 133 | // "numberFields": "", 134 | // "numberFieldArr": [], 135 | // "columncode": "wb_1541679666395165" 136 | // }], 137 | // "size": "1" 138 | // }] 139 | // }, 140 | // 141 | // { 142 | // "fieldId": "20181109101802zMgVW3gcEU", 143 | // "componentKey": "ColumnPanel", 144 | // "title": "一行两列", 145 | // "size": "2", 146 | // "available": true, 147 | // "bacColor": "", 148 | // "borderWidth": 1, 149 | // "layoutDetail": [{ 150 | // "componentKey": "ColumnPanel", 151 | // "layoutDetail": [{ 152 | // "componentKey": "Password", 153 | // "title": "密码", 154 | // "fieldId": "20181109101809alDirc1F1m", 155 | // "inLeft": false, 156 | // "required": false, 157 | // "crux": false, 158 | // "isTextArea": false, 159 | // "size": "large", 160 | // "borderColor": "", 161 | // "bacColor": "", 162 | // "titleFontColor": "", 163 | // "titleFontSize": 13, 164 | // "titleBold": false, 165 | // "titleUnderline": false, 166 | // "bacImg": null, 167 | // "tips": "", 168 | // "hideTitle": false, 169 | // "uniqueCheck": false, 170 | // "visible": false, 171 | // "calculate": false, 172 | // "calType": "2", 173 | // "complexCal": "", 174 | // "complexTrueVal": "", 175 | // "numberFields": "", 176 | // "numberFieldArr": [], 177 | // "columncode": "mm_1541729905915453" 178 | // }], 179 | // "size": "1" 180 | // }, { 181 | // "componentKey": "ColumnPanel", 182 | // "layoutDetail": [{ 183 | // "componentKey": "Text", 184 | // "title": "2112", 185 | // "fieldId": "20181109101802omluIDs3TQ", 186 | // "inLeft": false, 187 | // "required": false, 188 | // "crux": false, 189 | // "isTextArea": false, 190 | // "size": "large", 191 | // "borderColor": "", 192 | // "bacColor": "", 193 | // "titleFontColor": "", 194 | // "titleFontSize": 13, 195 | // "titleBold": false, 196 | // "titleUnderline": false, 197 | // "bacImg": null, 198 | // "tips": "", 199 | // "hideTitle": false, 200 | // "uniqueCheck": false, 201 | // "visible": false, 202 | // "calculate": false, 203 | // "calType": "2", 204 | // "complexCal": "", 205 | // "complexTrueVal": "", 206 | // "numberFields": "", 207 | // "numberFieldArr": [], 208 | // "columncode": "n2112_1541729905915431" 209 | // }], 210 | // "size": "1" 211 | // }] 212 | // } 213 | ], 214 | // layoutContentItem: [{text: '1111', upActive: false, downActive: false}, {text: '2222', upActive: false, downActive: false}, {text: '3333', upActive: false, downActive: false}, {text: '44444', upActive: false, downActive: false}, {text: '55555', upActive: false, downActive: false}], 215 | // layoutContentItem: [{text: '1111', upActive: false, downActive: false}], 216 | initPositionY: 999, // 中部 开始拖动组件的 index 217 | positionY: 999, // 进入到哪个组件的 index 218 | itemIsMoving: false, // 中间布局的item 是否被拖动 219 | leftDragItemIsMoving: false, // 左侧的item是否被拖动 220 | centerDraggingItemData: '', // 保存中间拖动的组件的数据 221 | leftDraggingItemData: '', // 保存左侧拖动的组件的数据 222 | isNeedUpdateDate: false // 是否需要更新数据 223 | }; 224 | 225 | const actions = { 226 | // item = {index:1, position: 1} position 1 是上 2 是下 227 | updateLayoutContentItem({ commit }, item) { 228 | if (item) { 229 | var layoutContentItemLength = state.layoutContentItem.length - 1 230 | if (item.index > layoutContentItemLength) { 231 | item.index = layoutContentItemLength 232 | } 233 | commit('LAYOUT_CONTENT_ITEM', item); 234 | } 235 | }, 236 | changeLayoutContentItem({ commit }) { 237 | commit('CHANGE_LAYOUT_CONTENT_ITEM'); 238 | }, 239 | updatePositionY({ commit }, position) { 240 | // var layoutContentItemLength = state.layoutContentItem.length - 1 241 | // if (position > layoutContentItemLength) { 242 | // position = layoutContentItemLength 243 | // } 244 | commit('UPDATE_POSITION_Y', position); 245 | }, 246 | updateInitPositionY({ commit }, position) { 247 | commit('UPDATE_INIT_POSITION_Y', position); 248 | }, 249 | updateItemIsMoving({ commit }, bool) { 250 | commit('UPDATE_ITEM_IS_MOVING', bool); 251 | }, 252 | updateLeftDragItemIsMoving({commit}, bool) { 253 | commit('UPDATE_LEFT_DRAG_ITEM_ISMOVING', bool); 254 | }, 255 | // 中间拖动的组件的数据 256 | updateCenterDraggingItemData({commit}, item) { 257 | commit('UPDATE_CENTER_DRAGGING_ITEM_DATA', item); 258 | }, 259 | // 左边拖动的组件的数据 260 | updateLeftDraggingItemData({commit}, item) { 261 | commit('UPDATE_LEFT_DRAGGING_ITEM_DATA', item); 262 | }, 263 | // 是否需要触发更新数据 264 | updateIsNeedUpdateDate({commit}, bool) { 265 | commit('UPDATE_IS_NEED_UPDATE_DATA', bool); 266 | } 267 | }; 268 | 269 | const getters = { 270 | // loginStatus: state => state.loginStatus, 271 | layoutContentItem: state => state.layoutContentItem, 272 | positionY: state => state.positionY, 273 | itemIsMoving: state => state.itemIsMoving, 274 | leftDragItemIsMoving: state => state.leftDragItemIsMoving, 275 | centerDraggingItemData: state => state.centerDraggingItemData, 276 | leftDraggingItemData: state => state.leftDraggingItemData 277 | }; 278 | 279 | const mutations = { 280 | // [types.SET_USER_INFO](state, userInfo) { 281 | // state.userInfo = userInfo; 282 | // } 283 | ['LAYOUT_CONTENT_ITEM'](state, item) { 284 | var index = item.index 285 | var data = state.layoutContentItem 286 | // 清空初始化 287 | for (var i = 0; i < data.length; i++) { 288 | if (data[i].downActive) { 289 | data[i].downActive = false 290 | } 291 | if (data[i].upActive) { 292 | data[i].upActive = false 293 | } 294 | } 295 | // console.log(item.position) 296 | if (item.position === 1 ) { 297 | data[index].upActive = true 298 | data[index].downActive = false 299 | } else { 300 | data[index].downActive = true 301 | data[index].upActive = false 302 | } 303 | state.layoutContentItem = data 304 | // console.log(data) 305 | }, 306 | ['UPDATE_POSITION_Y'](state, position) { 307 | state.positionY = position 308 | }, 309 | ['UPDATE_INIT_POSITION_Y'](state, position) { 310 | state.initPositionY = position 311 | }, 312 | ['UPDATE_ITEM_IS_MOVING'](state, bool) { 313 | state.itemIsMoving = bool 314 | }, 315 | ['CHANGE_LAYOUT_CONTENT_ITEM'](state) { 316 | // 拖动中间的组件 317 | if (state.centerDraggingItemData) { 318 | var isNeedUpdateDateIndex = '' 319 | for (var i = 0; i < state.layoutContentItem.length; i++) { 320 | if (state.layoutContentItem[i].upActive || state.layoutContentItem[i].downActive) { 321 | isNeedUpdateDateIndex = i 322 | } 323 | } 324 | // 这是拖动 临界的位置 在临界位置 325 | if (isNeedUpdateDateIndex == state.initPositionY) { 326 | state.isNeedUpdateDate = false 327 | } 328 | // 可以触发更新 329 | if (state.isNeedUpdateDate) { 330 | var data1 = state.layoutContentItem 331 | data1.splice(state.initPositionY, 1) 332 | state.layoutContentItem = data1 333 | var data2 = state.layoutContentItem 334 | for (let i = 0; i < data2.length; i++) { 335 | let upActive = data2[i].upActive 336 | let downActive = data2[i].downActive 337 | if (upActive) { 338 | state.centerDraggingItemData.upActive = false 339 | state.centerDraggingItemData.downActive = false 340 | data2.splice(i, 0, state.centerDraggingItemData) 341 | // 更新 positionY 342 | state.positionY = i 343 | break 344 | } 345 | if (downActive) { 346 | state.centerDraggingItemData.upActive = false 347 | state.centerDraggingItemData.downActive = false 348 | data2.splice(i + 1, 0, state.centerDraggingItemData) 349 | // 更新 positionY 350 | state.positionY = i + 1 351 | break 352 | } 353 | } 354 | state.layoutContentItem = data2 355 | } 356 | var data3 = state.layoutContentItem 357 | for (let i = 0; i < data3.length; i ++) { 358 | data3[i].downActive = false 359 | data3[i].upActive = false 360 | } 361 | state.layoutContentItem = data3 362 | } 363 | // 拖动左侧的组件 364 | if (state.leftDraggingItemData) { 365 | var title = state.leftDraggingItemData.title 366 | var obj = {} 367 | obj['title'] = title 368 | obj['upActive'] = false 369 | obj['downActive'] = false 370 | var data = state.layoutContentItem 371 | for (let i = 0; i < data.length; i++) { 372 | let upActive = data[i].upActive 373 | let downActive = data[i].downActive 374 | if (upActive) { 375 | data.splice(i, 0, obj) 376 | // 更新 positionY 377 | state.positionY = i 378 | break 379 | } 380 | if (downActive) { 381 | data.splice(i + 1, 0, obj) 382 | state.positionY = i + 1 383 | break 384 | } 385 | } 386 | for (let i = 0; i < data.length; i ++) { 387 | data[i].downActive = false 388 | data[i].upActive = false 389 | } 390 | state.layoutContentItem = data 391 | } 392 | }, 393 | ['UPDATE_LEFT_DRAG_ITEM_ISMOVING'](state, bool) { 394 | state.leftDragItemIsMoving = bool 395 | }, 396 | ['UPDATE_CENTER_DRAGGING_ITEM_DATA'](state, item) { 397 | state.centerDraggingItemData = item 398 | }, 399 | ['UPDATE_LEFT_DRAGGING_ITEM_DATA'](state, item) { 400 | state.leftDraggingItemData = item 401 | // console.log(state.leftDraggingItemData) 402 | }, 403 | ['UPDATE_IS_NEED_UPDATE_DATA'](state, bool) { 404 | state.isNeedUpdateDate = bool 405 | } 406 | }; 407 | 408 | export default { 409 | state, 410 | actions, 411 | getters, 412 | mutations, 413 | }; 414 | -------------------------------------------------------------------------------- /src/components/vue-slicksort/ContainerMixin.js: -------------------------------------------------------------------------------- 1 | import Manager from './Manager'; 2 | import { 3 | closest, 4 | events, 5 | vendorPrefix, 6 | limit, 7 | getElementMargin, 8 | arrayMove, 9 | } from './utils'; 10 | 11 | // Export Sortable Container Component Mixin 12 | export const ContainerMixin = { 13 | data() { 14 | return { 15 | sorting: false, 16 | sortingIndex: null, 17 | manager: new Manager(), 18 | events: { 19 | start: this.handleStart, 20 | move: this.handleMove, 21 | end: this.handleEnd, 22 | }, 23 | }; 24 | }, 25 | 26 | props: { 27 | value: { type: Array, required: true }, 28 | axis: { type: String, default: 'y' }, // 'x', 'y', 'xy' 29 | distance: { type: Number, default: 0 }, 30 | pressDelay: { type: Number, default: 0 }, 31 | pressThreshold: { type: Number, default: 5 }, 32 | useDragHandle: { type: Boolean, default: false }, 33 | useWindowAsScrollContainer: { type: Boolean, default: false }, 34 | hideSortableGhost: { type: Boolean, default: true }, 35 | lockToContainerEdges: { type: Boolean, default: false }, 36 | lockOffset: { type: [String, Number, Array], default: '50%' }, 37 | transitionDuration: { type: Number, default: 300 }, 38 | lockAxis: String, 39 | helperClass: String, 40 | contentWindow: Object, 41 | shouldCancelStart: { 42 | type: Function, 43 | default: (e) => { 44 | // Cancel sorting if the event target is an `input`, `textarea`, `select` or `option` 45 | const disabledElements = ['input', 'textarea', 'select', 'option', 'button']; 46 | return disabledElements.indexOf(e.target.tagName.toLowerCase()) !== -1; 47 | }, 48 | }, 49 | getHelperDimensions: { 50 | type: Function, 51 | default: ({node}) => ({ 52 | width: node.offsetWidth, 53 | height: node.offsetHeight, 54 | }), 55 | }, 56 | }, 57 | 58 | provide() { 59 | return { 60 | manager: this.manager, 61 | }; 62 | }, 63 | 64 | mounted() { 65 | this.container = this.$el; 66 | this.document = this.container.ownerDocument || document; 67 | this._window = this.contentWindow || window; 68 | this.scrollContainer = this.useWindowAsScrollContainer 69 | ? this.document.body 70 | : this.container; 71 | 72 | for (const key in this.events) { 73 | if (this.events.hasOwnProperty(key)) { 74 | events[key].forEach(eventName => 75 | this.container.addEventListener(eventName, this.events[key], false) 76 | // console.log(eventName + this.events[key]) 77 | ); 78 | } 79 | } 80 | }, 81 | 82 | beforeDestroy() { 83 | for (const key in this.events) { 84 | if (this.events.hasOwnProperty(key)) { 85 | events[key].forEach(eventName => 86 | this.container.removeEventListener(eventName, this.events[key]) 87 | ); 88 | } 89 | } 90 | }, 91 | 92 | methods: { 93 | 94 | handleStart(e) { 95 | const {distance, shouldCancelStart} = this.$props; 96 | 97 | if (e.button === 2 || shouldCancelStart(e)) { 98 | return false; 99 | } 100 | 101 | this._touched = true; 102 | this._pos = { 103 | x: e.pageX, 104 | y: e.pageY, 105 | }; 106 | 107 | const node = closest(e.target, el => el.sortableInfo != null); 108 | 109 | if ( 110 | node && 111 | node.sortableInfo && 112 | this.nodeIsChild(node) && 113 | !this.sorting 114 | ) { 115 | const {useDragHandle} = this.$props; 116 | const {index, collection} = node.sortableInfo; 117 | 118 | if ( 119 | useDragHandle && !closest(e.target, el => el.sortableHandle != null) 120 | ) 121 | return; 122 | 123 | this.manager.active = {index, collection}; 124 | 125 | /* 126 | * Fixes a bug in Firefox where the :active state of anchor tags 127 | * prevent subsequent 'mousemove' events from being fired 128 | * (see https://github.com/clauderic/react-sortable-hoc/issues/118) 129 | */ 130 | if (e.target.tagName.toLowerCase() === 'a') { 131 | e.preventDefault(); 132 | } 133 | // console.log(distance) 134 | if (!distance) { 135 | if (this.$props.pressDelay === 0) { 136 | this.handlePress(e); 137 | } else { 138 | this.pressTimer = setTimeout( 139 | () => this.handlePress(e), 140 | this.$props.pressDelay 141 | ); 142 | } 143 | } 144 | } 145 | // console.log('handleStart') 146 | }, 147 | 148 | nodeIsChild(node) { 149 | return node.sortableInfo.manager === this.manager; 150 | }, 151 | 152 | handleMove(e) { 153 | const {distance, pressThreshold} = this.$props; 154 | 155 | if (!this.sorting && this._touched) { 156 | this._delta = { 157 | x: this._pos.x - e.pageX, 158 | y: this._pos.y - e.pageY, 159 | }; 160 | const delta = Math.abs(this._delta.x) + Math.abs(this._delta.y); 161 | 162 | if (!distance && (!pressThreshold || pressThreshold && delta >= pressThreshold)) { 163 | clearTimeout(this.cancelTimer); 164 | this.cancelTimer = setTimeout(this.cancel, 0); 165 | } else if (distance && delta >= distance && this.manager.isActive()) { 166 | this.handlePress(e); 167 | } 168 | } 169 | // console.log('handleMove') 170 | }, 171 | 172 | handleEnd() { 173 | const {distance} = this.$props; 174 | 175 | this._touched = false; 176 | 177 | if (!distance) { 178 | this.cancel(); 179 | } 180 | // console.log('handleEnd') 181 | }, 182 | 183 | cancel() { 184 | if (!this.sorting) { 185 | clearTimeout(this.pressTimer); 186 | this.manager.active = null; 187 | } 188 | }, 189 | 190 | handlePress(e) { 191 | const active = this.manager.getActive(); 192 | 193 | if (active) { 194 | const { 195 | axis, 196 | getHelperDimensions, 197 | helperClass, 198 | hideSortableGhost, 199 | useWindowAsScrollContainer, 200 | } = this.$props; 201 | const {node, collection} = active; 202 | const {index} = node.sortableInfo; 203 | const margin = getElementMargin(node); 204 | 205 | const containerBoundingRect = this.container.getBoundingClientRect(); 206 | const dimensions = getHelperDimensions({index, node, collection}); 207 | 208 | this.node = node; 209 | this.margin = margin; 210 | this.width = dimensions.width; 211 | this.height = dimensions.height; 212 | this.marginOffset = { 213 | x: this.margin.left + this.margin.right, 214 | y: Math.max(this.margin.top, this.margin.bottom), 215 | }; 216 | this.boundingClientRect = node.getBoundingClientRect(); 217 | this.containerBoundingRect = containerBoundingRect; 218 | this.index = index; 219 | this.newIndex = index; 220 | 221 | this._axis = { 222 | x: axis.indexOf('x') >= 0, 223 | y: axis.indexOf('y') >= 0, 224 | }; 225 | this.offsetEdge = this.getEdgeOffset(node); 226 | this.initialOffset = this.getOffset(e); 227 | this.initialScroll = { 228 | top: this.scrollContainer.scrollTop, 229 | left: this.scrollContainer.scrollLeft, 230 | }; 231 | 232 | this.initialWindowScroll = { 233 | top: window.pageYOffset, 234 | left: window.pageXOffset, 235 | }; 236 | 237 | const fields = node.querySelectorAll('input, textarea, select'); 238 | const clonedNode = node.cloneNode(true); 239 | const clonedFields = [ 240 | ...clonedNode.querySelectorAll('input, textarea, select'), 241 | ]; // Convert NodeList to Array 242 | 243 | clonedFields.forEach((field, index) => { 244 | if (field.type !== 'file' && fields[index]) { 245 | field.value = fields[index].value; 246 | } 247 | }); 248 | 249 | this.helper = this.document.body.appendChild(clonedNode); 250 | // console.log(clonedNode) 251 | this.helper.style.position = 'fixed'; 252 | this.helper.style.top = `${this.boundingClientRect.top - margin.top}px`; 253 | this.helper.style.left = `${this.boundingClientRect.left - margin.left}px`; 254 | this.helper.style.width = `${this.width}px`; 255 | this.helper.style.height = `${this.height}px`; 256 | this.helper.style.boxSizing = 'border-box'; 257 | this.helper.style.pointerEvents = 'none'; 258 | 259 | if (hideSortableGhost) { 260 | this.sortableGhost = node; 261 | // yyl 262 | node.style.visibility = 'hidden'; 263 | node.style.opacity = 0; 264 | // node.style.background = 'red'; 265 | } 266 | 267 | this.minTranslate = {}; 268 | this.maxTranslate = {}; 269 | if (this._axis.x) { 270 | this.minTranslate.x = (useWindowAsScrollContainer 271 | ? 0 272 | : containerBoundingRect.left) - 273 | this.boundingClientRect.left - 274 | this.width / 2; 275 | this.maxTranslate.x = (useWindowAsScrollContainer 276 | ? this._window.innerWidth 277 | : containerBoundingRect.left + containerBoundingRect.width) - 278 | this.boundingClientRect.left - 279 | this.width / 2; 280 | } 281 | if (this._axis.y) { 282 | this.minTranslate.y = (useWindowAsScrollContainer 283 | ? 0 284 | : containerBoundingRect.top) - 285 | this.boundingClientRect.top - 286 | this.height / 2; 287 | this.maxTranslate.y = (useWindowAsScrollContainer 288 | ? this._window.innerHeight 289 | : containerBoundingRect.top + containerBoundingRect.height) - 290 | this.boundingClientRect.top - 291 | this.height / 2; 292 | } 293 | 294 | if (helperClass) { 295 | this.helper.classList.add(...helperClass.split(' ')); 296 | } 297 | 298 | this.listenerNode = e.touches ? node : this._window; 299 | 300 | // console.log(events.move) 301 | // ["touchmove", "mousemove"] 302 | events.move.forEach(eventName => 303 | this.listenerNode.addEventListener( 304 | eventName, 305 | this.handleSortMove, 306 | false 307 | )); 308 | 309 | // console.log(events.end) 310 | // ["touchend", "touchcancel", "mouseup"] 311 | 312 | events.end.forEach(eventName => 313 | this.listenerNode.addEventListener( 314 | eventName, 315 | this.handleSortEnd, 316 | false 317 | )); 318 | 319 | this.sorting = true; 320 | this.sortingIndex = index; 321 | 322 | // this.$emit('sortStart', {event: e, node, index, collection}); 323 | // console.log('handlePress') 324 | } 325 | }, 326 | 327 | handleSortMove(e) { 328 | e.preventDefault(); // Prevent scrolling on mobile 329 | 330 | // 这是移动节点 yyl 331 | this.updatePosition(e); 332 | this.animateNodes(); 333 | // this.autoscroll(); 334 | 335 | // this.$emit('sortMove', { event: e }); 336 | }, 337 | 338 | handleSortEnd(e) { 339 | const {collection} = this.manager.active; 340 | 341 | // Remove the event listeners if the node is still in the DOM 342 | if (this.listenerNode) { 343 | events.move.forEach(eventName => 344 | this.listenerNode.removeEventListener( 345 | eventName, 346 | this.handleSortMove 347 | )); 348 | events.end.forEach(eventName => 349 | this.listenerNode.removeEventListener(eventName, this.handleSortEnd)); 350 | } 351 | 352 | // Remove the helper from the DOM 353 | this.helper.parentNode.removeChild(this.helper); 354 | 355 | if (this.hideSortableGhost && this.sortableGhost) { 356 | // yyl 357 | this.sortableGhost.style.visibility = ''; 358 | this.sortableGhost.style.opacity = ''; 359 | // this.sortableGhost.style.background = ''; 360 | } 361 | 362 | const nodes = this.manager.refs[collection]; 363 | for (let i = 0, len = nodes.length; i < len; i++) { 364 | const node = nodes[i]; 365 | const el = node.node; 366 | 367 | // Clear the cached offsetTop / offsetLeft value 368 | node.edgeOffset = null; 369 | 370 | // Remove the transforms / transitions 371 | el.style[`${vendorPrefix}Transform`] = ''; 372 | el.style[`${vendorPrefix}TransitionDuration`] = ''; 373 | } 374 | 375 | // Stop autoscroll 376 | clearInterval(this.autoscrollInterval); 377 | this.autoscrollInterval = null; 378 | 379 | // Update state 380 | this.manager.active = null; 381 | 382 | this.sorting = false; 383 | this.sortingIndex = null; 384 | 385 | this.$emit('sortEnd', { 386 | event: e, 387 | oldIndex: this.index, 388 | newIndex: this.newIndex, 389 | collection, 390 | }); 391 | this.$emit('input', arrayMove(this.value, this.index, this.newIndex)); 392 | 393 | this._touched = false; 394 | }, 395 | 396 | getEdgeOffset(node, offset = {top: 0, left: 0}) { 397 | // Get the actual offsetTop / offsetLeft value, no matter how deep the node is nested 398 | if (node) { 399 | const nodeOffset = { 400 | top: offset.top + node.offsetTop, 401 | left: offset.left + node.offsetLeft, 402 | }; 403 | if (node.parentNode !== this.container) { 404 | return this.getEdgeOffset(node.parentNode, nodeOffset); 405 | } else { 406 | return nodeOffset; 407 | } 408 | } 409 | }, 410 | 411 | getOffset(e) { 412 | return { 413 | x: e.touches ? e.touches[0].pageX : e.pageX, 414 | y: e.touches ? e.touches[0].pageY : e.pageY, 415 | }; 416 | }, 417 | 418 | getLockPixelOffsets() { 419 | let {lockOffset} = this.$props; 420 | 421 | if (!Array.isArray(this.lockOffset)) { 422 | lockOffset = [lockOffset, lockOffset]; 423 | } 424 | 425 | if (lockOffset.length !== 2) { 426 | throw new Error(`lockOffset prop of SortableContainer should be a single value or an array of exactly two values. Given ${lockOffset}`); 427 | } 428 | 429 | const [minLockOffset, maxLockOffset] = lockOffset; 430 | 431 | return [ 432 | this.getLockPixelOffset(minLockOffset), 433 | this.getLockPixelOffset(maxLockOffset), 434 | ]; 435 | }, 436 | 437 | getLockPixelOffset(lockOffset) { 438 | let offsetX = lockOffset; 439 | let offsetY = lockOffset; 440 | let unit = 'px'; 441 | 442 | if (typeof lockOffset === 'string') { 443 | const match = /^[+-]?\d*(?:\.\d*)?(px|%)$/.exec(lockOffset); 444 | 445 | if (match === null) { 446 | throw new Error(`lockOffset value should be a number or a string of a number followed by "px" or "%". Given ${lockOffset}`); 447 | } 448 | 449 | offsetX = (offsetY = parseFloat(lockOffset)); 450 | unit = match[1]; 451 | } 452 | 453 | if (!isFinite(offsetX) || !isFinite(offsetY)) { 454 | throw new Error(`lockOffset value should be a finite. Given ${lockOffset}`); 455 | } 456 | 457 | if (unit === '%') { 458 | offsetX = offsetX * this.width / 100; 459 | offsetY = offsetY * this.height / 100; 460 | } 461 | 462 | return { 463 | x: offsetX, 464 | y: offsetY, 465 | }; 466 | }, 467 | 468 | updatePosition(e) { 469 | const {lockAxis, lockToContainerEdges} = this.$props; 470 | 471 | const offset = this.getOffset(e); 472 | const translate = { 473 | x: offset.x - this.initialOffset.x, 474 | y: offset.y - this.initialOffset.y, 475 | }; 476 | // Adjust for window scroll 477 | translate.y -= (window.pageYOffset - this.initialWindowScroll.top); 478 | translate.x -= (window.pageXOffset - this.initialWindowScroll.left); 479 | 480 | this.translate = translate; 481 | 482 | if (lockToContainerEdges) { 483 | const [minLockOffset, maxLockOffset] = this.getLockPixelOffsets(); 484 | const minOffset = { 485 | x: this.width / 2 - minLockOffset.x, 486 | y: this.height / 2 - minLockOffset.y, 487 | }; 488 | const maxOffset = { 489 | x: this.width / 2 - maxLockOffset.x, 490 | y: this.height / 2 - maxLockOffset.y, 491 | }; 492 | 493 | translate.x = limit( 494 | this.minTranslate.x + minOffset.x, 495 | this.maxTranslate.x - maxOffset.x, 496 | translate.x 497 | ); 498 | translate.y = limit( 499 | this.minTranslate.y + minOffset.y, 500 | this.maxTranslate.y - maxOffset.y, 501 | translate.y 502 | ); 503 | } 504 | 505 | if (lockAxis === 'x') { 506 | translate.y = 0; 507 | } else if (lockAxis === 'y') { 508 | translate.x = 0; 509 | } 510 | 511 | this.helper.style[ 512 | `${vendorPrefix}Transform` 513 | ] = `translate3d(${translate.x}px,${translate.y}px, 0)`; 514 | }, 515 | 516 | animateNodes() { 517 | const {transitionDuration, hideSortableGhost} = this.$props; 518 | const nodes = this.manager.getOrderedRefs(); 519 | const deltaScroll = { 520 | left: this.scrollContainer.scrollLeft - this.initialScroll.left, 521 | top: this.scrollContainer.scrollTop - this.initialScroll.top, 522 | }; 523 | const sortingOffset = { 524 | left: this.offsetEdge.left + this.translate.x + deltaScroll.left, 525 | top: this.offsetEdge.top + this.translate.y + deltaScroll.top, 526 | }; 527 | const scrollDifference = { 528 | top: (window.pageYOffset - this.initialWindowScroll.top), 529 | left: (window.pageXOffset - this.initialWindowScroll.left), 530 | }; 531 | this.newIndex = null; 532 | 533 | for (let i = 0, len = nodes.length; i < len; i++) { 534 | const {node} = nodes[i]; 535 | const index = node.sortableInfo.index; 536 | const width = node.offsetWidth; 537 | const height = node.offsetHeight; 538 | const offset = { 539 | width: this.width > width ? width / 2 : this.width / 2, 540 | height: this.height > height ? height / 2 : this.height / 2, 541 | }; 542 | 543 | const translate = { 544 | x: 0, 545 | y: 0, 546 | }; 547 | let {edgeOffset} = nodes[i]; 548 | 549 | // If we haven't cached the node's offsetTop / offsetLeft value 550 | if (!edgeOffset) { 551 | nodes[i].edgeOffset = (edgeOffset = this.getEdgeOffset(node)); 552 | } 553 | 554 | // Get a reference to the next and previous node 555 | const nextNode = i < nodes.length - 1 && nodes[i + 1]; 556 | const prevNode = i > 0 && nodes[i - 1]; 557 | 558 | // Also cache the next node's edge offset if needed. 559 | // We need this for calculating the animation in a grid setup 560 | if (nextNode && !nextNode.edgeOffset) { 561 | nextNode.edgeOffset = this.getEdgeOffset(nextNode.node); 562 | } 563 | 564 | // If the node is the one we're currently animating, skip it 565 | if (index === this.index) { 566 | if (hideSortableGhost) { 567 | /* 568 | * With windowing libraries such as `react-virtualized`, the sortableGhost 569 | * node may change while scrolling down and then back up (or vice-versa), 570 | * so we need to update the reference to the new node just to be safe. 571 | */ 572 | this.sortableGhost = node; 573 | // yyl 574 | node.style.visibility = 'hidden'; 575 | node.style.visibility = 'hidden'; 576 | // node.style.background = 'red'; 577 | } 578 | continue; 579 | } 580 | 581 | if (transitionDuration) { 582 | node.style[ 583 | `${vendorPrefix}TransitionDuration` 584 | ] = `${transitionDuration}ms`; 585 | } 586 | 587 | if (this._axis.x) { 588 | if (this._axis.y) { 589 | // Calculations for a grid setup 590 | if ( 591 | index < this.index && 592 | ( 593 | ((sortingOffset.left + scrollDifference.left) - offset.width <= edgeOffset.left && 594 | (sortingOffset.top + scrollDifference.top) <= edgeOffset.top + offset.height) || 595 | (sortingOffset.top + scrollDifference.top) + offset.height <= edgeOffset.top 596 | ) 597 | ) { 598 | // If the current node is to the left on the same row, or above the node that's being dragged 599 | // then move it to the right 600 | translate.x = this.width + this.marginOffset.x; 601 | if ( 602 | edgeOffset.left + translate.x > 603 | this.containerBoundingRect.width - offset.width 604 | ) { 605 | // If it moves passed the right bounds, then animate it to the first position of the next row. 606 | // We just use the offset of the next node to calculate where to move, because that node's original position 607 | // is exactly where we want to go 608 | translate.x = nextNode.edgeOffset.left - edgeOffset.left; 609 | translate.y = nextNode.edgeOffset.top - edgeOffset.top; 610 | } 611 | if (this.newIndex === null) { 612 | this.newIndex = index; 613 | } 614 | } else if ( 615 | index > this.index && 616 | ( 617 | ((sortingOffset.left + scrollDifference.left) + offset.width >= edgeOffset.left && 618 | (sortingOffset.top + scrollDifference.top) + offset.height >= edgeOffset.top) || 619 | (sortingOffset.top + scrollDifference.top) + offset.height >= edgeOffset.top + height 620 | ) 621 | ) { 622 | // If the current node is to the right on the same row, or below the node that's being dragged 623 | // then move it to the left 624 | translate.x = -(this.width + this.marginOffset.x); 625 | if ( 626 | edgeOffset.left + translate.x < 627 | this.containerBoundingRect.left + offset.width 628 | ) { 629 | // If it moves passed the left bounds, then animate it to the last position of the previous row. 630 | // We just use the offset of the previous node to calculate where to move, because that node's original position 631 | // is exactly where we want to go 632 | translate.x = prevNode.edgeOffset.left - edgeOffset.left; 633 | translate.y = prevNode.edgeOffset.top - edgeOffset.top; 634 | } 635 | this.newIndex = index; 636 | } 637 | } else { 638 | if ( 639 | index > this.index && 640 | (sortingOffset.left + scrollDifference.left) + offset.width >= edgeOffset.left 641 | ) { 642 | translate.x = -(this.width + this.marginOffset.x); 643 | this.newIndex = index; 644 | } else if ( 645 | index < this.index && 646 | (sortingOffset.left + scrollDifference.left) <= edgeOffset.left + offset.width 647 | ) { 648 | translate.x = this.width + this.marginOffset.x; 649 | if (this.newIndex == null) { 650 | this.newIndex = index; 651 | } 652 | } 653 | } 654 | } else if (this._axis.y) { 655 | if ( 656 | index > this.index && 657 | (sortingOffset.top + scrollDifference.top) + offset.height >= edgeOffset.top 658 | ) { 659 | translate.y = -(this.height + this.marginOffset.y); 660 | this.newIndex = index; 661 | } else if ( 662 | index < this.index && 663 | (sortingOffset.top + scrollDifference.top) <= edgeOffset.top + offset.height 664 | ) { 665 | translate.y = this.height + this.marginOffset.y; 666 | if (this.newIndex == null) { 667 | this.newIndex = index; 668 | } 669 | } 670 | } 671 | node.style[`${vendorPrefix}Transform`] = `translate3d(${translate.x}px,${translate.y}px,0)`; 672 | } 673 | 674 | if (this.newIndex == null) { 675 | this.newIndex = this.index; 676 | } 677 | }, 678 | 679 | autoscroll() { 680 | const translate = this.translate; 681 | const direction = { 682 | x: 0, 683 | y: 0, 684 | }; 685 | const speed = { 686 | x: 1, 687 | y: 1, 688 | }; 689 | const acceleration = { 690 | x: 10, 691 | y: 10, 692 | }; 693 | 694 | if (translate.y >= this.maxTranslate.y - this.height / 2) { 695 | direction.y = 1; // Scroll Down 696 | speed.y = acceleration.y * Math.abs((this.maxTranslate.y - this.height / 2 - translate.y) / this.height); 697 | } else if (translate.x >= this.maxTranslate.x - this.width / 2) { 698 | direction.x = 1; // Scroll Right 699 | speed.x = acceleration.x * Math.abs((this.maxTranslate.x - this.width / 2 - translate.x) / this.width); 700 | } else if (translate.y <= this.minTranslate.y + this.height / 2) { 701 | direction.y = -1; // Scroll Up 702 | speed.y = acceleration.y * Math.abs((translate.y - this.height / 2 - this.minTranslate.y) / this.height); 703 | } else if (translate.x <= this.minTranslate.x + this.width / 2) { 704 | direction.x = -1; // Scroll Left 705 | speed.x = acceleration.x * Math.abs((translate.x - this.width / 2 - this.minTranslate.x) / this.width); 706 | } 707 | 708 | if (this.autoscrollInterval) { 709 | clearInterval(this.autoscrollInterval); 710 | this.autoscrollInterval = null; 711 | this.isAutoScrolling = false; 712 | } 713 | 714 | if (direction.x !== 0 || direction.y !== 0) { 715 | this.autoscrollInterval = setInterval( 716 | () => { 717 | this.isAutoScrolling = true; 718 | const offset = { 719 | left: 1 * speed.x * direction.x, 720 | top: 1 * speed.y * direction.y, 721 | }; 722 | this.scrollContainer.scrollTop += offset.top; 723 | this.scrollContainer.scrollLeft += offset.left; 724 | this.translate.x += offset.left; 725 | this.translate.y += offset.top; 726 | this.animateNodes(); 727 | }, 728 | 5 729 | ); 730 | } 731 | }, 732 | }, 733 | }; 734 | --------------------------------------------------------------------------------