index.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417
  1. <template>
  2. <div v-if="hasPermission">
  3. <div
  4. v-loading="pageLoading"
  5. element-loading-background="#151A26"
  6. class="bs-preview-wrap"
  7. :style="previewWrapStyle"
  8. >
  9. <div
  10. class="bs-render-wrap render-theme-wrap"
  11. :style="renderStyle"
  12. >
  13. <div
  14. v-for="chart in chartList"
  15. :key="chart.code"
  16. :style="getStyle(chart)"
  17. >
  18. <Configuration
  19. :config="chart"
  20. @openDataViewDialog="openDataViewDialog"
  21. >
  22. <RenderCard
  23. ref="RenderCardRef"
  24. :key="chart.key"
  25. :config="chart"
  26. @styleHandler="styleHandler"
  27. />
  28. </Configuration>
  29. </div>
  30. </div>
  31. </div>
  32. <data-view-dialog
  33. ref="dataViewDialog"
  34. />
  35. </div>
  36. <NotPermission v-else />
  37. </template>
  38. <script>
  39. import RenderCard from 'data-room-ui/Render/RenderCard.vue'
  40. import { mapActions, mapMutations, mapState } from 'vuex'
  41. import { compile } from 'tiny-sass-compiler/dist/tiny-sass-compiler.esm-browser.prod.js'
  42. import NotPermission from 'data-room-ui/NotPermission'
  43. import Configuration from 'data-room-ui/Render/Configuration.vue'
  44. import DataViewDialog from 'data-room-ui/BigScreenDesign/DataViewDialog/index.vue'
  45. import { getFileUrl } from 'data-room-ui/js/utils/file'
  46. import cloneDeep from 'lodash/cloneDeep'
  47. export default {
  48. name: 'BigScreenRun',
  49. components: {
  50. DataViewDialog,
  51. Configuration,
  52. RenderCard,
  53. NotPermission
  54. },
  55. props: {
  56. config: {
  57. type: Object,
  58. default: () => ({
  59. code: '',
  60. fitSelector: '.inner-preview-wrap',
  61. fitMode: 'auto'
  62. })
  63. }
  64. },
  65. data () {
  66. return {
  67. innerHeight: window.innerHeight,
  68. innerWidth: window.innerWidth,
  69. timer: null,
  70. hasPermission: true,
  71. initChartList: [],
  72. }
  73. },
  74. computed: {
  75. ...mapState({
  76. pageInfo: state => state.bigScreen.pageInfo,
  77. pageConfig: state => state.bigScreen.pageInfo.pageConfig,
  78. chartList: state => state.bigScreen.pageInfo.chartList,
  79. stateFitMode: state => state.bigScreen.pageInfo.pageConfig.fitMode
  80. }),
  81. pageCode () {
  82. // 内部系统取到外部iframe上src链接的code参数
  83. const iframeCode = this.getIframeCode()
  84. // 兼容外部网页上的code,iframe上的code以及传入的code
  85. return this.$route.query.code ||
  86. iframeCode ||
  87. this.config.code
  88. },
  89. fitMode () {
  90. return this.config.fitMode || this.stateFitMode
  91. },
  92. fitSelector () {
  93. return this.config.fitSelector
  94. },
  95. pageLoading () {
  96. return this.$store.state.bigScreen.pageLoading
  97. },
  98. fitPageConfig () {
  99. return this.resolvePageConfig(this.pageConfig)
  100. },
  101. previewWrapStyle () {
  102. const bg = this.fitMode !== 'none'
  103. ? {
  104. backgroundColor: this.fitPageConfig.customTheme === 'light' ? this.fitPageConfig.lightBgColor : this.fitPageConfig.bgColor,
  105. backgroundImage: this.fitPageConfig.customTheme === 'light' ? `url(${this.getCoverPicture(this.fitPageConfig.lightBg)})` : `url(${this.getCoverPicture(this.fitPageConfig.bg)})`,
  106. backgroundSize: 'cover'
  107. }
  108. : {}
  109. return {
  110. overflowX: `${this.fitPageConfig.overflowX}`,
  111. overflowY: `${this.fitPageConfig.overflowY}`,
  112. ...bg
  113. }
  114. },
  115. renderStyle () {
  116. const style = {
  117. width: this.fitPageConfig.w,
  118. height: this.fitPageConfig.h,
  119. transform: `scaleX(${this.fitPageConfig.scaleX}) scaleY(${this.fitPageConfig.scaleY}) translate(${this.fitPageConfig.translate})`
  120. }
  121. const bg = this.fitMode === 'none'
  122. ? {
  123. backgroundColor: this.fitPageConfig.customTheme === 'light' ? this.fitPageConfig.lightBgColor : this.fitPageConfig.bgColor,
  124. backgroundImage: this.fitPageConfig.customTheme === 'light' ? `url(${this.getCoverPicture(this.fitPageConfig.lightBg)})` : `url(${this.getCoverPicture(this.fitPageConfig.bg)})`,
  125. backgroundSize: 'cover'
  126. }
  127. : {}
  128. return {
  129. ...style,
  130. ...bg
  131. }
  132. }
  133. },
  134. watch: {
  135. pageCode (val) {
  136. if (val) {
  137. this.init()
  138. }
  139. },
  140. 'pageInfo.pageConfig.refreshConfig.length': {
  141. handler (val) {
  142. if (val) {
  143. this.startTimer()
  144. }
  145. }
  146. },
  147. chartList: {
  148. handler (val) {
  149. if (this.initChartList.length === 0) {
  150. this.initChartList = cloneDeep(this.chartList)
  151. }
  152. },
  153. deep: true
  154. }
  155. },
  156. beforeRouteLeave (to, from, next) {
  157. // 离开的时候 重置大屏的vuex store
  158. this.$store.commit('bigScreen/resetStoreData')
  159. next()
  160. },
  161. created () {
  162. this.permission()
  163. this.getParentWH()
  164. this.windowSize()
  165. },
  166. mounted () {
  167. if (this.pageInfo.pageConfig.refreshConfig && this.pageInfo.pageConfig.refreshConfig.length > 0) {
  168. this.startTimer()
  169. }
  170. },
  171. beforeDestroy () {
  172. this.stopTimer()
  173. },
  174. methods: {
  175. ...mapActions('bigScreen', [
  176. 'initLayout' // -> this.initLayout()
  177. ]),
  178. ...mapMutations('bigScreen', [
  179. 'changeLayout',
  180. 'changePageLoading',
  181. 'changePageConfig',
  182. 'changeChartConfig'
  183. ]),
  184. getStyle (chart) {
  185. if (chart.perspective > 0) {
  186. return {
  187. position: 'absolute',
  188. width: chart.w + 'px',
  189. height: chart.h + 'px',
  190. left: chart.x + 'px',
  191. top: chart.y + 'px',
  192. transform: `perspective(${chart.perspective}px) skew(${chart.skewX}deg, ${chart.skewY}deg) rotateX(${chart.rotateX}deg) rotateY(${chart.rotateY}deg) rotateZ(${chart.rotateZ}deg)`,
  193. zIndex: chart.z || 0
  194. }
  195. }
  196. return {
  197. position: 'absolute',
  198. width: chart.w + 'px',
  199. height: chart.h + 'px',
  200. left: chart.x + 'px',
  201. top: chart.y + 'px',
  202. transform: `skew(${chart.skewX}deg, ${chart.skewY}deg) rotateX(${chart.rotateX}deg) rotateY(${chart.rotateY}deg) rotateZ(${chart.rotateZ}deg)`,
  203. zIndex: chart.z || 0
  204. }
  205. },
  206. // 右键点击查看数据
  207. openDataViewDialog (config) {
  208. this.$refs.dataViewDialog.init(config)
  209. },
  210. // 切换主题时针对远程组件触发样式修改的方法
  211. styleHandler (config) {
  212. this.chartList.forEach((chart, index) => {
  213. if (chart.code === config.code) {
  214. this.$refs.RenderCardRef[index].$refs[chart.code].changeStyle(config)
  215. }
  216. })
  217. },
  218. permission () {
  219. this.$dataRoomAxios.get(`/bigScreen/permission/check/${this.pageCode}`).then(res => {
  220. this.hasPermission = res
  221. if (res) {
  222. this.init()
  223. }
  224. })
  225. },
  226. init () {
  227. if (!this.pageCode) { return }
  228. this.changePageLoading(true)
  229. this.initLayout(this.pageCode).then(() => {
  230. this.changePageLoading(false)
  231. this.getParentWH()
  232. })
  233. },
  234. // 设置定时器
  235. startTimer () {
  236. let time = 1
  237. const that = this
  238. // 使用setTimeout代替setInterval,并在每次循环结束后重新设置定时器。这样可以避免定时器的堆积和性能问题
  239. // 同时,为了方便清除定时器,可以将定时器的引用保存在变量中,以便后续清除
  240. this.timer = setTimeout(function refresh () {
  241. that.pageInfo.pageConfig.refreshConfig.forEach(item => {
  242. if (item.code) {
  243. if (time === 1) {
  244. item.originTime = item.time
  245. }
  246. that.chartList.forEach((chart, index) => {
  247. if (item.code === chart.code && item.time === time) {
  248. item.time = item.time + item.originTime
  249. if (that.$refs.RenderCardRef[index].$refs[chart.code]?.componentInstance) {
  250. that.$refs.RenderCardRef[index].$refs[chart.code].componentInstance.changeData(chart)
  251. } else {
  252. that.$refs.RenderCardRef[index].$refs[chart.code].changeData(chart)
  253. }
  254. }
  255. })
  256. }
  257. })
  258. time += 1
  259. that.timer = setTimeout(refresh, 1000)
  260. }, 1000)
  261. },
  262. // 清除定时器
  263. stopTimer () {
  264. clearTimeout(this.timer)
  265. },
  266. getIframeCode () {
  267. // 获取当前页面的URL
  268. const url = window.location.href
  269. // 解析URL的参数
  270. let code = null
  271. // 检查URL中是否包含哈希值
  272. if (url.indexOf('#') !== -1) {
  273. // 获取哈希部分的URL
  274. const hashUrl = url.split('#')[1]
  275. // 解析哈希部分URL的参数
  276. const hashParams = new URLSearchParams(hashUrl)
  277. code = hashParams.get('code')
  278. } else {
  279. // 获取URL的查询字符串部分
  280. const searchParams = new URLSearchParams(url.split('?')[1])
  281. code = searchParams.get('code')
  282. }
  283. return code
  284. },
  285. windowSize () {
  286. window.onresize = () => {
  287. this.getParentWH()
  288. }
  289. },
  290. getParentWH () {
  291. this.$nextTick(() => {
  292. const parent = document.querySelector(this.fitSelector)
  293. // 如果设置了自适应的选择器
  294. if (parent) {
  295. this.innerHeight = parent.offsetHeight
  296. this.innerWidth = parent.offsetWidth
  297. } else {
  298. this.innerHeight = window.innerHeight
  299. this.innerWidth = window.innerWidth
  300. }
  301. // 设置bs-preview-wrap的高度为父元素的高度
  302. const previewWrap = document.querySelector('.bs-preview-wrap')
  303. if (previewWrap) {
  304. previewWrap.style.height = this.innerHeight + 'px'
  305. previewWrap.style.width = this.innerWidth + 'px'
  306. }
  307. })
  308. },
  309. // 获取到后端传来的主题样式并进行修改
  310. styleSet () {
  311. const style = document.createElement('style')
  312. if (this.pageConfig.themeJson && this.pageConfig.themeJson.themeCss) {
  313. const styleStr = this.pageConfig.themeJson.themeCss
  314. const themeCss = compile(styleStr).code
  315. style.type = 'text/css'
  316. style.innerText = themeCss
  317. document.getElementsByTagName('head')[0].appendChild(style)
  318. } else {
  319. style.remove()
  320. }
  321. },
  322. // 处理自适应下的页面配置
  323. resolvePageConfig (pageConfig) {
  324. const { w, h } = pageConfig
  325. let scaleX = 1
  326. let scaleY = 1
  327. let translate = '0px, 0px'
  328. let overflowX = 'auto'
  329. let overflowY = 'auto'
  330. // 自适应模式, 画布等比例自适应后保持一屏展示,会存在某一个方向两边留白,留白部分颜色背景和画布中的背景色一致
  331. if (pageConfig.fitMode === 'auto') {
  332. const scaleW = this.innerWidth / w
  333. const scaleH = this.innerHeight / h
  334. scaleX = Math.min(scaleW, scaleH)
  335. scaleY = Math.min(scaleW, scaleH)
  336. translate = `${((this.innerWidth - w) / 2) / scaleX}px, ${((this.innerHeight - h) / 2) / scaleY}px`
  337. overflowX = 'hidden'
  338. overflowY = 'hidden'
  339. }
  340. // 宽度铺满 预览时画布横向铺满,纵向超出时出现滚动条
  341. if (pageConfig.fitMode === 'fitWidth') {
  342. scaleX = this.innerWidth / w
  343. // 如果实际高度小于屏幕,纵向居中
  344. if (this.innerHeight > h) {
  345. translate = `${((this.innerWidth - w) / 2) / scaleX}px, ${((this.innerHeight - h) / 2) / scaleY}px`
  346. } else {
  347. translate = `${((this.innerWidth - w) / 2) / scaleX}px, 0px`
  348. }
  349. overflowX = 'hidden'
  350. }
  351. // 高度铺满 预览时画布纵向铺满,横向超出时出现滚动条
  352. if (pageConfig.fitMode === 'fitHeight') {
  353. scaleY = this.innerHeight / h
  354. // 如果实际宽度小于屏幕,横向居中
  355. if (this.innerWidth > w) {
  356. translate = `${((this.innerWidth - w) / 2) / scaleX}px, ${((this.innerHeight - h) / 2) / scaleY}px`
  357. } else {
  358. translate = `0px, ${((this.innerHeight - h) / 2) / scaleY}px`
  359. }
  360. overflowY = 'hidden'
  361. }
  362. // 双向铺满 预览时画布横向纵向铺满,无滚动条,但是元素可能会出现拉伸情况
  363. if (pageConfig.fitMode === 'cover') {
  364. scaleX = this.innerWidth / w
  365. scaleY = this.innerHeight / h
  366. translate = `${((this.innerWidth - w) / 2) / scaleX}px, ${((this.innerHeight - h) / 2) / scaleY}px`
  367. overflowX = 'hidden'
  368. overflowY = 'hidden'
  369. }
  370. // 无
  371. const newPageConfig = {
  372. ...pageConfig,
  373. w: w + 'px',
  374. h: h + 'px',
  375. scaleX,
  376. scaleY,
  377. translate,
  378. overflowX,
  379. overflowY
  380. }
  381. return newPageConfig
  382. },
  383. /**
  384. * 获取图片访问地址,如果是相对路径则拼接上文件访问前缀地址
  385. * @param url
  386. * @returns {*}
  387. */
  388. getCoverPicture (url) {
  389. return getFileUrl(url)
  390. }
  391. }
  392. }
  393. </script>
  394. <style lang="scss" scoped>
  395. .bs-preview-wrap {
  396. position: absolute;
  397. width: 100%;
  398. height: 100%;
  399. overflow: auto;
  400. .bs-render-wrap {
  401. position: relative;
  402. background-size: cover;
  403. }
  404. }
  405. </style>