index.vue 11 KB

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