feat(仪表板、大屏): 支持应用导出、应用导入相关优化

This commit is contained in:
wangjiahao 2024-07-17 23:25:57 +08:00
parent c68a9bfac3
commit f7ef95278b
14 changed files with 381 additions and 73 deletions

View File

@ -80,6 +80,7 @@ public class DatasetGroupManage {
private Lock lock = new ReentrantLock(); private Lock lock = new ReentrantLock();
@Transactional
public DatasetGroupInfoDTO save(DatasetGroupInfoDTO datasetGroupInfoDTO, boolean rename) throws Exception { public DatasetGroupInfoDTO save(DatasetGroupInfoDTO datasetGroupInfoDTO, boolean rename) throws Exception {
lock.lock(); lock.lock();
try { try {

View File

@ -9,7 +9,7 @@ import java.io.Serializable;
* </p> * </p>
* *
* @author fit2cloud * @author fit2cloud
* @since 2024-07-16 * @since 2024-07-17
*/ */
@TableName("visualization_template") @TableName("visualization_template")
public class VisualizationTemplate implements Serializable { public class VisualizationTemplate implements Serializable {

View File

@ -10,7 +10,7 @@ import org.apache.ibatis.annotations.Mapper;
* </p> * </p>
* *
* @author fit2cloud * @author fit2cloud
* @since 2024-07-16 * @since 2024-07-17
*/ */
@Mapper @Mapper
public interface VisualizationTemplateMapper extends BaseMapper<VisualizationTemplate> { public interface VisualizationTemplateMapper extends BaseMapper<VisualizationTemplate> {

View File

@ -2,8 +2,16 @@ package io.dataease.visualization.server;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import com.google.gson.Gson;
import io.dataease.api.dataset.union.DatasetGroupInfoDTO;
import io.dataease.api.visualization.request.VisualizationAppExportRequest; import io.dataease.api.visualization.request.VisualizationAppExportRequest;
import io.dataease.api.visualization.vo.*; import io.dataease.api.visualization.vo.*;
import io.dataease.dataset.dao.auto.entity.CoreDatasetGroup;
import io.dataease.dataset.dao.auto.entity.CoreDatasetTable;
import io.dataease.dataset.dao.auto.entity.CoreDatasetTableField;
import io.dataease.dataset.dao.auto.mapper.CoreDatasetGroupMapper;
import io.dataease.dataset.dao.auto.mapper.CoreDatasetTableFieldMapper;
import io.dataease.dataset.dao.auto.mapper.CoreDatasetTableMapper;
import io.dataease.dataset.manage.DatasetDataManage; import io.dataease.dataset.manage.DatasetDataManage;
import io.dataease.dataset.manage.DatasetGroupManage; import io.dataease.dataset.manage.DatasetGroupManage;
import io.dataease.extensions.datasource.dto.DatasetTableDTO; import io.dataease.extensions.datasource.dto.DatasetTableDTO;
@ -37,6 +45,7 @@ import io.dataease.template.dao.auto.mapper.VisualizationTemplateMapper;
import io.dataease.template.dao.ext.ExtVisualizationTemplateMapper; import io.dataease.template.dao.ext.ExtVisualizationTemplateMapper;
import io.dataease.template.manage.TemplateCenterManage; import io.dataease.template.manage.TemplateCenterManage;
import io.dataease.utils.*; import io.dataease.utils.*;
import io.dataease.visualization.dao.auto.entity.CoreStore;
import io.dataease.visualization.dao.auto.entity.DataVisualizationInfo; import io.dataease.visualization.dao.auto.entity.DataVisualizationInfo;
import io.dataease.visualization.dao.auto.entity.VisualizationWatermark; import io.dataease.visualization.dao.auto.entity.VisualizationWatermark;
import io.dataease.visualization.dao.auto.mapper.DataVisualizationInfoMapper; import io.dataease.visualization.dao.auto.mapper.DataVisualizationInfoMapper;
@ -45,6 +54,7 @@ import io.dataease.visualization.dao.ext.mapper.ExtDataVisualizationMapper;
import io.dataease.visualization.manage.CoreVisualizationManage; import io.dataease.visualization.manage.CoreVisualizationManage;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
@ -104,6 +114,15 @@ public class DataVisualizationServer implements DataVisualizationApi {
@Resource @Resource
private ExtVisualizationTemplateMapper appTemplateMapper; private ExtVisualizationTemplateMapper appTemplateMapper;
@Resource
private CoreDatasetGroupMapper coreDatasetGroupMapper;
@Resource
private CoreDatasetTableMapper coreDatasetTableMapper;
@Resource
private CoreDatasetTableFieldMapper coreDatasetTableFieldMapper;
@Override @Override
public DataVisualizationVO findCopyResource(Long dvId, String busiFlag) { public DataVisualizationVO findCopyResource(Long dvId, String busiFlag) {
DataVisualizationVO result = findById(new DataVisualizationBaseRequest(dvId, busiFlag)); DataVisualizationVO result = findById(new DataVisualizationBaseRequest(dvId, busiFlag));
@ -152,7 +171,84 @@ public class DataVisualizationServer implements DataVisualizationApi {
@DeLog(id = "#p0.id", pid = "#p0.pid", ot = LogOT.CREATE, stExp = "#p0.type") @DeLog(id = "#p0.id", pid = "#p0.pid", ot = LogOT.CREATE, stExp = "#p0.type")
@Override @Override
@Transactional @Transactional
public String saveCanvas(DataVisualizationBaseRequest request) { public String saveCanvas(DataVisualizationBaseRequest request) throws Exception{
Long time = System.currentTimeMillis();
// 如果是应用 则新进行应用校验 数据集名称和 数据源名称校验
VisualizationExport2AppVO appData = request.getAppData();
Map<Long,Long> dsGroupIdMap = new HashMap<>();
Map<Long,Long> dsTableIdMap = new HashMap<>();
Map<Long,Long> dsTableFieldsIdMap = new HashMap<>();
if(appData != null){
try {
Map<Long,Long> datasourceIdMap = appData.getDatasourceInfo().stream()
.collect(Collectors.toMap(AppCoreDatasourceVO::getId, AppCoreDatasourceVO::getSystemDatasourceId));
Long datasetFolderPid = request.getDatasetFolderPid();
String datasetFolderName = request.getDatasetFolderName();
QueryWrapper<CoreDatasetGroup> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("name", datasetFolderName);
queryWrapper.eq("pid", datasetFolderPid);
if (coreDatasetGroupMapper.exists(queryWrapper)) {
DEException.throwException("当前数据集分组名称已存在");
}
//新建数据集分组
DatasetGroupInfoDTO datasetFolderNewRequest = new DatasetGroupInfoDTO();
datasetFolderNewRequest.setName(datasetFolderName);
datasetFolderNewRequest.setNodeType("folder");
datasetFolderNewRequest.setPid(datasetFolderPid);
DatasetGroupInfoDTO datasetFolderNew = datasetGroupManage.save(datasetFolderNewRequest, false);
Long datasetFolderNewId = datasetFolderNew.getId();
//新建数据集
appData.getDatasetGroupsInfo().forEach(appDatasetGroup -> {
if ("dataset".equals(appDatasetGroup.getNodeType())) {
Long oldId = appDatasetGroup.getId();
Long newId = IDUtils.snowID();
DatasetGroupInfoDTO datasetNewRequest = new DatasetGroupInfoDTO();
BeanUtils.copyBean(datasetNewRequest, appDatasetGroup);
datasetNewRequest.setId(newId);
datasetNewRequest.setCreateBy(AuthUtils.getUser().getUserId() + "");
datasetNewRequest.setUpdateBy(AuthUtils.getUser().getUserId() + "");
datasetNewRequest.setCreateTime(time);
datasetNewRequest.setLastUpdateTime(time);
datasetNewRequest.setPid(datasetFolderNewId);
try {
datasetGroupManage.innerSave(datasetNewRequest);
dsGroupIdMap.put(oldId,newId);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
});
// 新建数据集表
appData.getDatasetTablesInfo().forEach(appCoreDatasetTableVO -> {
Long oldId = appCoreDatasetTableVO.getId();
Long newId = IDUtils.snowID();
CoreDatasetTable datasetTable = new CoreDatasetTable();
BeanUtils.copyBean(datasetTable,appCoreDatasetTableVO);
datasetTable.setDatasetGroupId(dsGroupIdMap.get(datasetTable.getDatasetGroupId()));
datasetTable.setId(newId);
datasetTable.setDatasourceId(datasourceIdMap.get(datasetTable.getDatasourceId()));
coreDatasetTableMapper.insert(datasetTable);
dsTableIdMap.put(oldId,newId);
});
// 新建数据字段
appData.getDatasetTableFieldsInfo().forEach( appDsTableFields ->{
Long oldId = appDsTableFields.getId();
Long newId = IDUtils.snowID();
CoreDatasetTableField dsDsField = new CoreDatasetTableField();
BeanUtils.copyBean(dsDsField,appDsTableFields);
dsDsField.setDatasetGroupId(dsGroupIdMap.get(dsDsField.getDatasetGroupId()));
dsDsField.setDatasetTableId(dsTableIdMap.get(dsDsField.getDatasetTableId()));
dsDsField.setDatasourceId(datasourceIdMap.get(dsDsField.getDatasourceId()));
dsDsField.setId(newId);
coreDatasetTableFieldMapper.insert(dsDsField);
dsTableFieldsIdMap.put(oldId,newId);
});
}catch (Exception e){
DEException.throwException("应用创建失败");
}
}
DataVisualizationInfo visualizationInfo = new DataVisualizationInfo(); DataVisualizationInfo visualizationInfo = new DataVisualizationInfo();
BeanUtils.copyBean(visualizationInfo, request); BeanUtils.copyBean(visualizationInfo, request);
visualizationInfo.setNodeType(request.getNodeType() == null ? DataVisualizationConstants.NODE_TYPE.LEAF : request.getNodeType()); visualizationInfo.setNodeType(request.getNodeType() == null ? DataVisualizationConstants.NODE_TYPE.LEAF : request.getNodeType());
@ -168,6 +264,7 @@ public class DataVisualizationServer implements DataVisualizationApi {
} }
Long newDvId = coreVisualizationManage.innerSave(visualizationInfo); Long newDvId = coreVisualizationManage.innerSave(visualizationInfo);
request.setId(newDvId); request.setId(newDvId);
// TODO 还原ID信息
//保存图表信息 //保存图表信息
chartDataManage.saveChartViewFromVisualization(request.getComponentData(), newDvId, request.getCanvasViewInfo()); chartDataManage.saveChartViewFromVisualization(request.getComponentData(), newDvId, request.getCanvasViewInfo());
return newDvId.toString(); return newDvId.toString();
@ -378,6 +475,11 @@ public class DataVisualizationServer implements DataVisualizationApi {
VisualizationTemplateExtendDataDTO extendDataDTO = new VisualizationTemplateExtendDataDTO(newDvId, newViewId, originViewData); VisualizationTemplateExtendDataDTO extendDataDTO = new VisualizationTemplateExtendDataDTO(newDvId, newViewId, originViewData);
extendDataInfo.put(newViewId, extendDataDTO); extendDataInfo.put(newViewId, extendDataDTO);
templateData = templateData.replaceAll(originViewId, newViewId.toString()); templateData = templateData.replaceAll(originViewId, newViewId.toString());
if(appData != null){
Map appDataFormat = JsonUtil.parse(appData,Map.class);
String sourceDvId = (String) appDataFormat.get("id");
appData = appData.replaceAll(originViewId, newViewId.toString()).replaceAll(sourceDvId, newDvId.toString());
}
canvasViewInfo.put(chartView.getId(), chartView); canvasViewInfo.put(chartView.getId(), chartView);
//插入模板数据 此处预先插入减少数据交互量 //插入模板数据 此处预先插入减少数据交互量
VisualizationTemplateExtendData extendData = new VisualizationTemplateExtendData(); VisualizationTemplateExtendData extendData = new VisualizationTemplateExtendData();

View File

@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ElMessage, ElMessageBox } from 'element-plus-secondary' import { ElMessage, ElMessageBox } from 'element-plus-secondary'
import eventBus from '@/utils/eventBus' import eventBus from '@/utils/eventBus'
import { ref, nextTick, computed } from 'vue' import { ref, nextTick, computed, toRefs } from 'vue'
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain' import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
import { snapshotStoreWithOut } from '@/store/modules/data-visualization/snapshot' import { snapshotStoreWithOut } from '@/store/modules/data-visualization/snapshot'
import { useAppStoreWithOut } from '@/store/modules/app' import { useAppStoreWithOut } from '@/store/modules/app'
@ -32,6 +32,7 @@ const dvMainStore = dvMainStoreWithOut()
const snapshotStore = snapshotStoreWithOut() const snapshotStore = snapshotStoreWithOut()
const { styleChangeTimes, snapshotIndex } = storeToRefs(snapshotStore) const { styleChangeTimes, snapshotIndex } = storeToRefs(snapshotStore)
const resourceGroupOpt = ref(null) const resourceGroupOpt = ref(null)
const resourceAppOpt = ref(null)
const dvToolbarMain = ref(null) const dvToolbarMain = ref(null)
const { componentData, canvasStyleData, canvasViewInfo, dvInfo, editMode } = const { componentData, canvasStyleData, canvasViewInfo, dvInfo, editMode } =
storeToRefs(dvMainStore) storeToRefs(dvMainStore)
@ -41,6 +42,15 @@ const dvModel = 'dataV'
const outerParamsSetRef = ref(null) const outerParamsSetRef = ref(null)
const fullScreeRef = ref(null) const fullScreeRef = ref(null)
const props = defineProps({
createType: {
type: String,
default: 'create'
}
})
const { createType } = toRefs(props)
const closeEditCanvasName = () => { const closeEditCanvasName = () => {
nameEdit.value = false nameEdit.value = false
if (!inputName.value || !inputName.value.trim()) { if (!inputName.value || !inputName.value.trim()) {
@ -89,11 +99,33 @@ const resourceOptFinish = param => {
saveCanvasWithCheck() saveCanvasWithCheck()
} }
} }
const appOptFinish = param => {
if (param && param.opt === 'newLeaf') {
dvInfo.value.dataState = 'ready'
dvInfo.value.pid = param.pid
dvInfo.value.name = param.name
}
}
const saveCanvasWithCheck = () => { const saveCanvasWithCheck = () => {
const appData = dvMainStore.getAppDataInfo()
if (dvInfo.value.dataState === 'prepare') { if (dvInfo.value.dataState === 'prepare') {
const params = { name: dvInfo.value.name, leaf: true, id: dvInfo.value.pid } if (appData) {
resourceGroupOpt.value.optInit('leaf', params, 'newLeaf', true) //
const params = {
base: {
pid: '',
name: dvInfo.value.name,
datasetFolderPid: null,
datasetFolderName: dvInfo.value.name
},
appData: appData
}
resourceAppOpt.value.init(params)
} else {
const params = { name: dvInfo.value.name, leaf: true, id: dvInfo.value.pid }
resourceGroupOpt.value.optInit('leaf', params, 'newLeaf', true)
}
return return
} }
saveResource() saveResource()
@ -345,10 +377,12 @@ const fullScreenPreview = () => {
ref="resourceGroupOpt" ref="resourceGroupOpt"
/> />
<de-app-apply <de-app-apply
ref="resourceGroupOpt" ref="resourceAppOpt"
:component-data="componentData" :component-data="componentData"
:dv-info="dvInfo" :dv-info="dvInfo"
:canvas-view-info="canvasViewInfo" :canvas-view-info="canvasViewInfo"
cur-canvas-type="dataV"
@saveApp="appOptFinish"
></de-app-apply> ></de-app-apply>
</div> </div>
<de-fullscreen ref="fullScreeRef" show-position="dvEdit"></de-fullscreen> <de-fullscreen ref="fullScreeRef" show-position="dvEdit"></de-fullscreen>

View File

@ -45,6 +45,7 @@ export const dvMainStore = defineStore('dataVisualization', {
inMobile: false, inMobile: false,
firstLoadMap: [], firstLoadMap: [],
canvasStyleData: { ...deepCopy(DEFAULT_CANVAS_STYLE_DATA_DARK), backgroundColor: null }, canvasStyleData: { ...deepCopy(DEFAULT_CANVAS_STYLE_DATA_DARK), backgroundColor: null },
appData: {}, //应用信息
// 当前展示画布缓存数据 // 当前展示画布缓存数据
componentDataCache: null, componentDataCache: null,
// PC布局画布组件数据 // PC布局画布组件数据
@ -245,7 +246,12 @@ export const dvMainStore = defineStore('dataVisualization', {
setCanvasViewInfo(canvasViewInfo) { setCanvasViewInfo(canvasViewInfo) {
this.canvasViewInfo = canvasViewInfo this.canvasViewInfo = canvasViewInfo
}, },
getAppDataInfo() {
return this.appData
},
setAppDataInfo(appDataInfo) {
this.appData = appDataInfo
},
setCurComponent({ component, index }) { setCurComponent({ component, index }) {
if (!component && this.curComponent) { if (!component && this.curComponent) {
this.curComponent['editing'] = false this.curComponent['editing'] = false
@ -1205,13 +1211,14 @@ export const dvMainStore = defineStore('dataVisualization', {
this.canvasState[key] = value this.canvasState[key] = value
} }
}, },
createInit(dvType, resourceId?, pid?, watermarkInfo?) { createInit(dvType, resourceId?, pid?, watermarkInfo?, preName) {
const optName = dvType === 'dashboard' ? '新建仪表板' : '新建数据大屏' const optName = dvType === 'dashboard' ? '新建仪表板' : '新建数据大屏'
const name = preName ? preName : optName
this.dvInfo = { this.dvInfo = {
dataState: 'prepare', dataState: 'prepare',
optType: null, optType: null,
id: resourceId, id: resourceId,
name: optName, name: name,
pid: pid, pid: pid,
type: dvType, type: dvType,
status: 1, status: 1,

View File

@ -25,7 +25,7 @@ import {
import { snapshotStoreWithOut } from '@/store/modules/data-visualization/snapshot' import { snapshotStoreWithOut } from '@/store/modules/data-visualization/snapshot'
import { deepCopy } from '@/utils/utils' import { deepCopy } from '@/utils/utils'
const dvMainStore = dvMainStoreWithOut() const dvMainStore = dvMainStoreWithOut()
const { curBatchOptComponents, dvInfo, canvasStyleData, componentData, canvasViewInfo } = const { curBatchOptComponents, dvInfo, canvasStyleData, componentData, canvasViewInfo, appData } =
storeToRefs(dvMainStore) storeToRefs(dvMainStore)
const snapshotStore = snapshotStoreWithOut() const snapshotStore = snapshotStoreWithOut()
@ -346,6 +346,7 @@ export async function canvasSave(callBack) {
canvasStyleData: JSON.stringify(canvasStyleData.value), canvasStyleData: JSON.stringify(canvasStyleData.value),
componentData: JSON.stringify(componentDataToSave), componentData: JSON.stringify(componentDataToSave),
canvasViewInfo: canvasViewInfo.value, canvasViewInfo: canvasViewInfo.value,
appData: appData.value,
...dvInfo.value, ...dvInfo.value,
watermarkInfo: null watermarkInfo: null
} }
@ -518,6 +519,10 @@ export async function decompressionPre(params, callBack) {
.then(response => { .then(response => {
const deTemplateDataTemp = response.data const deTemplateDataTemp = response.data
const sourceComponentData = JSON.parse(deTemplateDataTemp['componentData']) const sourceComponentData = JSON.parse(deTemplateDataTemp['componentData'])
let appData
if (deTemplateDataTemp['appData']) {
appData = JSON.parse(deTemplateDataTemp['appData'])
}
sourceComponentData.forEach(componentItem => { sourceComponentData.forEach(componentItem => {
// 2 为基础版本 此处需要增加仪表板矩阵密度 // 2 为基础版本 此处需要增加仪表板矩阵密度
if ( if (
@ -535,7 +540,10 @@ export async function decompressionPre(params, callBack) {
canvasStyleData: sourceCanvasStyle, canvasStyleData: sourceCanvasStyle,
componentData: sourceComponentData, componentData: sourceComponentData,
canvasViewInfo: deTemplateDataTemp['canvasViewInfo'], canvasViewInfo: deTemplateDataTemp['canvasViewInfo'],
appData: deTemplateDataTemp['appData'] appData: appData,
baseInfo: {
preName: deTemplateDataTemp.name
}
} }
}) })
.catch(e => { .catch(e => {

View File

@ -5,8 +5,9 @@ import { Plus, Search } from '@element-plus/icons-vue'
import { useI18n } from '@/hooks/web/useI18n' import { useI18n } from '@/hooks/web/useI18n'
import { useAppStoreWithOut } from '@/store/modules/app' import { useAppStoreWithOut } from '@/store/modules/app'
import _ from 'lodash' import _ from 'lodash'
import { getDatasetTree } from '@/api/dataset' import { getDatasetTree, getDatasourceList } from '@/api/dataset'
import { ElFormItem, FormInstance } from 'element-plus-secondary' import { ElFormItem, FormInstance } from 'element-plus-secondary'
import type { DataSource } from '@/views/visualized/data/dataset/form/util'
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
@ -14,10 +15,12 @@ const props = withDefaults(
modelValue?: string | number modelValue?: string | number
stateObj: any stateObj: any
viewId: string viewId: string
sourceType: string
}>(), }>(),
{ {
datasetTree: () => [], datasetTree: () => [],
themes: 'dark' themes: 'dark',
sourceType: 'dataset'
} }
) )
@ -29,9 +32,13 @@ const datasetTree = ref<Tree[]>([])
const toolTip = computed(() => { const toolTip = computed(() => {
return props.themes === 'dark' ? 'ndark' : 'dark' return props.themes === 'dark' ? 'ndark' : 'dark'
}) })
const sourceName = computed(() => (props.sourceType === 'datasource' ? '数据源' : '数据集'))
const initDataset = () => { const initDataset = () => {
loadingDatasetTree.value = true loadingDatasetTree.value = true
getDatasetTree({}) const method = props.sourceType === 'datasource' ? getDatasourceList : getDatasetTree
method({})
.then(res => { .then(res => {
datasetTree.value = (res as unknown as Tree[]) || [] datasetTree.value = (res as unknown as Tree[]) || []
}) })
@ -110,7 +117,7 @@ const exist = computed(() => {
const selectedNodeName = computed(() => { const selectedNodeName = computed(() => {
if (!exist.value) { if (!exist.value) {
return '数据集不存在' return sourceName.value + '不存在'
} }
return selectedNode.value?.name return selectedNode.value?.name
}) })
@ -212,7 +219,7 @@ onMounted(() => {
v-model="selectedNodeName" v-model="selectedNodeName"
readonly readonly
class="data-set-dark" class="data-set-dark"
placeholder="请选择数据集" :placeholder="'请选择' + sourceName"
> >
<template #suffix> <template #suffix>
<el-icon class="input-arrow-icon" :class="{ reverse: _popoverShow }"> <el-icon class="input-arrow-icon" :class="{ reverse: _popoverShow }">
@ -227,7 +234,7 @@ onMounted(() => {
<el-container :class="themes"> <el-container :class="themes">
<el-header> <el-header>
<div class="m-title" :class="{ dark: themes === 'dark' }"> <div class="m-title" :class="{ dark: themes === 'dark' }">
<div>{{ t('dataset.datalist') }}</div> <div>{{ sourceName }}</div>
<el-button type="primary" link class="refresh-btn" @click="refresh"> <el-button type="primary" link class="refresh-btn" @click="refresh">
{{ t('commons.refresh') }} {{ t('commons.refresh') }}
</el-button> </el-button>
@ -244,7 +251,7 @@ onMounted(() => {
<el-main :class="{ dark: themes === 'dark' }"> <el-main :class="{ dark: themes === 'dark' }">
<el-scrollbar max-height="252px" always> <el-scrollbar max-height="252px" always>
<div class="m-loading" v-if="loadingDatasetTree" v-loading="loadingDatasetTree"></div> <div class="m-loading" v-if="loadingDatasetTree" v-loading="loadingDatasetTree"></div>
<div class="empty-info" v-if="showEmptyInfo">暂无数据集</div> <div class="empty-info" v-if="showEmptyInfo">暂无{{ sourceName }}</div>
<!-- <div class="empty-info" v-if="showEmptySearchInfo">暂无相关数据</div>--> <!-- <div class="empty-info" v-if="showEmptySearchInfo">暂无相关数据</div>-->
<el-tree <el-tree
:class="{ dark: themes === 'dark' }" :class="{ dark: themes === 'dark' }"
@ -294,7 +301,7 @@ onMounted(() => {
<el-footer v-if="!isDataEaseBi"> <el-footer v-if="!isDataEaseBi">
<div class="footer-container"> <div class="footer-container">
<el-button type="primary" :icon="Plus" link class="add-btn" @click="addDataset"> <el-button type="primary" :icon="Plus" link class="add-btn" @click="addDataset">
新建数据集 新建{{ sourceName }}
</el-button> </el-button>
</div> </div>
</el-footer> </el-footer>

View File

@ -3,7 +3,7 @@
:title="'保存应用'" :title="'保存应用'"
v-model="state.appApplyDrawer" v-model="state.appApplyDrawer"
custom-class="de-user-drawer" custom-class="de-user-drawer"
size="600px" size="500px"
direction="rtl" direction="rtl"
> >
<div class="app-export"> <div class="app-export">
@ -12,16 +12,17 @@
:model="state.form" :model="state.form"
:rules="state.rule" :rules="state.rule"
class="de-form-item" class="de-form-item"
size="middle"
label-width="180px" label-width="180px"
label-position="top" label-position="top"
> >
<div class="de-row-rules" style="margin: 0 0 16px"> <div class="de-row-rules" style="margin: 0 0 16px">
<span>基本信息</span> <span>基本信息</span>
</div> </div>
<el-form-item :label="dvPreName + '名称'" prop="appName"> <el-form-item :label="dvPreName + '名称'" prop="name">
<el-input v-model="state.form.name" autocomplete="off" :placeholder="'请输入名称'" /> <el-input v-model="state.form.name" autocomplete="off" :placeholder="'请输入名称'" />
</el-form-item> </el-form-item>
<el-form-item :label="dvPreName + '所在位置'" prop="version"> <el-form-item :label="dvPreName + '所在位置'" prop="pid">
<el-tree-select <el-tree-select
style="width: 100%" style="width: 100%"
@keydown.stop @keydown.stop
@ -44,23 +45,23 @@
</template> </template>
</el-tree-select> </el-tree-select>
</el-form-item> </el-form-item>
<el-form-item :label="'数据集分组名称'" prop="appName"> <el-form-item :label="'数据集分组名称'" prop="datasetFolderName">
<el-input <el-input
v-model="state.form.datasetFolderName" v-model="state.form.datasetFolderName"
autocomplete="off" autocomplete="off"
:placeholder="'请输入名称'" :placeholder="'请输入名称'"
/> />
</el-form-item> </el-form-item>
<el-form-item label="数据集分组位置" prop="version"> <el-form-item label="数据集分组位置" prop="datasetFolderPid">
<el-tree-select <el-tree-select
style="width: 100%" style="width: 100%"
@keydown.stop @keydown.stop
@keyup.stop @keyup.stop
v-model="state.form.datasetFolderPid" v-model="state.form.datasetFolderPid"
:data="state.dvTree" :data="state.dsTree"
:props="state.propsTree" :props="state.propsTree"
@node-click="dvTreeSelect" @node-click="dsTreeSelect"
:filter-method="dvTreeFilterMethod" :filter-method="dsTreeFilterMethod"
:render-after-expand="false" :render-after-expand="false"
filterable filterable
> >
@ -77,13 +78,47 @@
<div class="de-row-rules" style="margin: 0 0 16px"> <div class="de-row-rules" style="margin: 0 0 16px">
<span>数据源信息</span> <span>数据源信息</span>
</div> </div>
<div>数据源信息配置</div> <el-row class="datasource-link">
<el-row class="head">
<el-col :span="11">应用数据源</el-col><el-col :span="2"></el-col
><el-col :span="11">系统数据源</el-col>
</el-row>
<el-row
:key="index"
class="content"
v-for="(appDatasource, index) in state.appData.datasourceInfo"
>
<el-col :span="11">
<el-select style="width: 100%" v-model="appDatasource.name" disabled>
<el-option
:key="appDatasource.name"
:label="appDatasource.name"
:value="appDatasource.name"
>
</el-option>
</el-select> </el-col
><el-col :span="2" class="icon-center">
<Icon style="width: 20px; height: 20px" name="dv-link-target" /></el-col
><el-col :span="11">
<dataset-select
ref="datasetSelector"
v-model="appDatasource.systemDatasourceId"
style="flex: 1"
:state-obj="state"
themes="light"
source-type="datasource"
@add-ds-window="addDsWindow"
view-id="0"
/>
</el-col>
</el-row>
</el-row>
</el-form> </el-form>
</div> </div>
<template #footer> <template #footer>
<div class="apply" style="width: 100%"> <div class="apply" style="width: 100%">
<el-button secondary @click="close">{{ $t('commons.cancel') }} </el-button> <el-button secondary @click="close">{{ $t('commons.cancel') }} </el-button>
<el-button type="primary" @click="downloadApp">保存</el-button> <el-button type="primary" @click="saveApp">保存</el-button>
</div> </div>
</template> </template>
</el-drawer> </el-drawer>
@ -98,11 +133,20 @@ import {
ElInput, ElInput,
ElTreeSelect ElTreeSelect
} from 'element-plus-secondary' } from 'element-plus-secondary'
import { computed, reactive, ref, toRefs } from 'vue' import { computed, PropType, reactive, ref, toRefs } from 'vue'
import { useI18n } from '@/hooks/web/useI18n' import { useI18n } from '@/hooks/web/useI18n'
import { queryTreeApi } from '@/api/visualization/dataVisualization'
import { BusiTreeNode, BusiTreeRequest } from '@/models/tree/TreeNode'
import { getDatasetTree } from '@/api/dataset'
import DatasetSelect from '@/views/chart/components/editor/dataset-select/DatasetSelect.vue'
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
import { storeToRefs } from 'pinia'
import { deepCopy } from '@/utils/utils'
const { t } = useI18n() const { t } = useI18n()
const emits = defineEmits(['closeDraw', 'downLoadApp']) const emits = defineEmits(['closeDraw', 'saveApp'])
const appSaveForm = ref(null) const appSaveForm = ref(null)
const dvMainStore = dvMainStoreWithOut()
const { dvInfo, appData } = storeToRefs(dvMainStore)
const props = defineProps({ const props = defineProps({
componentData: { componentData: {
@ -113,34 +157,42 @@ const props = defineProps({
type: Object, type: Object,
required: true required: true
}, },
dvInfo: { curCanvasType: {
type: Object, type: String,
required: true required: true
}, },
dvType: { themes: {
type: String, type: String as PropType<EditorTheme>,
default: 'dashboard' default: 'dark'
} }
}) })
const { componentData, canvasViewInfo, dvInfo, dvType } = toRefs(props) const { componentData, canvasViewInfo, curCanvasType, themes } = toRefs(props)
const dvPreName = computed(() => (dvType.value === 'dashboard' ? '仪表板' : '数据大屏')) const dvPreName = computed(() => (curCanvasType.value === 'dashboard' ? '仪表板' : '数据大屏'))
const addDsWindow = () => {
// do addDsWindow
const url = '#/data/datasource?opt=create'
window.open(url, '_blank')
}
const state = reactive({ const state = reactive({
appApplyDrawer: false, appApplyDrawer: false,
dvTree: [], dvTree: [],
dsTree: [],
propsTree: { propsTree: {
label: 'name', label: 'name',
children: 'children', children: 'children',
isLeaf: node => !node.children?.length isLeaf: node => !node.children?.length
}, },
appData: {
datasourceInfo: []
},
form: { form: {
pid: '', pid: '',
name: '新建', name: '新建',
datasetFolderPid: null, datasetFolderPid: null,
datasetFolderName: null, datasetFolderName: null
datasourceMap: {} // ID
}, },
rule: { rule: {
name: [ name: [
@ -173,8 +225,6 @@ const state = reactive({
datasetFolderPid: [ datasetFolderPid: [
{ {
required: true, required: true,
min: 2,
max: 25,
message: '请选择数据集分组所属文件夹', message: '请选择数据集分组所属文件夹',
trigger: 'blur' trigger: 'blur'
} }
@ -182,20 +232,59 @@ const state = reactive({
} }
}) })
const initData = () => {
const request = { busiFlag: curCanvasType.value, leaf: false, weight: 7 }
queryTreeApi(request).then(res => {
const resultTree = res || []
dfs(resultTree as unknown as BusiTreeNode[])
state.dvTree = (resultTree as unknown as BusiTreeNode[]) || []
if (state.dvTree.length && state.dvTree[0].name === 'root' && state.dvTree[0].id === '0') {
state.dvTree[0].name = curCanvasType.value === 'dataV' ? '数据大屏' : '仪表板'
}
})
const requestDs = { leaf: false, weight: 7 } as BusiTreeRequest
getDatasetTree(requestDs).then(res => {
dfs(res as unknown as BusiTreeNode[])
state.dsTree = (res as unknown as BusiTreeNode[]) || []
if (state.dsTree.length && state.dsTree[0].name === 'root' && state.dsTree[0].id === '0') {
state.dsTree[0].name = '数据集'
}
})
}
const dfs = (arr: BusiTreeNode[]) => {
arr.forEach(ele => {
ele['value'] = ele.id
if (ele.children?.length) {
dfs(ele.children)
}
})
}
const init = params => { const init = params => {
console.log('init==')
state.appApplyDrawer = true state.appApplyDrawer = true
state.form = params state.form = params.base
state.appData.datasourceInfo = deepCopy(appData.value?.datasourceInfo)
initData()
} }
const dvTreeFilterMethod = value => { const dvTreeFilterMethod = value => {
state.dvTree = [...state.dvTree].filter(item => item.name.includes(value)) state.dvTree = [...state.dvTree].filter(item => item.name.includes(value))
} }
const dsTreeFilterMethod = value => {
state.dsTree = [...state.dsTree].filter(item => item.name.includes(value))
}
const dvTreeSelect = element => { const dvTreeSelect = element => {
state.form.pid = element.id state.form.pid = element.id
} }
const dsTreeSelect = element => {
state.form.datasetFolderPid = element.id
}
const close = () => { const close = () => {
emits('closeDraw') emits('closeDraw')
state.appApplyDrawer = false state.appApplyDrawer = false
@ -204,8 +293,14 @@ const close = () => {
const saveApp = () => { const saveApp = () => {
appSaveForm.value?.validate(valid => { appSaveForm.value?.validate(valid => {
if (valid) { if (valid) {
const viewIds = [] // datasource
const dsIds = [] appData.value['datasourceInfo'] = state.appData.datasourceInfo
dvInfo.value['pid'] = state.form.pid
dvInfo.value['name'] = state.form.name
dvInfo.value['datasetFolderPid'] = state.form.datasetFolderPid
dvInfo.value['datasetFolderName'] = state.form.datasetFolderName
dvInfo.value['dataState'] = 'ready'
emits('saveApp')
} else { } else {
return false return false
} }
@ -241,6 +336,7 @@ defineExpose({
line-height: 22px; line-height: 22px;
padding-left: 10px; padding-left: 10px;
margin: 24px 0 16px 0; margin: 24px 0 16px 0;
color: var(--ed-text-color-regular);
&::before { &::before {
content: ''; content: '';
@ -253,4 +349,37 @@ defineExpose({
background: #3370ff; background: #3370ff;
} }
} }
.custom-tree-node {
display: flex;
align-items: center;
span {
margin-left: 8.75px;
width: 120px;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
}
.datasource-link {
color: var(--ed-text-color-regular);
font-size: 12px;
font-weight: 500;
width: 100%;
.head {
width: 100%;
}
.content {
width: 100%;
margin-top: 8px;
}
}
.icon-center {
padding: 0 8px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
</style> </style>

View File

@ -326,13 +326,15 @@ onMounted(async () => {
console.error('can not find watermark info') console.error('can not find watermark info')
} }
let deTemplateData let deTemplateData
let preName
if (createType === 'template') { if (createType === 'template') {
const templateParamsApply = JSON.parse(Base64.decode(decodeURIComponent(templateParams + ''))) const templateParamsApply = JSON.parse(Base64.decode(decodeURIComponent(templateParams + '')))
await decompressionPre(templateParamsApply, result => { await decompressionPre(templateParamsApply, result => {
deTemplateData = result deTemplateData = result
preName = deTemplateData.baseInfo?.preName
}) })
} }
dvMainStore.createInit('dataV', null, pid, watermarkBaseInfo) dvMainStore.createInit('dataV', null, pid, watermarkBaseInfo, preName)
nextTick(() => { nextTick(() => {
state.canvasInitStatus = true state.canvasInitStatus = true
dvMainStore.setDataPrepareState(true) dvMainStore.setDataPrepareState(true)
@ -342,6 +344,7 @@ onMounted(async () => {
dvMainStore.setComponentData(deTemplateData['componentData']) dvMainStore.setComponentData(deTemplateData['componentData'])
dvMainStore.setCanvasStyle(deTemplateData['canvasStyleData']) dvMainStore.setCanvasStyle(deTemplateData['canvasStyleData'])
dvMainStore.setCanvasViewInfo(deTemplateData['canvasViewInfo']) dvMainStore.setCanvasViewInfo(deTemplateData['canvasViewInfo'])
dvMainStore.setAppDataInfo(deTemplateData['appData'])
setTimeout(() => { setTimeout(() => {
snapshotStore.recordSnapshotCache() snapshotStore.recordSnapshotCache()
}, 1500) }, 1500)

View File

@ -46,7 +46,7 @@ public interface DataVisualizationApi {
@PostMapping("/saveCanvas") @PostMapping("/saveCanvas")
@DePermit(value = {"#p0.pid + ':manage'"}, busiFlag = "#p0.type") @DePermit(value = {"#p0.pid + ':manage'"}, busiFlag = "#p0.type")
@Operation(summary = "画布保存") @Operation(summary = "画布保存")
String saveCanvas(@RequestBody DataVisualizationBaseRequest request); String saveCanvas(@RequestBody DataVisualizationBaseRequest request) throws Exception;
@PostMapping("/updateCanvas") @PostMapping("/updateCanvas")
@DePermit(value = {"#p0.id + ':manage'"}, busiFlag = "#p0.type") @DePermit(value = {"#p0.id + ':manage'"}, busiFlag = "#p0.type")

View File

@ -1,6 +1,9 @@
package io.dataease.api.visualization.request; package io.dataease.api.visualization.request;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import io.dataease.api.visualization.vo.DataVisualizationVO; import io.dataease.api.visualization.vo.DataVisualizationVO;
import io.dataease.api.visualization.vo.VisualizationExport2AppVO;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
@ -32,11 +35,22 @@ public class DataVisualizationBaseRequest extends DataVisualizationVO {
private String source; private String source;
// 定时报告id // 定时报告id
@JsonSerialize(using = ToStringSerializer.class)
private Long reportId; private Long reportId;
// 定时报告任务id // 定时报告任务id
@JsonSerialize(using = ToStringSerializer.class)
private Long taskId; private Long taskId;
private VisualizationExport2AppVO appData;
@JsonSerialize(using = ToStringSerializer.class)
// 数据集分组PID
private Long datasetFolderPid;
// 数据集分组名称
private String datasetFolderName;
public DataVisualizationBaseRequest(Long id,String busiFlag) { public DataVisualizationBaseRequest(Long id,String busiFlag) {
this.busiFlag = busiFlag; this.busiFlag = busiFlag;

View File

@ -79,4 +79,9 @@ public class AppCoreDatasourceVO implements Serializable {
*/ */
private String taskStatus; private String taskStatus;
/**
* 映射系统数据源ID
*/
private Long systemDatasourceId;
} }

View File

@ -17,27 +17,27 @@ public class VisualizationExport2AppVO {
private String visualizationViewsInfo; private String visualizationViewsInfo;
private String chartViewsInfo; List<AppCoreChartViewVO> chartViewsInfo;
private String datasetGroupsInfo; List<AppCoreDatasetGroupVO> datasetGroupsInfo;
private String datasetTablesInfo; List<AppCoreDatasetTableVO> datasetTablesInfo;
private String datasetTableFieldsInfo; List<AppCoreDatasetTableFieldVO> datasetTableFieldsInfo;
private String datasourceInfo; List<AppCoreDatasourceVO> datasourceInfo;
private String datasourceTaskInfo; List<AppCoreDatasourceTaskVO> datasourceTaskInfo;
private String linkJumps; List<VisualizationLinkJumpVO> linkJumps;
private String linkJumpInfos; List<VisualizationLinkJumpInfoVO> linkJumpInfos;
private String linkJumpTargetInfos; List<VisualizationLinkJumpTargetViewInfoVO> linkJumpTargetInfos;
private String linkages; List<VisualizationLinkageVO> linkages;
private String linkageFields; List<VisualizationLinkageFieldVO> linkageFields;
public VisualizationExport2AppVO() { public VisualizationExport2AppVO() {
@ -58,20 +58,18 @@ public class VisualizationExport2AppVO {
List<VisualizationLinkJumpTargetViewInfoVO> linkJumpTargetViewVOInfo, List<VisualizationLinkJumpTargetViewInfoVO> linkJumpTargetViewVOInfo,
List<VisualizationLinkageVO> linkagesVOInfo, List<VisualizationLinkageVO> linkagesVOInfo,
List<VisualizationLinkageFieldVO> linkageFieldVOInfo) { List<VisualizationLinkageFieldVO> linkageFieldVOInfo) {
List<Object> empty = new ArrayList<>();
Gson gson = new Gson();
this.checkStatus = true; this.checkStatus = true;
this.checkMes = "success"; this.checkMes = "success";
this.chartViewsInfo = gson.toJson(chartViewVOInfo != null ? chartViewVOInfo : empty); this.chartViewsInfo = chartViewVOInfo != null ? chartViewVOInfo : new ArrayList<>();
this.datasetGroupsInfo = gson.toJson(datasetGroupVOInfo != null ? datasetGroupVOInfo : empty); this.datasetGroupsInfo = datasetGroupVOInfo != null ? datasetGroupVOInfo : new ArrayList<>();
this.datasetTablesInfo = gson.toJson(datasetTableVOInfo != null ? datasetTableVOInfo : empty); this.datasetTablesInfo = datasetTableVOInfo != null ? datasetTableVOInfo : new ArrayList<>();
this.datasetTableFieldsInfo = gson.toJson(datasetTableFieldVOInfo != null ? datasetTableFieldVOInfo : empty); this.datasetTableFieldsInfo = datasetTableFieldVOInfo != null ? datasetTableFieldVOInfo : new ArrayList<>();
this.datasourceTaskInfo = gson.toJson(datasourceTaskVOInfo != null ? datasourceTaskVOInfo : empty); this.datasourceTaskInfo = datasourceTaskVOInfo != null ? datasourceTaskVOInfo : new ArrayList<>();
this.datasourceInfo = gson.toJson(datasourceVOInfo != null ? datasourceVOInfo : empty); this.datasourceInfo = datasourceVOInfo != null ? datasourceVOInfo : new ArrayList<>();
this.linkJumps = gson.toJson(linkJumpVOInfo != null ? linkJumpVOInfo : empty); this.linkJumps = linkJumpVOInfo != null ? linkJumpVOInfo : new ArrayList<>();
this.linkJumpInfos = gson.toJson(linkJumpInfoVOInfo != null ? linkJumpInfoVOInfo : empty); this.linkJumpInfos = linkJumpInfoVOInfo != null ? linkJumpInfoVOInfo : new ArrayList<>();
this.linkJumpTargetInfos = gson.toJson(linkJumpTargetViewVOInfo != null ? linkJumpTargetViewVOInfo : empty); this.linkJumpTargetInfos = linkJumpTargetViewVOInfo != null ? linkJumpTargetViewVOInfo : new ArrayList<>();
this.linkages = gson.toJson(linkagesVOInfo != null ? linkagesVOInfo : empty); this.linkages = linkagesVOInfo != null ? linkagesVOInfo : new ArrayList<>();
this.linkageFields = gson.toJson(linkageFieldVOInfo != null ? linkageFieldVOInfo : empty); this.linkageFields = linkageFieldVOInfo != null ? linkageFieldVOInfo : new ArrayList<>();
} }
} }