<template>
<el-dialog
v-loading="state.loading"
ref="enlargeDialog"
:append-to-body="true"
:title="t('visualization.jump_set')"
v-model="dialogShow"
width="70vw"
top="10vh"
trigger="click"
>
<div @keydown.stop @keyup.stop v-if="state.initState" style="height: 550px">
<el-row style="flex-direction: row">
<div class="top-area">
<span class="top-area-text">{{ t('visualization.selected_view') }}:</span>
<span class="top-area-value">
<Icon class-name="view-type-icon"
><component
class="svg-icon view-type-icon"
:is="iconChartMap[state.curJumpViewInfo.type]"
></component
></Icon>
{{ state.curJumpViewInfo.title }}</span
>
<span class="top-area-text margin-left">{{ t('visualization.used_dataset') }}:</span>
<span class="top-area-value">
<Icon name="dataset-outline"
><datasetOutline style="vertical-align: -0.2em" class="svg-icon view-type-icon"
/></Icon>
{{ state.curDatasetInfo.name }}</span
>
</div>
</el-row>
<el-row v-loading="state.loading">
<el-row class="preview">
<el-col :span="8" style="height: 100%; overflow-y: auto">
<el-row class="tree-head">
<span class="head-text">{{ t('visualization.to_select_view') }}</span>
<span class="head-filter">
{{ t('visualization.show_selected_only') }}
<el-switch size="small" v-model="state.showSelected" />
</span>
</el-row>
<el-tree
menu
ref="linkJumpInfoTree"
:filter-node-method="filterNodeMethod"
:data="state.linkJumpInfoXArray"
node-key="sourceFieldId"
highlight-current
:props="state.treeProp"
@node-click="nodeClick"
>
<template #default="{ data }">
<span class="custom-tree-node">
<span>
<div @click.stop>
<span class="auth-span">
<el-checkbox
v-model="data.checked"
@change="sourceFieldCheckedChange(data)"
/>
</span>
</div>
</span>
<span :title="data.sourceFieldName">
<span class="tree-select-field">
<el-icon style="margin-right: 4px">
<Icon
><component
class="svg-icon"
:class="`field-icon-${fieldType[data.sourceDeType]}`"
:is="iconFieldMap[fieldType[data.sourceDeType]]"
></component
></Icon>
</el-icon>
{{ data.sourceFieldName }}
</span>
</span>
</span>
</template>
</el-tree>
</el-col>
<el-col :span="16" class="preview-show">
<el-container class="settings-container">
<el-header class="settings-header">
<el-form-item class="radio-group-box">
<template #label>
<span class="title">{{ t('visualization.link_type') }}</span>
</template>
<el-radio-group
class="larger-radio"
v-if="state.linkJumpInfo"
v-model="state.linkJumpInfo.linkType"
>
<el-radio value="outer">{{ t('visualization.link_outer') }}</el-radio>
<el-radio value="inner">{{ t('visualization.dashboard_dataV') }}</el-radio>
</el-radio-group>
<el-radio-group class="larger-radio" v-if="!state.linkJumpInfo" disabled>
<el-radio value="outer">{{ t('visualization.link_outer') }}</el-radio>
<el-radio value="inner">{{ resourceType }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item class="radio-group-box">
<template #label>
<span class="title">{{ t('visualization.open_model') }}</span>
</template>
<el-radio-group
class="larger-radio"
v-if="state.linkJumpInfo"
v-model="state.linkJumpInfo.jumpType"
>
<el-radio value="_self">{{ t('visualization.now_window') }}</el-radio>
<el-radio value="_blank">{{ t('visualization.new_window') }}</el-radio>
<el-radio value="newPop">{{ t('visualization.pop_window') }}</el-radio>
</el-radio-group>
<el-radio-group class="larger-radio" v-if="!state.linkJumpInfo" disabled>
<el-radio value="_self">{{ t('visualization.now_window') }}</el-radio>
<el-radio value="_blank">{{ t('visualization.new_window') }}</el-radio>
<el-radio value="newPop">{{ t('visualization.pop_window') }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item
class="radio-group-box"
v-if="state.linkJumpInfo?.jumpType === 'newPop'"
>
<template #label>
<span class="title">{{ t('visualization.window_size') }}</span>
</template>
<el-radio-group class="larger-radio" v-model="state.linkJumpInfo.windowSize">
<el-radio value="large">{{ t('visualization.window_size_large') }}</el-radio>
<el-radio value="middle">{{ t('visualization.window_size_middle') }}</el-radio>
<el-radio value="small">{{ t('visualization.window_size_small') }}</el-radio>
</el-radio-group>
</el-form-item>
</el-header>
<el-main class="settings-main">
<template v-if="state.linkJumpInfo">
<template v-if="state.linkJumpInfo.linkType === 'inner'">
<el-form label-position="top" class="main-form">
<div class="m-row">
<div style="flex: 1">
<el-form-item>
<template #label>
{{
dvInfo.type === 'dashboard'
? t('visualization.cur_dashboard')
: t('visualization.cur_screen')
}}
</template>
<el-select style="width: 100%" v-model="dvInfo.name" disabled>
<el-option
:key="dvInfo.name"
:label="dvInfo.name"
:value="dvInfo.name"
>
</el-option>
</el-select>
</el-form-item>
</div>
<div class="icon-center">
<Icon name="dv-link-target"
><dvLinkTarget style="width: 20px; height: 20px" class="svg-icon"
/></Icon>
</div>
<div style="flex: 1">
<el-form-item>
<template #label>
{{ targetSource }}
</template>
<el-tree-select
v-model="state.linkJumpInfo.targetDvId"
:data="state.panelList"
:props="state.dvSelectProps"
:render-after-expand="false"
filterable
@node-click="dvNodeClick"
class="dv-selector"
>
<template #default="{ node, data }">
<div class="label-content-details">
<el-icon
size="18px"
style="display: inline-block"
v-if="data.leaf"
>
<Icon name="dv-dashboard-spine"
><dvDashboardSpine
v-if="data.type === 'dashboard'"
class="svg-icon"
/>
<dvScreenSpine v-else class="svg-icon"> </dvScreenSpine>
</Icon>
</el-icon>
<el-icon size="18px" style="display: inline-block" v-else>
<Icon name="dv-folder"><dvFolder class="svg-icon" /></Icon>
</el-icon>
<span
style="margin-left: 8px; font-size: 14px"
:title="node.label"
>{{ node.label }}</span
>
</div>
</template>
</el-tree-select>
</el-form-item>
</div>
</div>
<template v-if="state.linkJumpInfo.targetDvId">
<div class="jump-com-list">
<el-tabs size="small" v-model="state.activeCollapse">
<el-tab-pane
v-if="!isIndicator"
:label="t('visualization.linkage_view')"
name="view"
>
</el-tab-pane>
<el-tab-pane
:label="t('visualization.with_filter_params')"
name="filter"
>
</el-tab-pane>
</el-tabs>
</div>
<template v-if="state.activeCollapse === 'view'">
<el-row style="margin-bottom: 8px" :gutter="8">
<el-col :span="7"> {{ t('visualization.source_field') }} </el-col>
<el-col :span="2"></el-col>
<el-col :span="7" style="margin-left: -2.9%">
{{ t('visualization.link_view_field') }}
</el-col>
<el-col :span="8"></el-col>
</el-row>
<div
class="main-scrollbar-container"
:class="{
'main-scrollbar-container-min':
state.linkJumpInfo?.jumpType === 'newPop'
}"
>
<el-scrollbar
height="fit-content"
:max-height="
state.linkJumpInfo?.jumpType === 'newPop' ? '138px' : '178px'
"
>
<div
style="display: flex; margin-bottom: 6px"
v-for="(
targetViewInfo, index
) in state.linkJumpInfo.targetViewInfoList.filter(
item => item.targetType === 'view'
)"
:key="index"
>
<div style="flex: 1">
<el-select
v-model="targetViewInfo.sourceFieldActiveId"
:placeholder="t('chart.pls_select_field')"
style="width: 100%"
>
<el-option
v-for="curViewField in state.linkJumpCurViewFieldArray"
:key="curViewField.id"
:label="curViewField.name"
:value="curViewField.id"
>
<span class="custom-option">
<Icon
><component
class="svg-icon"
style="width: 14px; height: 14px"
:class="`field-icon-${fieldType[curViewField.deType]}`"
:is="iconFieldMap[fieldType[curViewField.deType]]"
></component
></Icon>
<span
style="float: left; margin-left: 4px; font-size: 14px"
>{{ curViewField.name }}</span
>
</span>
</el-option>
</el-select>
</div>
<div class="icon-center">
<Icon name="dv-link-target"
><dvLinkTarget
style="width: 20px; height: 20px"
class="svg-icon"
/></Icon>
</div>
<div style="flex: 1">
<el-select
v-model="targetViewInfo.targetViewId"
:disabled="!targetViewInfo.sourceFieldActiveId"
:placeholder="t('visualization.select_view')"
style="width: 100%"
@change="viewInfoOnChange(targetViewInfo)"
>
<el-option
v-for="item in state.currentLinkPanelViewArray.filter(
item => item.type !== 'outerParams'
)"
:key="item.id"
:label="item.title"
:value="item.id"
>
<span class="custom-option">
<Icon
><component
class="svg-icon view-type-icon"
style="width: 14px; height: 14px"
:is="iconChartMap[item.type]"
></component
></Icon>
<span
style="float: left; margin-left: 4px; font-size: 14px"
>{{ item.title }}</span
>
</span>
</el-option>
</el-select>
</div>
<div style="flex: 1; margin: 0 8px">
<el-select
v-model="targetViewInfo.targetFieldId"
:placeholder="t('chart.pls_select_field')"
:disabled="fieldIdDisabledCheck(targetViewInfo)"
style="width: 100%"
>
<el-option
v-for="viewField in state.viewIdFieldArrayMap[
targetViewInfo.targetViewId
]"
:key="viewField.id"
:label="viewField.name"
:value="viewField.id"
>
<span class="custom-option">
<Icon
><component
class="svg-icon"
style="width: 14px; height: 14px"
:class="`field-icon-${fieldType[viewField.deType]}`"
:is="iconFieldMap[fieldType[viewField.deType]]"
></component
></Icon>
<span
style="float: left; margin-left: 4px; font-size: 14px"
>{{ viewField.name }}</span
>
</span>
</el-option>
</el-select>
</div>
<el-button
class="m-del-icon-btn"
text
@click="deleteLinkJumpFieldById(targetViewInfo.targetId)"
>
<el-icon size="20px">
<Icon name="icon_delete-trash_outlined"
><icon_deleteTrash_outlined class="svg-icon"
/></Icon>
</el-icon>
</el-button>
</div>
</el-scrollbar>
<el-button
style="margin-top: 8px"
:disabled="!state.linkJumpInfo.targetDvId"
type="primary"
icon="Plus"
text
@click="addLinkJumpField('view')"
>
{{ t('visualization.add_jump_field') }}
</el-button>
</div>
</template>
<template v-if="state.activeCollapse === 'filter'">
<template v-if="state.currentOutParams.length === 0">
<span
>{{ t('visualization.link_target_tips1')
}}<a
class="target_jump"
@click="resourceEdit(state.linkJumpInfo.targetDvId)"
>{{ t('visualization.link_target_tips2') }}</a
></span
>
</template>
<template v-else-if="state.linkJumpCurFilterFieldArray.length === 0">
<span>{{ t('visualization.jump_no_banding_tips') }}</span>
</template>
<template v-else-if="state.currentOutParams.length > 0">
<el-row style="margin-bottom: 8px" :gutter="8">
<el-col :span="12"> {{ t('visualization.source_filter') }} </el-col>
<el-col :span="1"></el-col>
<el-col :span="10" style="margin-left: -2.9%">
{{ t('visualization.link_outer_params') }}
</el-col>
</el-row>
<div
class="main-scrollbar-container"
:class="{
'main-scrollbar-container-min':
state.linkJumpInfo?.jumpType === 'newPop'
}"
>
<el-scrollbar height="fit-content" max-height="178px">
<div
style="display: flex; margin-bottom: 6px"
v-for="(
targetViewInfo, index
) in state.linkJumpInfo.targetViewInfoList.filter(
item => item.targetType === 'outerParams'
)"
:key="index"
>
<div style="flex: 1">
<el-select
v-model="targetViewInfo.sourceFieldActiveId"
:placeholder="t('chart.pls_select_field')"
style="width: 100%"
>
<el-option
v-for="curFilterField in state.linkJumpCurFilterFieldArray"
:key="curFilterField.id"
:label="curFilterField.name"
:value="curFilterField.id"
>
<span class="custom-option">
<Icon
><component
class="svg-icon"
style="width: 14px; height: 14px"
:is="iconChartMap['filter']"
></component
></Icon>
<span
style="float: left; margin-left: 4px; font-size: 14px"
>{{ curFilterField.name }}</span
>
</span>
</el-option>
</el-select>
</div>
<div class="icon-center">
<Icon name="dv-link-target"
><dvLinkTarget
style="width: 20px; height: 20px"
class="svg-icon"
/></Icon>
</div>
<div style="flex: 1">
<el-select
v-model="targetViewInfo.targetViewId"
:disabled="!targetViewInfo.sourceFieldActiveId"
:placeholder="t('visualization.select_param')"
style="width: 100%"
@change="viewInfoOnChange(targetViewInfo)"
>
<el-option
v-for="item in state.currentOutParams"
:key="item.id"
:label="item.title"
:value="item.id"
>
<span class="custom-option">
<Icon
><component
class="svg-icon view-type-icon"
style="width: 14px; height: 14px"
:is="iconChartMap[item.type]"
></component
></Icon>
<span
style="float: left; margin-left: 4px; font-size: 14px"
>{{ item.title }}</span
>
</span>
</el-option>
</el-select>
</div>
<el-button
class="m-del-icon-btn"
text
@click="deleteLinkJumpFieldById(targetViewInfo.targetId)"
>
<el-icon size="20px">
<Icon name="icon_delete-trash_outlined"
><icon_deleteTrash_outlined class="svg-icon"
/></Icon>
</el-icon>
</el-button>
</div>
</el-scrollbar>
<el-button
style="margin-top: 8px"
:disabled="!state.linkJumpInfo.targetDvId"
type="primary"
icon="Plus"
text
@click="addLinkJumpField('outerParams')"
>
{{ t('visualization.add_jump_field') }}
</el-button>
</div>
</template>
</template>
</template>
<template v-else>
<empty-background
style="height: auto"
:description="selectSourceTips"
img-type="noneWhite"
/>
</template>
</el-form>
</template>
<template v-if="outerContentShow">
<el-row :gutter="8" class="main-form">
<el-col :span="16" style="height: 100%">
<div class="url-text">
{{ t('visualization.target_url') }}
<el-tooltip class="item" effect="dark" placement="bottom">
<template #content>
{{ $t('visualization.target_url_tips') }}
</template>
<el-icon size="16px" class="hint-icon">
<Icon name="icon_info_outlined"
><icon_info_outlined class="svg-icon"
/></Icon>
</el-icon>
</el-tooltip>
</div>
<div class="outer-content-mirror">
<jump-set-outer-content-editor
ref="outerContentEditor"
:link-jump-info="state.linkJumpInfo"
:link-jump-info-array="state.linkJumpInfoArray"
/>
</div>
</el-col>
<el-col :span="8" style="height: 100%">
<div class="url-text">
{{ t('visualization.select_world') }}
<el-tooltip class="item" effect="dark" placement="bottom">
<template #content>
<span v-html="$t('chart.reference_field_tip')"></span>
</template>
<el-icon size="16px" class="hint-icon">
<Icon name="icon_info_outlined"
><icon_info_outlined class="svg-icon"
/></Icon>
</el-icon>
</el-tooltip>
</div>
<div class="outer-content-right">
<el-input
v-model="state.searchField"
:placeholder="t('dataset.search')"
:prefix-icon="Search"
clearable
/>
<el-scrollbar style="margin-top: 12px" height="250px">
<span
v-for="item in state.linkJumpInfoArray.filter(
item =>
!state.searchField ||
item.sourceFieldName.indexOf(state.searchField) > -1
)"
:key="item.sourceFieldId"
class="item-dimension"
:title="item.sourceFieldName"
@click="insertFieldToCodeMirror('[' + item.sourceFieldName + ']')"
>
<el-icon>
<Icon
><component
class="svg-icon"
:class="`field-icon-${fieldType[item.sourceDeType]}`"
:is="iconFieldMap[fieldType[item.sourceDeType]]"
></component
></Icon>
</el-icon>
{{ item.sourceFieldName }}
</span>
</el-scrollbar>
</div>
</el-col>
</el-row>
</template>
</template>
<div v-else class="empty">
<empty-background
:description="t('visualization.select_dimension_hint')"
img-type="noneWhite"
/>
</div>
</el-main>
</el-container>
</el-col>
</el-row>
</el-row>
<el-row class="root-class">
<el-button size="mini" @click="cancel()">{{ t('common.cancel') }} </el-button>
<el-button type="primary" size="mini" @click="save()"
>{{ t('dataset.confirm') }}
</el-button>
</el-row>
</div>
<XpackComponent
ref="openHandler"
jsname="L2NvbXBvbmVudC9lbWJlZGRlZC1pZnJhbWUvT3BlbkhhbmRsZXI="
/>
</el-dialog>
</template>
<script lang="ts" setup>
import { iconFieldMap } from '@/components/icon-group/field-list'
import { iconChartMap } from '@/components/icon-group/chart-list'
import datasetOutline from '@/assets/svg/dataset-outline.svg'
import dvLinkTarget from '@/assets/svg/dv-link-target.svg'
import dvDashboardSpine from '@/assets/svg/dv-dashboard-spine.svg'
import dvFolder from '@/assets/svg/dv-folder.svg'
import icon_deleteTrash_outlined from '@/assets/svg/icon_delete-trash_outlined.svg'
import icon_info_outlined from '@/assets/svg/icon_info_outlined.svg'
import dvScreenSpine from '@/assets/svg/dv-screen-spine.svg'
import {
queryVisualizationJumpInfo,
queryWithViewId,
updateJumpSet,
viewTableDetailList
} from '@/api/visualization/linkJump'
import { reactive, ref, nextTick, computed, watch } from 'vue'
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
import { fieldType } from '@/utils/attr'
import { storeToRefs } from 'pinia'
import { findDvType, queryTreeApi } from '@/api/visualization/dataVisualization'
import { ElMessage, ElScrollbar } from 'element-plus-secondary'
import { useI18n } from '@/hooks/web/useI18n'
import { getDatasetDetails, listFieldByDatasetGroup } from '@/api/dataset'
import { BusiTreeRequest } from '@/models/tree/TreeNode'
import JumpSetOuterContentEditor from '@/components/visualization/JumpSetOuterContentEditor.vue'
import { Search } from '@element-plus/icons-vue'
import { snapshotStoreWithOut } from '@/store/modules/data-visualization/snapshot'
import EmptyBackground from '@/components/empty-background/src/EmptyBackground.vue'
import { filterEmptyFolderTree } from '@/utils/canvasUtils'
import { useEmitt } from '@/hooks/web/useEmitt'
import { useAppStoreWithOut } from '@/store/modules/app'
import { XpackComponent } from '@/components/plugin'
import { useCache } from '@/hooks/web/useCache'
import { useEmbedded } from '@/store/modules/embedded'
import { guid } from '@/views/visualized/data/dataset/form/util'
import treeSort from '@/utils/treeSortUtils'
const dvMainStore = dvMainStoreWithOut()
const { dvInfo, canvasViewInfo, componentData } = storeToRefs(dvMainStore)
const linkJumpInfoTree = ref(null)
const { t } = useI18n()
const dialogShow = ref(false)
const snapshotStore = snapshotStoreWithOut()
const appStore = useAppStoreWithOut()
const embeddedStore = useEmbedded()
const resourceType = computed(() =>
dvInfo.value.type === 'dashboard' ? t('work_branch.dashboard') : t('work_branch.big_data_screen')
)
const selectSourceTips = t('visualization.select_target_resource')
const targetSource = t('visualization.target_dashboard_dataV')
const state = reactive({
curDataVWeight: 0,
activeCollapse: 'view',
loading: false,
showSelected: false,
curJumpViewInfo: {},
curDatasetInfo: {},
tempId: null,
initState: false,
viewId: null,
viewType: null,
name2Auto: [],
searchField: '',
searchFunction: '',
inputType: 'self',
fieldName: 'name',
tableRadio: null,
keyWordSearch: '',
columnLabel: t('visualization.belong_to_category'),
templateList: [],
importTemplateInfo: {
snapshot: ''
},
sourceViewFields: [],
dvSelectProps: {
label: 'name',
children: 'children',
value: 'id',
isLeaf: 'leaf',
disabled: 'disabled'
},
treeProp: {
id: 'sourceFieldId',
label: 'sourceFieldName',
children: 'children'
},
linkJump: null,
linkJumpInfoArray: [],
linkJumpInfoXArray: [],
linkJumpCurViewFieldArray: [],
linkJumpCurFilterFieldArray: [], //当前过滤条件明细
mapJumpInfoArray: {},
panelList: [],
linkJumpInfo: null,
currentFiledTreeNode: null,
defaultLinkJumpInfo: {
linkType: 'outer',
jumpType: '_self',
targetViewInfoList: []
},
defaultTargetViewInfo: {
targetViewId: null,
targetFieldId: null
},
currentLinkPanelViewArray: [],
viewIdFieldArrayMap: {},
dimensionData: [],
dimensionList: [],
quotaList: [],
quotaData: [],
dimension: [],
quota: [],
currentOutParams: []
})
const { wsCache } = useCache()
const outerContentEditor = ref(null)
const resetParams = () => {
state.linkJump = null
state.linkJumpInfoArray = []
state.linkJumpInfoXArray = []
state.linkJumpCurViewFieldArray = []
state.linkJumpCurFilterFieldArray = []
state.mapJumpInfoArray = {}
state.linkJumpInfo = null
}
const dialogInit = viewItem => {
resetParams()
state.showSelected = false
dialogShow.value = true
state.initState = false
init(viewItem)
}
const initCurFilterFieldArray = componentDataCheck => {
componentDataCheck.forEach(componentItem => {
if (componentItem.component === 'VQuery' && componentItem.propValue instanceof Array) {
componentItem.propValue.forEach(filterItem => {
if (filterItem.checkedFields.includes(state.viewId)) {
state.linkJumpCurFilterFieldArray.push({
id: filterItem.id,
name: filterItem.name,
deType: 'filter'
})
}
})
} else if (componentItem.component === 'Group') {
initCurFilterFieldArray(componentItem.propValue)
} else if (componentItem.component === 'DeTabs') {
componentItem.propValue.forEach(tabItem => {
initCurFilterFieldArray(tabItem.componentData)
})
}
})
}
const isIndicator = computed(() => 'indicator' === state.viewType)
const init = viewItem => {
state.initState = false
state.viewId = viewItem.id
state.viewType = viewItem.type
state.activeCollapse = isIndicator.value ? 'filter' : 'view'
const chartDetails = canvasViewInfo.value[state.viewId] as ChartObj
state.curJumpViewInfo = chartDetails
let checkAllAxisStr =
JSON.stringify(chartDetails.xAxis) +
JSON.stringify(chartDetails.xAxisExt) +
JSON.stringify(chartDetails.drillFields)
let checkJumpStr
// 堆叠图的可选参数分两种情况 1.如果有堆叠项 则指标只有第一个可选 2.如果没有堆叠项泽所有指标都可以选
if (chartDetails.type.indexOf('stack') > -1 && chartDetails.extStack.length > 2) {
const yAxisArray = chartDetails.yAxis
const yAxisNew = yAxisArray.length > 0 ? JSON.stringify(yAxisArray[0]) : '[]'
checkAllAxisStr =
JSON.stringify(chartDetails.xAxis) +
JSON.stringify(chartDetails.xAxisExt) +
JSON.stringify(yAxisNew) +
JSON.stringify(chartDetails.yAxisExt) +
JSON.stringify(chartDetails.drillFields)
checkJumpStr = checkAllAxisStr
} else if (
['table-normal', 'table-info', 'table-pivot', 'indicator'].includes(chartDetails.type)
) {
checkJumpStr =
checkAllAxisStr + JSON.stringify(chartDetails.yAxis) + JSON.stringify(chartDetails.yAxisExt)
} else if (chartDetails.type === 'multi-scatter') {
// 多维散点图跳转字段只列出维度,引用字段可选所有轴字段
const multiScatterExtra =
JSON.stringify(chartDetails.yAxis || []) +
JSON.stringify(chartDetails.extColor || []) +
JSON.stringify(chartDetails.extBubble || []) +
JSON.stringify(chartDetails.yAxisExt || [])
checkAllAxisStr = checkAllAxisStr + multiScatterExtra
checkJumpStr = JSON.stringify(chartDetails.extColor || [])
} else {
checkJumpStr = checkAllAxisStr
}
const request = { busiFlag: 'dashboard-dataV' } as BusiTreeRequest
// 获取可关联的仪表板
queryTreeApi(request).then(rsp => {
if (rsp && rsp[0]?.id === '0') {
state.panelList = rsp[0].children
} else {
state.panelList = rsp
}
state.panelList = filterEmptyFolderTree(state.panelList)
const curSortType = wsCache.get(`TreeSort-${dvInfo.value.type}`) || 'time_asc'
state.panelList = treeSort(state.panelList, curSortType)
})
// 获取当前过滤条件明细 过滤原则:1.在当前仪表板或者大屏 2.作用于当前图表
state.linkJumpCurFilterFieldArray = []
initCurFilterFieldArray(componentData.value)
if (chartDetails.tableId) {
// 获取当前数据集信息
getDatasetDetails(chartDetails.tableId).then(res => {
state.curDatasetInfo = res || {}
})
// 获取当前图表的字段信息
listFieldByDatasetGroup(chartDetails.tableId).then(rsp => {
state.linkJumpCurViewFieldArray = []
const sourceCurViewFieldArray = rsp.data
sourceCurViewFieldArray.forEach(fieldItem => {
if (checkAllAxisStr.indexOf(fieldItem.id) > -1) {
state.linkJumpCurViewFieldArray.push(fieldItem)
}
})
})
// 获取当前图表的关联信息
queryWithViewId(dvInfo.value.id, state.viewId).then(rsp => {
state.linkJump = rsp.data
state.linkJumpInfoArray = []
state.linkJumpInfoXArray = []
state.linkJump.linkJumpInfoArray.forEach(linkJumpInfo => {
if (checkJumpStr.indexOf(linkJumpInfo.sourceFieldId) > -1) {
state.mapJumpInfoArray[linkJumpInfo.sourceFieldId] = linkJumpInfo
state.linkJumpInfoArray.push(linkJumpInfo)
state.linkJumpInfoXArray.push(linkJumpInfo)
} else if (checkAllAxisStr.indexOf(linkJumpInfo.sourceFieldId) > -1) {
state.linkJumpInfoArray.push(linkJumpInfo)
}
})
const firstNode = state.linkJumpInfoArray[0]
state.initState = true
nextTick(() => {
linkJumpInfoTree.value.setCurrentKey(firstNode?.sourceFieldId)
nodeClick(firstNode)
})
})
}
}
const save = () => {
// 字段检查
let subCheckCountAll = 0
state.linkJump.linkJumpInfoArray.forEach(linkJumpInfo => {
if (linkJumpInfo.checked) {
let subCheckCount = 0
if (linkJumpInfo.linkType === 'inner') {
if (!linkJumpInfo.targetDvId) {
subCheckCount++
subCheckCountAll++
}
linkJumpInfo.targetViewInfoList &&
linkJumpInfo.targetViewInfoList.forEach(function (link) {
if (!(link.sourceFieldActiveId && link.targetFieldId && link.targetViewId)) {
subCheckCount++
subCheckCountAll++
}
})
}
if (linkJumpInfo.linkType === 'outer') {
if (!linkJumpInfo.content) {
subCheckCount++
subCheckCountAll++
}
}
if (subCheckCount > 0) {
ElMessage.error(t('visualization.jump_null_tips', [linkJumpInfo.sourceFieldName]))
}
}
})
if (subCheckCountAll) {
return
}
state.loading = true
updateJumpSet(state.linkJump)
.then(() => {
snapshotStore.recordSnapshotCache('updateJumpSet')
ElMessage.success(t('common.save_success'))
// 刷新跳转信息
queryVisualizationJumpInfo(dvInfo.value.id).then(rsp => {
dvMainStore.setNowPanelJumpInfo(rsp.data)
cancel()
})
state.loading = false
})
.catch(() => {
state.loading = false
})
}
const nodeClick = data => {
if (!data) {
return
}
state.linkJumpInfo = state.mapJumpInfoArray[data.sourceFieldId]
if (!state.linkJumpInfo.windowSize) {
state.linkJumpInfo.windowSize = 'middle'
}
if (!state.linkJumpInfo.linkType) {
state.linkJumpInfo.linkType = 'outer'
}
if (!state.linkJumpInfo.jumpType) {
state.linkJumpInfo.jumpType = '_blank'
}
if (!state.linkJumpInfo.content) {
state.linkJumpInfo.content = 'http://'
}
if (!state.linkJumpInfo.attachParams) {
state.linkJumpInfo.attachParams = false
}
if (state.linkJumpInfo.targetDvId) {
getPanelViewList(state.linkJumpInfo.targetDvId)
}
codeMirrorContentSet(state.linkJumpInfo.content)
}
const codeMirrorContentSet = content => {
nextTick(() => {
outerContentEditor.value?.editorInit(content)
})
}
// 获取当前图表字段 关联仪表板的图表信息列表
const getPanelViewList = dvId => {
viewTableDetailList(dvId).then(rsp => {
state.viewIdFieldArrayMap = {}
state.currentLinkPanelViewArray = rsp.data.visualizationViewTables
if (state.currentLinkPanelViewArray) {
state.currentLinkPanelViewArray.forEach(view => {
state.viewIdFieldArrayMap[view.id] = view.tableFields
})
}
// 外部参数 currentLinkPanelViewArray 也加入
// 在图表侧进行隐藏 保存的时候直接保存currentLinkPanelViewArray 方便处理
state.currentOutParams = rsp.data.outParamsJumpInfo || []
if (state.currentOutParams && state.currentOutParams.length > 0) {
state.currentOutParams.forEach(outerParamsItem => {
state.currentLinkPanelViewArray.push(outerParamsItem)
state.viewIdFieldArrayMap[outerParamsItem.id] = [
{ id: '1000001', name: t('visualization.out_params_no_select') }
]
})
}
// 增加过滤组件匹配
JSON.parse(rsp.data.bashComponentData).forEach(componentItem => {
if (componentItem.component === 'VQuery' && componentItem.propValue instanceof Array) {
componentItem.propValue.forEach(filterItem => {
state.currentLinkPanelViewArray.push({
id: filterItem.id,
type: 'filter',
name: filterItem.name,
title: filterItem.name
})
state.viewIdFieldArrayMap[filterItem.id] = [
{ id: '1000001', name: t('visualization.filter_no_select') }
]
})
}
})
})
}
const dvNodeClick = data => {
if (data.leaf) {
state.curDataVWeight = data.weight
state.linkJumpInfo.targetViewInfoList = []
if (!isIndicator.value) {
addLinkJumpField()
}
getPanelViewList(data.id)
}
}
const addLinkJumpField = (type = 'view') => {
state.linkJumpInfo.targetViewInfoList.push({
targetId: guid(),
targetViewId: '',
targetType: type,
targetFieldId: ''
})
}
const deleteLinkJumpFieldById = targetId => {
if (targetId) {
let indexResult
state.linkJumpInfo.targetViewInfoList.forEach((item, index) => {
if (targetId === item.targetId) {
indexResult = index
}
})
if (indexResult !== undefined) {
state.linkJumpInfo.targetViewInfoList.splice(indexResult, 1)
}
}
}
const fieldIdDisabledCheck = targetViewInfo => {
return (
(state.viewIdFieldArrayMap[targetViewInfo.targetViewId] &&
state.viewIdFieldArrayMap[targetViewInfo.targetViewId].length === 1 &&
state.viewIdFieldArrayMap[targetViewInfo.targetViewId][0].id === '1000001') ||
!targetViewInfo.sourceFieldActiveId
)
}
const viewInfoOnChange = targetViewInfo => {
if (
state.viewIdFieldArrayMap[targetViewInfo.targetViewId] &&
state.viewIdFieldArrayMap[targetViewInfo.targetViewId].length === 1 &&
state.viewIdFieldArrayMap[targetViewInfo.targetViewId][0].id === '1000001'
) {
targetViewInfo.targetFieldId = '1000001'
} else {
targetViewInfo.targetFieldId = null
}
}
const sourceFieldCheckedChange = data => {
nextTick(() => {
linkJumpInfoTree.value.setCurrentKey(data.sourceFieldId)
nodeClick(data)
})
}
const cancel = () => {
dialogShow.value = false
state.initState = false
}
const insertFieldToCodeMirror = (value: string) => {
outerContentEditor.value.insertFieldToCodeMirror(value)
}
const outerContentShow = computed(() => {
return state.linkJumpInfo && state.linkJumpInfo.linkType === 'outer' && dialogShow.value
})
const filterNodeMethod = (value, data) => {
return !value || data.checked
}
const isEmbedded = computed(() => appStore.getIsDataEaseBi || appStore.getIsIframe)
const openType = '_blank'
const resourceEdit = async resourceId => {
if (state.curDataVWeight && state.curDataVWeight < 7) {
ElMessage.error(t('visualization.no_edit_auth'))
return
}
let busiFlagResult
await findDvType(resourceId).then(res => {
busiFlagResult = res.data
})
const baseUrl = busiFlagResult === 'dataV' ? '#/dvCanvas?dvId=' : '#/dashboard?resourceId='
if (isEmbedded.value) {
embeddedStore.clearState()
if (dvInfo.value.type === 'dataV') {
embeddedStore.setDvId(resourceId)
} else {
embeddedStore.setResourceId(resourceId)
}
useEmitt().emitter.emit(
'changeCurrentComponent',
dvInfo.value.type === 'dataV' ? 'VisualizationEditor' : 'DashboardEditor'
)
return
}
const newWindow = window.open(baseUrl + resourceId, openType)
initOpenHandler(newWindow)
}
const openHandler = ref(null)
const initOpenHandler = newWindow => {
if (openHandler?.value) {
const pm = {
methodName: 'initOpenHandler',
args: newWindow
}
openHandler.value.invokeMethod(pm)
}
}
watch(
() => state.showSelected,
newValue => {
linkJumpInfoTree.value?.filter(newValue)
}
)
watch(
() => outerContentShow.value,
newValue => {
if (newValue) {
codeMirrorContentSet(state.linkJumpInfo.content)
}
}
)
defineExpose({
dialogInit
})
</script>
<style scoped lang="less">
.root-class {
margin: 15px 0px 5px;
justify-content: right;
}
.preview {
margin-top: 5px;
border: 1px solid #e6e6e6;
border-radius: 6px;
height: 470px !important;
overflow: hidden;
background-size: 100% 100% !important;
}
.preview-show {
border-left: 1px solid #e6e6e6;
height: 470px;
background-size: 100% 100% !important;
}
.top_border {
border-top: 1px solid #e6e6e6;
}
.slot-class {
color: white;
}
.bottom {
margin-top: 10px;
justify-content: center;
}
.ellip {
margin-left: 10px;
margin-right: 10px;
overflow: hidden; /*超出部分隐藏*/
white-space: nowrap; /*不换行*/
text-overflow: ellipsis; /*超出部分文字以...显示*/
text-align: center;
background-color: #f7f8fa;
color: #3d4d66;
font-size: 12px;
line-height: 24px;
height: 24px;
border-radius: 3px;
}
.select-filed {
margin-right: 8px;
overflow: hidden; /*超出部分隐藏*/
white-space: nowrap; /*不换行*/
text-overflow: ellipsis; /*超出部分文字以...显示*/
color: #3d4d66;
font-size: 12px;
line-height: 35px;
height: 35px;
border-radius: 3px;
}
.custom-position {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 14px;
flex-flow: row nowrap;
color: #9ea6b2;
}
.tree-style {
padding: 10px 15px;
height: 100%;
overflow-y: auto;
}
.custom-tree-node {
display: flex;
align-items: center;
justify-content: space-between;
font-size: 14px;
}
.auth-span {
float: left;
width: 30px;
margin-left: -8px;
}
.tree-head {
height: 40px;
line-height: 40px;
font-size: 12px;
color: #3d4d66;
.head-text {
margin-left: 16px;
font-weight: 500;
font-size: 14px;
color: #1f2329;
}
.head-filter {
flex: 1;
display: flex;
align-items: center;
justify-content: end;
margin-right: 16px;
font-weight: 400;
font-size: 12px;
color: #646a73;
.ed-switch {
margin-left: 8px;
}
}
}
.padding-lr {
padding: 0 4px;
}
.field-height {
height: calc(100% - 25px);
margin-top: 12px;
}
.drag-list {
height: calc(100% - 26px);
overflow: auto;
}
.item-dimension {
display: flex;
height: 28px;
padding: 1px 8px;
align-items: center;
gap: 4px;
flex-shrink: 0;
word-break: break-all;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
border-radius: 6px;
border: 1px solid #dee0e3;
background: #fff;
color: var(--neutral-900, #1f2329);
/* 中文/桌面端/正文 14 22 Regular */
font-family: var(--de-custom_font, 'PingFang');
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 22px;
cursor: pointer;
}
.item-dimension + .item-dimension {
margin-top: 4px;
}
.item-dimension:hover {
border: 1px solid var(--ed-color-primary, #3370ff);
background: var(--ed-color-primary-1a, rgba(51, 112, 255, 0.1));
}
.item-quota {
padding: 2px 10px;
margin: 2px 2px 0 2px;
border: solid 1px #eee;
text-align: left;
color: #606266;
background-color: white;
display: block;
word-break: break-all;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.item-quota + .item-quota {
margin-top: 2px;
}
.item-quota:hover {
color: #67c23a;
background: #f0f9eb;
border-color: #b2d3a3;
cursor: pointer;
}
.blackTheme .item-quota:hover {
}
span {
font-size: 12px;
}
.set-name-area {
font-weight: 600;
margin-right: 20px;
}
:deep(.ed-row) {
width: 100%;
}
.dv-selector {
width: 100%;
}
.top-area {
float: left;
line-height: 33px;
display: flex;
flex-direction: row;
align-items: center;
}
.top-area-text {
font-weight: 400;
font-size: 14px;
color: #646a73;
&.margin-left {
margin-left: 24px;
}
}
.settings-container {
height: 100%;
.settings-header {
height: auto;
border-bottom: 1px solid #e6e6e6;
.radio-group-box {
margin-top: 8px;
margin-bottom: 8px;
.title {
color: #646a73;
font-size: 14px;
font-style: normal;
font-weight: 400;
}
:deep(.ed-radio__label) {
color: #1f2329;
font-size: 14px;
font-style: normal;
font-weight: 400;
}
}
}
.settings-main {
padding: 16px;
overflow: hidden;
.empty {
width: 100%;
height: 100%;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
}
.main-form {
height: 100%;
width: 100%;
.m-row {
width: 100%;
display: flex;
}
.icon-center {
padding: 0 8px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.main-scrollbar-container {
height: calc(100% - 132px);
:deep(.ed-scrollbar) {
height: fit-content;
max-height: 208px;
}
}
}
}
}
.main-scrollbar-container-min {
:deep(.ed-scrollbar) {
height: fit-content;
max-height: 138px !important;
}
}
.top-area-value {
font-weight: 400;
font-size: 14px;
color: #1f2329;
display: flex;
flex-direction: row;
align-items: center;
}
.view-type-icon {
color: var(--ed-color-primary);
width: 22px;
height: 16px;
}
.content-head {
height: 30px;
margin-top: 10px;
.content-head-text {
margin-left: 16px;
font-weight: 400;
font-size: 14px;
color: #646a73;
line-height: 32px;
margin-right: 16px;
}
}
.link-icon-area {
text-align: center;
line-height: 35px;
}
.inner-content {
width: 100%;
padding: 16px 16px 8px 16px;
font-size: 14px !important;
}
.outer-content {
height: 340px;
border-radius: 6px;
}
.padding-lr {
height: 500px;
border: 1px solid var(--deCardStrokeColor, #dee0e3);
border-radius: 6px;
padding: 12px;
box-sizing: border-box;
margin-left: 12px;
width: 214px;
overflow-y: hidden;
}
.mb8 {
margin-bottom: 8px;
display: inline-flex;
align-items: center;
i {
margin-left: 4.67px;
}
}
.field-height {
height: calc(50% - 41px);
margin-top: 4px;
overflow-y: auto;
}
.class-na {
margin-top: 8px;
text-align: center;
font-size: 14px;
color: var(--deTextDisable);
}
.outer-content-mirror {
border: 1px solid #d9dcdf;
border-radius: 6px;
height: calc(100% - 30px);
width: 100%;
overflow: hidden;
}
.url-text {
width: 100%;
line-height: 22px;
margin-bottom: 8px;
font-size: 14px;
font-style: normal;
font-weight: 400;
color: #1f2329;
display: flex;
align-items: center;
}
.outer-content-right {
border: 1px solid #d9dcdf;
border-radius: 6px;
height: calc(100% - 30px);
width: 100%;
padding: 12px;
}
.tree-select-field {
font-size: 14px;
display: flex;
align-items: center;
overflow: hidden;
}
.label-content-details {
width: 100%;
display: flex;
align-items: center;
}
.hint-icon {
margin-left: 4px;
cursor: pointer;
color: #646a73;
}
.m-del-icon-btn {
color: #646a73;
margin-top: 2px;
margin-left: -4px;
&:hover {
background: rgba(31, 35, 41, 0.1) !important;
}
&:focus {
background: rgba(31, 35, 41, 0.1) !important;
}
&:active {
background: rgba(31, 35, 41, 0.2) !important;
}
}
.larger-radio {
.ed-radio__inner {
width: 16px;
height: 16px;
}
}
.custom-option {
font-size: 14px;
display: flex;
align-items: center;
}
.jump-com-list {
width: 100%;
margin-top: -18px;
:deep(.ed-collapse) {
--ed-collapse-header-font-size: 14px;
--ed-collapse-content-font-size: 14px;
}
:deep(.ed-tabs__active-bar) {
height: 2px;
}
& > :deep(.ed-tabs) {
--ed-tabs-header-height: 36px;
margin-bottom: 12px;
position: sticky;
background: #fff;
.ed-tabs__header {
&::before {
content: '';
width: 8px;
height: 1px;
position: absolute;
bottom: 0;
left: 0;
background: #1f232926;
}
}
}
:deep(.ed-tabs__item) {
font-size: 14px;
}
:deep(.ed-tabs__item):not(.is-active) {
color: #646a73;
}
}
.target_jump {
color: var(--ed-color-primary);
cursor: pointer;
}
</style>