<script lang="ts" setup>
|
import icon_info_outlined from '@/assets/svg/icon_info_outlined.svg'
|
import icon_linkRecord_outlined from '@/assets/svg/icon_link-record_outlined.svg'
|
import icon_viewinchat_outlined from '@/assets/svg/icon_viewinchat_outlined.svg'
|
import { cancelRequestBatch } from '@/config/axios/service'
|
import icon_drilling_outlined from '@/assets/svg/icon_drilling_outlined.svg'
|
import { useI18n } from '@/hooks/web/useI18n'
|
import ChartComponentG2Plot from './components/ChartComponentG2Plot.vue'
|
import DeIndicator from '@/custom-component/indicator/DeIndicator.vue'
|
import { useAppearanceStoreWithOut } from '@/store/modules/appearance'
|
import { useAppStoreWithOut } from '@/store/modules/app'
|
import { useEmbedded } from '@/store/modules/embedded'
|
import { XpackComponent } from '@/components/plugin'
|
import { PluginComponent } from '@/components/plugin'
|
import {
|
computed,
|
CSSProperties,
|
nextTick,
|
onBeforeMount,
|
onMounted,
|
PropType,
|
provide,
|
reactive,
|
ref,
|
shallowRef,
|
toRefs,
|
watch
|
} from 'vue'
|
import { useEmitt } from '@/hooks/web/useEmitt'
|
import { hexColorToRGBA, parseJson } from '@/views/chart/components/js/util.js'
|
import {
|
CHART_FONT_FAMILY_MAP,
|
DEFAULT_TITLE_STYLE
|
} from '@/views/chart/components/editor/util/chart'
|
import DrillPath from '@/views/chart/components/views/components/DrillPath.vue'
|
import { ElIcon, ElInput, ElMessage } from 'element-plus-secondary'
|
import { useFilter } from '@/hooks/web/useFilter'
|
import { useCache } from '@/hooks/web/useCache'
|
|
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
|
import { cloneDeep, debounce } from 'lodash-es'
|
import ChartComponentS2 from '@/views/chart/components/views/components/ChartComponentS2.vue'
|
import { ChartLibraryType } from '@/views/chart/components/js/panel/types'
|
import chartViewManager from '@/views/chart/components/js/panel'
|
import { storeToRefs } from 'pinia'
|
import { checkAddHttp, setIdValueTrans } from '@/utils/canvasUtils'
|
import { Base64 } from 'js-base64'
|
import DeRichTextView from '@/custom-component/rich-text/DeRichTextView.vue'
|
import DePictureGroup from '@/custom-component/picture-group/Component.vue'
|
import ChartEmptyInfo from '@/views/chart/components/views/components/ChartEmptyInfo.vue'
|
import { snapshotStoreWithOut } from '@/store/modules/data-visualization/snapshot'
|
import { viewFieldTimeTrans } from '@/utils/viewUtils'
|
import { CHART_TYPE_CONFIGS } from '@/views/chart/components/editor/util/chart'
|
import request from '@/config/axios'
|
import { store } from '@/store'
|
import { clearExtremum } from '@/views/chart/components/js/extremumUitl'
|
import DePreviewPopDialog from '@/components/visualization/DePreviewPopDialog.vue'
|
import { useRoute } from 'vue-router_2'
|
const route = useRoute()
|
const { wsCache } = useCache()
|
const chartComponent = ref<any>()
|
const { t } = useI18n()
|
const dvMainStore = dvMainStoreWithOut()
|
const { emitter } = useEmitt()
|
const dePreviewPopDialogRef = ref(null)
|
let innerRefreshTimer = null
|
let innerSearchCount = 0
|
const appStore = useAppStoreWithOut()
|
const appearanceStore = useAppearanceStoreWithOut()
|
const isDataEaseBi = computed(() => appStore.getIsDataEaseBi)
|
const isIframe = computed(() => appStore.getIsIframe)
|
|
const emit = defineEmits(['onPointClick', 'onComponentEvent'])
|
|
const {
|
nowPanelJumpInfo,
|
publicLinkStatus,
|
dvInfo,
|
curComponent,
|
canvasStyleData,
|
mobileInPc,
|
inMobile,
|
editMode
|
} = storeToRefs(dvMainStore)
|
|
const props = defineProps({
|
// 公共参数集
|
commonParams: {
|
type: Object,
|
required: false
|
},
|
active: {
|
type: Boolean,
|
default: false
|
},
|
element: {
|
type: Object,
|
default() {
|
return {
|
propValue: null
|
}
|
}
|
},
|
view: {
|
type: Object as PropType<ChartObj>,
|
default() {
|
return {
|
propValue: null
|
}
|
}
|
},
|
themes: {
|
type: String,
|
required: false,
|
default: 'dark'
|
},
|
showPosition: {
|
type: String,
|
required: false,
|
default: 'preview'
|
},
|
// 仪表板刷新计时器
|
searchCount: {
|
type: Number,
|
required: false,
|
default: 0
|
},
|
disabled: {
|
type: Boolean,
|
required: false,
|
default: false
|
},
|
scale: {
|
type: Number,
|
required: false,
|
default: 1
|
},
|
suffixId: {
|
type: String,
|
required: false,
|
default: 'common'
|
},
|
fontFamily: {
|
type: String,
|
required: false,
|
default: 'inherit'
|
},
|
optType: {
|
type: String,
|
required: false
|
}
|
})
|
const dynamicAreaId = ref('')
|
const { view, showPosition, element, active, searchCount, scale, suffixId } = toRefs(props)
|
const titleShow = computed(() => {
|
return (
|
!['rich-text', 'picture-group'].includes(element.value.innerType) &&
|
state.title_show &&
|
showPosition.value !== 'viewDialog'
|
)
|
})
|
const snapshotStore = snapshotStoreWithOut()
|
|
const state = reactive({
|
initReady: true, //curComponent 切换期间 不接收外部的calcData 和 renderChart 事件
|
title_show: true,
|
title_remark: {
|
show: false,
|
remark: ''
|
},
|
title_class: {
|
fontSize: '18px',
|
color: '#303133',
|
textAlign: 'left',
|
fontStyle: 'normal',
|
fontWeight: 'normal',
|
background: '',
|
fontFamily: '',
|
textShadow: 'none',
|
letterSpacing: '0px',
|
fontSynthesis: 'style weight',
|
width: 'fit-content',
|
maxWidth: '100%',
|
wordBreak: 'break-word',
|
whiteSpace: 'pre-wrap!important'
|
} as CSSProperties,
|
drillFilters: [],
|
viewInfoData: null,
|
drillClickDimensionList: []
|
})
|
|
const drillClickLength = computed(() => state.drillClickDimensionList.length)
|
|
const titleAlign = computed<string>(() => {
|
if (!titleShow.value) {
|
return 'flex-start'
|
}
|
|
if (state.title_class.textAlign === 'center') {
|
return 'center'
|
} else if (state.title_class.textAlign === 'right') {
|
return 'flex-end'
|
}
|
|
return 'flex-start'
|
})
|
|
const trackMenu = computed<Array<string>>(() => {
|
return chartComponent?.value?.trackMenu ?? []
|
})
|
|
const hasLinkIcon = computed(() => {
|
return trackMenu.value.indexOf('linkage') > -1 || trackMenu.value.indexOf('linkageAndDrill') > -1
|
})
|
const hasJumpIcon = computed(() => {
|
return trackMenu.value.indexOf('jump') > -1 && !mobileInPc.value
|
})
|
const hasDrillIcon = computed(() => {
|
return trackMenu.value.indexOf('drill') > -1 || trackMenu.value.indexOf('linkageAndDrill') > -1
|
})
|
|
const loading = ref(false)
|
|
const resultMode = computed(() => {
|
return canvasStyleData.value.dashboard?.resultMode || null
|
})
|
|
const resultCount = computed(() => {
|
return canvasStyleData.value.dashboard?.resultCount || null
|
})
|
|
const embeddedStore = useEmbedded()
|
// 编辑状态下 不启动刷新
|
const buildInnerRefreshTimer = (
|
refreshViewEnable = false,
|
refreshUnit = 'minute',
|
refreshTime = 5
|
) => {
|
if (showPosition.value === 'preview' && !innerRefreshTimer && refreshViewEnable) {
|
innerRefreshTimer && clearInterval(innerRefreshTimer)
|
const timerRefreshTime = refreshUnit === 'second' ? refreshTime * 1000 : refreshTime * 60000
|
innerRefreshTimer = setInterval(() => {
|
clearViewLinkage()
|
queryData()
|
innerSearchCount++
|
}, timerRefreshTime)
|
}
|
}
|
|
// 清除相同sourceViewId 的 联动条件
|
const clearViewLinkage = () => {
|
dvMainStore.clearViewLinkage(element.value.id)
|
useEmitt().emitter.emit('clearPanelLinkage', { viewId: element.value.id })
|
}
|
|
watch([() => scale.value], () => {
|
initTitle()
|
})
|
|
watch([() => searchCount.value], () => {
|
// 内部计时器启动 忽略外部计时器
|
if (!innerRefreshTimer) {
|
queryData()
|
}
|
})
|
// 仪表板的查询结果设置变化 图表数据需要刷新
|
watch([() => resultCount.value], () => {
|
queryData()
|
})
|
|
watch([() => resultMode.value], () => {
|
queryData()
|
})
|
|
watch([() => scale.value], () => {
|
nextTick(() => {
|
chartComponent?.value?.renderChart?.(view.value)
|
})
|
})
|
|
watch([() => curComponent.value], () => {
|
if (curComponent.value && curComponent.value.id === view.value.id) {
|
state.initReady = false
|
nextTick(() => {
|
state.initReady = true
|
})
|
}
|
})
|
|
const chartExtRequest = shallowRef(null)
|
provide('chartExtRequest', chartExtRequest)
|
|
const initTitle = () => {
|
if (view.value.customStyle) {
|
const customStyle = view.value.customStyle
|
if (customStyle.text) {
|
state.title_show = customStyle.text.show
|
state.title_class.fontSize = customStyle.text.fontSize * scale.value + 'px'
|
state.title_class.color = customStyle.text.color
|
state.title_class.textAlign = customStyle.text.hPosition as CSSProperties['textAlign']
|
state.title_class.fontStyle = customStyle.text.isItalic ? 'italic' : 'normal'
|
state.title_class.fontWeight = customStyle.text.isBolder ? 'bold' : 'normal'
|
if (!!appearanceStore.fontList.length) {
|
appearanceStore.fontList.forEach(ele => {
|
CHART_FONT_FAMILY_MAP[ele.name] = ele.name
|
})
|
}
|
state.title_class.fontFamily = customStyle.text.fontFamily
|
? CHART_FONT_FAMILY_MAP[customStyle.text.fontFamily]
|
: DEFAULT_TITLE_STYLE.fontFamily
|
if (!CHART_FONT_FAMILY_MAP[customStyle.text.fontFamily]) {
|
state.title_class.fontFamily = appearanceStore.fontList.find(ele => ele.isDefault)?.name
|
customStyle.text.fontFamily = state.title_class.fontFamily
|
}
|
appearanceStore.setCurrentFont(state.title_class.fontFamily)
|
state.title_class.letterSpacing =
|
(customStyle.text.letterSpace
|
? customStyle.text.letterSpace
|
: DEFAULT_TITLE_STYLE.letterSpace) + 'px'
|
state.title_class.textShadow = customStyle.text.fontShadow ? '2px 2px 4px' : 'none'
|
}
|
if (customStyle.background) {
|
state.title_class.background = hexColorToRGBA(
|
customStyle.background.color,
|
customStyle.background.alpha
|
)
|
}
|
|
state.title_remark.show = customStyle.text.show && customStyle.text.remarkShow
|
state.title_remark.remark = customStyle.text.remark
|
}
|
}
|
|
const drillJump = (index: number) => {
|
state.drillClickDimensionList.splice(index)
|
view.value.chartExtRequest = filter()
|
calcData(view.value)
|
}
|
|
const onPointClick = param => {
|
try {
|
const msg = {
|
sourceDvId: dvInfo.value.id,
|
sourceViewId: view.value.id,
|
message: Base64.encode(JSON.stringify(param))
|
}
|
emit('onPointClick', msg)
|
} catch (e) {
|
console.warn('de_inner_params send error')
|
}
|
}
|
|
const chartClick = param => {
|
// 下钻字段第一个没有在维度中不允许下钻
|
const xIds = view.value.xAxis.map(ele => ele.id)
|
if (xIds.indexOf(props.view.drillFields[0].id) == -1) {
|
ElMessage.error(t('chart.drill_field_error'))
|
return
|
}
|
if (view.value.type === 'circle-packing' && param.data.name === t('commons.all')) {
|
ElMessage.error(t('chart.last_layer'))
|
return
|
}
|
if (state.drillClickDimensionList.length < props.view.drillFields.length - 1) {
|
state.drillClickDimensionList.push({
|
dimensionList: param.data.dimensionList,
|
extra: param.extra
|
})
|
view.value.chartExtRequest = filter()
|
calcData(view.value)
|
} else if (props.view.drillFields.length > 0) {
|
ElMessage.error(t('chart.last_layer'))
|
}
|
}
|
|
// 仪表板和大屏所有额外过滤参数都在此处
|
const filter = (firstLoad?: boolean) => {
|
const { filter } = useFilter(view.value.id, firstLoad)
|
const result = {
|
user: wsCache.get('user.uid'),
|
filter,
|
linkageFilters: element.value.linkageFilters,
|
outerParamsFilters: element.value.outerParamsFilters,
|
webParamsFilters: element.value.webParamsFilters,
|
drill: state.drillClickDimensionList,
|
resultCount: resultCount.value,
|
resultMode: resultMode.value
|
}
|
// 定时报告相关勿动
|
if (route.path === '/preview' && route.query.taskId) {
|
const sceneId = view.value['sceneId']
|
const filterJson = window[`de-report-filter-${sceneId}`]
|
let filterObj = {}
|
if (filterJson) {
|
filterObj = JSON.parse(filterJson)
|
}
|
filterObj[view.value.id] = result
|
window[`de-report-filter-${sceneId}`] = JSON.stringify(filterObj)
|
}
|
return result
|
}
|
|
const onDrillFilters = param => {
|
state.drillFilters = param ? param : []
|
}
|
const openHandler = ref(null)
|
const initOpenHandler = newWindow => {
|
if (openHandler?.value) {
|
const pm = {
|
methodName: 'initOpenHandler',
|
args: newWindow
|
}
|
openHandler.value.invokeMethod(pm)
|
}
|
}
|
|
const divEmbedded = type => {
|
useEmitt().emitter.emit('changeCurrentComponent', type)
|
}
|
|
const windowsJump = (url, jumpType, size = 'middle') => {
|
try {
|
let newWindow
|
if ('newPop' === jumpType) {
|
dePreviewPopDialogRef.value.previewInit({ url, size })
|
} else if ('_self' === jumpType) {
|
newWindow = window.open(url, jumpType)
|
if (inMobile.value) {
|
window.location.reload()
|
}
|
} else {
|
newWindow = window.open(url, jumpType)
|
if (inMobile.value) {
|
window.location.reload()
|
}
|
}
|
initOpenHandler(newWindow)
|
} catch (e) {
|
console.warn(t('visualization.url_check_error') + ':' + url)
|
}
|
}
|
|
const jumpClick = param => {
|
let dimension, jumpInfo, sourceInfo
|
// 如果有名称name 获取和name匹配的dimension 否则倒序取最后一个能匹配的
|
if (param.name) {
|
const colList = [...param.dimensionList, ...param.quotaList]
|
colList.forEach(dimensionItem => {
|
if (
|
dimensionItem.id === param.name ||
|
dimensionItem.value === param.name ||
|
dimensionItem.name === param.name
|
) {
|
dimension = dimensionItem
|
sourceInfo = param.viewId + '#' + dimension.id
|
jumpInfo = nowPanelJumpInfo.value[sourceInfo]
|
}
|
})
|
} else {
|
for (let i = param.dimensionList.length - 1; i >= 0; i--) {
|
dimension = param.dimensionList[i]
|
sourceInfo = param.viewId + '#' + dimension.id
|
jumpInfo = nowPanelJumpInfo.value[sourceInfo]
|
if (jumpInfo) {
|
break
|
}
|
}
|
}
|
if (jumpInfo) {
|
// 维度日期类型转换
|
viewFieldTimeTrans(dvMainStore.getViewDataDetails(param.viewId), param)
|
param.sourceDvId = dvInfo.value.id
|
param.sourceViewId = param.viewId
|
param.sourceFieldId = dimension.id
|
let embeddedBaseUrl = ''
|
const divSelf = isDataEaseBi.value && jumpInfo.jumpType === '_self'
|
const iframeSelf = isIframe.value && jumpInfo.jumpType === '_self'
|
if (isDataEaseBi.value) {
|
embeddedBaseUrl = embeddedStore.baseUrl
|
}
|
const jumpInfoParam = `&jumpInfoParam=${encodeURIComponent(
|
Base64.encode(JSON.stringify(param))
|
)}`
|
|
// 内部仪表板跳转
|
if (jumpInfo.linkType === 'inner') {
|
if (jumpInfo.targetDvId) {
|
const editPreviewParams = ['canvas', 'edit-preview'].includes(showPosition.value)
|
? '&editPreview=true'
|
: ''
|
const filterOuterParams = {}
|
const curFilter = dvMainStore.getLastViewRequestInfo(param.viewId)
|
const targetViewInfoList = jumpInfo.targetViewInfoList
|
if (
|
curFilter &&
|
curFilter.filter &&
|
curFilter.filter.length > 0 &&
|
targetViewInfoList &&
|
targetViewInfoList.length > 0
|
) {
|
// do filter
|
curFilter.filter.forEach(filterItem => {
|
if (filterItem.filterFrom !== 'optionFilter') {
|
targetViewInfoList.forEach(targetViewInfo => {
|
if (targetViewInfo.sourceFieldActiveId === filterItem.filterId) {
|
const outerFilterItem = filterOuterParams[targetViewInfo.outerParamsName]
|
if (outerFilterItem) {
|
// 当前已经存在 根据arrayType 放置位置
|
if (filterItem['arrayType'] === 'END') {
|
outerFilterItem.value[outerFilterItem.value.length - 1] = filterItem.value[0]
|
} else {
|
outerFilterItem.value[0] = filterItem.value[0]
|
}
|
} else {
|
filterOuterParams[targetViewInfo.outerParamsName] = {
|
operator: filterItem.operator,
|
value: filterItem.value
|
}
|
}
|
}
|
})
|
}
|
})
|
}
|
let attachParamsInfo
|
if (Object.keys(filterOuterParams).length > 0) {
|
filterOuterParams['outerParamsVersion'] = 'v2'
|
attachParamsInfo =
|
'&attachParams=' + encodeURIComponent(Base64.encode(JSON.stringify(filterOuterParams)))
|
}
|
// 携带外部参数
|
if (publicLinkStatus.value) {
|
// 判断是否有公共链接ID
|
if (jumpInfo.publicJumpId) {
|
let url = `${embeddedBaseUrl}#/de-link/${jumpInfo.publicJumpId}?fromLink=true&dvType=${jumpInfo.targetDvType}`
|
if (attachParamsInfo) {
|
url = url + attachParamsInfo + jumpInfoParam + editPreviewParams
|
} else {
|
url = url + '&ignoreParams=true' + jumpInfoParam + editPreviewParams
|
}
|
const currentUrl = window.location.href
|
localStorage.setItem('beforeJumpUrl', currentUrl)
|
windowsJump(url, jumpInfo.jumpType, jumpInfo.windowSize)
|
} else {
|
ElMessage.warning(t('visualization.public_link_tips'))
|
}
|
} else {
|
let url = `${embeddedBaseUrl}#/preview?dvId=${jumpInfo.targetDvId}&fromLink=true&dvType=${jumpInfo.targetDvType}`
|
if (attachParamsInfo) {
|
url = url + attachParamsInfo + jumpInfoParam + editPreviewParams
|
} else {
|
url = url + '&ignoreParams=true' + jumpInfoParam + editPreviewParams
|
}
|
const currentUrl = window.location.href
|
localStorage.setItem('beforeJumpUrl', currentUrl)
|
if (divSelf || iframeSelf) {
|
embeddedStore.setDvId(jumpInfo.targetDvId)
|
embeddedStore.setJumpInfoParam(encodeURIComponent(Base64.encode(JSON.stringify(param))))
|
divEmbedded('Preview')
|
return
|
}
|
windowsJump(url, jumpInfo.jumpType, jumpInfo.windowSize)
|
}
|
} else {
|
ElMessage.warning('未指定跳转仪表板')
|
}
|
} else {
|
const colList = [...param.dimensionList, ...param.quotaList]
|
let url = setIdValueTrans('id', 'value', jumpInfo.content, colList)
|
url = checkAddHttp(url)
|
|
if (isIframe.value || isDataEaseBi.value) {
|
embeddedStore.clearState()
|
}
|
if (divSelf) {
|
embeddedStore.setOuterUrl(url)
|
divEmbedded('Iframe')
|
return
|
}
|
|
windowsJump(url, jumpInfo.jumpType, jumpInfo.windowSize)
|
}
|
} else {
|
}
|
}
|
|
const queryDataFromSelect = (firstLoad = false) => {
|
cancelRequestBatch(`chartData/getData/${view.value.id}`)
|
loading.value = false
|
queryData(firstLoad)
|
}
|
|
const queryData = debounce((firstLoad = false) => {
|
if (loading.value) {
|
return
|
}
|
const searched = dvMainStore.firstLoadMap.includes(element.value.id)
|
const queryFilter = filter(searched ? false : firstLoad)
|
let params = cloneDeep(view.value)
|
params['chartExtRequest'] = queryFilter
|
chartExtRequest.value = queryFilter
|
calcData(params)
|
}, 300)
|
|
const calcData = params => {
|
dvMainStore.setLastViewRequestInfo(params.id, params.chartExtRequest)
|
if (chartComponent?.value) {
|
loading.value = true
|
if (view.value.isPlugin) {
|
chartComponent?.value?.invokeMethod({
|
methodName: 'calcData',
|
args: [
|
params,
|
() => {
|
loading.value = false
|
}
|
]
|
})
|
} else {
|
chartComponent?.value?.calcData?.(params, () => {
|
loading.value = false
|
})
|
}
|
}
|
}
|
|
const showChartView = (...libs: ChartLibraryType[]) => {
|
if (view.value?.render && view.value?.type) {
|
const chartView = chartViewManager.getChartView(view.value.render, view.value.type)
|
return chartView && libs?.includes(chartView.library)
|
} else {
|
return false
|
}
|
}
|
|
onBeforeMount(() => {
|
if (!showPosition.value.includes('viewDialog')) {
|
nextTick(() => {
|
useEmitt({
|
name: `query-data-${view.value.id}`,
|
callback: queryDataFromSelect
|
})
|
})
|
}
|
})
|
// 部分场景不需要更新图表,例如放大页面
|
const listenerEnable = computed(() => {
|
return !showPosition.value.includes('viewDialog')
|
})
|
// 存储所有数据集字段,用于判断图表拖入的字段是否存在
|
const viewAllDatasetFields = new Map()
|
const showEmpty = ref(false)
|
const checkFieldIsAllowEmpty = (allField?) => {
|
showEmpty.value = false
|
if (view.value?.render && view.value?.type) {
|
const chartView = chartViewManager.getChartView(view.value.render, view.value.type)
|
// 插件
|
if (!chartView) {
|
return
|
}
|
const map = parseJson(view.value.customAttr).map
|
if (['bubble-map', 'map'].includes(view.value?.type) && !map?.id) {
|
showEmpty.value = true
|
return
|
}
|
const axisConfigMap = new Map(Object.entries(chartView.axisConfig))
|
// 验证拖入的字段是否包含在当前数据集字段中,如果有一个不在数据集字段中,则显示空图表
|
let includeDatasetField = false
|
if (allField && allField.length > 0) {
|
viewAllDatasetFields.set(view.value.id, allField)
|
outerLoop: for (const [key, value] of axisConfigMap) {
|
// 只判断必须的
|
if (value['allowEmpty']) continue
|
if (!view.value?.[key]?.length) continue
|
for (const item of view.value[key]) {
|
if (!allField.find(field => field.id === item.id)) {
|
includeDatasetField = true
|
break outerLoop
|
}
|
}
|
}
|
}
|
if (includeDatasetField) {
|
showEmpty.value = true
|
return
|
}
|
for (const [key, value] of axisConfigMap) {
|
// 跳过允许为空的配置项
|
if (value['allowEmpty']) continue
|
|
// 如果有数据集字段并且字段值存在且不为空
|
if (viewAllDatasetFields.get(view.value?.id)) {
|
if (view.value?.[key]?.length) {
|
// 检查图表字段是否有不在数据集中
|
for (const item of view.value[key]) {
|
if (!viewAllDatasetFields.get(view.value?.id).find(field => field.id === item.id)) {
|
includeDatasetField = true
|
break
|
}
|
}
|
}
|
// 如果有不在数据集中
|
if (includeDatasetField) {
|
showEmpty.value = true
|
break
|
}
|
}
|
|
// 如果没有限制长度,且值为空,标记为空并跳出
|
if (!value['limit'] && view.value?.[key]?.length === 0) {
|
showEmpty.value = true
|
break
|
}
|
|
// 如果有限制长度,且字段长度不足,标记为空并跳出
|
if (
|
value['limit'] &&
|
(!view.value?.[key] || view.value?.[key]?.length < parseInt(value['limit']))
|
) {
|
showEmpty.value = true
|
break
|
}
|
|
// 如果是table-info类型且字段为空,标记为空并跳出
|
if (view.value?.type === 'table-info' && view.value?.[key]?.length === 0) {
|
showEmpty.value = true
|
break
|
}
|
}
|
}
|
}
|
const changeChartType = () => {
|
checkFieldIsAllowEmpty()
|
}
|
const changeDataset = () => {
|
checkFieldIsAllowEmpty()
|
}
|
|
const loadPlugin = ref(false)
|
|
// 渲染图表回调
|
const renderChartCallback = val => {
|
if (!state.initReady) {
|
return
|
}
|
initTitle()
|
const viewInfo = val ? val : view.value
|
nextTick(() => {
|
if (view.value?.plugin?.isPlugin) {
|
chartComponent?.value?.invokeMethod({
|
methodName: 'renderChart',
|
args: [viewInfo]
|
})
|
return
|
}
|
chartComponent?.value?.renderChart?.(viewInfo)
|
})
|
}
|
onMounted(() => {
|
if (!view.value.isPlugin) {
|
state.drillClickDimensionList = view.value?.chartExtRequest?.drill ?? []
|
queryData(!showPosition.value.includes('viewDialog'))
|
} else {
|
const searched = dvMainStore.firstLoadMap.includes(element.value.id)
|
const queryFilter = filter(!searched)
|
view.value['chartExtRequest'] = queryFilter
|
chartExtRequest.value = queryFilter
|
loadPlugin.value = true
|
}
|
if (!listenerEnable.value) {
|
return
|
}
|
useEmitt({
|
name: 'checkShowEmpty',
|
callback: param => {
|
if (param.view?.id === view.value.id) {
|
checkFieldIsAllowEmpty(param.allFields)
|
}
|
}
|
})
|
useEmitt({ name: 'chart-type-change', callback: changeChartType })
|
useEmitt({ name: 'dataset-change', callback: changeDataset })
|
useEmitt({
|
name: 'clearPanelLinkage',
|
callback: function (param) {
|
if (param.viewId === 'all' || param.viewId === element.value.id) {
|
chartComponent?.value?.clearLinkage?.()
|
}
|
}
|
})
|
useEmitt({
|
name: 'snapshotChangeToView',
|
callback: function (cacheViewInfo) {
|
initTitle()
|
nextTick(() => {
|
if (
|
cacheViewInfo.snapshotCacheViewCalc.includes(view.value.id) ||
|
cacheViewInfo.snapshotCacheViewCalc.includes('all')
|
) {
|
view.value.chartExtRequest = filter(false)
|
calcData(view.value)
|
} else if (
|
cacheViewInfo.snapshotCacheViewRender.includes(view.value.id) ||
|
cacheViewInfo.snapshotCacheViewRender.includes('all')
|
) {
|
chartComponent?.value?.renderChart?.(view.value)
|
}
|
})
|
}
|
})
|
|
useEmitt({
|
name: 'calcData-' + view.value.id,
|
callback: function (val) {
|
if (!state.initReady) {
|
return
|
}
|
initTitle()
|
nextTick(() => {
|
view.value.chartExtRequest = filter(false)
|
const targetVal = val || view.value
|
calcData(targetVal)
|
})
|
}
|
})
|
|
useEmitt({
|
name: 'calcData-all',
|
callback: function () {
|
if (!state.initReady) {
|
return
|
}
|
initTitle()
|
nextTick(() => {
|
view.value.chartExtRequest = filter(false)
|
calcData(view.value)
|
})
|
}
|
})
|
useEmitt({
|
name: 'renderChart-' + view.value.id,
|
callback: function (val) {
|
renderChartCallback(val)
|
}
|
})
|
useEmitt({
|
name: 'resetDrill-' + view.value.id,
|
callback: function (val) {
|
nextTick(() => {
|
drillJump(val)
|
})
|
}
|
})
|
useEmitt({
|
name: 'tabCanvasChange-' + element.value.canvasId,
|
callback: function () {
|
if (!state.initReady && !view.value.type.includes('table')) {
|
return
|
}
|
setTimeout(function () {
|
chartComponent?.value?.renderChart?.(view.value)
|
}, 200)
|
}
|
})
|
useEmitt({
|
name: 'updateTitle-' + view.value.id,
|
callback: () => {
|
initTitle()
|
}
|
})
|
useEmitt({
|
name: 'chart-type-change-' + view.value.id,
|
callback: () => {
|
const chart = cloneDeep(view.value)
|
chart.container =
|
'container-' + showPosition.value + '-' + view.value.id + '-' + suffixId.value
|
clearExtremum(chart)
|
}
|
})
|
if (showPosition.value === 'viewDialog') {
|
useEmitt({
|
name: 'renderChart-viewDialog-' + view.value.id,
|
callback: function (val) {
|
renderChartCallback(val)
|
}
|
})
|
}
|
|
const { refreshViewEnable, refreshUnit, refreshTime } = view.value
|
buildInnerRefreshTimer(refreshViewEnable, refreshUnit, refreshTime)
|
|
initTitle()
|
})
|
|
// 1.开启仪表板刷新 2.首次加载(searchCount =0 )3.正在请求数据 则显示加载状态
|
const loadingFlag = computed(() => {
|
return (
|
(canvasStyleData.value.refreshViewLoading ||
|
(searchCount.value === 0 && innerSearchCount === 0)) &&
|
loading.value
|
)
|
})
|
|
const chartAreaShow = computed(() => {
|
if (view.value.tableId) {
|
if (element.value['state'] === undefined || element.value['state'] === 'ready') {
|
return true
|
}
|
}
|
if (['rich-text', 'picture-group'].includes(view.value.type)) {
|
return true
|
}
|
if (view.value?.isPlugin) {
|
return true
|
}
|
if (view.value['dataFrom'] === 'template') {
|
return true
|
}
|
if (view.value.customAttr?.map?.id) {
|
const MAP_CHARTS = ['map', 'bubble-map', 'flow-map']
|
if (MAP_CHARTS.includes(view.value.type)) {
|
return true
|
}
|
}
|
return false
|
})
|
|
const titleInputRef = ref()
|
const titleEditStatus = ref(false)
|
function changeEditTitle() {
|
if (!props.active || mobileInPc.value) {
|
return
|
}
|
if (!titleEditStatus.value) {
|
titleEditStatus.value = true
|
nextTick(() => {
|
titleInputRef.value?.focus()
|
element.value['editing'] = true
|
})
|
}
|
}
|
|
function onLeaveTitleInput() {
|
element.value['editing'] = false
|
titleEditStatus.value = false
|
}
|
|
//v-click-outside 指令
|
const vClickOutside = {
|
beforeMount(el, binding) {
|
// 在元素上绑定一个事件监听器
|
el.clickOutsideEvent = function (event) {
|
// 判断点击事件是否发生在元素外部
|
if (!(el === event.target || el.contains(event.target))) {
|
// 如果是外部点击,则执行绑定的函数
|
binding.value(event)
|
}
|
}
|
// 在全局添加点击事件监听器
|
document.addEventListener('click', el.clickOutsideEvent)
|
},
|
unmounted(el) {
|
// 在组件销毁前,移除事件监听器以避免内存泄漏
|
document.removeEventListener('click', el.clickOutsideEvent)
|
}
|
}
|
|
function onTitleChange() {
|
element.value.name = view.value.title
|
element.value.label = view.value.title
|
snapshotStore.recordSnapshotCache('onTitleChange')
|
}
|
|
const toolTip = computed(() => {
|
return props.themes === 'dark' ? 'light' : 'dark'
|
})
|
|
const marginBottom = computed<string | 0>(() => {
|
if (!titleShow.value) {
|
return 0
|
}
|
if (titleShow.value || trackMenu.value.length > 0 || state.title_remark.show) {
|
return 12 * scale.value + 'px'
|
}
|
return 0
|
})
|
|
const iconSize = computed<string>(() => {
|
return 16 * scale.value + 'px'
|
})
|
/**
|
* 修改透明度
|
* 边框透明度为0时会是存色,顾配置低透明度
|
* @param {boolean} isBorder 是否为边框
|
*/
|
const modifyAlpha = isBorder => {
|
const { backgroundColor, backgroundType, backgroundImageEnable, backgroundColorSelect } =
|
element.value.commonBackground
|
// 透明
|
const transparent = 'rgba(0,0,0,0.01)'
|
// 背景图时,设置透明度为0.01
|
if (backgroundType === 'outerImage' && backgroundImageEnable) return transparent
|
// hex转rgba
|
if (backgroundColor.includes('#'))
|
return isBorder || !backgroundColorSelect ? transparent : backgroundColor
|
const match = backgroundColor.match(/rgba\((\d+), (\d+), (\d+), (\d+|0.\d+)\)/)
|
if (!match) return backgroundColor
|
const [r, g, b, a] = match.slice(1).map(Number)
|
// 边框或者不设置背景色时,设置透明度为0.01,否则原透明度
|
return `rgba(${r}, ${g}, ${b}, ${!backgroundColorSelect || isBorder ? 0.01 : a})`
|
}
|
|
const titleIconStyle = computed(() => {
|
const bgColor = modifyAlpha(false)
|
const borderColor = modifyAlpha(true)
|
// 不显示标题时,图标的样式
|
const style = {
|
position: 'absolute',
|
border: `1px solid ${borderColor}`,
|
'background-color': bgColor,
|
'border-radius': '2px',
|
padding: '0 2px 0 2px',
|
'z-index': 1,
|
top: '2px',
|
left: '2px',
|
...(trackMenu.value.length ? {} : { display: 'none' })
|
}
|
return {
|
color: canvasStyleData.value.component.seniorStyleSetting.linkageIconColor,
|
...(titleShow.value ? {} : style)
|
}
|
})
|
const chartHover = ref(false)
|
const showActionIcons = computed(() => {
|
if (!chartHover.value) {
|
return false
|
}
|
return trackMenu.value.length > 0 || state.title_remark.show
|
})
|
const chartConfigs = ref(CHART_TYPE_CONFIGS)
|
const pluginLoaded = computed(() => {
|
let result = false
|
chartConfigs.value.forEach(cat => {
|
result = cat.details.find(chart => view.value?.type === chart.value) !== undefined
|
})
|
return result
|
})
|
// TODO 统一加载
|
const loadPluginCategory = data => {
|
data.forEach(item => {
|
const { category, title, render, chartValue, chartTitle, icon, staticMap } = item
|
const node = {
|
render,
|
category,
|
icon,
|
value: chartValue,
|
title: chartTitle,
|
isPlugin: true,
|
staticMap
|
}
|
if (view.value?.type === node.value) {
|
view.value.plugin = {
|
isPlugin: true,
|
staticMap
|
}
|
}
|
const stack = [...chartConfigs.value]
|
let findParent = false
|
while (stack?.length) {
|
const parent = stack.pop()
|
if (parent.category === category) {
|
const chart = parent.details.find(chart => chart.value === node.value)
|
if (!chart) {
|
parent.details.push(node)
|
}
|
findParent = true
|
}
|
}
|
if (!findParent) {
|
stack.push({
|
category,
|
title,
|
display: 'show',
|
details: [node]
|
})
|
}
|
})
|
}
|
|
const allEmptyCheck = computed(() => {
|
return ['rich-text', 'picture-group'].includes(element.value.innerType)
|
})
|
/**
|
* 标题提示的最大宽度
|
*/
|
const titleTooltipWidth = computed(() => {
|
if (inMobile.value) {
|
return `${screen.width - 10}px`
|
}
|
if (mobileInPc.value) {
|
return '270px'
|
}
|
return '500px'
|
})
|
const clearG2Tooltip = () => {
|
const g2TooltipWrapper = document.getElementById('g2-tooltip-wrapper')
|
if (g2TooltipWrapper) {
|
for (const ele of g2TooltipWrapper.children) {
|
ele.style.display = 'none'
|
}
|
}
|
}
|
</script>
|
|
<template>
|
<div
|
class="chart-area report-load"
|
:class="{ 'report-load-finish': !loadingFlag }"
|
v-loading="loadingFlag"
|
element-loading-background="rgba(0,0,0,0)"
|
@mouseover="chartHover = true"
|
@mouseleave="chartHover = false"
|
>
|
<div
|
class="title-container"
|
:style="{ 'justify-content': titleAlign, 'margin-bottom': marginBottom }"
|
>
|
<template v-if="!titleEditStatus">
|
<p class="ellipsis" v-if="titleShow" :style="state.title_class" @dblclick="changeEditTitle">
|
{{ view.title }}
|
</p>
|
</template>
|
<template v-else>
|
<el-input
|
style="flex: 1"
|
:effect="canvasStyleData.dashboard.themeColor"
|
ref="titleInputRef"
|
v-model="view.title"
|
@keydown.stop
|
@keydown.enter="onLeaveTitleInput"
|
v-click-outside="onLeaveTitleInput"
|
@change="onTitleChange"
|
/>
|
</template>
|
<transition name="fade">
|
<div
|
class="icons-container"
|
:class="{ 'is-editing': titleEditStatus }"
|
:style="titleIconStyle"
|
v-show="showActionIcons"
|
>
|
<el-tooltip :effect="toolTip" placement="top" v-if="state.title_remark.show">
|
<template #content>
|
<div
|
:style="{
|
maxWidth: titleTooltipWidth,
|
wordBreak: 'break-all',
|
wordWrap: 'break-word',
|
whiteSpace: 'pre-wrap'
|
}"
|
v-html="state.title_remark.remark"
|
></div>
|
</template>
|
<el-icon :size="iconSize" class="inner-icon">
|
<Icon name="icon_info_outlined"><icon_info_outlined class="svg-icon" /></Icon>
|
</el-icon>
|
</el-tooltip>
|
<el-tooltip :effect="toolTip" placement="top" content="已设置联动" v-if="hasLinkIcon">
|
<el-icon :size="iconSize" class="inner-icon">
|
<Icon name="icon_link-record_outlined"
|
><icon_linkRecord_outlined class="svg-icon"
|
/></Icon>
|
</el-icon>
|
</el-tooltip>
|
<el-tooltip
|
:effect="toolTip"
|
placement="top"
|
:content="t('visualization.jump_set_tips')"
|
v-if="hasJumpIcon"
|
>
|
<el-icon :size="iconSize" class="inner-icon">
|
<Icon name="icon_viewinchat_outlined"
|
><icon_viewinchat_outlined class="svg-icon"
|
/></Icon>
|
</el-icon>
|
</el-tooltip>
|
<el-tooltip
|
:effect="toolTip"
|
placement="top"
|
:content="t('visualization.drill_set_tips')"
|
v-if="hasDrillIcon"
|
>
|
<el-icon :size="iconSize" class="inner-icon">
|
<Icon name="icon_drilling_outlined"><icon_drilling_outlined class="svg-icon" /></Icon>
|
</el-icon>
|
</el-tooltip>
|
</div>
|
</transition>
|
</div>
|
<!--这里去渲染不同图库的图表-->
|
<div v-if="allEmptyCheck || (chartAreaShow && !showEmpty)" style="flex: 1; overflow: hidden">
|
<plugin-component
|
v-if="view.plugin?.isPlugin && loadPlugin"
|
:jsname="view.plugin.staticMap['index']"
|
:scale="scale"
|
:dynamic-area-id="dynamicAreaId"
|
:view="view"
|
:show-position="showPosition"
|
:element="element"
|
:request="request"
|
:emitter="emitter"
|
:store="store"
|
:suffixId="suffixId"
|
:active="active"
|
:disabled="!['canvas', 'canvasDataV'].includes(showPosition) || disabled"
|
:edit-mode="editMode"
|
ref="chartComponent"
|
@onChartClick="chartClick"
|
@onPointClick="onPointClick"
|
@onDrillFilters="onDrillFilters"
|
@onJumpClick="jumpClick"
|
@resetLoading="() => (loading = false)"
|
/>
|
<de-picture-group
|
v-else-if="showChartView(ChartLibraryType.PICTURE_GROUP)"
|
:themes="canvasStyleData.dashboard.themeColor"
|
ref="chartComponent"
|
:element="element"
|
:active="active"
|
:view="view"
|
:show-position="showPosition"
|
:suffixId="suffixId"
|
>
|
</de-picture-group>
|
<de-rich-text-view
|
v-else-if="showChartView(ChartLibraryType.RICH_TEXT)"
|
:scale="scale"
|
:themes="canvasStyleData.dashboard.themeColor"
|
ref="chartComponent"
|
:element="element"
|
:disabled="!['canvas', 'canvasDataV'].includes(showPosition) || disabled"
|
:active="active"
|
:show-position="showPosition"
|
:edit-mode="editMode"
|
:suffixId="suffixId"
|
/>
|
<de-indicator
|
:scale="scale"
|
v-else-if="showChartView(ChartLibraryType.INDICATOR)"
|
:themes="canvasStyleData.dashboard.themeColor"
|
ref="chartComponent"
|
:view="view"
|
:element="element"
|
:show-position="showPosition"
|
:suffixId="suffixId"
|
:font-family="fontFamily"
|
:common-params="commonParams"
|
@touchstart="clearG2Tooltip"
|
@onChartClick="chartClick"
|
@onPointClick="onPointClick"
|
@onDrillFilters="onDrillFilters"
|
@onJumpClick="jumpClick"
|
@onComponentEvent="() => emit('onComponentEvent')"
|
/>
|
<chart-component-g2-plot
|
:scale="scale"
|
:dynamic-area-id="dynamicAreaId"
|
:view="view"
|
:show-position="showPosition"
|
:element="element"
|
:suffixId="suffixId"
|
:font-family="fontFamily"
|
:active="active"
|
v-else-if="
|
showChartView(ChartLibraryType.G2_PLOT, ChartLibraryType.L7_PLOT, ChartLibraryType.L7)
|
"
|
ref="chartComponent"
|
@onChartClick="chartClick"
|
@onPointClick="onPointClick"
|
@onDrillFilters="onDrillFilters"
|
@onJumpClick="jumpClick"
|
@resetLoading="() => (loading = false)"
|
/>
|
<chart-component-s2
|
:view="view"
|
:scale="scale"
|
:show-position="showPosition"
|
:element="element"
|
:drill-length="drillClickLength"
|
:font-family="fontFamily"
|
v-else-if="showChartView(ChartLibraryType.S2)"
|
ref="chartComponent"
|
@onPointClick="onPointClick"
|
@onChartClick="chartClick"
|
@onDrillFilters="onDrillFilters"
|
@onJumpClick="jumpClick"
|
:suffixId="suffixId"
|
/>
|
</div>
|
<chart-empty-info
|
v-if="(!chartAreaShow || showEmpty) && !allEmptyCheck"
|
:themes="canvasStyleData.dashboard.themeColor"
|
:view-icon="view.type"
|
@touchstart="clearG2Tooltip"
|
></chart-empty-info>
|
<drill-path
|
:disabled="optType === 'enlarge'"
|
:drill-filters="state.drillFilters"
|
@onDrillJump="drillJump"
|
/>
|
<XpackComponent
|
ref="openHandler"
|
jsname="L2NvbXBvbmVudC9lbWJlZGRlZC1pZnJhbWUvT3BlbkhhbmRsZXI="
|
/>
|
<XpackComponent
|
v-if="!pluginLoaded && view.isPlugin"
|
jsname="L2NvbXBvbmVudC9wbHVnaW5zLWhhbmRsZXIvVmlld0NhdGVnb3J5SGFuZGxlcg=="
|
@load-plugin-category="loadPluginCategory"
|
/>
|
<DePreviewPopDialog ref="dePreviewPopDialogRef"></DePreviewPopDialog>
|
</div>
|
</template>
|
|
<style lang="less" scoped>
|
.chart-area {
|
width: 100%;
|
height: 100%;
|
display: flex;
|
flex-direction: column;
|
overflow: hidden;
|
}
|
.title-container {
|
position: relative;
|
margin: 0;
|
width: 100%;
|
|
display: inline-flex;
|
flex-wrap: nowrap;
|
justify-content: center;
|
|
gap: 8px;
|
|
.icons-container {
|
display: inline-flex;
|
flex-direction: row;
|
align-items: center;
|
flex-wrap: nowrap;
|
gap: 8px;
|
|
color: #646a73;
|
|
&.icons-container__dark {
|
color: #a6a6a6;
|
}
|
|
&.is-editing {
|
gap: 6px;
|
}
|
|
.inner-icon {
|
cursor: pointer;
|
}
|
}
|
}
|
.fade-enter-active,
|
.fade-leave-active {
|
transition: opacity 0.5s ease;
|
}
|
|
.fade-enter-from,
|
.fade-leave-to {
|
opacity: 0;
|
}
|
|
.ellipsis {
|
white-space: nowrap !important;
|
overflow: hidden;
|
text-overflow: ellipsis;
|
width: 100%;
|
}
|
</style>
|