index.vue 11 KB

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