1. 画尺寸ruler

    image.png

    1. background: repeating-linear-gradient(
    2. 90deg
    3. , #000 0px, transparent 1px, transparent 4px), repeating-linear-gradient(
    4. 90deg
    5. , #000 0px, transparent 1px, transparent 20px), repeating-linear-gradient(#000 0px, transparent 1px, transparent 4px), repeating-linear-gradient(#000 0px, transparent 1px, transparent 20px), #e6e6e6;
    6. background-size: 100% 10px, 100% 15px, 10px 100%, 15px 100%;
    7. background-position: 40px 0, 40px 0, 0 40px, 0 40px;
    8. background-repeat: no-repeat;
    1. 画格子

    image.png

    1. background: linear-gradient(
    2. -90deg
    3. , #eee 1px, transparent 1px), linear-gradient(#eee 1px, transparent 1px), #fff;
    4. background-size: 8px 8px;
    5. background-repeat: repeat;
    6. background-position: 1px 1px, 4px 4px;
    1. 代码展示

    monaco-editor
    image.png

    1. 导出代码 ```css import JSZip from ‘jszip’; import FileSaver from ‘file-saver’; import { CodeFile } from ‘@/types/code-editor’;

    export function exportZip(fileList: CodeFile[]) { // const blobs = codeStrArray.map(codeStr => new Blob([codeStr], {type: ‘text/plain’})); const blobs = fileList.map((file: CodeFile) => { const code = Object.values(file.codeMap).join(‘\n’); return { blob: new Blob([code], {type: ‘text/plain’}), name: file.name, }; }); const zip = JSZip(); blobs.forEach((file) => { zip.file(file.name, file.blob); }); zip.generateAsync({type: ‘blob’}).then(zipFile => { const fileName = ‘combined.zip’; return FileSaver.saveAs(zipFile, fileName); }); }

    1. 5. 组件拖拽和resize
    2. ```css
    3. <template>
    4. <div
    5. :style="style"
    6. :class="[
    7. {
    8. [classNameActive]: enabled,
    9. [classNameDragging]: dragging,
    10. [classNameResizing]: resizing,
    11. [classNameDraggable]: draggable,
    12. [classNameResizable]: resizable,
    13. },
    14. className,
    15. ]"
    16. @mousedown="elementMouseDown"
    17. @touchstart="elementTouchDown"
    18. @click="selectCurComponent"
    19. >
    20. <div
    21. v-for="(handle, index) in actualHandles"
    22. :key="handle + index"
    23. :class="[classNameHandle, classNameHandle + '-' + handle]"
    24. :style="handleStyle(handle)"
    25. @mousedown.stop.prevent="handleDown(handle, $event)"
    26. @touchstart.stop.prevent="handleTouchDown(handle, $event)"
    27. >
    28. <slot :name="handle"></slot>
    29. </div>
    30. <slot></slot>
    31. </div>
    32. </template>
    33. <script>
    34. import { matchesSelectorToParentElements, getComputedSize, addEvent, removeEvent } from '../utils/dom';
    35. import { computeWidth, computeHeight, restrictToBounds, snapToGrid } from '../utils/fns';
    36. const events = {
    37. mouse: {
    38. start: 'mousedown',
    39. move: 'mousemove',
    40. stop: 'mouseup'
    41. },
    42. touch: {
    43. start: 'touchstart',
    44. move: 'touchmove',
    45. stop: 'touchend'
    46. }
    47. };
    48. // 禁止用户选取
    49. const userSelectNone = {
    50. userSelect: 'none',
    51. MozUserSelect: 'none',
    52. WebkitUserSelect: 'none',
    53. MsUserSelect: 'none'
    54. };
    55. // 用户选中自动
    56. const userSelectAuto = {
    57. userSelect: 'auto',
    58. MozUserSelect: 'auto',
    59. WebkitUserSelect: 'auto',
    60. MsUserSelect: 'auto'
    61. };
    62. let eventsFor = events.mouse;
    63. export default {
    64. replace: true,
    65. name: 'vue-draggable-resizable',
    66. props: {
    67. className: {
    68. type: String,
    69. default: 'vdr'
    70. },
    71. classNameDraggable: {
    72. type: String,
    73. default: 'draggable'
    74. },
    75. classNameResizable: {
    76. type: String,
    77. default: 'resizable'
    78. },
    79. classNameDragging: {
    80. type: String,
    81. default: 'dragging'
    82. },
    83. classNameResizing: {
    84. type: String,
    85. default: 'resizing'
    86. },
    87. classNameActive: {
    88. type: String,
    89. default: 'active'
    90. },
    91. classNameHandle: {
    92. type: String,
    93. default: 'handle'
    94. },
    95. disableUserSelect: {
    96. type: Boolean,
    97. default: true
    98. },
    99. enableNativeDrag: {
    100. type: Boolean,
    101. default: false
    102. },
    103. preventDeactivation: {
    104. type: Boolean,
    105. default: false
    106. },
    107. active: {
    108. type: Boolean,
    109. default: false
    110. },
    111. draggable: {
    112. type: Boolean,
    113. default: true
    114. },
    115. resizable: {
    116. type: Boolean,
    117. default: true
    118. },
    119. // 锁定宽高比
    120. lockAspectRatio: {
    121. type: Boolean,
    122. default: false
    123. },
    124. w: {
    125. type: [Number, String],
    126. default: 200,
    127. validator: (val) => {
    128. if (typeof val === 'number') {
    129. return val > 0;
    130. }
    131. return val === 'auto';
    132. }
    133. },
    134. h: {
    135. type: [Number, String],
    136. default: 200,
    137. validator: (val) => {
    138. if (typeof val === 'number') {
    139. return val > 0;
    140. }
    141. return val === 'auto';
    142. }
    143. },
    144. minWidth: {
    145. type: Number,
    146. default: 0,
    147. validator: (val) => val >= 0
    148. },
    149. minHeight: {
    150. type: Number,
    151. default: 0,
    152. validator: (val) => val >= 0
    153. },
    154. maxWidth: {
    155. type: Number,
    156. default: null,
    157. validator: (val) => val >= 0
    158. },
    159. maxHeight: {
    160. type: Number,
    161. default: null,
    162. validator: (val) => val >= 0
    163. },
    164. x: {
    165. type: Number,
    166. default: 0
    167. },
    168. y: {
    169. type: Number,
    170. default: 0
    171. },
    172. z: {
    173. type: [String, Number],
    174. default: 'auto',
    175. validator: (val) => (typeof val === 'string' ? val === 'auto' : val >= 0)
    176. },
    177. handles: {
    178. type: Array,
    179. default: () => ['tl', 'tm', 'tr', 'mr', 'br', 'bm', 'bl', 'ml'],
    180. validator: (val) => {
    181. const s = new Set(['tl', 'tm', 'tr', 'mr', 'br', 'bm', 'bl', 'ml']);
    182. return new Set(val.filter(h => s.has(h))).size === val.length;
    183. }
    184. },
    185. dragHandle: {
    186. type: String,
    187. default: null
    188. },
    189. dragCancel: {
    190. type: String,
    191. default: null
    192. },
    193. axis: {
    194. type: String,
    195. default: 'both',
    196. validator: (val) => ['x', 'y', 'both'].includes(val)
    197. },
    198. grid: {
    199. type: Array,
    200. default: () => [1, 1]
    201. },
    202. parent: {
    203. type: [Boolean, String],
    204. default: false
    205. },
    206. onDragStart: {
    207. type: Function,
    208. default: () => true
    209. },
    210. onDrag: {
    211. type: Function,
    212. default: () => true
    213. },
    214. onResizeStart: {
    215. type: Function,
    216. default: () => true
    217. },
    218. onResize: {
    219. type: Function,
    220. default: () => true
    221. },
    222. // 冲突检测
    223. isConflictCheck: {
    224. type: Boolean, default: false
    225. },
    226. // 元素对齐
    227. snap: {
    228. type: Boolean, default: false
    229. },
    230. // 当调用对齐时,用来设置组件与组件之间的对齐距离,以像素为单位
    231. snapTolerance: {
    232. type: Number,
    233. default: 5,
    234. validator: function (val) {
    235. return typeof val === 'number';
    236. }
    237. },
    238. // 缩放比例
    239. scaleRatio: {
    240. type: Number,
    241. default: 1,
    242. validator: (val) => typeof val === 'number'
    243. },
    244. // handle是否缩放
    245. handleInfo: {
    246. type: Object,
    247. default: () => {
    248. return {
    249. size: 8,
    250. offset: -5,
    251. switch: true
    252. };
    253. }
    254. },
    255. componentName: {
    256. type: String,
    257. default: ''
    258. },
    259. componentKey: {
    260. type: String,
    261. default: ''
    262. },
    263. },
    264. data: function () {
    265. return {
    266. left: this.x,
    267. top: this.y,
    268. right: null,
    269. bottom: null,
    270. width: null,
    271. height: null,
    272. widthTouched: false,
    273. heightTouched: false,
    274. aspectFactor: null,
    275. parentWidth: null,
    276. parentHeight: null,
    277. minW: this.minWidth,
    278. minH: this.minHeight,
    279. maxW: this.maxWidth,
    280. maxH: this.maxHeight,
    281. handle: null,
    282. enabled: this.active,
    283. resizing: false,
    284. dragging: false,
    285. zIndex: this.z,
    286. hasMove: false,
    287. hasResize: false,
    288. };
    289. },
    290. created: function () {
    291. // eslint-disable-next-line
    292. if (this.maxWidth && this.minWidth > this.maxWidth) console.warn('[Vdr warn]: Invalid prop: minWidth cannot be greater than maxWidth')
    293. // eslint-disable-next-line
    294. if (this.maxWidth && this.minHeight > this.maxHeight) console.warn('[Vdr warn]: Invalid prop: minHeight cannot be greater than maxHeight')
    295. this.resetBoundsAndMouseState();
    296. },
    297. mounted: function () {
    298. if (!this.enableNativeDrag) {
    299. this.$el.ondragstart = () => false;
    300. }
    301. const [parentWidth, parentHeight] = this.getParentSize();
    302. this.parentWidth = parentWidth;
    303. this.parentHeight = parentHeight;
    304. const [width, height] = getComputedSize(this.$el);
    305. this.aspectFactor = (this.w !== 'auto' ? this.w : width) / (this.h !== 'auto' ? this.h : height);
    306. this.width = this.w !== 'auto' ? this.w : width;
    307. this.height = this.h !== 'auto' ? this.h : height;
    308. this.right = this.parentWidth - this.width - this.left;
    309. this.bottom = this.parentHeight - this.height - this.top;
    310. this.settingAttribute();
    311. addEvent(document.documentElement, 'mousedown', this.deselect);
    312. addEvent(document.documentElement, 'touchend touchcancel', this.deselect);
    313. addEvent(window, 'resize', this.checkParentSize);
    314. },
    315. beforeDestroy: function () {
    316. removeEvent(document.documentElement, 'mousedown', this.deselect);
    317. removeEvent(document.documentElement, 'touchstart', this.handleUp);
    318. removeEvent(document.documentElement, 'mousemove', this.move);
    319. removeEvent(document.documentElement, 'touchmove', this.move);
    320. removeEvent(document.documentElement, 'mouseup', this.handleUp);
    321. removeEvent(document.documentElement, 'touchend touchcancel', this.deselect);
    322. removeEvent(window, 'resize', this.checkParentSize);
    323. },
    324. methods: {
    325. // 重置边界和鼠标状态
    326. resetBoundsAndMouseState () {
    327. this.mouseClickPosition = { mouseX: 0, mouseY: 0, x: 0, y: 0, w: 0, h: 0 };
    328. this.bounds = {
    329. minLeft: null,
    330. maxLeft: null,
    331. minRight: null,
    332. maxRight: null,
    333. minTop: null,
    334. maxTop: null,
    335. minBottom: null,
    336. maxBottom: null
    337. };
    338. },
    339. // 检查父元素大小
    340. checkParentSize () {
    341. if (this.parent) {
    342. const [newParentWidth, newParentHeight] = this.getParentSize();
    343. // 修复父元素改变大小后,组件resizing时活动异常
    344. this.right = newParentWidth - this.width - this.left;
    345. this.bottom = newParentHeight - this.height - this.top;
    346. this.parentWidth = newParentWidth;
    347. this.parentHeight = newParentHeight;
    348. }
    349. },
    350. // 获取父元素大小
    351. getParentSize () {
    352. if (this.parent === true) {
    353. const style = window.getComputedStyle(this.$el.parentNode, null);
    354. return [
    355. parseInt(style.getPropertyValue('width'), 10),
    356. parseInt(style.getPropertyValue('height'), 10)
    357. ];
    358. }
    359. if (typeof this.parent === 'string') {
    360. const parentNode = document.querySelector(this.parent);
    361. if (!(parentNode instanceof HTMLElement)) {
    362. throw new Error(`The selector ${this.parent} does not match any element`);
    363. }
    364. return [parentNode.offsetWidth, parentNode.offsetHeight];
    365. }
    366. return [null, null];
    367. },
    368. // 元素触摸按下
    369. elementTouchDown (e) {
    370. eventsFor = events.touch;
    371. this.elementDown(e);
    372. },
    373. elementMouseDown (e) {
    374. eventsFor = events.mouse;
    375. this.elementDown(e);
    376. },
    377. // 元素按下
    378. elementDown (e) {
    379. if (e instanceof MouseEvent && e.which !== 1 && e.which !== 3) {
    380. return;
    381. }
    382. const target = e.target || e.srcElement;
    383. if (this.$el.contains(target)) {
    384. if (this.onDragStart(e) === false) {
    385. return;
    386. }
    387. if (
    388. (this.dragHandle && !matchesSelectorToParentElements(target, this.dragHandle, this.$el)) ||
    389. (this.dragCancel && matchesSelectorToParentElements(target, this.dragCancel, this.$el))
    390. ) {
    391. this.dragging = false;
    392. return;
    393. }
    394. if (!this.enabled) {
    395. this.enabled = true;
    396. this.$emit('activated', this.componentKey);
    397. this.$emit('update:active', true);
    398. }
    399. if (this.draggable) {
    400. this.dragging = true;
    401. }
    402. this.mouseClickPosition.mouseX = e.touches ? e.touches[0].pageX : e.pageX;
    403. this.mouseClickPosition.mouseY = e.touches ? e.touches[0].pageY : e.pageY;
    404. this.mouseClickPosition.left = this.left;
    405. this.mouseClickPosition.right = this.right;
    406. this.mouseClickPosition.top = this.top;
    407. this.mouseClickPosition.bottom = this.bottom;
    408. this.mouseClickPosition.w = this.width;
    409. this.mouseClickPosition.h = this.height;
    410. if (this.parent) {
    411. this.bounds = this.calcDragLimits();
    412. }
    413. this.hasMove = false;
    414. addEvent(document.documentElement, eventsFor.move, this.move);
    415. addEvent(document.documentElement, eventsFor.stop, this.handleUp);
    416. }
    417. },
    418. selectCurComponent (e) {
    419. // 阻止向父组件冒泡
    420. e.stopPropagation();
    421. e.preventDefault();
    422. this.$store.commit('hideContextMenu');
    423. },
    424. // 计算移动范围
    425. calcDragLimits () {
    426. return {
    427. minLeft: this.left % this.grid[0],
    428. maxLeft: Math.floor((this.parentWidth - this.width - this.left) / this.grid[0]) * this.grid[0] + this.left,
    429. minRight: this.right % this.grid[0],
    430. maxRight: Math.floor((this.parentWidth - this.width - this.right) / this.grid[0]) * this.grid[0] + this.right,
    431. minTop: this.top % this.grid[1],
    432. maxTop: Math.floor((this.parentHeight - this.height - this.top) / this.grid[1]) * this.grid[1] + this.top,
    433. minBottom: this.bottom % this.grid[1],
    434. maxBottom: Math.floor((this.parentHeight - this.height - this.bottom) / this.grid[1]) * this.grid[1] + this.bottom
    435. };
    436. },
    437. // 取消
    438. deselect (e) {
    439. const target = e.target || e.srcElement;
    440. const regex = new RegExp(this.className + '-([trmbl]{2})', '');
    441. if (!this.$el.contains(target) && !regex.test(target.className)) {
    442. if (this.enabled && !this.preventDeactivation) {
    443. this.enabled = false;
    444. this.$emit('deactivated');
    445. this.$emit('update:active', false);
    446. }
    447. removeEvent(document.documentElement, eventsFor.move, this.handleResize);
    448. }
    449. this.resetBoundsAndMouseState();
    450. },
    451. // 控制柄触摸按下
    452. handleTouchDown (handle, e) {
    453. eventsFor = events.touch;
    454. this.handleDown(handle, e);
    455. },
    456. // 控制柄按下
    457. handleDown (handle, e) {
    458. if (e instanceof MouseEvent && e.which !== 1) {
    459. return;
    460. }
    461. if (this.onResizeStart(handle, e) === false) {
    462. return;
    463. }
    464. if (e.stopPropagation) e.stopPropagation();
    465. // Here we avoid a dangerous recursion by faking
    466. // corner handles as middle handles
    467. if (this.lockAspectRatio && !handle.includes('m')) {
    468. this.handle = 'm' + handle.substring(1);
    469. } else {
    470. this.handle = handle;
    471. }
    472. this.resizing = true;
    473. this.mouseClickPosition.mouseX = e.touches ? e.touches[0].pageX : e.pageX;
    474. this.mouseClickPosition.mouseY = e.touches ? e.touches[0].pageY : e.pageY;
    475. this.mouseClickPosition.left = this.left;
    476. this.mouseClickPosition.right = this.right;
    477. this.mouseClickPosition.top = this.top;
    478. this.mouseClickPosition.bottom = this.bottom;
    479. this.mouseClickPosition.w = this.width;
    480. this.mouseClickPosition.h = this.height;
    481. this.bounds = this.calcResizeLimits();
    482. this.hasResize = false;
    483. addEvent(document.documentElement, eventsFor.move, this.handleResize);
    484. addEvent(document.documentElement, eventsFor.stop, this.handleUp);
    485. },
    486. // 计算调整大小范围
    487. calcResizeLimits () {
    488. let minW = this.minW;
    489. let minH = this.minH;
    490. let maxW = this.maxW;
    491. let maxH = this.maxH;
    492. const aspectFactor = this.aspectFactor;
    493. const [gridX, gridY] = this.grid;
    494. const width = this.width;
    495. const height = this.height;
    496. const left = this.left;
    497. const top = this.top;
    498. const right = this.right;
    499. const bottom = this.bottom;
    500. if (this.lockAspectRatio) {
    501. if (minW / minH > aspectFactor) {
    502. minH = minW / aspectFactor;
    503. } else {
    504. minW = aspectFactor * minH;
    505. }
    506. if (maxW && maxH) {
    507. maxW = Math.min(maxW, aspectFactor * maxH);
    508. maxH = Math.min(maxH, maxW / aspectFactor);
    509. } else if (maxW) {
    510. maxH = maxW / aspectFactor;
    511. } else if (maxH) {
    512. maxW = aspectFactor * maxH;
    513. }
    514. }
    515. maxW = maxW - (maxW % gridX);
    516. maxH = maxH - (maxH % gridY);
    517. const limits = {
    518. minLeft: null,
    519. maxLeft: null,
    520. minTop: null,
    521. maxTop: null,
    522. minRight: null,
    523. maxRight: null,
    524. minBottom: null,
    525. maxBottom: null
    526. };
    527. if (this.parent) {
    528. limits.minLeft = left % gridX;
    529. limits.maxLeft = left + Math.floor((width - minW) / gridX) * gridX;
    530. limits.minTop = top % gridY;
    531. limits.maxTop = top + Math.floor((height - minH) / gridY) * gridY;
    532. limits.minRight = right % gridX;
    533. limits.maxRight = right + Math.floor((width - minW) / gridX) * gridX;
    534. limits.minBottom = bottom % gridY;
    535. limits.maxBottom = bottom + Math.floor((height - minH) / gridY) * gridY;
    536. if (maxW) {
    537. limits.minLeft = Math.max(limits.minLeft, this.parentWidth - right - maxW);
    538. limits.minRight = Math.max(limits.minRight, this.parentWidth - left - maxW);
    539. }
    540. if (maxH) {
    541. limits.minTop = Math.max(limits.minTop, this.parentHeight - bottom - maxH);
    542. limits.minBottom = Math.max(limits.minBottom, this.parentHeight - top - maxH);
    543. }
    544. if (this.lockAspectRatio) {
    545. limits.minLeft = Math.max(limits.minLeft, left - top * aspectFactor);
    546. limits.minTop = Math.max(limits.minTop, top - left / aspectFactor);
    547. limits.minRight = Math.max(limits.minRight, right - bottom * aspectFactor);
    548. limits.minBottom = Math.max(limits.minBottom, bottom - right / aspectFactor);
    549. }
    550. } else {
    551. limits.minLeft = null;
    552. limits.maxLeft = left + Math.floor((width - minW) / gridX) * gridX;
    553. limits.minTop = null;
    554. limits.maxTop = top + Math.floor((height - minH) / gridY) * gridY;
    555. limits.minRight = null;
    556. limits.maxRight = right + Math.floor((width - minW) / gridX) * gridX;
    557. limits.minBottom = null;
    558. limits.maxBottom = bottom + Math.floor((height - minH) / gridY) * gridY;
    559. if (maxW) {
    560. limits.minLeft = -(right + maxW);
    561. limits.minRight = -(left + maxW);
    562. }
    563. if (maxH) {
    564. limits.minTop = -(bottom + maxH);
    565. limits.minBottom = -(top + maxH);
    566. }
    567. if (this.lockAspectRatio && (maxW && maxH)) {
    568. limits.minLeft = Math.min(limits.minLeft, -(right + maxW));
    569. limits.minTop = Math.min(limits.minTop, -(maxH + bottom));
    570. limits.minRight = Math.min(limits.minRight, -left - maxW);
    571. limits.minBottom = Math.min(limits.minBottom, -top - maxH);
    572. }
    573. }
    574. return limits;
    575. },
    576. // 移动
    577. move (e) {
    578. if (this.resizing) {
    579. this.handleResize(e);
    580. } else if (this.dragging) {
    581. this.handleDrag(e);
    582. }
    583. },
    584. // 元素移动
    585. async handleDrag (e) {
    586. const axis = this.axis;
    587. const grid = this.grid;
    588. const bounds = this.bounds;
    589. const mouseClickPosition = this.mouseClickPosition;
    590. const tmpDeltaX = axis && axis !== 'y' ? mouseClickPosition.mouseX - (e.touches ? e.touches[0].pageX : e.pageX) : 0;
    591. const tmpDeltaY = axis && axis !== 'x' ? mouseClickPosition.mouseY - (e.touches ? e.touches[0].pageY : e.pageY) : 0;
    592. const [deltaX, deltaY] = snapToGrid(grid, tmpDeltaX, tmpDeltaY, this.scaleRatio);
    593. const left = restrictToBounds(mouseClickPosition.left - deltaX, bounds.minLeft, bounds.maxLeft);
    594. const top = restrictToBounds(mouseClickPosition.top - deltaY, bounds.minTop, bounds.maxTop);
    595. if (this.onDrag(left, top, this.componentKey) === false) {
    596. return;
    597. }
    598. const right = restrictToBounds(mouseClickPosition.right + deltaX, bounds.minRight, bounds.maxRight);
    599. const bottom = restrictToBounds(mouseClickPosition.bottom + deltaY, bounds.minBottom, bounds.maxBottom);
    600. this.left = left;
    601. this.top = top;
    602. this.right = right;
    603. this.bottom = bottom;
    604. await this.snapCheck();
    605. this.hasMove = true;
    606. this.$emit('dragging', this.left, this.top, this.componentKey);
    607. },
    608. moveHorizontally (val) {
    609. // eslint-disable-next-line
    610. const [deltaX, _] = snapToGrid(this.grid, val, this.top, this.scale)
    611. const left = restrictToBounds(deltaX, this.bounds.minLeft, this.bounds.maxLeft);
    612. this.left = left;
    613. this.right = this.parentWidth - this.width - left;
    614. },
    615. moveVertically (val) {
    616. // eslint-disable-next-line
    617. const [_, deltaY] = snapToGrid(this.grid, this.left, val, this.scale)
    618. const top = restrictToBounds(deltaY, this.bounds.minTop, this.bounds.maxTop);
    619. this.top = top;
    620. this.bottom = this.parentHeight - this.height - top;
    621. },
    622. // 控制柄移动
    623. handleResize (e) {
    624. console.log('handleResize-------');
    625. let left = this.left;
    626. let top = this.top;
    627. let right = this.right;
    628. let bottom = this.bottom;
    629. const mouseClickPosition = this.mouseClickPosition;
    630. // eslint-disable-next-line
    631. const lockAspectRatio = this.lockAspectRatio
    632. const aspectFactor = this.aspectFactor;
    633. const tmpDeltaX = mouseClickPosition.mouseX - (e.touches ? e.touches[0].pageX : e.pageX);
    634. const tmpDeltaY = mouseClickPosition.mouseY - (e.touches ? e.touches[0].pageY : e.pageY);
    635. if (!this.widthTouched && tmpDeltaX) {
    636. this.widthTouched = true;
    637. }
    638. if (!this.heightTouched && tmpDeltaY) {
    639. this.heightTouched = true;
    640. }
    641. const [deltaX, deltaY] = snapToGrid(this.grid, tmpDeltaX, tmpDeltaY, this.scaleRatio);
    642. if (this.handle.includes('b')) {
    643. bottom = restrictToBounds(
    644. mouseClickPosition.bottom + deltaY,
    645. this.bounds.minBottom,
    646. this.bounds.maxBottom
    647. );
    648. if (this.lockAspectRatio && this.resizingOnY) {
    649. right = this.right - (this.bottom - bottom) * aspectFactor;
    650. }
    651. } else if (this.handle.includes('t')) {
    652. top = restrictToBounds(
    653. mouseClickPosition.top - deltaY,
    654. this.bounds.minTop,
    655. this.bounds.maxTop
    656. );
    657. if (this.lockAspectRatio && this.resizingOnY) {
    658. left = this.left - (this.top - top) * aspectFactor;
    659. }
    660. }
    661. if (this.handle.includes('r')) {
    662. right = restrictToBounds(
    663. mouseClickPosition.right + deltaX,
    664. this.bounds.minRight,
    665. this.bounds.maxRight
    666. );
    667. if (this.lockAspectRatio && this.resizingOnX) {
    668. bottom = this.bottom - (this.right - right) / aspectFactor;
    669. }
    670. } else if (this.handle.includes('l')) {
    671. left = restrictToBounds(
    672. mouseClickPosition.left - deltaX,
    673. this.bounds.minLeft,
    674. this.bounds.maxLeft
    675. );
    676. if (this.lockAspectRatio && this.resizingOnX) {
    677. top = this.top - (this.left - left) / aspectFactor;
    678. }
    679. }
    680. const width = computeWidth(this.parentWidth, left, right);
    681. const height = computeHeight(this.parentHeight, top, bottom);
    682. if (this.onResize(this.handle, left, top, width, height) === false) {
    683. return;
    684. }
    685. this.left = left;
    686. this.top = top;
    687. this.right = right;
    688. this.bottom = bottom;
    689. this.width = width;
    690. this.height = height;
    691. this.hasResize = true;
    692. this.$emit('resizing', this.left, this.top, this.width, this.height, this.componentKey);
    693. },
    694. changeWidth (val) {
    695. // eslint-disable-next-line
    696. const [newWidth, _] = snapToGrid(this.grid, val, 0, this.scale)
    697. let right = restrictToBounds(
    698. (this.parentWidth - newWidth - this.left),
    699. this.bounds.minRight,
    700. this.bounds.maxRight
    701. );
    702. let bottom = this.bottom;
    703. if (this.lockAspectRatio) {
    704. bottom = this.bottom - (this.right - right) / this.aspectFactor;
    705. }
    706. const width = computeWidth(this.parentWidth, this.left, right);
    707. const height = computeHeight(this.parentHeight, this.top, bottom);
    708. this.right = right;
    709. this.bottom = bottom;
    710. this.width = width;
    711. this.height = height;
    712. },
    713. changeHeight (val) {
    714. // eslint-disable-next-line
    715. const [_, newHeight] = snapToGrid(this.grid, 0, val, this.scale)
    716. let bottom = restrictToBounds(
    717. (this.parentHeight - newHeight - this.top),
    718. this.bounds.minBottom,
    719. this.bounds.maxBottom
    720. );
    721. let right = this.right;
    722. if (this.lockAspectRatio) {
    723. right = this.right - (this.bottom - bottom) * this.aspectFactor;
    724. }
    725. const width = computeWidth(this.parentWidth, this.left, right);
    726. const height = computeHeight(this.parentHeight, this.top, bottom);
    727. this.right = right;
    728. this.bottom = bottom;
    729. this.width = width;
    730. this.height = height;
    731. },
    732. // 从控制柄松开
    733. async handleUp () {
    734. this.handle = null;
    735. // 初始化辅助线数据
    736. const temArr = new Array(3).fill({ display: false, position: '', origin: '', lineLength: '' });
    737. const refLine = { vLine: [], hLine: [] };
    738. for (let i in refLine) { refLine[i] = JSON.parse(JSON.stringify(temArr)); }
    739. if (this.resizing) {
    740. this.resizing = false;
    741. await this.conflictCheck();
    742. this.$emit('refLineParams', refLine);
    743. this.$emit('resizestop', this.left, this.top, this.width, this.height, this.componentKey);
    744. this.hasResize && this.$store.commit('recordSnapshot');
    745. }
    746. if (this.dragging) {
    747. this.dragging = false;
    748. await this.conflictCheck();
    749. this.$emit('refLineParams', refLine);
    750. this.$emit('dragstop', this.left, this.top, this.componentKey);
    751. this.hasMove && this.$store.commit('recordSnapshot');
    752. }
    753. this.resetBoundsAndMouseState();
    754. removeEvent(document.documentElement, eventsFor.move, this.handleResize);
    755. },
    756. // 新增方法 ↓↓↓
    757. // 设置属性
    758. settingAttribute () {
    759. // 设置冲突检测
    760. this.$el.setAttribute('data-is-check', `${this.isConflictCheck}`);
    761. // 设置对齐元素
    762. this.$el.setAttribute('data-is-snap', `${this.snap}`);
    763. },
    764. // 冲突检测
    765. conflictCheck () {
    766. const top = this.top;
    767. const left = this.left;
    768. const width = this.width;
    769. const height = this.height;
    770. if (this.isConflictCheck) {
    771. const nodes = this.$el.parentNode.childNodes; // 获取当前父节点下所有子节点
    772. for (let item of nodes) {
    773. if (item.className !== undefined && !item.className.includes(this.classNameActive) && item.getAttribute('data-is-check') !== null && item.getAttribute('data-is-check') !== 'false') {
    774. const tw = item.offsetWidth;
    775. const th = item.offsetHeight;
    776. // 正则获取left与right
    777. let [tl, tt] = this.formatTransformVal(item.style.transform);
    778. // 左上角与右下角重叠
    779. const tfAndBr = (top >= tt && left >= tl && tt + th > top && tl + tw > left) || (top <= tt && left < tl && top + height > tt && left + width > tl);
    780. // 右上角与左下角重叠
    781. const brAndTf = (left <= tl && top >= tt && left + width > tl && top < tt + th) || (top < tt && left > tl && top + height > tt && left < tl + tw);
    782. // 下边与上边重叠
    783. const bAndT = (top <= tt && left >= tl && top + height > tt && left < tl + tw) || (top >= tt && left <= tl && top < tt + th && left > tl + tw);
    784. // 上边与下边重叠(宽度不一样)
    785. const tAndB = (top <= tt && left >= tl && top + height > tt && left < tl + tw) || (top >= tt && left <= tl && top < tt + th && left > tl + tw);
    786. // 左边与右边重叠
    787. const lAndR = (left >= tl && top >= tt && left < tl + tw && top < tt + th) || (top > tt && left <= tl && left + width > tl && top < tt + th);
    788. // 左边与右边重叠(高度不一样)
    789. const rAndL = (top <= tt && left >= tl && top + height > tt && left < tl + tw) || (top >= tt && left <= tl && top < tt + th && left + width > tl);
    790. // 如果冲突,就将回退到移动前的位置
    791. if (tfAndBr || brAndTf || bAndT || tAndB || lAndR || rAndL) {
    792. this.top = this.mouseClickPosition.top;
    793. this.left = this.mouseClickPosition.left;
    794. this.right = this.mouseClickPosition.right;
    795. this.bottom = this.mouseClickPosition.bottom;
    796. this.width = this.mouseClickPosition.w;
    797. this.height = this.mouseClickPosition.h;
    798. }
    799. }
    800. }
    801. }
    802. },
    803. // 检测对齐元素
    804. async snapCheck () {
    805. let width = this.width;
    806. let height = this.height;
    807. if (this.snap) {
    808. let activeLeft = this.left;
    809. let activeRight = this.left + width;
    810. let activeTop = this.top;
    811. let activeBottom = this.top + height;
    812. // 初始化辅助线数据
    813. const temArr = new Array(3).fill({ display: false, position: '', origin: '', lineLength: '' });
    814. const refLine = { vLine: [], hLine: [] };
    815. for (let i in refLine) { refLine[i] = JSON.parse(JSON.stringify(temArr)); }
    816. // 获取当前父节点下所有子节点
    817. const nodes = this.$el.parentNode.childNodes;
    818. let tem = {
    819. value: { x: [[], [], []], y: [[], [], []] },
    820. display: [],
    821. position: []
    822. };
    823. const { groupWidth, groupHeight, groupLeft, groupTop, bln } = await this.getActiveAll(nodes);
    824. if (!bln) {
    825. width = groupWidth;
    826. height = groupHeight;
    827. activeLeft = groupLeft;
    828. activeRight = groupLeft + groupWidth;
    829. activeTop = groupTop;
    830. activeBottom = groupTop + groupHeight;
    831. }
    832. for (let item of nodes) {
    833. if (item.className !== undefined && !item.className.includes(this.classNameActive) && item.getAttribute('data-is-snap') !== null && item.getAttribute('data-is-snap') !== 'false') {
    834. const w = item.offsetWidth;
    835. const h = item.offsetHeight;
    836. const [l, t] = this.formatTransformVal(item.style.transform);
    837. const r = l + w; // 对齐目标right
    838. const b = t + h; // 对齐目标的bottom
    839. const hc = Math.abs((activeTop + height / 2) - (t + h / 2)) <= this.snapTolerance; // 水平中线
    840. const vc = Math.abs((activeLeft + width / 2) - (l + w / 2)) <= this.snapTolerance; // 垂直中线
    841. const ts = Math.abs(t - activeBottom) <= this.snapTolerance; // 从上到下
    842. const TS = Math.abs(b - activeBottom) <= this.snapTolerance; // 从上到下
    843. const bs = Math.abs(t - activeTop) <= this.snapTolerance; // 从下到上
    844. const BS = Math.abs(b - activeTop) <= this.snapTolerance; // 从下到上
    845. const ls = Math.abs(l - activeRight) <= this.snapTolerance; // 外左
    846. const LS = Math.abs(r - activeRight) <= this.snapTolerance; // 外左
    847. const rs = Math.abs(l - activeLeft) <= this.snapTolerance; // 外右
    848. const RS = Math.abs(r - activeLeft) <= this.snapTolerance; // 外右
    849. tem['display'] = [ts, TS, bs, BS, hc, hc, ls, LS, rs, RS, vc, vc];
    850. tem['position'] = [t, b, t, b, t + h / 2, t + h / 2, l, r, l, r, l + w / 2, l + w / 2];
    851. if (ts) {
    852. if (bln) {
    853. this.top = t - height;
    854. this.bottom = this.parentHeight - this.top - height;
    855. }
    856. tem.value.y[0].push(l, r, activeLeft, activeRight);
    857. }
    858. if (bs) {
    859. if (bln) {
    860. this.top = t;
    861. this.bottom = this.parentHeight - this.top - height;
    862. }
    863. tem.value.y[0].push(l, r, activeLeft, activeRight);
    864. }
    865. if (TS) {
    866. if (bln) {
    867. this.top = b - height;
    868. this.bottom = this.parentHeight - this.top - height;
    869. }
    870. tem.value.y[1].push(l, r, activeLeft, activeRight);
    871. }
    872. if (BS) {
    873. if (bln) {
    874. this.top = b;
    875. this.bottom = this.parentHeight - this.top - height;
    876. }
    877. tem.value.y[1].push(l, r, activeLeft, activeRight);
    878. }
    879. if (ls) {
    880. if (bln) {
    881. this.left = l - width;
    882. this.right = this.parentWidth - this.left - width;
    883. }
    884. tem.value.x[0].push(t, b, activeTop, activeBottom);
    885. }
    886. if (rs) {
    887. if (bln) {
    888. this.left = l;
    889. this.right = this.parentWidth - this.left - width;
    890. }
    891. tem.value.x[0].push(t, b, activeTop, activeBottom);
    892. }
    893. if (LS) {
    894. if (bln) {
    895. this.left = r - width;
    896. this.right = this.parentWidth - this.left - width;
    897. }
    898. tem.value.x[1].push(t, b, activeTop, activeBottom);
    899. }
    900. if (RS) {
    901. if (bln) {
    902. this.left = r;
    903. this.right = this.parentWidth - this.left - width;
    904. }
    905. tem.value.x[1].push(t, b, activeTop, activeBottom);
    906. }
    907. if (hc) {
    908. if (bln) {
    909. this.top = t + h / 2 - height / 2;
    910. this.bottom = this.parentHeight - this.top - height;
    911. }
    912. tem.value.y[2].push(l, r, activeLeft, activeRight);
    913. }
    914. if (vc) {
    915. if (bln) {
    916. this.left = l + w / 2 - width / 2;
    917. this.right = this.parentWidth - this.left - width;
    918. }
    919. tem.value.x[2].push(t, b, activeTop, activeBottom);
    920. }
    921. // 辅助线坐标与是否显示(display)对应的数组,易于循环遍历
    922. const arrTem = [0, 1, 0, 1, 2, 2, 0, 1, 0, 1, 2, 2];
    923. for (let i = 0; i <= arrTem.length; i++) {
    924. // 前6为Y辅助线,后6为X辅助线
    925. const xory = i < 6 ? 'y' : 'x';
    926. const horv = i < 6 ? 'hLine' : 'vLine';
    927. if (tem.display[i]) {
    928. const { origin, length } = this.calcLineValues(tem.value[xory][arrTem[i]]);
    929. refLine[horv][arrTem[i]].display = tem.display[i];
    930. refLine[horv][arrTem[i]].position = tem.position[i] + 'px';
    931. refLine[horv][arrTem[i]].origin = origin;
    932. refLine[horv][arrTem[i]].lineLength = length;
    933. }
    934. }
    935. }
    936. }
    937. this.$emit('refLineParams', refLine);
    938. }
    939. },
    940. calcLineValues (arr) {
    941. const length = Math.max(...arr) - Math.min(...arr) + 'px';
    942. const origin = Math.min(...arr) + 'px';
    943. return { length, origin };
    944. },
    945. async getActiveAll (nodes) {
    946. const activeAll = [];
    947. const XArray = [];
    948. const YArray = [];
    949. let groupWidth = 0;
    950. let groupHeight = 0;
    951. let groupLeft = 0;
    952. let groupTop = 0;
    953. for (let item of nodes) {
    954. if (item.className !== undefined && item.className.includes(this.classNameActive)) {
    955. activeAll.push(item);
    956. }
    957. }
    958. const AllLength = activeAll.length;
    959. if (AllLength > 1) {
    960. for (let i of activeAll) {
    961. const l = i.offsetLeft;
    962. const r = l + i.offsetWidth;
    963. const t = i.offsetTop;
    964. const b = t + i.offsetHeight;
    965. XArray.push(t, b);
    966. YArray.push(l, r);
    967. }
    968. groupWidth = Math.max(...YArray) - Math.min(...YArray);
    969. groupHeight = Math.max(...XArray) - Math.min(...XArray);
    970. groupLeft = Math.min(...YArray);
    971. groupTop = Math.min(...XArray);
    972. }
    973. const bln = AllLength === 1;
    974. return { groupWidth, groupHeight, groupLeft, groupTop, bln };
    975. },
    976. // 正则获取left与top
    977. formatTransformVal (string) {
    978. let [left, top] = string.replace(/[^0-9\-,]/g, '').split(',');
    979. if (top === undefined) top = 0;
    980. return [+left, +top];
    981. }
    982. },
    983. computed: {
    984. handleStyle () {
    985. return (stick) => {
    986. if (!this.handleInfo.switch) return { display: this.enabled ? 'block' : 'none' };
    987. const size = (this.handleInfo.size / this.scaleRatio).toFixed(2);
    988. const offset = (this.handleInfo.offset / this.scaleRatio).toFixed(2);
    989. const center = (size / 2).toFixed(2);
    990. const styleMap = {
    991. tl: {
    992. top: `${offset}px`,
    993. left: `${offset}px`
    994. },
    995. tm: {
    996. top: `${offset}px`,
    997. left: `calc(50% - ${center}px)`
    998. },
    999. tr: {
    1000. top: `${offset}px`,
    1001. right: `${offset}px`
    1002. },
    1003. mr: {
    1004. top: `calc(50% - ${center}px)`,
    1005. right: `${offset}px`
    1006. },
    1007. br: {
    1008. bottom: `${offset}px`,
    1009. right: `${offset}px`
    1010. },
    1011. bm: {
    1012. bottom: `${offset}px`,
    1013. right: `calc(50% - ${center}px)`
    1014. },
    1015. bl: {
    1016. bottom: `${offset}px`,
    1017. left: `${offset}px`
    1018. },
    1019. ml: {
    1020. top: `calc(50% - ${center}px)`,
    1021. left: `${offset}px`
    1022. }
    1023. };
    1024. const stickStyle = {
    1025. width: `${size}px`,
    1026. height: `${size}px`,
    1027. top: styleMap[stick].top,
    1028. left: styleMap[stick].left,
    1029. right: styleMap[stick].right,
    1030. bottom: styleMap[stick].bottom
    1031. };
    1032. stickStyle.display = this.enabled ? 'block' : 'none';
    1033. return stickStyle;
    1034. };
    1035. },
    1036. style () {
    1037. return {
    1038. transform: `translate(${this.left}px, ${this.top}px)`,
    1039. width: this.computedWidth,
    1040. height: this.computedHeight,
    1041. zIndex: this.zIndex,
    1042. ...(this.dragging && this.disableUserSelect ? userSelectNone : userSelectAuto)
    1043. };
    1044. },
    1045. // 控制柄显示与否
    1046. actualHandles () {
    1047. if (!this.resizable) return [];
    1048. return this.handles;
    1049. },
    1050. computedWidth () {
    1051. if (this.w === 'auto') {
    1052. if (!this.widthTouched) {
    1053. return 'auto';
    1054. }
    1055. }
    1056. return this.width + 'px';
    1057. },
    1058. computedHeight () {
    1059. if (this.h === 'auto') {
    1060. if (!this.heightTouched) {
    1061. return 'auto';
    1062. }
    1063. }
    1064. return this.height + 'px';
    1065. },
    1066. resizingOnX () {
    1067. return (Boolean(this.handle) && (this.handle.includes('l') || this.handle.includes('r')));
    1068. },
    1069. resizingOnY () {
    1070. return (Boolean(this.handle) && (this.handle.includes('t') || this.handle.includes('b')));
    1071. },
    1072. isCornerHandle () {
    1073. return (Boolean(this.handle) && ['tl', 'tr', 'br', 'bl'].includes(this.handle));
    1074. }
    1075. },
    1076. watch: {
    1077. active (val) {
    1078. this.enabled = val;
    1079. if (val) {
    1080. this.$emit('activated');
    1081. } else {
    1082. this.$emit('deactivated');
    1083. }
    1084. },
    1085. z (val) {
    1086. if (val >= 0 || val === 'auto') {
    1087. this.zIndex = val;
    1088. }
    1089. },
    1090. x (val) {
    1091. if (this.resizing || this.dragging) {
    1092. return;
    1093. }
    1094. if (this.parent) {
    1095. this.bounds = this.calcDragLimits();
    1096. }
    1097. this.moveHorizontally(val);
    1098. },
    1099. y (val) {
    1100. if (this.resizing || this.dragging) {
    1101. return;
    1102. }
    1103. if (this.parent) {
    1104. this.bounds = this.calcDragLimits();
    1105. }
    1106. this.moveVertically(val);
    1107. },
    1108. lockAspectRatio (val) {
    1109. if (val) {
    1110. this.aspectFactor = this.width / this.height;
    1111. } else {
    1112. this.aspectFactor = undefined;
    1113. }
    1114. },
    1115. minWidth (val) {
    1116. if (val > 0 && val <= this.width) {
    1117. this.minW = val;
    1118. }
    1119. },
    1120. minHeight (val) {
    1121. if (val > 0 && val <= this.height) {
    1122. this.minH = val;
    1123. }
    1124. },
    1125. maxWidth (val) {
    1126. this.maxW = val;
    1127. },
    1128. maxHeight (val) {
    1129. this.maxH = val;
    1130. },
    1131. w (val) {
    1132. if (this.resizing || this.dragging) {
    1133. return;
    1134. }
    1135. if (this.parent) {
    1136. this.bounds = this.calcResizeLimits();
    1137. }
    1138. this.changeWidth(val);
    1139. },
    1140. h (val) {
    1141. if (this.resizing || this.dragging) {
    1142. return;
    1143. }
    1144. if (this.parent) {
    1145. this.bounds = this.calcResizeLimits();
    1146. }
    1147. this.changeHeight(val);
    1148. }
    1149. }
    1150. };
    1151. </script>
    1152. <template>
    1153. <div
    1154. :style="style"
    1155. :class="[
    1156. {
    1157. [classNameActive]: enabled,
    1158. [classNameDragging]: dragging,
    1159. [classNameResizing]: resizing,
    1160. [classNameDraggable]: draggable,
    1161. [classNameResizable]: resizable,
    1162. },
    1163. className,
    1164. ]"
    1165. @mousedown="elementMouseDown"
    1166. @touchstart="elementTouchDown"
    1167. @click="selectCurComponent"
    1168. >
    1169. <div
    1170. v-for="(handle, index) in actualHandles"
    1171. :key="handle + index"
    1172. :class="[classNameHandle, classNameHandle + '-' + handle]"
    1173. :style="handleStyle(handle)"
    1174. @mousedown.stop.prevent="handleDown(handle, $event)"
    1175. @touchstart.stop.prevent="handleTouchDown(handle, $event)"
    1176. >
    1177. <slot :name="handle"></slot>
    1178. </div>
    1179. <slot></slot>
    1180. </div>
    1181. </template>
    1182. <script>
    1183. import { matchesSelectorToParentElements, getComputedSize, addEvent, removeEvent } from '../utils/dom';
    1184. import { computeWidth, computeHeight, restrictToBounds, snapToGrid } from '../utils/fns';
    1185. const events = {
    1186. mouse: {
    1187. start: 'mousedown',
    1188. move: 'mousemove',
    1189. stop: 'mouseup'
    1190. },
    1191. touch: {
    1192. start: 'touchstart',
    1193. move: 'touchmove',
    1194. stop: 'touchend'
    1195. }
    1196. };
    1197. // 禁止用户选取
    1198. const userSelectNone = {
    1199. userSelect: 'none',
    1200. MozUserSelect: 'none',
    1201. WebkitUserSelect: 'none',
    1202. MsUserSelect: 'none'
    1203. };
    1204. // 用户选中自动
    1205. const userSelectAuto = {
    1206. userSelect: 'auto',
    1207. MozUserSelect: 'auto',
    1208. WebkitUserSelect: 'auto',
    1209. MsUserSelect: 'auto'
    1210. };
    1211. let eventsFor = events.mouse;
    1212. export default {
    1213. replace: true,
    1214. name: 'vue-draggable-resizable',
    1215. props: {
    1216. className: {
    1217. type: String,
    1218. default: 'vdr'
    1219. },
    1220. classNameDraggable: {
    1221. type: String,
    1222. default: 'draggable'
    1223. },
    1224. classNameResizable: {
    1225. type: String,
    1226. default: 'resizable'
    1227. },
    1228. classNameDragging: {
    1229. type: String,
    1230. default: 'dragging'
    1231. },
    1232. classNameResizing: {
    1233. type: String,
    1234. default: 'resizing'
    1235. },
    1236. classNameActive: {
    1237. type: String,
    1238. default: 'active'
    1239. },
    1240. classNameHandle: {
    1241. type: String,
    1242. default: 'handle'
    1243. },
    1244. disableUserSelect: {
    1245. type: Boolean,
    1246. default: true
    1247. },
    1248. enableNativeDrag: {
    1249. type: Boolean,
    1250. default: false
    1251. },
    1252. preventDeactivation: {
    1253. type: Boolean,
    1254. default: false
    1255. },
    1256. active: {
    1257. type: Boolean,
    1258. default: false
    1259. },
    1260. draggable: {
    1261. type: Boolean,
    1262. default: true
    1263. },
    1264. resizable: {
    1265. type: Boolean,
    1266. default: true
    1267. },
    1268. // 锁定宽高比
    1269. lockAspectRatio: {
    1270. type: Boolean,
    1271. default: false
    1272. },
    1273. w: {
    1274. type: [Number, String],
    1275. default: 200,
    1276. validator: (val) => {
    1277. if (typeof val === 'number') {
    1278. return val > 0;
    1279. }
    1280. return val === 'auto';
    1281. }
    1282. },
    1283. h: {
    1284. type: [Number, String],
    1285. default: 200,
    1286. validator: (val) => {
    1287. if (typeof val === 'number') {
    1288. return val > 0;
    1289. }
    1290. return val === 'auto';
    1291. }
    1292. },
    1293. minWidth: {
    1294. type: Number,
    1295. default: 0,
    1296. validator: (val) => val >= 0
    1297. },
    1298. minHeight: {
    1299. type: Number,
    1300. default: 0,
    1301. validator: (val) => val >= 0
    1302. },
    1303. maxWidth: {
    1304. type: Number,
    1305. default: null,
    1306. validator: (val) => val >= 0
    1307. },
    1308. maxHeight: {
    1309. type: Number,
    1310. default: null,
    1311. validator: (val) => val >= 0
    1312. },
    1313. x: {
    1314. type: Number,
    1315. default: 0
    1316. },
    1317. y: {
    1318. type: Number,
    1319. default: 0
    1320. },
    1321. z: {
    1322. type: [String, Number],
    1323. default: 'auto',
    1324. validator: (val) => (typeof val === 'string' ? val === 'auto' : val >= 0)
    1325. },
    1326. handles: {
    1327. type: Array,
    1328. default: () => ['tl', 'tm', 'tr', 'mr', 'br', 'bm', 'bl', 'ml'],
    1329. validator: (val) => {
    1330. const s = new Set(['tl', 'tm', 'tr', 'mr', 'br', 'bm', 'bl', 'ml']);
    1331. return new Set(val.filter(h => s.has(h))).size === val.length;
    1332. }
    1333. },
    1334. dragHandle: {
    1335. type: String,
    1336. default: null
    1337. },
    1338. dragCancel: {
    1339. type: String,
    1340. default: null
    1341. },
    1342. axis: {
    1343. type: String,
    1344. default: 'both',
    1345. validator: (val) => ['x', 'y', 'both'].includes(val)
    1346. },
    1347. grid: {
    1348. type: Array,
    1349. default: () => [1, 1]
    1350. },
    1351. parent: {
    1352. type: [Boolean, String],
    1353. default: false
    1354. },
    1355. onDragStart: {
    1356. type: Function,
    1357. default: () => true
    1358. },
    1359. onDrag: {
    1360. type: Function,
    1361. default: () => true
    1362. },
    1363. onResizeStart: {
    1364. type: Function,
    1365. default: () => true
    1366. },
    1367. onResize: {
    1368. type: Function,
    1369. default: () => true
    1370. },
    1371. // 冲突检测
    1372. isConflictCheck: {
    1373. type: Boolean, default: false
    1374. },
    1375. // 元素对齐
    1376. snap: {
    1377. type: Boolean, default: false
    1378. },
    1379. // 当调用对齐时,用来设置组件与组件之间的对齐距离,以像素为单位
    1380. snapTolerance: {
    1381. type: Number,
    1382. default: 5,
    1383. validator: function (val) {
    1384. return typeof val === 'number';
    1385. }
    1386. },
    1387. // 缩放比例
    1388. scaleRatio: {
    1389. type: Number,
    1390. default: 1,
    1391. validator: (val) => typeof val === 'number'
    1392. },
    1393. // handle是否缩放
    1394. handleInfo: {
    1395. type: Object,
    1396. default: () => {
    1397. return {
    1398. size: 8,
    1399. offset: -5,
    1400. switch: true
    1401. };
    1402. }
    1403. },
    1404. componentName: {
    1405. type: String,
    1406. default: ''
    1407. },
    1408. componentKey: {
    1409. type: String,
    1410. default: ''
    1411. },
    1412. },
    1413. data: function () {
    1414. return {
    1415. left: this.x,
    1416. top: this.y,
    1417. right: null,
    1418. bottom: null,
    1419. width: null,
    1420. height: null,
    1421. widthTouched: false,
    1422. heightTouched: false,
    1423. aspectFactor: null,
    1424. parentWidth: null,
    1425. parentHeight: null,
    1426. minW: this.minWidth,
    1427. minH: this.minHeight,
    1428. maxW: this.maxWidth,
    1429. maxH: this.maxHeight,
    1430. handle: null,
    1431. enabled: this.active,
    1432. resizing: false,
    1433. dragging: false,
    1434. zIndex: this.z,
    1435. hasMove: false,
    1436. hasResize: false,
    1437. };
    1438. },
    1439. created: function () {
    1440. // eslint-disable-next-line
    1441. if (this.maxWidth && this.minWidth > this.maxWidth) console.warn('[Vdr warn]: Invalid prop: minWidth cannot be greater than maxWidth')
    1442. // eslint-disable-next-line
    1443. if (this.maxWidth && this.minHeight > this.maxHeight) console.warn('[Vdr warn]: Invalid prop: minHeight cannot be greater than maxHeight')
    1444. this.resetBoundsAndMouseState();
    1445. },
    1446. mounted: function () {
    1447. if (!this.enableNativeDrag) {
    1448. this.$el.ondragstart = () => false;
    1449. }
    1450. const [parentWidth, parentHeight] = this.getParentSize();
    1451. this.parentWidth = parentWidth;
    1452. this.parentHeight = parentHeight;
    1453. const [width, height] = getComputedSize(this.$el);
    1454. this.aspectFactor = (this.w !== 'auto' ? this.w : width) / (this.h !== 'auto' ? this.h : height);
    1455. this.width = this.w !== 'auto' ? this.w : width;
    1456. this.height = this.h !== 'auto' ? this.h : height;
    1457. this.right = this.parentWidth - this.width - this.left;
    1458. this.bottom = this.parentHeight - this.height - this.top;
    1459. this.settingAttribute();
    1460. addEvent(document.documentElement, 'mousedown', this.deselect);
    1461. addEvent(document.documentElement, 'touchend touchcancel', this.deselect);
    1462. addEvent(window, 'resize', this.checkParentSize);
    1463. },
    1464. beforeDestroy: function () {
    1465. removeEvent(document.documentElement, 'mousedown', this.deselect);
    1466. removeEvent(document.documentElement, 'touchstart', this.handleUp);
    1467. removeEvent(document.documentElement, 'mousemove', this.move);
    1468. removeEvent(document.documentElement, 'touchmove', this.move);
    1469. removeEvent(document.documentElement, 'mouseup', this.handleUp);
    1470. removeEvent(document.documentElement, 'touchend touchcancel', this.deselect);
    1471. removeEvent(window, 'resize', this.checkParentSize);
    1472. },
    1473. methods: {
    1474. // 重置边界和鼠标状态
    1475. resetBoundsAndMouseState () {
    1476. this.mouseClickPosition = { mouseX: 0, mouseY: 0, x: 0, y: 0, w: 0, h: 0 };
    1477. this.bounds = {
    1478. minLeft: null,
    1479. maxLeft: null,
    1480. minRight: null,
    1481. maxRight: null,
    1482. minTop: null,
    1483. maxTop: null,
    1484. minBottom: null,
    1485. maxBottom: null
    1486. };
    1487. },
    1488. // 检查父元素大小
    1489. checkParentSize () {
    1490. if (this.parent) {
    1491. const [newParentWidth, newParentHeight] = this.getParentSize();
    1492. // 修复父元素改变大小后,组件resizing时活动异常
    1493. this.right = newParentWidth - this.width - this.left;
    1494. this.bottom = newParentHeight - this.height - this.top;
    1495. this.parentWidth = newParentWidth;
    1496. this.parentHeight = newParentHeight;
    1497. }
    1498. },
    1499. // 获取父元素大小
    1500. getParentSize () {
    1501. if (this.parent === true) {
    1502. const style = window.getComputedStyle(this.$el.parentNode, null);
    1503. return [
    1504. parseInt(style.getPropertyValue('width'), 10),
    1505. parseInt(style.getPropertyValue('height'), 10)
    1506. ];
    1507. }
    1508. if (typeof this.parent === 'string') {
    1509. const parentNode = document.querySelector(this.parent);
    1510. if (!(parentNode instanceof HTMLElement)) {
    1511. throw new Error(`The selector ${this.parent} does not match any element`);
    1512. }
    1513. return [parentNode.offsetWidth, parentNode.offsetHeight];
    1514. }
    1515. return [null, null];
    1516. },
    1517. // 元素触摸按下
    1518. elementTouchDown (e) {
    1519. eventsFor = events.touch;
    1520. this.elementDown(e);
    1521. },
    1522. elementMouseDown (e) {
    1523. eventsFor = events.mouse;
    1524. this.elementDown(e);
    1525. },
    1526. // 元素按下
    1527. elementDown (e) {
    1528. if (e instanceof MouseEvent && e.which !== 1 && e.which !== 3) {
    1529. return;
    1530. }
    1531. const target = e.target || e.srcElement;
    1532. if (this.$el.contains(target)) {
    1533. if (this.onDragStart(e) === false) {
    1534. return;
    1535. }
    1536. if (
    1537. (this.dragHandle && !matchesSelectorToParentElements(target, this.dragHandle, this.$el)) ||
    1538. (this.dragCancel && matchesSelectorToParentElements(target, this.dragCancel, this.$el))
    1539. ) {
    1540. this.dragging = false;
    1541. return;
    1542. }
    1543. if (!this.enabled) {
    1544. this.enabled = true;
    1545. this.$emit('activated', this.componentKey);
    1546. this.$emit('update:active', true);
    1547. }
    1548. if (this.draggable) {
    1549. this.dragging = true;
    1550. }
    1551. this.mouseClickPosition.mouseX = e.touches ? e.touches[0].pageX : e.pageX;
    1552. this.mouseClickPosition.mouseY = e.touches ? e.touches[0].pageY : e.pageY;
    1553. this.mouseClickPosition.left = this.left;
    1554. this.mouseClickPosition.right = this.right;
    1555. this.mouseClickPosition.top = this.top;
    1556. this.mouseClickPosition.bottom = this.bottom;
    1557. this.mouseClickPosition.w = this.width;
    1558. this.mouseClickPosition.h = this.height;
    1559. if (this.parent) {
    1560. this.bounds = this.calcDragLimits();
    1561. }
    1562. this.hasMove = false;
    1563. addEvent(document.documentElement, eventsFor.move, this.move);
    1564. addEvent(document.documentElement, eventsFor.stop, this.handleUp);
    1565. }
    1566. },
    1567. selectCurComponent (e) {
    1568. // 阻止向父组件冒泡
    1569. e.stopPropagation();
    1570. e.preventDefault();
    1571. this.$store.commit('hideContextMenu');
    1572. },
    1573. // 计算移动范围
    1574. calcDragLimits () {
    1575. return {
    1576. minLeft: this.left % this.grid[0],
    1577. maxLeft: Math.floor((this.parentWidth - this.width - this.left) / this.grid[0]) * this.grid[0] + this.left,
    1578. minRight: this.right % this.grid[0],
    1579. maxRight: Math.floor((this.parentWidth - this.width - this.right) / this.grid[0]) * this.grid[0] + this.right,
    1580. minTop: this.top % this.grid[1],
    1581. maxTop: Math.floor((this.parentHeight - this.height - this.top) / this.grid[1]) * this.grid[1] + this.top,
    1582. minBottom: this.bottom % this.grid[1],
    1583. maxBottom: Math.floor((this.parentHeight - this.height - this.bottom) / this.grid[1]) * this.grid[1] + this.bottom
    1584. };
    1585. },
    1586. // 取消
    1587. deselect (e) {
    1588. const target = e.target || e.srcElement;
    1589. const regex = new RegExp(this.className + '-([trmbl]{2})', '');
    1590. if (!this.$el.contains(target) && !regex.test(target.className)) {
    1591. if (this.enabled && !this.preventDeactivation) {
    1592. this.enabled = false;
    1593. this.$emit('deactivated');
    1594. this.$emit('update:active', false);
    1595. }
    1596. removeEvent(document.documentElement, eventsFor.move, this.handleResize);
    1597. }
    1598. this.resetBoundsAndMouseState();
    1599. },
    1600. // 控制柄触摸按下
    1601. handleTouchDown (handle, e) {
    1602. eventsFor = events.touch;
    1603. this.handleDown(handle, e);
    1604. },
    1605. // 控制柄按下
    1606. handleDown (handle, e) {
    1607. if (e instanceof MouseEvent && e.which !== 1) {
    1608. return;
    1609. }
    1610. if (this.onResizeStart(handle, e) === false) {
    1611. return;
    1612. }
    1613. if (e.stopPropagation) e.stopPropagation();
    1614. // Here we avoid a dangerous recursion by faking
    1615. // corner handles as middle handles
    1616. if (this.lockAspectRatio && !handle.includes('m')) {
    1617. this.handle = 'm' + handle.substring(1);
    1618. } else {
    1619. this.handle = handle;
    1620. }
    1621. this.resizing = true;
    1622. this.mouseClickPosition.mouseX = e.touches ? e.touches[0].pageX : e.pageX;
    1623. this.mouseClickPosition.mouseY = e.touches ? e.touches[0].pageY : e.pageY;
    1624. this.mouseClickPosition.left = this.left;
    1625. this.mouseClickPosition.right = this.right;
    1626. this.mouseClickPosition.top = this.top;
    1627. this.mouseClickPosition.bottom = this.bottom;
    1628. this.mouseClickPosition.w = this.width;
    1629. this.mouseClickPosition.h = this.height;
    1630. this.bounds = this.calcResizeLimits();
    1631. this.hasResize = false;
    1632. addEvent(document.documentElement, eventsFor.move, this.handleResize);
    1633. addEvent(document.documentElement, eventsFor.stop, this.handleUp);
    1634. },
    1635. // 计算调整大小范围
    1636. calcResizeLimits () {
    1637. let minW = this.minW;
    1638. let minH = this.minH;
    1639. let maxW = this.maxW;
    1640. let maxH = this.maxH;
    1641. const aspectFactor = this.aspectFactor;
    1642. const [gridX, gridY] = this.grid;
    1643. const width = this.width;
    1644. const height = this.height;
    1645. const left = this.left;
    1646. const top = this.top;
    1647. const right = this.right;
    1648. const bottom = this.bottom;
    1649. if (this.lockAspectRatio) {
    1650. if (minW / minH > aspectFactor) {
    1651. minH = minW / aspectFactor;
    1652. } else {
    1653. minW = aspectFactor * minH;
    1654. }
    1655. if (maxW && maxH) {
    1656. maxW = Math.min(maxW, aspectFactor * maxH);
    1657. maxH = Math.min(maxH, maxW / aspectFactor);
    1658. } else if (maxW) {
    1659. maxH = maxW / aspectFactor;
    1660. } else if (maxH) {
    1661. maxW = aspectFactor * maxH;
    1662. }
    1663. }
    1664. maxW = maxW - (maxW % gridX);
    1665. maxH = maxH - (maxH % gridY);
    1666. const limits = {
    1667. minLeft: null,
    1668. maxLeft: null,
    1669. minTop: null,
    1670. maxTop: null,
    1671. minRight: null,
    1672. maxRight: null,
    1673. minBottom: null,
    1674. maxBottom: null
    1675. };
    1676. if (this.parent) {
    1677. limits.minLeft = left % gridX;
    1678. limits.maxLeft = left + Math.floor((width - minW) / gridX) * gridX;
    1679. limits.minTop = top % gridY;
    1680. limits.maxTop = top + Math.floor((height - minH) / gridY) * gridY;
    1681. limits.minRight = right % gridX;
    1682. limits.maxRight = right + Math.floor((width - minW) / gridX) * gridX;
    1683. limits.minBottom = bottom % gridY;
    1684. limits.maxBottom = bottom + Math.floor((height - minH) / gridY) * gridY;
    1685. if (maxW) {
    1686. limits.minLeft = Math.max(limits.minLeft, this.parentWidth - right - maxW);
    1687. limits.minRight = Math.max(limits.minRight, this.parentWidth - left - maxW);
    1688. }
    1689. if (maxH) {
    1690. limits.minTop = Math.max(limits.minTop, this.parentHeight - bottom - maxH);
    1691. limits.minBottom = Math.max(limits.minBottom, this.parentHeight - top - maxH);
    1692. }
    1693. if (this.lockAspectRatio) {
    1694. limits.minLeft = Math.max(limits.minLeft, left - top * aspectFactor);
    1695. limits.minTop = Math.max(limits.minTop, top - left / aspectFactor);
    1696. limits.minRight = Math.max(limits.minRight, right - bottom * aspectFactor);
    1697. limits.minBottom = Math.max(limits.minBottom, bottom - right / aspectFactor);
    1698. }
    1699. } else {
    1700. limits.minLeft = null;
    1701. limits.maxLeft = left + Math.floor((width - minW) / gridX) * gridX;
    1702. limits.minTop = null;
    1703. limits.maxTop = top + Math.floor((height - minH) / gridY) * gridY;
    1704. limits.minRight = null;
    1705. limits.maxRight = right + Math.floor((width - minW) / gridX) * gridX;
    1706. limits.minBottom = null;
    1707. limits.maxBottom = bottom + Math.floor((height - minH) / gridY) * gridY;
    1708. if (maxW) {
    1709. limits.minLeft = -(right + maxW);
    1710. limits.minRight = -(left + maxW);
    1711. }
    1712. if (maxH) {
    1713. limits.minTop = -(bottom + maxH);
    1714. limits.minBottom = -(top + maxH);
    1715. }
    1716. if (this.lockAspectRatio && (maxW && maxH)) {
    1717. limits.minLeft = Math.min(limits.minLeft, -(right + maxW));
    1718. limits.minTop = Math.min(limits.minTop, -(maxH + bottom));
    1719. limits.minRight = Math.min(limits.minRight, -left - maxW);
    1720. limits.minBottom = Math.min(limits.minBottom, -top - maxH);
    1721. }
    1722. }
    1723. return limits;
    1724. },
    1725. // 移动
    1726. move (e) {
    1727. if (this.resizing) {
    1728. this.handleResize(e);
    1729. } else if (this.dragging) {
    1730. this.handleDrag(e);
    1731. }
    1732. },
    1733. // 元素移动
    1734. async handleDrag (e) {
    1735. const axis = this.axis;
    1736. const grid = this.grid;
    1737. const bounds = this.bounds;
    1738. const mouseClickPosition = this.mouseClickPosition;
    1739. const tmpDeltaX = axis && axis !== 'y' ? mouseClickPosition.mouseX - (e.touches ? e.touches[0].pageX : e.pageX) : 0;
    1740. const tmpDeltaY = axis && axis !== 'x' ? mouseClickPosition.mouseY - (e.touches ? e.touches[0].pageY : e.pageY) : 0;
    1741. const [deltaX, deltaY] = snapToGrid(grid, tmpDeltaX, tmpDeltaY, this.scaleRatio);
    1742. const left = restrictToBounds(mouseClickPosition.left - deltaX, bounds.minLeft, bounds.maxLeft);
    1743. const top = restrictToBounds(mouseClickPosition.top - deltaY, bounds.minTop, bounds.maxTop);
    1744. if (this.onDrag(left, top, this.componentKey) === false) {
    1745. return;
    1746. }
    1747. const right = restrictToBounds(mouseClickPosition.right + deltaX, bounds.minRight, bounds.maxRight);
    1748. const bottom = restrictToBounds(mouseClickPosition.bottom + deltaY, bounds.minBottom, bounds.maxBottom);
    1749. this.left = left;
    1750. this.top = top;
    1751. this.right = right;
    1752. this.bottom = bottom;
    1753. await this.snapCheck();
    1754. this.hasMove = true;
    1755. this.$emit('dragging', this.left, this.top, this.componentKey);
    1756. },
    1757. moveHorizontally (val) {
    1758. // eslint-disable-next-line
    1759. const [deltaX, _] = snapToGrid(this.grid, val, this.top, this.scale)
    1760. const left = restrictToBounds(deltaX, this.bounds.minLeft, this.bounds.maxLeft);
    1761. this.left = left;
    1762. this.right = this.parentWidth - this.width - left;
    1763. },
    1764. moveVertically (val) {
    1765. // eslint-disable-next-line
    1766. const [_, deltaY] = snapToGrid(this.grid, this.left, val, this.scale)
    1767. const top = restrictToBounds(deltaY, this.bounds.minTop, this.bounds.maxTop);
    1768. this.top = top;
    1769. this.bottom = this.parentHeight - this.height - top;
    1770. },
    1771. // 控制柄移动
    1772. handleResize (e) {
    1773. console.log('handleResize-------');
    1774. let left = this.left;
    1775. let top = this.top;
    1776. let right = this.right;
    1777. let bottom = this.bottom;
    1778. const mouseClickPosition = this.mouseClickPosition;
    1779. // eslint-disable-next-line
    1780. const lockAspectRatio = this.lockAspectRatio
    1781. const aspectFactor = this.aspectFactor;
    1782. const tmpDeltaX = mouseClickPosition.mouseX - (e.touches ? e.touches[0].pageX : e.pageX);
    1783. const tmpDeltaY = mouseClickPosition.mouseY - (e.touches ? e.touches[0].pageY : e.pageY);
    1784. if (!this.widthTouched && tmpDeltaX) {
    1785. this.widthTouched = true;
    1786. }
    1787. if (!this.heightTouched && tmpDeltaY) {
    1788. this.heightTouched = true;
    1789. }
    1790. const [deltaX, deltaY] = snapToGrid(this.grid, tmpDeltaX, tmpDeltaY, this.scaleRatio);
    1791. if (this.handle.includes('b')) {
    1792. bottom = restrictToBounds(
    1793. mouseClickPosition.bottom + deltaY,
    1794. this.bounds.minBottom,
    1795. this.bounds.maxBottom
    1796. );
    1797. if (this.lockAspectRatio && this.resizingOnY) {
    1798. right = this.right - (this.bottom - bottom) * aspectFactor;
    1799. }
    1800. } else if (this.handle.includes('t')) {
    1801. top = restrictToBounds(
    1802. mouseClickPosition.top - deltaY,
    1803. this.bounds.minTop,
    1804. this.bounds.maxTop
    1805. );
    1806. if (this.lockAspectRatio && this.resizingOnY) {
    1807. left = this.left - (this.top - top) * aspectFactor;
    1808. }
    1809. }
    1810. if (this.handle.includes('r')) {
    1811. right = restrictToBounds(
    1812. mouseClickPosition.right + deltaX,
    1813. this.bounds.minRight,
    1814. this.bounds.maxRight
    1815. );
    1816. if (this.lockAspectRatio && this.resizingOnX) {
    1817. bottom = this.bottom - (this.right - right) / aspectFactor;
    1818. }
    1819. } else if (this.handle.includes('l')) {
    1820. left = restrictToBounds(
    1821. mouseClickPosition.left - deltaX,
    1822. this.bounds.minLeft,
    1823. this.bounds.maxLeft
    1824. );
    1825. if (this.lockAspectRatio && this.resizingOnX) {
    1826. top = this.top - (this.left - left) / aspectFactor;
    1827. }
    1828. }
    1829. const width = computeWidth(this.parentWidth, left, right);
    1830. const height = computeHeight(this.parentHeight, top, bottom);
    1831. if (this.onResize(this.handle, left, top, width, height) === false) {
    1832. return;
    1833. }
    1834. this.left = left;
    1835. this.top = top;
    1836. this.right = right;
    1837. this.bottom = bottom;
    1838. this.width = width;
    1839. this.height = height;
    1840. this.hasResize = true;
    1841. this.$emit('resizing', this.left, this.top, this.width, this.height, this.componentKey);
    1842. },
    1843. changeWidth (val) {
    1844. // eslint-disable-next-line
    1845. const [newWidth, _] = snapToGrid(this.grid, val, 0, this.scale)
    1846. let right = restrictToBounds(
    1847. (this.parentWidth - newWidth - this.left),
    1848. this.bounds.minRight,
    1849. this.bounds.maxRight
    1850. );
    1851. let bottom = this.bottom;
    1852. if (this.lockAspectRatio) {
    1853. bottom = this.bottom - (this.right - right) / this.aspectFactor;
    1854. }
    1855. const width = computeWidth(this.parentWidth, this.left, right);
    1856. const height = computeHeight(this.parentHeight, this.top, bottom);
    1857. this.right = right;
    1858. this.bottom = bottom;
    1859. this.width = width;
    1860. this.height = height;
    1861. },
    1862. changeHeight (val) {
    1863. // eslint-disable-next-line
    1864. const [_, newHeight] = snapToGrid(this.grid, 0, val, this.scale)
    1865. let bottom = restrictToBounds(
    1866. (this.parentHeight - newHeight - this.top),
    1867. this.bounds.minBottom,
    1868. this.bounds.maxBottom
    1869. );
    1870. let right = this.right;
    1871. if (this.lockAspectRatio) {
    1872. right = this.right - (this.bottom - bottom) * this.aspectFactor;
    1873. }
    1874. const width = computeWidth(this.parentWidth, this.left, right);
    1875. const height = computeHeight(this.parentHeight, this.top, bottom);
    1876. this.right = right;
    1877. this.bottom = bottom;
    1878. this.width = width;
    1879. this.height = height;
    1880. },
    1881. // 从控制柄松开
    1882. async handleUp () {
    1883. this.handle = null;
    1884. // 初始化辅助线数据
    1885. const temArr = new Array(3).fill({ display: false, position: '', origin: '', lineLength: '' });
    1886. const refLine = { vLine: [], hLine: [] };
    1887. for (let i in refLine) { refLine[i] = JSON.parse(JSON.stringify(temArr)); }
    1888. if (this.resizing) {
    1889. this.resizing = false;
    1890. await this.conflictCheck();
    1891. this.$emit('refLineParams', refLine);
    1892. this.$emit('resizestop', this.left, this.top, this.width, this.height, this.componentKey);
    1893. this.hasResize && this.$store.commit('recordSnapshot');
    1894. }
    1895. if (this.dragging) {
    1896. this.dragging = false;
    1897. await this.conflictCheck();
    1898. this.$emit('refLineParams', refLine);
    1899. this.$emit('dragstop', this.left, this.top, this.componentKey);
    1900. this.hasMove && this.$store.commit('recordSnapshot');
    1901. }
    1902. this.resetBoundsAndMouseState();
    1903. removeEvent(document.documentElement, eventsFor.move, this.handleResize);
    1904. },
    1905. // 新增方法 ↓↓↓
    1906. // 设置属性
    1907. settingAttribute () {
    1908. // 设置冲突检测
    1909. this.$el.setAttribute('data-is-check', `${this.isConflictCheck}`);
    1910. // 设置对齐元素
    1911. this.$el.setAttribute('data-is-snap', `${this.snap}`);
    1912. },
    1913. // 冲突检测
    1914. conflictCheck () {
    1915. const top = this.top;
    1916. const left = this.left;
    1917. const width = this.width;
    1918. const height = this.height;
    1919. if (this.isConflictCheck) {
    1920. const nodes = this.$el.parentNode.childNodes; // 获取当前父节点下所有子节点
    1921. for (let item of nodes) {
    1922. if (item.className !== undefined && !item.className.includes(this.classNameActive) && item.getAttribute('data-is-check') !== null && item.getAttribute('data-is-check') !== 'false') {
    1923. const tw = item.offsetWidth;
    1924. const th = item.offsetHeight;
    1925. // 正则获取left与right
    1926. let [tl, tt] = this.formatTransformVal(item.style.transform);
    1927. // 左上角与右下角重叠
    1928. const tfAndBr = (top >= tt && left >= tl && tt + th > top && tl + tw > left) || (top <= tt && left < tl && top + height > tt && left + width > tl);
    1929. // 右上角与左下角重叠
    1930. const brAndTf = (left <= tl && top >= tt && left + width > tl && top < tt + th) || (top < tt && left > tl && top + height > tt && left < tl + tw);
    1931. // 下边与上边重叠
    1932. const bAndT = (top <= tt && left >= tl && top + height > tt && left < tl + tw) || (top >= tt && left <= tl && top < tt + th && left > tl + tw);
    1933. // 上边与下边重叠(宽度不一样)
    1934. const tAndB = (top <= tt && left >= tl && top + height > tt && left < tl + tw) || (top >= tt && left <= tl && top < tt + th && left > tl + tw);
    1935. // 左边与右边重叠
    1936. const lAndR = (left >= tl && top >= tt && left < tl + tw && top < tt + th) || (top > tt && left <= tl && left + width > tl && top < tt + th);
    1937. // 左边与右边重叠(高度不一样)
    1938. const rAndL = (top <= tt && left >= tl && top + height > tt && left < tl + tw) || (top >= tt && left <= tl && top < tt + th && left + width > tl);
    1939. // 如果冲突,就将回退到移动前的位置
    1940. if (tfAndBr || brAndTf || bAndT || tAndB || lAndR || rAndL) {
    1941. this.top = this.mouseClickPosition.top;
    1942. this.left = this.mouseClickPosition.left;
    1943. this.right = this.mouseClickPosition.right;
    1944. this.bottom = this.mouseClickPosition.bottom;
    1945. this.width = this.mouseClickPosition.w;
    1946. this.height = this.mouseClickPosition.h;
    1947. }
    1948. }
    1949. }
    1950. }
    1951. },
    1952. // 检测对齐元素
    1953. async snapCheck () {
    1954. let width = this.width;
    1955. let height = this.height;
    1956. if (this.snap) {
    1957. let activeLeft = this.left;
    1958. let activeRight = this.left + width;
    1959. let activeTop = this.top;
    1960. let activeBottom = this.top + height;
    1961. // 初始化辅助线数据
    1962. const temArr = new Array(3).fill({ display: false, position: '', origin: '', lineLength: '' });
    1963. const refLine = { vLine: [], hLine: [] };
    1964. for (let i in refLine) { refLine[i] = JSON.parse(JSON.stringify(temArr)); }
    1965. // 获取当前父节点下所有子节点
    1966. const nodes = this.$el.parentNode.childNodes;
    1967. let tem = {
    1968. value: { x: [[], [], []], y: [[], [], []] },
    1969. display: [],
    1970. position: []
    1971. };
    1972. const { groupWidth, groupHeight, groupLeft, groupTop, bln } = await this.getActiveAll(nodes);
    1973. if (!bln) {
    1974. width = groupWidth;
    1975. height = groupHeight;
    1976. activeLeft = groupLeft;
    1977. activeRight = groupLeft + groupWidth;
    1978. activeTop = groupTop;
    1979. activeBottom = groupTop + groupHeight;
    1980. }
    1981. for (let item of nodes) {
    1982. if (item.className !== undefined && !item.className.includes(this.classNameActive) && item.getAttribute('data-is-snap') !== null && item.getAttribute('data-is-snap') !== 'false') {
    1983. const w = item.offsetWidth;
    1984. const h = item.offsetHeight;
    1985. const [l, t] = this.formatTransformVal(item.style.transform);
    1986. const r = l + w; // 对齐目标right
    1987. const b = t + h; // 对齐目标的bottom
    1988. const hc = Math.abs((activeTop + height / 2) - (t + h / 2)) <= this.snapTolerance; // 水平中线
    1989. const vc = Math.abs((activeLeft + width / 2) - (l + w / 2)) <= this.snapTolerance; // 垂直中线
    1990. const ts = Math.abs(t - activeBottom) <= this.snapTolerance; // 从上到下
    1991. const TS = Math.abs(b - activeBottom) <= this.snapTolerance; // 从上到下
    1992. const bs = Math.abs(t - activeTop) <= this.snapTolerance; // 从下到上
    1993. const BS = Math.abs(b - activeTop) <= this.snapTolerance; // 从下到上
    1994. const ls = Math.abs(l - activeRight) <= this.snapTolerance; // 外左
    1995. const LS = Math.abs(r - activeRight) <= this.snapTolerance; // 外左
    1996. const rs = Math.abs(l - activeLeft) <= this.snapTolerance; // 外右
    1997. const RS = Math.abs(r - activeLeft) <= this.snapTolerance; // 外右
    1998. tem['display'] = [ts, TS, bs, BS, hc, hc, ls, LS, rs, RS, vc, vc];
    1999. tem['position'] = [t, b, t, b, t + h / 2, t + h / 2, l, r, l, r, l + w / 2, l + w / 2];
    2000. if (ts) {
    2001. if (bln) {
    2002. this.top = t - height;
    2003. this.bottom = this.parentHeight - this.top - height;
    2004. }
    2005. tem.value.y[0].push(l, r, activeLeft, activeRight);
    2006. }
    2007. if (bs) {
    2008. if (bln) {
    2009. this.top = t;
    2010. this.bottom = this.parentHeight - this.top - height;
    2011. }
    2012. tem.value.y[0].push(l, r, activeLeft, activeRight);
    2013. }
    2014. if (TS) {
    2015. if (bln) {
    2016. this.top = b - height;
    2017. this.bottom = this.parentHeight - this.top - height;
    2018. }
    2019. tem.value.y[1].push(l, r, activeLeft, activeRight);
    2020. }
    2021. if (BS) {
    2022. if (bln) {
    2023. this.top = b;
    2024. this.bottom = this.parentHeight - this.top - height;
    2025. }
    2026. tem.value.y[1].push(l, r, activeLeft, activeRight);
    2027. }
    2028. if (ls) {
    2029. if (bln) {
    2030. this.left = l - width;
    2031. this.right = this.parentWidth - this.left - width;
    2032. }
    2033. tem.value.x[0].push(t, b, activeTop, activeBottom);
    2034. }
    2035. if (rs) {
    2036. if (bln) {
    2037. this.left = l;
    2038. this.right = this.parentWidth - this.left - width;
    2039. }
    2040. tem.value.x[0].push(t, b, activeTop, activeBottom);
    2041. }
    2042. if (LS) {
    2043. if (bln) {
    2044. this.left = r - width;
    2045. this.right = this.parentWidth - this.left - width;
    2046. }
    2047. tem.value.x[1].push(t, b, activeTop, activeBottom);
    2048. }
    2049. if (RS) {
    2050. if (bln) {
    2051. this.left = r;
    2052. this.right = this.parentWidth - this.left - width;
    2053. }
    2054. tem.value.x[1].push(t, b, activeTop, activeBottom);
    2055. }
    2056. if (hc) {
    2057. if (bln) {
    2058. this.top = t + h / 2 - height / 2;
    2059. this.bottom = this.parentHeight - this.top - height;
    2060. }
    2061. tem.value.y[2].push(l, r, activeLeft, activeRight);
    2062. }
    2063. if (vc) {
    2064. if (bln) {
    2065. this.left = l + w / 2 - width / 2;
    2066. this.right = this.parentWidth - this.left - width;
    2067. }
    2068. tem.value.x[2].push(t, b, activeTop, activeBottom);
    2069. }
    2070. // 辅助线坐标与是否显示(display)对应的数组,易于循环遍历
    2071. const arrTem = [0, 1, 0, 1, 2, 2, 0, 1, 0, 1, 2, 2];
    2072. for (let i = 0; i <= arrTem.length; i++) {
    2073. // 前6为Y辅助线,后6为X辅助线
    2074. const xory = i < 6 ? 'y' : 'x';
    2075. const horv = i < 6 ? 'hLine' : 'vLine';
    2076. if (tem.display[i]) {
    2077. const { origin, length } = this.calcLineValues(tem.value[xory][arrTem[i]]);
    2078. refLine[horv][arrTem[i]].display = tem.display[i];
    2079. refLine[horv][arrTem[i]].position = tem.position[i] + 'px';
    2080. refLine[horv][arrTem[i]].origin = origin;
    2081. refLine[horv][arrTem[i]].lineLength = length;
    2082. }
    2083. }
    2084. }
    2085. }
    2086. this.$emit('refLineParams', refLine);
    2087. }
    2088. },
    2089. calcLineValues (arr) {
    2090. const length = Math.max(...arr) - Math.min(...arr) + 'px';
    2091. const origin = Math.min(...arr) + 'px';
    2092. return { length, origin };
    2093. },
    2094. async getActiveAll (nodes) {
    2095. const activeAll = [];
    2096. const XArray = [];
    2097. const YArray = [];
    2098. let groupWidth = 0;
    2099. let groupHeight = 0;
    2100. let groupLeft = 0;
    2101. let groupTop = 0;
    2102. for (let item of nodes) {
    2103. if (item.className !== undefined && item.className.includes(this.classNameActive)) {
    2104. activeAll.push(item);
    2105. }
    2106. }
    2107. const AllLength = activeAll.length;
    2108. if (AllLength > 1) {
    2109. for (let i of activeAll) {
    2110. const l = i.offsetLeft;
    2111. const r = l + i.offsetWidth;
    2112. const t = i.offsetTop;
    2113. const b = t + i.offsetHeight;
    2114. XArray.push(t, b);
    2115. YArray.push(l, r);
    2116. }
    2117. groupWidth = Math.max(...YArray) - Math.min(...YArray);
    2118. groupHeight = Math.max(...XArray) - Math.min(...XArray);
    2119. groupLeft = Math.min(...YArray);
    2120. groupTop = Math.min(...XArray);
    2121. }
    2122. const bln = AllLength === 1;
    2123. return { groupWidth, groupHeight, groupLeft, groupTop, bln };
    2124. },
    2125. // 正则获取left与top
    2126. formatTransformVal (string) {
    2127. let [left, top] = string.replace(/[^0-9\-,]/g, '').split(',');
    2128. if (top === undefined) top = 0;
    2129. return [+left, +top];
    2130. }
    2131. },
    2132. computed: {
    2133. handleStyle () {
    2134. return (stick) => {
    2135. if (!this.handleInfo.switch) return { display: this.enabled ? 'block' : 'none' };
    2136. const size = (this.handleInfo.size / this.scaleRatio).toFixed(2);
    2137. const offset = (this.handleInfo.offset / this.scaleRatio).toFixed(2);
    2138. const center = (size / 2).toFixed(2);
    2139. const styleMap = {
    2140. tl: {
    2141. top: `${offset}px`,
    2142. left: `${offset}px`
    2143. },
    2144. tm: {
    2145. top: `${offset}px`,
    2146. left: `calc(50% - ${center}px)`
    2147. },
    2148. tr: {
    2149. top: `${offset}px`,
    2150. right: `${offset}px`
    2151. },
    2152. mr: {
    2153. top: `calc(50% - ${center}px)`,
    2154. right: `${offset}px`
    2155. },
    2156. br: {
    2157. bottom: `${offset}px`,
    2158. right: `${offset}px`
    2159. },
    2160. bm: {
    2161. bottom: `${offset}px`,
    2162. right: `calc(50% - ${center}px)`
    2163. },
    2164. bl: {
    2165. bottom: `${offset}px`,
    2166. left: `${offset}px`
    2167. },
    2168. ml: {
    2169. top: `calc(50% - ${center}px)`,
    2170. left: `${offset}px`
    2171. }
    2172. };
    2173. const stickStyle = {
    2174. width: `${size}px`,
    2175. height: `${size}px`,
    2176. top: styleMap[stick].top,
    2177. left: styleMap[stick].left,
    2178. right: styleMap[stick].right,
    2179. bottom: styleMap[stick].bottom
    2180. };
    2181. stickStyle.display = this.enabled ? 'block' : 'none';
    2182. return stickStyle;
    2183. };
    2184. },
    2185. style () {
    2186. return {
    2187. transform: `translate(${this.left}px, ${this.top}px)`,
    2188. width: this.computedWidth,
    2189. height: this.computedHeight,
    2190. zIndex: this.zIndex,
    2191. ...(this.dragging && this.disableUserSelect ? userSelectNone : userSelectAuto)
    2192. };
    2193. },
    2194. // 控制柄显示与否
    2195. actualHandles () {
    2196. if (!this.resizable) return [];
    2197. return this.handles;
    2198. },
    2199. computedWidth () {
    2200. if (this.w === 'auto') {
    2201. if (!this.widthTouched) {
    2202. return 'auto';
    2203. }
    2204. }
    2205. return this.width + 'px';
    2206. },
    2207. computedHeight () {
    2208. if (this.h === 'auto') {
    2209. if (!this.heightTouched) {
    2210. return 'auto';
    2211. }
    2212. }
    2213. return this.height + 'px';
    2214. },
    2215. resizingOnX () {
    2216. return (Boolean(this.handle) && (this.handle.includes('l') || this.handle.includes('r')));
    2217. },
    2218. resizingOnY () {
    2219. return (Boolean(this.handle) && (this.handle.includes('t') || this.handle.includes('b')));
    2220. },
    2221. isCornerHandle () {
    2222. return (Boolean(this.handle) && ['tl', 'tr', 'br', 'bl'].includes(this.handle));
    2223. }
    2224. },
    2225. watch: {
    2226. active (val) {
    2227. this.enabled = val;
    2228. if (val) {
    2229. this.$emit('activated');
    2230. } else {
    2231. this.$emit('deactivated');
    2232. }
    2233. },
    2234. z (val) {
    2235. if (val >= 0 || val === 'auto') {
    2236. this.zIndex = val;
    2237. }
    2238. },
    2239. x (val) {
    2240. if (this.resizing || this.dragging) {
    2241. return;
    2242. }
    2243. if (this.parent) {
    2244. this.bounds = this.calcDragLimits();
    2245. }
    2246. this.moveHorizontally(val);
    2247. },
    2248. y (val) {
    2249. if (this.resizing || this.dragging) {
    2250. return;
    2251. }
    2252. if (this.parent) {
    2253. this.bounds = this.calcDragLimits();
    2254. }
    2255. this.moveVertically(val);
    2256. },
    2257. lockAspectRatio (val) {
    2258. if (val) {
    2259. this.aspectFactor = this.width / this.height;
    2260. } else {
    2261. this.aspectFactor = undefined;
    2262. }
    2263. },
    2264. minWidth (val) {
    2265. if (val > 0 && val <= this.width) {
    2266. this.minW = val;
    2267. }
    2268. },
    2269. minHeight (val) {
    2270. if (val > 0 && val <= this.height) {
    2271. this.minH = val;
    2272. }
    2273. },
    2274. maxWidth (val) {
    2275. this.maxW = val;
    2276. },
    2277. maxHeight (val) {
    2278. this.maxH = val;
    2279. },
    2280. w (val) {
    2281. if (this.resizing || this.dragging) {
    2282. return;
    2283. }
    2284. if (this.parent) {
    2285. this.bounds = this.calcResizeLimits();
    2286. }
    2287. this.changeWidth(val);
    2288. },
    2289. h (val) {
    2290. if (this.resizing || this.dragging) {
    2291. return;
    2292. }
    2293. if (this.parent) {
    2294. this.bounds = this.calcResizeLimits();
    2295. }
    2296. this.changeHeight(val);
    2297. }
    2298. }
    2299. };
    2300. </script>
    .vdr {
      touch-action: none;
      position: absolute;
      box-sizing: border-box;
      border: 1px dashed #d6d6d6;
    }
    .vdr.active {
      z-index: 1!important;
    }
    .handle {
      box-sizing: border-box;
      position: absolute;
      width: 8px;
      height: 8px;
      background: #ffffff;
      border: 1px solid #333;
      box-shadow: 0 0 2px #bbb;
      z-index: 2;
    }
    .handle-tl {
      top: -5px;
      left: -5px;
      cursor: nw-resize;
    }
    .handle-tm {
      top: -5px;
      left: calc(50% - 4px);
      /*margin-left: -5px;*/
      cursor: n-resize;
    }
    .handle-tr {
      top: -5px;
      right: -5px;
      cursor: ne-resize;
    }
    .handle-ml {
      top: calc(50% - 4px);
      /*margin-top: -5px;*/
      left: -5px;
      cursor: w-resize;
    }
    .handle-mr {
      top: calc(50% - 4px);
      /*margin-top: -5px;*/
      right: -5px;
      cursor: e-resize;
    }
    .handle-bl {
      bottom: -5px;
      left: -5px;
      cursor: sw-resize;
    }
    .handle-bm {
      bottom: -5px;
      left: calc(50% - 4px);
      /*margin-left: -5px;*/
      cursor: s-resize;
    }
    .handle-br {
      bottom: -5px;
      right: -5px;
      cursor: se-resize;
    }
    /*旋转handle*/
    /*.handle-rot {
      top: 0;
      left: 50%;
      margin-top: -20px;
      margin-left: -1px;
      border-radius: 50%;
      width: 10px;
      height: 10px;
      cursor: grab;
      transform: translate(-50%, 0);
    }
    .handle-rot:before {
      content: '';
      position: absolute;
      top: 8px;
      left: 50%;
      width: 0;
      height: 7px;
      border-left: 1px solid #000000;
      transform: translate(-50%, 0);
    }*/
    .ref-line{
      position: absolute;
      background-color: rgb(255, 0, 204);
      z-index: 9999;
    }
    .v-line{
      width: 1px;
    }
    .h-line{
      height: 1px;
    }
    @media only screen and (max-width: 768px) {
      [class*="handle-"]:before {
        content: '';
        left: -10px;
        right: -10px;
        bottom: -10px;
        top: -10px;
        position: absolute;
      }
    }