SketchRuler.vue 16 KB

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