index.vue 11 KB

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