SketchRuler.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499
  1. <template>
  2. <div
  3. id="wrapper"
  4. class="wrapper vue-ruler-wrapper"
  5. :style="{
  6. width: width + thick + 'px',
  7. height: height + thick + 'px'
  8. }"
  9. >
  10. <i
  11. :class="{
  12. 'iconfont-bigscreen': true,
  13. 'icon-eye': isShowReferLine,
  14. 'icon-eye-close': !isShowReferLine
  15. }"
  16. @click="handleCornerClick"
  17. />
  18. <SketchRule
  19. :key="scale"
  20. :lang="lang"
  21. :thick="thick"
  22. :scale="scale"
  23. :width="width"
  24. :height="height"
  25. :start-x="startX"
  26. :start-y="startY"
  27. :shadow="shadow"
  28. :hor-line-arr="[...lines.h, ...presetLines.h]"
  29. :ver-line-arr="[...lines.v, ...presetLines.v]"
  30. :palette="Palette"
  31. :is-show-refer-line="isShowReferLine"
  32. :corner-active="cornerActive"
  33. @handleLine="handleLine"
  34. @onCornerClick="handleCornerClick"
  35. />
  36. <div
  37. id="screens"
  38. ref="screensRef"
  39. :style="{
  40. width: innerWidth + 'px',
  41. height: innerHeight + 'px'
  42. }"
  43. @scroll="throttleScroll"
  44. >
  45. <div
  46. id="screen-container"
  47. ref="containerRef"
  48. class="screen-container grid-bg"
  49. :style="containerRefStyle"
  50. >
  51. <div
  52. id="canvas"
  53. :style="canvasStyle"
  54. >
  55. <slot />
  56. </div>
  57. </div>
  58. </div>
  59. </div>
  60. </template>
  61. <script>
  62. import SketchRule from 'vue-sketch-ruler'
  63. import { mapState, mapMutations } from 'vuex'
  64. import throttle from 'lodash/throttle'
  65. export default {
  66. components: {
  67. SketchRule
  68. },
  69. props: {
  70. width: {
  71. type: Number,
  72. default: 500
  73. },
  74. height: {
  75. type: Number,
  76. default: 400
  77. },
  78. pageWidth: {
  79. type: Number,
  80. default: 1920
  81. },
  82. pageHeight: {
  83. type: Number,
  84. default: 1080
  85. }
  86. },
  87. data () {
  88. return {
  89. canvasLeft: 0, // 存储画布到视口的left距离
  90. canvasTop: 0, // 存储画布到视口的top距离
  91. isDrag: false, // 小地图白块是否拖拽
  92. startX: 0,
  93. startY: 0,
  94. lines: {
  95. h: [],
  96. v: []
  97. },
  98. thick: 20,
  99. lang: 'zh-CN', // 中英文
  100. isShowRuler: true, // 显示标尺
  101. isShowReferLine: true, // 显示参考线
  102. cornerActive: true, // 左上角激活状态
  103. Palette: {
  104. bgColor: 'rgba(225,225,225, 0)',
  105. longfgColor: '#BABBBC',
  106. shortfgColor: '#C8CDD0',
  107. fontColor: '#7D8694',
  108. shadowColor: 'transparent',
  109. lineColor: '#0089d0',
  110. borderColor: '#transparent',
  111. cornerActiveColor: 'rgb(235, 86, 72, 0.6)'
  112. },
  113. containerRefStyle: {
  114. width: this.width + 'px',
  115. height: this.height + 'px'
  116. },
  117. innerHeight: 0,
  118. innerWidth: 0
  119. }
  120. },
  121. watch: {
  122. // 缩放改变的时候,改变startX,startY
  123. scale (scale) {
  124. // 防抖调用方法
  125. this.throttleScroll()
  126. },
  127. pageWidth (pageWidth) {
  128. if (this.fitZoom === this.zoom) {
  129. this.initZoom()
  130. }
  131. },
  132. pageHeight (pageHeight) {
  133. if (this.fitZoom === this.zoom) {
  134. this.initZoom()
  135. }
  136. }
  137. },
  138. computed: {
  139. ...mapState('bigScreen', {
  140. scale: state => state.zoom / 100,
  141. fitZoom: state => state.fitZoom,
  142. zoom: state => state.zoom
  143. }),
  144. presetLines () {
  145. const presetLine = this.$store.state.bigScreen.presetLine
  146. // { type: 'h', site: y || 0 },
  147. const v = presetLine?.filter(p => p.type === 'h')?.map(p => p.site)
  148. const h = presetLine?.filter(p => p.type === 'v')?.map(p => p.site)
  149. return {
  150. h,
  151. v
  152. }
  153. },
  154. shadow () {
  155. return {
  156. x: 0,
  157. y: 0,
  158. width: this.width,
  159. height: this.height
  160. }
  161. },
  162. canvasStyle () {
  163. return {
  164. width: this.width + 'px',
  165. height: this.height + 'px',
  166. transform: `scale(${this.scale})`,
  167. transformOrigin: '0 0 0'
  168. }
  169. }
  170. },
  171. mounted () {
  172. // 初始化canvasLeft canvasTop
  173. const canvasRect = document.querySelector('#canvas').getBoundingClientRect()
  174. this.canvasLeft = canvasRect.left
  175. this.canvasTop = canvasRect.top
  176. // 监听屏幕改变
  177. this.listenSize()
  178. this.initRuleHeight()
  179. this.throttleScroll()
  180. this.throttleDrag()
  181. },
  182. methods: {
  183. ...mapMutations('bigScreen', [
  184. 'changeZoom',
  185. 'changeFitZoom'
  186. ]),
  187. throttleDrag () {
  188. throttle(() => {
  189. this.dragSelection()
  190. this.viewMapDrag()
  191. }, 100)()
  192. },
  193. // 绑定滑块拖拽效果
  194. dragSelection () {
  195. const that = this
  196. const draggableElement = document.getElementById('selectionWin')
  197. const dragContainer = document.getElementById('selectWin')
  198. const screenElement = document.getElementById('screens')
  199. const screenContainer = document.getElementById('screen-container')
  200. const maxContainer = document.querySelector('.bs-page-design-wrap')
  201. // 鼠标按下的位置
  202. let posX, posY
  203. // 白色拖拽块相对于父盒子初始位置
  204. let initialX, initialY
  205. // 滚动条初始位置
  206. let scrollTop, scrollLeft
  207. draggableElement.addEventListener('mousedown', function (event) {
  208. that.isDrag = true
  209. posX = event.clientX
  210. posY = event.clientY
  211. initialX = draggableElement.getBoundingClientRect().left - dragContainer.getBoundingClientRect().left
  212. initialY = draggableElement.getBoundingClientRect().top - dragContainer.getBoundingClientRect().top
  213. scrollLeft = screenElement.scrollLeft
  214. scrollTop = screenElement.scrollTop
  215. maxContainer.addEventListener('mousemove', function (event) {
  216. // 在鼠标移动过程中判断出鼠标左键未点击,则停止拖拽
  217. if (event.buttons !== 1) {
  218. that.isDrag = false
  219. }
  220. if (that.isDrag) {
  221. event.preventDefault()
  222. // 鼠标移动距离
  223. let moveX = event.clientX - posX
  224. let moveY = event.clientY - posY
  225. // 避免白色拖拽移出边框
  226. if (moveX < -initialX) {
  227. moveX = -initialX
  228. } else if (moveX > dragContainer.getBoundingClientRect().width - draggableElement.getBoundingClientRect().width - initialX) {
  229. moveX = dragContainer.getBoundingClientRect().width - draggableElement.getBoundingClientRect().width - initialX
  230. }
  231. if (moveY < -initialY) {
  232. moveY = -initialY
  233. } else if (moveY > dragContainer.getBoundingClientRect().height - draggableElement.getBoundingClientRect().height - initialY) {
  234. moveY = dragContainer.getBoundingClientRect().height - draggableElement.getBoundingClientRect().height - initialY
  235. }
  236. const newX = moveX + initialX
  237. const newY = moveY + initialY
  238. // 移动拖拽白块
  239. draggableElement.style.left = newX + 'px'
  240. draggableElement.style.top = newY + 'px'
  241. // 移动比例
  242. const percentageX = moveX / (dragContainer.getBoundingClientRect().width - draggableElement.getBoundingClientRect().width)
  243. const percentageY = moveY / (dragContainer.getBoundingClientRect().height - draggableElement.getBoundingClientRect().height)
  244. // 进度条需要滚动的距离
  245. const scrollTopLength = percentageY * (screenContainer.getBoundingClientRect().height - screenElement.getBoundingClientRect().height)
  246. const scrollLeftLength = percentageX * (screenContainer.getBoundingClientRect().width - screenElement.getBoundingClientRect().width)
  247. screenElement.scrollLeft = scrollLeft + scrollLeftLength
  248. screenElement.scrollTop = scrollTop + scrollTopLength
  249. }
  250. })
  251. })
  252. maxContainer.addEventListener('mouseup', function (event) {
  253. that.isDrag = false
  254. })
  255. draggableElement.addEventListener('mouseup', function () {
  256. that.isDrag = false
  257. })
  258. // 禁止H5自带的拖拽事件
  259. draggableElement.ondragstart = function (ev) {
  260. ev.preventDefault()
  261. }
  262. draggableElement.ondragend = function (ev) {
  263. ev.preventDefault()
  264. }
  265. screenElement.ondragstart = function (ev) {
  266. ev.preventDefault()
  267. }
  268. screenElement.ondragend = function (ev) {
  269. ev.preventDefault()
  270. }
  271. },
  272. // 小地图拖拽
  273. viewMapDrag () {
  274. const mapElement = document.getElementById('minimap')
  275. const mapDragElement = document.getElementById('mapHeader')
  276. const pageElement = document.querySelector('.bs-page-design-wrap')
  277. let mapDrag = false
  278. let curX, curY
  279. let curBottom, curRight
  280. mapDragElement.addEventListener('mousedown', function (event) {
  281. mapDrag = true
  282. curX = event.clientX
  283. curY = event.clientY
  284. curBottom = window.getComputedStyle(mapElement).bottom
  285. curRight = window.getComputedStyle(mapElement).right
  286. pageElement.addEventListener('mousemove', function (event) {
  287. if (mapDrag) {
  288. event.preventDefault()
  289. const dragX = event.clientX - curX
  290. const dragY = event.clientY - curY
  291. mapElement.style.bottom = parseInt(curBottom) - dragY + 'px'
  292. mapElement.style.right = parseInt(curRight) - dragX + 'px'
  293. }
  294. })
  295. })
  296. pageElement.addEventListener('mouseup', function () {
  297. mapDrag = false
  298. })
  299. mapDragElement.addEventListener('mouseup', function () {
  300. mapDrag = false
  301. })
  302. },
  303. listenSize () {
  304. window.onresize = throttle(() => {
  305. this.initRuleHeight()
  306. }, 100)
  307. },
  308. initRuleHeight () {
  309. setTimeout(() => {
  310. const screensRect = document
  311. .querySelector('.grid-wrap-box')
  312. ?.getBoundingClientRect()
  313. if (!screensRect) {
  314. return
  315. }
  316. // 30是grid-wrap-box的底部工具栏高度
  317. this.innerHeight = screensRect.height
  318. this.innerWidth = screensRect.width
  319. this.diffX = this.width - screensRect.width
  320. this.diffY = this.height - screensRect.height
  321. this.containerRefStyle = {
  322. width: this.diffX > 0 ? ((this.width + this.diffX + this.thick + 30) + 'px') : (this.width + 'px'),
  323. height: this.diffY > 0 ? ((this.height + this.diffY + this.thick + 30) + 'px') : (this.height + 'px')
  324. }
  325. if (this.fitZoom === this.zoom) {
  326. this.initZoom()
  327. }
  328. })
  329. },
  330. handleLine (lines) {
  331. this.lines = lines
  332. },
  333. handleCornerClick () {
  334. this.isShowReferLine = !this.isShowReferLine
  335. this.cornerActive = !this.cornerActive
  336. },
  337. throttleScroll () {
  338. throttle(() => {
  339. this.handleScroll()
  340. }, 100)()
  341. },
  342. handleScroll () {
  343. const screensRect = document
  344. .querySelector('#screens')
  345. .getBoundingClientRect()
  346. const canvasRect = document
  347. .querySelector('#canvas')
  348. .getBoundingClientRect()
  349. // const container = document.querySelector('#selectWin').getBoundingClientRect()
  350. const screenContainer = document.querySelector('#screen-container').getBoundingClientRect()
  351. const draggableElement = document.getElementById('selectionWin')
  352. // 标尺开始的刻度
  353. const startX = (screensRect.left + this.thick - canvasRect.left) / this.scale
  354. const startY = (screensRect.top + this.thick - canvasRect.top) / this.scale
  355. this.startX = startX >> 0
  356. this.startY = startY >> 0
  357. this.$emit('changeStart', {
  358. x: this.startX * this.scale + 50 - this.thick,
  359. y: this.startY * this.scale + 50 - this.thick
  360. })
  361. // 拖动进度条移动小地图
  362. if (!this.isDrag) {
  363. const leftDrag = canvasRect.left - this.canvasLeft
  364. const topDrag = canvasRect.top - this.canvasTop
  365. // 小方块需要移动的距离
  366. const leftLength = leftDrag / (screenContainer.width - screensRect.width - 9) * (150 - 30)
  367. const topLength = topDrag / (screenContainer.height - screensRect.height - 9) * (150 - 30)
  368. draggableElement.style.left = -leftLength + 'px'
  369. draggableElement.style.top = -topLength + 'px'
  370. }
  371. },
  372. // 保证画布能完整展示大屏
  373. initZoom () {
  374. // 横向比例
  375. const xRadio = this.innerWidth / (this.pageWidth + 120)
  376. // 纵向比例
  377. const yRadio = this.innerHeight / (this.pageHeight + 120)
  378. // 取最小的适应比例
  379. const scale = Math.floor(Math.min(xRadio * 100, yRadio * 100))
  380. if (scale < 100) {
  381. this.changeZoom(scale)
  382. this.changeFitZoom(scale)
  383. } else {
  384. this.changeZoom(100)
  385. this.changeFitZoom(100)
  386. }
  387. }
  388. }
  389. }
  390. </script>
  391. <style lang="scss" scoped>
  392. @import '../../BigScreenDesign/fonts/iconfont.css';
  393. .wrapper {
  394. box-sizing: border-box;
  395. position: absolute;
  396. .iconfont-bigscreen {
  397. position: absolute;
  398. left: 0;
  399. top: 0;
  400. font-size: 16px;
  401. color: #fff;
  402. z-index: 999;
  403. cursor: pointer;
  404. }
  405. }
  406. #screens {
  407. position: absolute;
  408. overflow: scroll;
  409. // 滚动条美化,始终在最下方和最右方
  410. &::-webkit-scrollbar {
  411. width: 10px;
  412. height: 10px;
  413. }
  414. &::-webkit-scrollbar-thumb {
  415. border-radius: 10px;
  416. background-color: var(--bs-el-background-2) !important;
  417. }
  418. &::-webkit-scrollbar-track {
  419. border-radius: 10px;
  420. background-color: transparent !important;
  421. }
  422. }
  423. .screen-container {
  424. position: absolute;
  425. overflow:hidden;
  426. width: 6000px;
  427. height: 6000px;
  428. }
  429. .scale-value {
  430. position: absolute;
  431. left: 0;
  432. bottom: 100%;
  433. }
  434. .button {
  435. position: absolute;
  436. left: 100px;
  437. bottom: 100%;
  438. }
  439. .button-ch {
  440. position: absolute;
  441. left: 200px;
  442. bottom: 100%;
  443. }
  444. .button-en {
  445. position: absolute;
  446. left: 230px;
  447. bottom: 100%;
  448. }
  449. #canvas {
  450. position: absolute;
  451. top: 50px;
  452. left: 50px;
  453. }
  454. ::v-deep .line {
  455. border-left: 1px dashed #0089d0 !important;
  456. border-top: 1px dashed #0089d0 !important;
  457. }
  458. ::v-deep.action {
  459. .value {
  460. background: var(--bs-el-color-primary);
  461. padding: 4px;
  462. color: #fff;
  463. }
  464. .del {
  465. color: var(--bs-el-color-primary);
  466. }
  467. }
  468. ::v-deep .ruler, ::v-deep .corner {
  469. background: var(--bs-background-1);
  470. }
  471. ::v-deep .corner {
  472. z-index: 999;
  473. background: var(--bs-background-1) !important;
  474. }
  475. ::v-deep .mb-ruler {
  476. z-index: 998
  477. }
  478. .grid-bg {
  479. background-color: #2a2e33 !important;
  480. background-image: url(./images/canvas-bg.png);
  481. background-repeat: repeat;
  482. word-spacing: 10px;
  483. }
  484. </style>