index.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481
  1. <template>
  2. <div
  3. ref="bs-render-wrap"
  4. :key="`${pageInfo.pageConfig.w}${pageInfo.pageConfig.h}`"
  5. class="bs-render-wrap design-drag-wrap render-theme-wrap"
  6. :style="{
  7. width: pageInfo.pageConfig.w + 'px',
  8. height: pageInfo.pageConfig.h + 'px',
  9. backgroundColor:pageInfo.pageConfig.customTheme ==='light' ? pageInfo.pageConfig.lightBgColor:pageInfo.pageConfig.bgColor ,
  10. backgroundImage:pageInfo.pageConfig.customTheme ==='light' ? `url(${pageInfo.pageConfig.lightBg})`:`url(${pageInfo.pageConfig.bg})`
  11. }"
  12. @drop="drop($event)"
  13. @dragover.prevent
  14. @click="handleClickOutside($event)"
  15. >
  16. <vdr
  17. v-for="chart in chartList"
  18. :id="chart.code"
  19. :key="chart.updateKey || chart.code"
  20. class="drag-item"
  21. :class="{
  22. 'multiple-selected': activeCodes.includes(chart.code),
  23. }"
  24. :scale-ratio="scale"
  25. :x="chart.x"
  26. :y="chart.y"
  27. :w="chart.w"
  28. :h="chart.h"
  29. :min-width="10"
  30. :min-height="10"
  31. :draggable="!chart.locked"
  32. :resizable="!chart.locked"
  33. :parent="true"
  34. :debug="false"
  35. :is-conflict-check="false"
  36. :snap="true"
  37. :snap-tolerance="2"
  38. :style="{
  39. zIndex: chart.z || 0,
  40. }"
  41. :grid="[1,1]"
  42. @activated="activated(...arguments, chart)"
  43. @dragging="onDrag(...arguments, chart)"
  44. @resizing="onResize(...arguments, chart)"
  45. @resizestop="resizestop(...arguments, chart)"
  46. @dragstop="dragstop(...arguments, chart)"
  47. @refLineParams="getRefLineParams"
  48. @mouseleave.native="resetPresetLineDelay"
  49. >
  50. <Configuration
  51. v-if="isInit"
  52. :config="chart"
  53. @openRightPanel="openRightPanel"
  54. @openDataViewDialog="openDataViewDialog"
  55. >
  56. <RenderCard
  57. :ref="'RenderCard' + chart.code"
  58. :config="chart"
  59. @styleHandler="styleHandler"
  60. />
  61. </Configuration>
  62. </vdr>
  63. <span
  64. v-for="(vl, index) in vLine"
  65. v-show="vl.display"
  66. :key="index + 'vLine'"
  67. class="ref-line v-line"
  68. :style="{ left: vl.position, top: vl.origin, height: vl.lineLength }"
  69. />
  70. <span
  71. v-for="(hl, index) in hLine"
  72. v-show="hl.display"
  73. :key="index + 'hLine'"
  74. class="ref-line h-line"
  75. :style="{ top: hl.position, left: hl.origin, width: hl.lineLength }"
  76. />
  77. </div>
  78. </template>
  79. <script>
  80. import { mapState, mapMutations } from 'vuex'
  81. import RenderCard from './RenderCard.vue'
  82. import Configuration from './Configuration.vue'
  83. // import _ from 'lodash'
  84. import cloneDeep from 'lodash/cloneDeep'
  85. import vdr from 'vue-draggable-resizable-gorkys'
  86. import 'vue-draggable-resizable-gorkys/dist/VueDraggableResizable.css'
  87. import { randomString } from '../js/utils'
  88. import { compile } from 'tiny-sass-compiler/dist/tiny-sass-compiler.esm-browser.prod.js'
  89. import plotList, { getCustomPlots } from '../G2Plots/plotList'
  90. import { settingToTheme } from 'data-room-ui/js/utils/themeFormatting'
  91. export default {
  92. name: 'BigScreenRender',
  93. components: {
  94. RenderCard,
  95. Configuration,
  96. vdr
  97. },
  98. props: {
  99. ruleKey: {
  100. type: Number,
  101. default: 0
  102. }
  103. },
  104. data () {
  105. return {
  106. vLine: [],
  107. hLine: [],
  108. themeCss: '',
  109. // 临时冻结拖拽
  110. freeze: false,
  111. plotList,
  112. rawChart: []
  113. }
  114. },
  115. computed: {
  116. ...mapState({
  117. pageConfig: (state) => state.bigScreen.pageInfo.pageConfig,
  118. pageInfo: (state) => state.bigScreen.pageInfo,
  119. chartList: (state) => state.bigScreen.pageInfo.chartList,
  120. activeCode: (state) => state.bigScreen.activeCode,
  121. activeCodes: (state) => state.bigScreen.activeCodes,
  122. hoverCode: (state) => state.bigScreen.hoverCode,
  123. themeJson: (state) => state.bigScreen.pageInfo.pageConfig.themeJson,
  124. isInit: (state) => !state.bigScreen.pageLoading,
  125. scale: (state) => state.bigScreen.zoom / 100
  126. })
  127. },
  128. watch: {
  129. pageConfig: {
  130. handler (pageConfig) {
  131. this.$nextTick(() => {
  132. const style = document.createElement('style')
  133. if (
  134. pageConfig &&
  135. pageConfig.themeJson &&
  136. pageConfig.themeJson.themeCss
  137. ) {
  138. const themeCss = pageConfig.themeJson.themeCss
  139. if (themeCss) {
  140. const themeStr = compile(themeCss).code
  141. style.type = 'text/css'
  142. style.innerText = themeStr
  143. document.getElementsByTagName('head')[0].appendChild(style)
  144. }
  145. }
  146. })
  147. },
  148. deep: true,
  149. immediate: true
  150. }
  151. },
  152. mounted () {
  153. this.styleSet()
  154. this.plotList = [...this.plotList, ...getCustomPlots()]
  155. },
  156. methods: {
  157. ...mapMutations('bigScreen', [
  158. 'changeLayout',
  159. 'changeActiveCode',
  160. 'changeChartConfig',
  161. 'changeActiveItemConfig',
  162. 'changeActiveItemWH',
  163. 'addItem',
  164. 'delItem',
  165. 'resetPresetLine',
  166. 'changeGridShow',
  167. 'setPresetLine',
  168. 'saveTimeLine',
  169. 'changeActiveCodes'
  170. ]),
  171. // 判断鼠标点击的是画布中的高亮元素(被框选的)还是非高亮元素或者空白区域
  172. // 如果是高亮元素则不会取消高亮状态,如果不是则取消高亮状态
  173. handleClickOutside (event) {
  174. // 获取被点击的元素
  175. const clickedElement = event.target
  176. const elementToHighlights = []
  177. // 获取需要高亮的元素的引用
  178. for (const code of this.activeCodes) {
  179. elementToHighlights.push(this.$refs['RenderCard' + code][0])
  180. }
  181. const isElementInHighlights = elementToHighlights.some((elementToHighlight) => {
  182. return elementToHighlight?.$el?.contains(clickedElement)
  183. })
  184. if (!isElementInHighlights) {
  185. this.changeActiveCodes([])
  186. }
  187. },
  188. // 切换主题时针对远程组件触发样式修改的方法
  189. styleHandler (config) {
  190. this.$nextTick(() => {
  191. this.$refs['RenderCard' + config.code][0]?.$refs[
  192. config.code
  193. ]?.changeStyle(cloneDeep(config), true)
  194. })
  195. },
  196. // 获取到后端传来的主题样式并进行修改
  197. styleSet () {
  198. const style = document.createElement('style')
  199. if (this.themeJson && this.themeJson.themeCss) {
  200. const styleStr = this.themeJson.themeCss
  201. const themeCss = compile(styleStr).code
  202. style.type = 'text/css'
  203. style.innerText = themeCss
  204. document.getElementsByTagName('head')[0].appendChild(style)
  205. } else {
  206. style.remove()
  207. }
  208. },
  209. resetPresetLineDelay () {
  210. setTimeout(() => {
  211. this.resetPresetLine()
  212. }, 500)
  213. },
  214. // 点击当前组件时打开右侧面板
  215. openRightPanel (config) {
  216. this.$emit('openRightPanel', config)
  217. },
  218. // 查看数据
  219. openDataViewDialog (config) {
  220. this.$emit('openDataViewDialog', config)
  221. },
  222. drop (e) {
  223. e.preventDefault()
  224. // 解决:火狐拖放后,总会默认打开百度搜索,如果是图片,则会打开图片的问题。
  225. e.stopPropagation()
  226. const transferData = e.dataTransfer.getData('dragComponent')
  227. if (transferData) {
  228. this.addChart(transferData, { x: e?.x, y: e?.y })
  229. }
  230. },
  231. /**
  232. * 改变组件大小
  233. * @param x
  234. * @param y
  235. * @param width
  236. * @param height
  237. * @param chart
  238. */
  239. onResize (x, y, width, height, chart) {
  240. chart.x = x
  241. chart.y = y
  242. chart.w = width
  243. chart.h = height
  244. this.changeGridShow(true)
  245. this.setPresetLine({
  246. ...chart
  247. })
  248. },
  249. /**
  250. *
  251. * @param x
  252. * @param y
  253. * @param chart
  254. */
  255. onDrag (x, y, chart) {
  256. // 防止事件冒泡
  257. event.stopPropagation()
  258. if (chart.group) {
  259. // 查找和自己是一个组合的组件
  260. this.dragGroupChart(x, y, chart)
  261. } else {
  262. chart.x = x
  263. chart.y = y
  264. }
  265. this.changeGridShow(true)
  266. this.setPresetLine({
  267. ...chart
  268. })
  269. },
  270. resizestop (left, top, width, height, chart) {
  271. this.changeChartConfig({
  272. ...chart,
  273. w: width,
  274. h: height,
  275. x: left,
  276. y: top
  277. })
  278. this.changeActiveItemConfig({
  279. ...chart,
  280. w: width,
  281. h: height,
  282. x: left,
  283. y: top
  284. })
  285. if (chart.code === this.activeCode) {
  286. this.changeActiveItemWH({
  287. code: chart.code,
  288. w: width,
  289. h: height
  290. })
  291. }
  292. this.saveTimeLine(`改变${chart?.title}大小`)
  293. this.changeGridShow(false)
  294. },
  295. activated (chart) {
  296. this.rawChart = cloneDeep(chart)
  297. },
  298. dragstop (left, top, chart) {
  299. if (!this.freeze) {
  300. if (this.rawChart.x !== left || this.rawChart.y !== top) {
  301. this.changeChartConfig({
  302. ...chart,
  303. x: left,
  304. y: top
  305. })
  306. this.changeActiveItemConfig({
  307. ...chart,
  308. x: left,
  309. y: top
  310. })
  311. if (chart.code === this.activeCode) {
  312. this.changeActiveItemWH({
  313. code: chart.code,
  314. x: left,
  315. y: top
  316. })
  317. }
  318. this.rawChart = cloneDeep(chart)
  319. }
  320. } else {
  321. const index = this.chartList.findIndex(
  322. (_chart) => _chart.code === chart.code
  323. )
  324. this.$set(this.chartList, index, chart)
  325. this.changeChartConfig({
  326. ...chart,
  327. updateKey: new Date().getTime()
  328. })
  329. }
  330. this.changeGridShow(false)
  331. this.freeze = false
  332. this.saveTimeLine(`拖拽${chart?.title}`)
  333. },
  334. // 辅助线
  335. getRefLineParams (params) {
  336. const { vLine, hLine } = params
  337. this.vLine = vLine
  338. this.hLine = hLine
  339. },
  340. // 新增元素
  341. addChart (chart, position, isComponent) {
  342. const { left, top } = this.$el.getBoundingClientRect()
  343. const _chart = !chart.code ? JSON.parse(chart) : chart
  344. let option = _chart.option
  345. if (_chart.type === 'customComponent') {
  346. option = {
  347. ...this.plotList?.find((plot) => plot.name === _chart.name)?.option,
  348. theme: this.pageConfig.customTheme === 'dark' ? 'transparent' : 'light'
  349. }
  350. }
  351. const config = {
  352. ..._chart,
  353. x: parseInt(!chart.code
  354. ? (position.x - left - _chart.offsetX) / this.scale
  355. : position.x),
  356. y: parseInt(!chart.code
  357. ? (position.y - top - _chart.offsetX) / this.scale
  358. : position.y),
  359. width: 200 * this.scale,
  360. height: 200 * this.scale,
  361. code: !chart.code ? randomString(8) : chart.code,
  362. option
  363. }
  364. config.key = config.code
  365. // isComponent = false 从左侧新增时需要初始化theme的内容
  366. // isComponent = true从组件库添加自定义组件时不用初始化
  367. if (!isComponent) {
  368. config.theme = settingToTheme(config, 'dark')
  369. config.theme = settingToTheme(config, 'light')
  370. }
  371. this.addItem(config)
  372. },
  373. addSourceChart (chart, position) {
  374. const { left, top } = this.$el.getBoundingClientRect()
  375. const _chart = JSON.parse(chart)
  376. let option = _chart.option
  377. if (_chart.type === 'customComponent') {
  378. option = {
  379. ...this.plotList?.find((plot) => plot.name === _chart.name)?.option,
  380. theme: this.pageConfig.customTheme === 'dark' ? 'transparent' : 'light'
  381. }
  382. }
  383. const config = {
  384. ..._chart,
  385. x: parseInt((position.x - left) / this.scale),
  386. y: parseInt((position.y - top) / this.scale),
  387. width: 200 * this.scale,
  388. height: 200 * this.scale,
  389. code: randomString(8),
  390. option
  391. }
  392. config.key = config.code
  393. this.addItem(config)
  394. },
  395. /**
  396. * 拖拽相同组合的组件
  397. * @param x 组合元素当前x
  398. * @param y 组合元素当前y
  399. * @param chart
  400. */
  401. dragGroupChart (x, y, chart) {
  402. if (chart.group) {
  403. const diffX = x - chart.x
  404. const diffY = y - chart.y
  405. const group = chart.group
  406. // 找到相同group的组件,并找到边界
  407. const groupChartList = this.chartList.filter(
  408. (groupChart) => groupChart.group === group
  409. )
  410. const groupMinX = Math.min(
  411. ...groupChartList?.map((groupChart) => groupChart.x + diffX)
  412. )
  413. const groupMinY = Math.min(
  414. ...groupChartList?.map((groupChart) => groupChart.y + diffY)
  415. )
  416. const groupMaxX = Math.max(
  417. ...groupChartList?.map(
  418. (groupChart) => groupChart.x + diffX + groupChart.w
  419. )
  420. )
  421. const groupMaxY = Math.max(
  422. ...groupChartList?.map(
  423. (groupChart) => groupChart.y + diffY + groupChart.h
  424. )
  425. )
  426. // 如果其中某个组件超出画布,则不移动 (此处无法阻止移动,故在拖拽结束后重置位置)
  427. if (
  428. (groupMinX <= 0 ||
  429. groupMinY <= 0 ||
  430. groupMaxX >= this.pageConfig.w ||
  431. groupMaxY >= this.pageConfig.h) &&
  432. // 偏移的绝对值要大于0
  433. (Math.abs(diffX) > 0 || Math.abs(diffY) > 0)
  434. ) {
  435. this.freeze = true
  436. return
  437. }
  438. // 移动相应的diff距离
  439. groupChartList?.map((groupChart) => {
  440. this.changeChartConfig({
  441. ...groupChart,
  442. x: groupChart.x + diffX,
  443. y: groupChart.y + diffY
  444. })
  445. })
  446. }
  447. }
  448. }
  449. }
  450. </script>
  451. <style lang="scss" scoped>
  452. .bs-render-wrap {
  453. position: relative;
  454. background-size: cover;
  455. .drag-item {
  456. cursor: move;
  457. }
  458. ::v-deep .vdr {
  459. border: none;
  460. }
  461. .h-line {
  462. border-bottom: 1px dashed #0089d0;
  463. }
  464. .v-line {
  465. border-left: 1px dashed #0089d0;
  466. }
  467. .ref-line {
  468. background-color: transparent;
  469. }
  470. }
  471. .design-drag-wrap {
  472. box-shadow: 0 0 30px 0 rgba(0, 0, 0, 0.5);
  473. }
  474. .multiple-selected {
  475. border: 1px dashed #fff !important;
  476. }
  477. </style>