index.vue 11 KB

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