SketchRuler.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536
  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. if (that.isDrag) {
  217. event.preventDefault()
  218. // 鼠标移动距离
  219. let moveX = event.clientX - posX
  220. let moveY = event.clientY - posY
  221. // 避免白色拖拽移出边框
  222. if (moveX < -initialX) {
  223. moveX = -initialX
  224. } else if (moveX > dragContainer.getBoundingClientRect().width - draggableElement.getBoundingClientRect().width - initialX) {
  225. moveX = dragContainer.getBoundingClientRect().width - draggableElement.getBoundingClientRect().width - initialX
  226. }
  227. if (moveY < -initialY) {
  228. moveY = -initialY
  229. } else if (moveY > dragContainer.getBoundingClientRect().height - draggableElement.getBoundingClientRect().height - initialY) {
  230. moveY = dragContainer.getBoundingClientRect().height - draggableElement.getBoundingClientRect().height - initialY
  231. }
  232. const newX = moveX + initialX
  233. const newY = moveY + initialY
  234. // 移动拖拽白块
  235. draggableElement.style.left = newX + 'px'
  236. draggableElement.style.top = newY + 'px'
  237. // 移动比例
  238. const percentageX = moveX / (dragContainer.getBoundingClientRect().width - draggableElement.getBoundingClientRect().width)
  239. const percentageY = moveY / (dragContainer.getBoundingClientRect().height - draggableElement.getBoundingClientRect().height)
  240. // 进度条需要滚动的距离
  241. const scrollTopLength = percentageY * (screenContainer.getBoundingClientRect().height - screenElement.getBoundingClientRect().height)
  242. const scrollLeftLength = percentageX * (screenContainer.getBoundingClientRect().width - screenElement.getBoundingClientRect().width)
  243. screenElement.scrollLeft = scrollLeft + scrollLeftLength
  244. screenElement.scrollTop = scrollTop + scrollTopLength
  245. }
  246. })
  247. })
  248. maxContainer.addEventListener('mouseup', function (event) {
  249. that.isDrag = false
  250. })
  251. draggableElement.addEventListener('mouseup', function () {
  252. that.isDrag = false
  253. })
  254. // 禁止H5自带的拖拽事件
  255. draggableElement.ondragstart = function (ev) {
  256. ev.preventDefault()
  257. }
  258. draggableElement.ondragend = function (ev) {
  259. ev.preventDefault()
  260. }
  261. screenElement.ondragstart = function (ev) {
  262. ev.preventDefault()
  263. }
  264. screenElement.ondragend = function (ev) {
  265. ev.preventDefault()
  266. }
  267. },
  268. // 小地图拖拽
  269. viewMapDrag () {
  270. const mapElement = document.getElementById('minimap')
  271. const mapDragElement = document.getElementById('mapHeader')
  272. const pageElement = document.querySelector('.bs-page-design-wrap')
  273. let mapDrag = false
  274. let curX, curY
  275. let curBottom, curRight
  276. mapDragElement.addEventListener('mousedown', function (event) {
  277. mapDrag = true
  278. curX = event.clientX
  279. curY = event.clientY
  280. curBottom = window.getComputedStyle(mapElement).bottom
  281. curRight = window.getComputedStyle(mapElement).right
  282. pageElement.addEventListener('mousemove', function (event) {
  283. if (mapDrag) {
  284. event.preventDefault()
  285. const dragX = event.clientX - curX
  286. const dragY = event.clientY - curY
  287. mapElement.style.bottom = parseInt(curBottom) - dragY + 'px'
  288. mapElement.style.right = parseInt(curRight) - dragX + 'px'
  289. }
  290. })
  291. })
  292. pageElement.addEventListener('mouseup', function () {
  293. mapDrag = false
  294. })
  295. mapDragElement.addEventListener('mouseup', function () {
  296. mapDrag = false
  297. })
  298. },
  299. listenSize () {
  300. window.onresize = throttle(() => {
  301. this.initRuleHeight()
  302. }, 100)
  303. },
  304. initRuleHeight () {
  305. setTimeout(() => {
  306. const screensRect = document
  307. .querySelector('.grid-wrap-box')
  308. ?.getBoundingClientRect()
  309. if (!screensRect) {
  310. return
  311. }
  312. // 30是grid-wrap-box的底部工具栏高度
  313. this.innerHeight = screensRect.height
  314. this.innerWidth = screensRect.width
  315. this.diffX = this.width - screensRect.width
  316. this.diffY = this.height - screensRect.height
  317. this.containerRefStyle = {
  318. width: this.diffX > 0 ? ((this.width + this.diffX + this.thick + 30) + 'px') : (this.width + 'px'),
  319. height: this.diffY > 0 ? ((this.height + this.diffY + this.thick + 30) + 'px') : (this.height + 'px')
  320. }
  321. if (this.fitZoom === this.zoom) {
  322. this.initZoom()
  323. }
  324. })
  325. },
  326. handleLine (lines) {
  327. this.lines = lines
  328. },
  329. handleCornerClick () {
  330. this.isShowReferLine = !this.isShowReferLine
  331. this.cornerActive = !this.cornerActive
  332. },
  333. throttleScroll () {
  334. throttle(() => {
  335. this.handleScroll()
  336. }, 100)()
  337. },
  338. handleScroll () {
  339. const screensRect = document
  340. .querySelector('#screens')
  341. .getBoundingClientRect()
  342. const canvasRect = document
  343. .querySelector('#canvas')
  344. .getBoundingClientRect()
  345. const container = document.querySelector('#selectWin').getBoundingClientRect()
  346. const screenContainer = document.querySelector('#screen-container').getBoundingClientRect()
  347. const draggableElement = document.getElementById('selectionWin')
  348. // 标尺开始的刻度
  349. const startX = (screensRect.left + this.thick - canvasRect.left) / this.scale
  350. const startY = (screensRect.top + this.thick - canvasRect.top) / this.scale
  351. this.startX = startX >> 0
  352. this.startY = startY >> 0
  353. this.$emit('changeStart', {
  354. x: this.startX * this.scale + 50 - this.thick,
  355. y: this.startY * this.scale + 50 - this.thick
  356. })
  357. // 拖动进度条移动小地图
  358. if (!this.isDrag) {
  359. const leftDrag = canvasRect.left - this.canvasLeft
  360. const topDrag = canvasRect.top - this.canvasTop
  361. // 小方块需要移动的距离
  362. const leftLength = leftDrag / (screenContainer.width - screensRect.width - 9) * (container.width - draggableElement.getBoundingClientRect().width)
  363. const topLength = topDrag / (screenContainer.height - screensRect.height - 9) * (container.height - draggableElement.getBoundingClientRect().height)
  364. draggableElement.style.left = -leftLength + 'px'
  365. draggableElement.style.top = -topLength + 'px'
  366. }
  367. },
  368. // 保证画布能完整展示大屏
  369. initZoom () {
  370. // 横向比例
  371. const xRadio = this.innerWidth / (this.pageWidth + 120)
  372. // 纵向比例
  373. const yRadio = this.innerHeight / (this.pageHeight + 120)
  374. // 取最小的适应比例
  375. const scale = Math.floor(Math.min(xRadio * 100, yRadio * 100))
  376. if (scale < 100) {
  377. this.changeZoom(scale)
  378. this.changeFitZoom(scale)
  379. } else {
  380. this.changeZoom(100)
  381. this.changeFitZoom(100)
  382. }
  383. }
  384. }
  385. }
  386. </script>
  387. <style lang="scss" scoped>
  388. @import '../../BigScreenDesign/fonts/iconfont.css';
  389. .wrapper {
  390. box-sizing: border-box;
  391. position: absolute;
  392. .iconfont-bigscreen {
  393. position: absolute;
  394. left: 0;
  395. top: 0;
  396. font-size: 16px;
  397. color: #fff;
  398. z-index: 999;
  399. cursor: pointer;
  400. }
  401. }
  402. #screens {
  403. position: absolute;
  404. overflow: scroll;
  405. // 滚动条美化,始终在最下方和最右方
  406. &::-webkit-scrollbar {
  407. width: 10px;
  408. height: 10px;
  409. }
  410. &::-webkit-scrollbar-thumb {
  411. border-radius: 10px;
  412. background-color: var(--bs-el-background-2) !important;
  413. }
  414. &::-webkit-scrollbar-track {
  415. border-radius: 10px;
  416. background-color: transparent !important;
  417. }
  418. }
  419. .screen-container {
  420. position: absolute;
  421. width: 6000px;
  422. height: 6000px;
  423. }
  424. .minimap{
  425. position: fixed;
  426. bottom: 15px;
  427. right: 15px;
  428. border: 1px solid #f6f7fb;
  429. z-index:10000;
  430. /*cursor: move;*/
  431. }
  432. .minimap .mapHeader{
  433. background-color:#303640;
  434. padding: 0 10px;
  435. display: flex;
  436. justify-content: space-between;
  437. height: 20px;
  438. width: 150px;
  439. font-size: 12px;
  440. border-bottom: 1px solid #fff;
  441. color: #ffffff;
  442. cursor: pointer;
  443. span {
  444. user-select: none;
  445. }
  446. }
  447. .minimap .selectWin{
  448. background-color: #232832;
  449. height: 150px;
  450. width: 150px;
  451. position: relative;
  452. }
  453. .minimap .selectionWin{
  454. position: absolute;
  455. left: 0px;
  456. top: 0px;
  457. width: 30px;
  458. height: 30px;
  459. background-color: white;
  460. opacity: 0.5;
  461. cursor: move;
  462. }
  463. .scale-value {
  464. position: absolute;
  465. left: 0;
  466. bottom: 100%;
  467. }
  468. .button {
  469. position: absolute;
  470. left: 100px;
  471. bottom: 100%;
  472. }
  473. .button-ch {
  474. position: absolute;
  475. left: 200px;
  476. bottom: 100%;
  477. }
  478. .button-en {
  479. position: absolute;
  480. left: 230px;
  481. bottom: 100%;
  482. }
  483. #canvas {
  484. position: absolute;
  485. top: 50px;
  486. left: 50px;
  487. }
  488. ::v-deep .line {
  489. border-left: 1px dashed #0089d0 !important;
  490. border-top: 1px dashed #0089d0 !important;
  491. }
  492. ::v-deep.action {
  493. .value {
  494. background: var(--bs-el-color-primary);
  495. padding: 4px;
  496. color: #fff;
  497. }
  498. .del {
  499. color: var(--bs-el-color-primary);
  500. }
  501. }
  502. ::v-deep .ruler, ::v-deep .corner {
  503. background: var(--bs-background-1);
  504. }
  505. ::v-deep .corner {
  506. z-index: 999;
  507. background: var(--bs-background-1) !important;
  508. }
  509. ::v-deep .mb-ruler {
  510. z-index: 998
  511. }
  512. .grid-bg {
  513. background-color: #2a2e33 !important;
  514. background-image: url(./images/canvas-bg.png);
  515. background-repeat: repeat;
  516. word-spacing: 10px;
  517. }
  518. </style>