index.vue 11 KB

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