<template> <div v-loading="loading" class="bs-remote-wrap" element-loading-text="远程组件加载中..." > <component :is="remoteComponent" :ref="'remoteComponent'+config.code" :config="config" @linkage="linkEvent" /> </div> </template> <script> import linkageMixins from 'data-room-ui/js/mixins/linkageMixins' import commonMixins from 'data-room-ui/js/mixins/commonMixins' import remoteVueLoader from 'remote-vue-loader' import * as g2Plot from '@antv/g2plot' import * as _echarts from 'echarts' import { mapMutations, mapState } from 'vuex' import innerRemoteComponents, { getRemoteComponents } from 'data-room-ui/RemoteComponents/remoteComponentsList' import { getBizComponentInfo } from 'data-room-ui/js/api/bigScreenApi' import { settingToTheme } from 'data-room-ui/js/utils/themeFormatting' import cloneDeep from 'lodash/cloneDeep' export default { name: 'LcdpRemoteComponent', mixins: [linkageMixins, commonMixins], props: { config: { type: Object, default: () => ({}) } }, computed: { ...mapState('bigScreen', { pageInfo: state => state.pageInfo, customTheme: state => state.pageInfo.pageConfig.customTheme }), isPreview () { return (this.$route.path === window?.BS_CONFIG?.routers?.previewUrl) || (this.$route.path === '/big-screen/preview') } }, data () { return { loading: false, remoteComponent: null, componentInstance: null } }, watch: { }, created () { this.getRemoteComponent() }, mounted () { this.chartInit() }, methods: { ...mapMutations('bigScreen', ['changeChartConfig']), // 尝试渲染远程文件或远程字符串 getRemoteComponent () { this.loading = true // 1. 系统组件 通过配置获取 if (this.config.customize.vueSysComponentDirName) { const remoteComponentList = [...innerRemoteComponents, ...getRemoteComponents()] // 获得系统组件最新的配置, 同步 const config = remoteComponentList?.find(item => item.customize.vueSysComponentDirName === this.config.customize.vueSysComponentDirName) const vueFile = config?.customize?.vueFile const option = config?.option const setting = config?.setting // 同步配置 this.synchConfig(option, setting) this.remoteComponent = vueFile this.loading = false return } // 2. 业务组件 通过请求获取 if (this.config.customize.vueBizComponentCode) { getBizComponentInfo(this.config.customize.vueBizComponentCode).then(data => { const vueContent = data.vueContent const settingContent = data.settingContent if (!this.config?.option?.data) { this.resolveStrSetting(settingContent) this.config = this.dataFormatting(this.config, { success: false }) } this.remoteComponent = remoteVueLoader('data:text/plain,' + encodeURIComponent(vueContent)) }).finally(() => { this.loading = false }) } }, async chartInit () { let config = this.config config.g2Plots = g2Plot config.echarts = _echarts this.componentInstance = this.$refs['remoteComponent' + config.code] // key和code相等,说明是一进来刷新,调用list接口 if (this.config.code === this.config.key || this.isPreview) { // 改变样式 config = this.changeStyle(config) ? this.changeStyle(config) : config // 改变数据 config = await this.changeDataByCode(config) } else { // 否则说明是更新,这里的更新只指更新数据(改变样式时是直接调取changeStyle方法),因为更新数据会改变key,调用chart接口 // TODO 直接改变prop控制台会报错,待优化 try { this.config = await this.changeData(config) } catch (e) { console.error(e) } } }, linkEvent (formData) { this.linkage(formData) }, /** * 处理当前组件的字符串配置 */ resolveStrSetting (settingContent) { // eslint-disable-next-line prefer-const let option = {} // eslint-disable-next-line prefer-const let setting = [] // eslint-disable-next-line prefer-const, no-unused-vars let title = [] // eslint-disable-next-line prefer-const, no-unused-vars let data = [] // eslint-disable-next-line prefer-const settingContent = settingContent.replaceAll('const ', '') // 去掉 export default及后面代码 settingContent = settingContent.replace(/export default[\s\S]*/, '') eval(settingContent) // this.config.option = { // ...this.config.option, // ...option // } this.config.option = { ...option, ...this.config.option } this.config.setting = setting // 样式改变时更新主题配置 // this.config.theme = settingToTheme(cloneDeep(this.config), this.customTheme) // 获取到setting后将其转化为theme // this.config.theme = settingToTheme(this.config, this.customTheme) return { option, setting } }, /** * 组件的配置 * @returns {Promise<unknown>} */ // 将config.setting的配置转化为option里的配置,这里之所以将转化的方法提出来,是因为在改变维度指标和样式的时候都需要转化 transformSettingToOption (config, type) { let option = null config.setting.forEach(set => { if (set.optionField) { const optionField = set.optionField.split('.') option = config.option optionField.forEach((field, index) => { if (index === optionField.length - 1) { // 数据配置时,必须有值才更新 if ((set.tabName === type && type === 'data' && set.value) || (set.tabName === type && type === 'custom')) { option[field] = set.value } } else { option = option[field] } }) } }) config.option = { ...config.option, ...option } return config }, dataFormatting (config, data) { // 数据返回成功则赋值 if (data.success) { data = data.data config = this.transformSettingToOption(config, 'data') // 获取到后端返回的数据,有则赋值 const option = config.option const setting = config.setting if (config.dataHandler) { try { // 此处函数处理data eval(config.dataHandler) } catch (e) { console.error(e) } } config.option.data = data } else { // 数据返回失败则赋前端的模拟数据 config.option.data = this.plotList?.find(plot => plot.name === config.name)?.option?.data || config?.option?.data } return config }, // 组件的样式改变,返回改变后的config changeStyle (config, isUpdateTheme) { config = { ...this.config, ...config } config = this.transformSettingToOption(config, 'custom') // 这里定义了option和setting是为了保证在执行eval时,optionHandler、dataHandler里面可能会用到, const option = config.option const setting = config.setting if (this.config.optionHandler) { try { // 此处函数处理config eval(this.config.optionHandler) } catch (e) { console.error(e) } } // 只有样式改变时更新主题配置,切换主题时不需要保存 if (!isUpdateTheme) { config.theme = settingToTheme(cloneDeep(config), this.customTheme) } if (this.chart) { this.chart.update(config.option) } this.changeChartConfig(config) if (config.code === this.activeCode) { this.changeActiveItemConfig(config) } // this.changeChartConfig(config) // 在this.$refs['remoteComponent' + config.code]这个实例里判断是否存在customStyle方法,如果存在则执行 const componentInstance = this.$refs['remoteComponent' + config.code] if (componentInstance && componentInstance.$options.methods && componentInstance.$options.methods.customStyle) { // 调用 customStyle 方法 this.$refs['remoteComponent' + config.code]?.customStyle(config) } return config }, // 同步配置 synchConfig (option, setting) { // 对比this.config.setting 和 setting,进行合并,数据以this.config.option对象的value为准 // TODO } } } </script> <style lang="scss" scoped> .bs-remote-wrap { width: 100%; height: 100%; } </style>