Merge branch 'dev-v2' into pr@dev-v2_dzz

This commit is contained in:
dataeaseShu 2023-11-23 10:18:17 +08:00
commit ea19bc38b0
63 changed files with 1835 additions and 88 deletions

View File

@ -211,7 +211,7 @@ public class DatasetDataManage {
}
public Long getDatasetTotal(Long datasetGroupId) throws Exception {
DatasetGroupInfoDTO dto = datasetGroupManage.get(datasetGroupId, null);
DatasetGroupInfoDTO dto = datasetGroupManage.getForCount(datasetGroupId);
if (StringUtils.equalsIgnoreCase(dto.getNodeType(), "dataset")) {
return getDatasetTotal(dto);
}

View File

@ -346,6 +346,30 @@ public class DatasetGroupManage {
}
}
public DatasetGroupInfoDTO getForCount(Long id) throws Exception {
CoreDatasetGroup coreDatasetGroup = coreDatasetGroupMapper.selectById(id);
if (coreDatasetGroup == null) {
return null;
}
DatasetGroupInfoDTO dto = new DatasetGroupInfoDTO();
BeanUtils.copyBean(dto, coreDatasetGroup);
if (StringUtils.equalsIgnoreCase(dto.getNodeType(), "dataset")) {
dto.setUnion(JsonUtil.parseList(coreDatasetGroup.getInfo(), new TypeReference<>() {
}));
// 获取field
List<DatasetTableFieldDTO> dsFields = datasetTableFieldManage.selectByDatasetGroupId(id);
List<DatasetTableFieldDTO> allFields = dsFields.stream().map(ele -> {
DatasetTableFieldDTO datasetTableFieldDTO = new DatasetTableFieldDTO();
BeanUtils.copyBean(datasetTableFieldDTO, ele);
datasetTableFieldDTO.setFieldShortName(ele.getDataeaseName());
return datasetTableFieldDTO;
}).collect(Collectors.toList());
dto.setAllFields(allFields);
}
return dto;
}
public DatasetGroupInfoDTO get(Long id, String type) throws Exception {
CoreDatasetGroup coreDatasetGroup = coreDatasetGroupMapper.selectById(id);
if (coreDatasetGroup == null) {

View File

@ -11,7 +11,6 @@ import io.dataease.menu.dao.auto.mapper.CoreMenuMapper;
import io.dataease.utils.BeanUtils;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
@ -19,8 +18,6 @@ import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static io.dataease.constant.CacheConstant.OrgCacheConstant.CORE_MENU_CACHE;
@Component
public class MenuManage {
@ -39,7 +36,7 @@ public class MenuManage {
return convertTree(treeNodes);
}
// @Cacheable(cacheNames = CORE_MENU_CACHE, key = "'-dataease-'")
// @Cacheable(cacheNames = CORE_MENU_CACHE, key = "'-dataease-'")
public List<CoreMenu> coreMenus() {
QueryWrapper<CoreMenu> wrapper = new QueryWrapper<>();
wrapper.orderByAsc("menu_sort");
@ -67,7 +64,7 @@ public class MenuManage {
if (CollectionUtil.isNotEmpty(children = menuTreeNode.getChildren())) {
vo.setChildren(convertTree(children));
}
if (CollectionUtil.isNotEmpty(children) || menuTreeNode.getType() != 1) {
if (CollectionUtil.isNotEmpty(vo.getChildren()) || menuTreeNode.getType() != 1) {
result.add(vo);
}
}
@ -91,6 +88,14 @@ public class MenuManage {
}
private boolean isXpackMenu(CoreMenu coreMenu) {
return coreMenu.getId().equals(7L) || coreMenu.getPid().equals(7L) || coreMenu.getId().equals(14L) || coreMenu.getId().equals(17L) || coreMenu.getId().equals(18L);
return coreMenu.getId().equals(7L)
|| coreMenu.getPid().equals(7L)
|| coreMenu.getId().equals(14L)
|| coreMenu.getId().equals(17L)
|| coreMenu.getId().equals(18L)
|| coreMenu.getId().equals(21L)
|| coreMenu.getPid().equals(21L)
|| coreMenu.getId().equals(25L)
|| coreMenu.getId().equals(26L);
}
}

View File

@ -64,7 +64,7 @@ public class SysParameterManage {
public Map<String,String> groupVal(String groupKey) {
QueryWrapper<CoreSysSetting> queryWrapper = new QueryWrapper<>();
queryWrapper.likeLeft("pkey", groupKey);
queryWrapper.like("pkey", groupKey);
List<CoreSysSetting> sysSettings = coreSysSettingMapper.selectList(queryWrapper);
if (!CollectionUtils.isEmpty(sysSettings)) {
return sysSettings.stream().collect(Collectors.toMap(CoreSysSetting::getPkey, CoreSysSetting::getPval));

View File

@ -1,10 +1,10 @@
package io.dataease.template.manage;
import com.fasterxml.jackson.core.type.TypeReference;
import io.dataease.api.template.dto.TemplateManageFileDTO;
import io.dataease.api.template.dto.TemplateMarketDTO;
import io.dataease.api.template.request.TemplateMarketSearchRequest;
import io.dataease.api.template.response.MarketBaseResponse;
import io.dataease.api.template.response.*;
import io.dataease.api.template.vo.MarketApplicationSpecVO;
import io.dataease.api.template.vo.MarketMetaDataVO;
import io.dataease.api.template.vo.TemplateCategoryVO;
import io.dataease.exception.DEException;
import io.dataease.system.manage.SysParameterManage;
@ -16,6 +16,7 @@ import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
@ -28,8 +29,12 @@ import java.util.stream.Collectors;
public class TemplateMarketManage {
private final static String POSTS_API = "/api/content/posts?page=0&size=2000";
private final static String POSTS_API_V2 = "/apis/api.store.halo.run/v1alpha1/applications?keyword=&priceMode=&sort=latestReleaseTimestamp%2Cdesc&type=THEME&deVersion=V2&templateType=&label=&page=1&size=2000";
private final static String CATEGORIES_API = "/api/content/categories";
private final static String TEMPLATE_META_DATA_URL = "/upload/meta_data.json";
@Resource
private SysParameterManage sysParameterManage;
@ -41,7 +46,7 @@ public class TemplateMarketManage {
if (StringUtils.isNotEmpty(templateUrl)) {
String sufUrl = sysParameterManage.groupVal("template.").get("template.url");
String templateInfo = HttpClientUtil.get(sufUrl + templateUrl, null);
return JsonUtil.parseObject(templateInfo,TemplateManageFileDTO.class);
return JsonUtil.parseObject(templateInfo, TemplateManageFileDTO.class);
} else {
return null;
}
@ -57,26 +62,46 @@ public class TemplateMarketManage {
return HttpClientUtil.get(url, config);
}
public MarketBaseResponse searchTemplate(TemplateMarketSearchRequest request) {
public MarketBaseResponse searchTemplate() {
try {
Map<String,String> templateParams = sysParameterManage.groupVal("template.");
String result = marketGet(templateParams.get("template.url") + POSTS_API, templateParams.get("template.accessKey"));
TypeReference<List<TemplateMarketDTO>> market = new TypeReference<>() {
};
List<TemplateMarketDTO> postsResult = JsonUtil.parseList(result,market);
return new MarketBaseResponse(templateParams.get("template.url"), postsResult);
Map<String, String> templateParams = sysParameterManage.groupVal("template.");
String result = marketGet(templateParams.get("template.url") + POSTS_API_V2, null);
MarketTemplateV2BaseResponse postsResult = JsonUtil.parseObject(result, MarketTemplateV2BaseResponse.class);
return baseResponseV2Trans(postsResult, templateParams.get("template.url"));
} catch (Exception e) {
DEException.throwException(e);
}
return null;
}
public List<String> getCategories() {
Map<String,String> templateParams = sysParameterManage.groupVal("template.");
private MarketBaseResponse baseResponseV2Trans(MarketTemplateV2BaseResponse v2BaseResponse, String url) {
Map<String, String> categoriesMap = getCategoriesBaseV2();
List<TemplateMarketDTO> contents = new ArrayList<>();
v2BaseResponse.getItems().stream().forEach(marketTemplateV2ItemResult -> {
MarketApplicationSpecVO spec = marketTemplateV2ItemResult.getApplication().getSpec();
contents.add(new TemplateMarketDTO(spec.getReadmeName(), spec.getDisplayName(), spec.getScreenshots().get(0).getUrl(), spec.getLinks().get(0).getUrl(), categoriesMap.get(spec.getLabel())));
});
return new MarketBaseResponse(url, contents);
}
public MarketBaseResponse searchTemplateV1() {
try {
Map<String, String> templateParams = sysParameterManage.groupVal("template.");
String result = marketGet(templateParams.get("template.url") + POSTS_API, templateParams.get("template.accessKey"));
MarketTemplateBaseResponse postsResult = JsonUtil.parseObject(result, MarketTemplateBaseResponse.class);
MarketBaseResponse response = new MarketBaseResponse(templateParams.get("template.url"), postsResult.getData().getContent());
return response;
} catch (Exception e) {
DEException.throwException(e);
}
return null;
}
public List<String> getCategoriesV1() {
Map<String, String> templateParams = sysParameterManage.groupVal("template.");
String resultStr = marketGet(templateParams.get("template.url") + CATEGORIES_API, templateParams.get("template.accessKey"));
TypeReference<List<TemplateCategoryVO>> market = new TypeReference<>() {
};
List<TemplateCategoryVO> categories = JsonUtil.parseList(resultStr,market);
MarketCategoryBaseResponse categoryBaseResponse = JsonUtil.parseObject(resultStr, MarketCategoryBaseResponse.class);
List<TemplateCategoryVO> categories = categoryBaseResponse.getData();
if (CollectionUtils.isNotEmpty(categories)) {
return categories.stream().filter(item -> !"应用系列".equals(item.getName())).sorted(Comparator.comparing(TemplateCategoryVO::getPriority)).map(TemplateCategoryVO::getName).collect(Collectors.toList());
} else {
@ -84,4 +109,23 @@ public class TemplateMarketManage {
}
}
public List<String> getCategories() {
return getCategoriesV2().stream().map(MarketMetaDataVO::getLabel)
.collect(Collectors.toList());
}
public Map<String, String> getCategoriesBaseV2() {
Map<String, String> categories = getCategoriesV2().stream()
.collect(Collectors.toMap(MarketMetaDataVO::getSlug, MarketMetaDataVO::getLabel));
return categories;
}
public List<MarketMetaDataVO> getCategoriesV2() {
Map<String, String> templateParams = sysParameterManage.groupVal("template.");
String resultStr = marketGet(templateParams.get("template.url") + TEMPLATE_META_DATA_URL, null);
MarketMetaDataBaseResponse metaData = JsonUtil.parseObject(resultStr, MarketMetaDataBaseResponse.class);
List<MarketMetaDataVO> categories = metaData.getLabels();
return categories;
}
}

View File

@ -0,0 +1,31 @@
package io.dataease.template.service;
import io.dataease.api.template.TemplateMarketApi;
import io.dataease.api.template.response.MarketBaseResponse;
import io.dataease.template.manage.TemplateMarketManage;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* @author : WangJiaHao
* @date : 2023/11/17 13:20
*/
@RestController
@RequestMapping("/templateMarket")
public class TemplateMarketService implements TemplateMarketApi {
@Resource
private TemplateMarketManage templateMarketManage;
@Override
public MarketBaseResponse searchTemplate() {
return templateMarketManage.searchTemplate();
}
@Override
public List<String> categories() {
return templateMarketManage.getCategories();
}
}

View File

@ -255,8 +255,8 @@ public class DataVisualizationServer implements DataVisualizationApi {
templateData = templateFileInfo.getComponentData();
dynamicData = templateFileInfo.getDynamicData();
staticResource = templateFileInfo.getStaticResource();
name = request.getName();
dvType = request.getType();
name = templateFileInfo.getName();
dvType = templateFileInfo.getDvType();
}
// 解析动态数据
Map<String, String> dynamicDataMap = JsonUtil.parseObject(dynamicData, Map.class);

View File

@ -1,6 +1,6 @@
spring:
datasource:
url: jdbc:mysql://localhost:3306/dataease?autoReconnect=false&useUnicode=true&characterEncoding=UTF-8&characterSetResults=UTF-8&zeroDateTimeBehavior=convertToNull&useSSL=false
url: jdbc:mysql://localhost:3306/dataease?autoReconnect=false&useUnicode=true&characterEncoding=UTF-8&characterSetResults=UTF-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: 123456
messages:

View File

@ -18,6 +18,12 @@ i18n_menu.datasource=\u6570\u636E\u6E90
i18n_menu.user=\u7528\u6237\u7BA1\u7406
i18n_menu.org=\u7EC4\u7EC7\u7BA1\u7406
i18n_menu.auth=\u6743\u9650\u914D\u7F6E
i18n_menu.sync=\u540C\u6B65\u7BA1\u7406
i18n_menu.summary=\u6982\u89C8
i18n_menu.ds=\u6570\u636E\u6E90\u7BA1\u7406
i18n_menu.task=\u4EFB\u52A1\u7BA1\u7406
i18n_menu.embedded=\u5D4C\u5165\u5F0F\u7BA1\u7406
i18n_menu.platform=\u5E73\u53F0\u5BF9\u63A5
i18n_field_name_repeat=\u6709\u91CD\u590D\u5B57\u6BB5\u540D\uFF1A
i18n_pid_not_eq_id=\u79FB\u52A8\u76EE\u6807\u4E0D\u80FD\u662F\u81EA\u5DF1\u6216\u5B50\u76EE\u5F55
i18n_ds_name_exists=\u8BE5\u5206\u7EC4\u4E0B\u540D\u79F0\u91CD\u590D

View File

@ -0,0 +1,13 @@
import request from '@/config/axios'
export function searchMarket() {
return request.get({
url: '/templateMarket/search'
})
}
export function getCategories() {
return request.get({
url: '/templateMarket/categories'
})
}

View File

@ -1,14 +0,0 @@
import request from '@/config/axios'
export function searchMarket(data) {
return request.post({
url: '/template/market/search',
data
})
}
export function getCategories() {
return request.post({
url: '/template/market/categories'
})
}

View File

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M17.0855 1.00006H4C3.73478 1.00006 3.48043 1.10542 3.29289 1.29295C3.10536 1.48049 3 1.73484 3 2.00006V22.0001C3 22.2653 3.10536 22.5196 3.29289 22.7072C3.48043 22.8947 3.73478 23.0001 4 23.0001H20C20.2652 23.0001 20.5196 22.8947 20.7071 22.7072C20.8946 22.5196 21 22.2653 21 22.0001V4.91556C21.0001 4.65044 20.8949 4.39614 20.7075 4.20856L17.793 1.29306C17.7001 1.20014 17.5898 1.12643 17.4684 1.07616C17.347 1.02588 17.2169 1.00002 17.0855 1.00006ZM7 8.5C7 8.22386 7.22386 8 7.5 8H16.5C16.7761 8 17 8.22386 17 8.5V9.5C17 9.77614 16.7761 10 16.5 10H7.5C7.22386 10 7 9.77614 7 9.5V8.5ZM11 12.5C11 12.2239 11.2239 12 11.5 12H16.5C16.7761 12 17 12.2239 17 12.5V17.5C17 17.7761 16.7761 18 16.5 18H11.5C11.2239 18 11 17.7761 11 17.5V12.5ZM7 12.5C7 12.2239 7.22386 12 7.5 12H8.5C8.77614 12 9 12.2239 9 12.5V17.5C9 17.7761 8.77614 18 8.5 18H7.5C7.22386 18 7 17.7761 7 17.5V12.5Z" fill="#FF8800"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1,4 @@
<svg width="18" height="16" viewBox="0 0 18 16" xmlns="http://www.w3.org/2000/svg">
<path d="M9.22368 0.225297L17.6143 5.56475C17.8715 5.72848 17.8715 6.10407 17.6143 6.2678L9.22369 11.6073C9.0872 11.6941 8.91278 11.6941 8.77629 11.6073L0.385719 6.2678C0.128427 6.10407 0.128427 5.72848 0.385718 5.56475L8.77629 0.225297C8.91278 0.138443 9.0872 0.138443 9.22368 0.225297ZM2.9377 5.91628L8.99999 9.77409L15.0623 5.91628L8.99999 2.05846L2.9377 5.91628Z" />
<path d="M1.16368 9.57137C0.967505 9.45108 0.71096 9.51261 0.590674 9.70879L0.155081 10.4192C0.0347947 10.6154 0.0963176 10.8719 0.292496 10.9922L7.92179 15.6701C8.4571 15.9983 9.12617 15.9983 9.66148 15.6701L17.2908 10.9922C17.487 10.8719 17.5485 10.6154 17.4282 10.4192L16.9926 9.70879C16.8723 9.51261 16.6158 9.45108 16.4196 9.57137L8.90053 14.1816C8.83371 14.2226 8.74955 14.2226 8.68274 14.1816L1.16368 9.57137Z" />
</svg>

After

Width:  |  Height:  |  Size: 883 B

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg class="icon" width="200px" height="200.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M314.49 3.082H157.692C70.793 3.082 0 74.073 0 161.271V864.32c0 87.198 70.792 158.189 157.592 158.189H314.49c86.9 0 157.592-70.991 157.592-158.189V702.652h-72.98V864.32c0 46.93-37.98 84.91-84.612 84.91H157.693c-46.731 0-84.613-38.18-84.613-84.91V161.27c0-46.929 37.981-84.91 84.613-84.91h156.896c46.73 0 84.612 38.18 84.612 84.91v159.68h72.98V161.27c-0.1-87.197-70.793-158.188-157.692-158.188z m546.252 0H703.746c-86.899 0-157.592 70.991-157.592 158.189v159.68h72.98V161.27c0-46.93 37.981-84.911 84.612-84.911h156.896c46.731 0 84.613 38.18 84.613 84.91v703.05c0 46.93-37.981 84.91-84.613 84.91H703.746c-46.73 0-84.612-38.18-84.612-84.91V702.652h-72.98V864.32c0 87.198 70.792 158.189 157.592 158.189h156.896c86.9 0 157.592-70.991 157.592-158.189V161.27c0.1-87.197-70.692-158.188-157.492-158.188z m0 0" /><path d="M191.995 485.602H461.84c-0.596-27.74-23.465-50.31-51.304-50.31H186.129c-28.238 0-51.305 23.166-51.305 51.503v105.492c0 28.337 23.067 51.504 51.305 51.504h224.506c28.237 0 51.305-23.167 51.305-51.504v-1.093H191.995V485.602z m0 0" /><path d="M395.423 512.447h230.074v46.035H395.423z" /><path d="M556.395 485.602H826.34v105.492H556.395v1.094c0 28.337 23.067 51.503 51.305 51.503h224.506c28.238 0 51.305-23.166 51.305-51.503V486.696c0-28.337-23.067-51.503-51.305-51.503H607.7c-27.84 0.099-50.708 22.669-51.305 50.41z m0 0" /></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1,3 @@
<svg width="20" height="16" viewBox="0 0 20 16" xmlns="http://www.w3.org/2000/svg">
<path d="M10.8333 14.25H7.08325C3.63147 14.25 0.833252 11.4518 0.833252 8.00003C0.833252 5.52942 2.26677 3.39363 4.34741 2.37905L5.18374 3.82761C3.60043 4.54958 2.49992 6.14629 2.49992 8.00003C2.49992 10.5313 4.55195 12.5834 7.08325 12.5834H10.8333V11.4642C10.8333 11.2226 11.0291 11.0267 11.2708 11.0267C11.3767 11.0267 11.4791 11.0651 11.5588 11.1349L13.7903 13.0874C13.9721 13.2466 13.9906 13.5229 13.8315 13.7048C13.8187 13.7194 13.8049 13.7332 13.7903 13.7459L11.5588 15.6985C11.377 15.8576 11.1006 15.8391 10.9415 15.6573C10.8717 15.5776 10.8333 15.4752 10.8333 15.3692V14.25ZM9.16659 3.41671V4.53589C9.16659 4.64187 9.12813 4.74424 9.05834 4.82399C8.89923 5.00583 8.62284 5.02426 8.44099 4.86515L6.20955 2.91263C6.19493 2.89984 6.18118 2.88609 6.16839 2.87147C6.00928 2.68963 6.0277 2.41324 6.20955 2.25413L8.44099 0.301607C8.52075 0.231824 8.62312 0.193359 8.72909 0.193359C8.97071 0.193359 9.16659 0.389235 9.16659 0.630859V1.75004H12.9166C16.3684 1.75004 19.1666 4.54826 19.1666 8.00004C19.1666 10.4527 17.7539 12.5753 15.6978 13.5987L14.862 12.1512C16.4207 11.4195 17.4999 9.8358 17.4999 8.00004C17.4999 5.46874 15.4479 3.41671 12.9166 3.41671H9.16659Z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -6,7 +6,7 @@ import { snapshotStoreWithOut } from '@/store/modules/data-visualization/snapsho
import { changeSizeWithScale } from '@/utils/changeComponentsSizeWithScale'
import { useEmitt } from '@/hooks/web/useEmitt'
const dvMainStore = dvMainStoreWithOut()
const { canvasStyleData } = storeToRefs(dvMainStore)
const { canvasStyleData, editMode } = storeToRefs(dvMainStore)
const snapshotStore = snapshotStoreWithOut()
const scale = ref(60)
@ -42,6 +42,9 @@ const reposition = () => {
let lastWheelNum = 0
const handleMouseWheel = e => {
if (editMode.value === 'preview') {
return
}
let dvMain = document.getElementById('dv-main-center')
let dvMainLeftSlide = document.getElementById('dv-main-left-sidebar')
let areaLeftWidth = dvMainLeftSlide.clientWidth
@ -86,6 +89,7 @@ onUnmounted(() => {
:min="10"
:max="200"
:controls="false"
@change="handleScaleChange()"
class="scale-input-number"
>
<template #suffix> % </template>

View File

@ -101,7 +101,7 @@
style="width: 100%"
>
<el-option
v-for="item in state.sourceLinkageInfo.targetViewFields"
v-for="item in sourceLinkageInfoFilter"
:key="item.id"
:label="item.name"
:value="item.id"
@ -184,7 +184,7 @@
<script lang="ts" setup>
import { queryVisualizationJumpInfo } from '@/api/visualization/linkJump'
import { reactive, ref, nextTick, watch } from 'vue'
import { reactive, ref, nextTick, watch, computed } from 'vue'
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
import { storeToRefs } from 'pinia'
import { ElMessage } from 'element-plus-secondary'
@ -379,6 +379,22 @@ const linkageFieldAdaptor = async data => {
}
}
}
const sourceLinkageInfoFilter = computed(() => {
if (state.sourceLinkageInfo.targetViewFields) {
const curCheckAllAxisStr =
JSON.stringify(state.curLinkageViewInfo.xAxis) +
JSON.stringify(state.curLinkageViewInfo.xAxisExt) +
JSON.stringify(state.curLinkageViewInfo.yAxis) +
JSON.stringify(state.curLinkageViewInfo.yAxisExt)
return state.sourceLinkageInfo.targetViewFields.filter(item =>
curCheckAllAxisStr.includes(item.id)
)
} else {
return []
}
})
const targetViewCheckedChange = data => {
nextTick(() => {
linkageInfoTree.value.setCurrentKey(data.targetViewId)

View File

@ -335,7 +335,6 @@ const calcData = (view: Chart, callback) => {
}
const initCurFields = chartDetails => {
curFields.value = []
dataRowFiledName.value = []
dataRowSelect.value = {}
dataRowNameSelect.value = {}
@ -347,10 +346,12 @@ const initCurFields = chartDetails => {
JSON.stringify(chartDetails.yAxisExt)
chartDetails.data.sourceFields.forEach(field => {
if (checkAllAxisStr.indexOf(field.id) > -1) {
curFields.value.push(field)
dataRowFiledName.value.push(`[${field.name}]`)
}
})
if (checkAllAxisStr.indexOf('"记录数*"') > -1) {
dataRowFiledName.value.push(`[记录数*]`)
}
// Get the corresponding relationship between id and value
const nameIdMap = chartDetails.data.fields.reduce((pre, next) => {
pre[next['dataeaseName']] = next['id']

View File

@ -952,6 +952,7 @@ export default {
field_can_not_empty: '字段不能为空',
conditions_can_not_empty: '字段的条件不能为空,若无条件,请直接删除该字段',
remark: '备注',
remark_placeholder: '备注限制50个字符',
remark_show: '显示备注',
remark_edit: '编辑备注',
remark_bg_color: '背景填充',

View File

@ -0,0 +1,22 @@
export const loadScript = (url: string, jsId?: string) => {
return new Promise(function (resolve, reject) {
const scriptId = jsId || 'de-fit2cloud-script-id'
let dom = document.getElementById(scriptId)
if (dom) {
dom.parentElement?.removeChild(dom)
dom = null
}
const script = document.createElement('script')
script.id = scriptId
script.onload = function () {
return resolve(null)
}
script.onerror = function () {
return reject(new Error('Load script from '.concat(url, ' failed')))
}
script.src = url
const head = document.head || document.getElementsByTagName('head')[0]
;(document.body || head).appendChild(script)
})
}

View File

@ -18,9 +18,8 @@ import { storeToRefs } from 'pinia'
import { BASE_VIEW_CONFIG } from '../util/chart'
import { cloneDeep, defaultsDeep } from 'lodash-es'
const dvMainStore = dvMainStoreWithOut()
const { dvInfo } = storeToRefs(dvMainStore)
const { nowPanelTrackInfo, nowPanelJumpInfo } = storeToRefs(dvMainStore)
const { nowPanelTrackInfo, nowPanelJumpInfo, dvInfo } = storeToRefs(dvMainStore)
const { t } = useI18n()
const linkJumpRef = ref(null)
@ -154,7 +153,7 @@ const noSenior = computed(() => {
const linkJumpActiveChange = () => {
//
const params = {
sourceDvId: chart.value.sceneId,
sourceDvId: dvInfo.value.id,
sourceViewId: chart.value.id,
activeStatus: chart.value.jumpActive
}
@ -164,7 +163,7 @@ const linkJumpActiveChange = () => {
}
const linkageActiveChange = () => {
const params = {
dvId: chart.value.sceneId,
dvId: dvInfo.value.id,
sourceViewId: chart.value.id,
activeStatus: chart.value.linkageActive
}

View File

@ -358,7 +358,14 @@ watch(
>
<div @keydown.stop @keyup.stop>
<el-form-item :label="t('chart.remark')" class="form-item" prop="chartShowName">
<el-input type="textarea" autosize v-model="tempRemark" :maxlength="50" clearable />
<el-input
type="textarea"
autosize
v-model="tempRemark"
:maxlength="50"
clearable
:placeholder="t('chart.remark_placeholder')"
/>
</el-form-item>
</div>
<template #footer>

View File

@ -78,6 +78,7 @@ export class Map extends L7PlotChartView<ChoroplethOptions, Choropleth> {
}
const geoJson = cloneDeep(await getGeoJsonFile(areaId))
let options: ChoroplethOptions = {
preserveDrawingBuffer: true,
map: {
type: 'mapbox',
style: 'blank'

View File

@ -101,15 +101,19 @@ const state = reactive({
templateCreatePid: 0
})
const dvSvgType = computed(() =>
curCanvasType.value === 'dashboard' ? 'dv-dashboard-spine' : 'dv-screen-spine'
)
state.resourceTypeList = [
{
label: newResourceLabel,
svgName: curCanvasType.value === 'dashboard' ? 'dv-dashboard-spine' : 'dv-screen-spine',
label: '空白新建',
svgName: dvSvgType.value,
command: 'newLeaf'
},
{
label: '模版新建',
svgName: curCanvasType.value === 'dashboard' ? 'dv-dashboard-spine' : 'dv-screen-spine',
label: '使用模版新建',
svgName: 'dv-use-template',
command: 'newFromTemplate'
},
{
@ -247,7 +251,7 @@ const addOperation = (
window.open(baseUrl, '_blank')
}
} else if (cmd === 'newFromTemplate') {
state.templateCreatePid = data.id
state.templateCreatePid = data?.id
// newFromTemplate
resourceCreateOpt.value.optInit()
} else {
@ -337,11 +341,28 @@ defineExpose({
<Icon name="dv-new-folder" />
</el-icon>
</el-tooltip>
<el-tooltip :content="newResourceLabel" placement="top" effect="dark">
<el-icon class="custom-icon btn" @click="addOperation('newLeaf', null, 'leaf', true)">
<el-dropdown popper-class="menu-outer-dv_popper" trigger="click">
<el-icon class="custom-icon btn" @click.stop>
<Icon name="icon_file-add_outlined" />
</el-icon>
</el-tooltip>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="addOperation('newLeaf', null, 'leaf', true)">
<el-icon class="handle-icon">
<Icon :name="dvSvgType"></Icon>
</el-icon>
空白新建
</el-dropdown-item>
<el-dropdown-item @click="addOperation('newFromTemplate', null, 'leaf', true)">
<el-icon class="handle-icon">
<Icon name="dv-use-template"></Icon>
</el-icon>
使用模版新建
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
<el-input
@ -459,6 +480,7 @@ defineExpose({
line-height: 24px;
}
.custom-icon {
font-size: 20px;
&.btn {
color: #3370ff;
}
@ -539,3 +561,10 @@ defineExpose({
}
}
</style>
<style lang="less">
.menu-outer-dv_popper {
width: 140px;
margin-top: -2px !important;
}
</style>

View File

@ -98,7 +98,7 @@ watch(
:weight="dvInfo.weight"
:resource-type="dvInfo.type"
/>
<el-button v-if="dvInfo.weight > 6" type="primary" @click="dvEdit()">
<el-button class="custom-button" v-if="dvInfo.weight > 6" type="primary" @click="dvEdit()">
<template #icon>
<icon name="icon_edit_outlined"></icon>
</template>
@ -176,4 +176,8 @@ watch(
font-size: 16px;
color: #646a73;
}
.custom-button {
margin-left: 12px;
}
</style>

View File

@ -2,7 +2,7 @@
<div class="info-template-container">
<div class="info-template-header">
<div class="info-template-title">
<span>基础设置</span>
<span>{{ curTitle }}</span>
</div>
<div>
<el-button type="primary" @click="edit">{{ t('commons.edit') }}</el-button>
@ -40,7 +40,7 @@
</div>
</template>
<script lang="ts" setup>
import { ref, defineProps, PropType } from 'vue'
import { ref, defineProps, PropType, computed } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
import { SettingRecord, ToolTipRecord } from './SettingTemplate'
const { t } = useI18n()
@ -52,9 +52,29 @@ const props = defineProps({
labelTooltips: {
type: Array as PropType<ToolTipRecord[]>,
default: () => []
},
settingData: {
type: Array as PropType<SettingRecord[]>,
default: () => []
},
settingTitle: {
type: String,
default: '基础设置'
}
})
const curTitle = computed(() => {
return props.settingTitle
})
const loadList = () => {
if (props.settingData?.length) {
props.settingData.forEach(item => {
settingList.value.push(item)
})
}
}
const settingList = ref([] as SettingRecord[])
const loadBasic = () => {
@ -136,6 +156,9 @@ const init = () => {
if (props.settingKey === 'email') {
loadEmail()
}
if (props.settingData?.length) {
loadList()
}
}
const pwdItem = ref({})

View File

@ -1,5 +1,10 @@
<template>
<InfoTemplate :label-tooltips="tooltips" setting-key="basic" @edit="edit" />
<InfoTemplate
:label-tooltips="tooltips"
setting-key="basic"
setting-title="基础设置"
@edit="edit"
/>
<basic-edit ref="editor" />
</template>

View File

@ -1,5 +1,10 @@
<template>
<InfoTemplate :label-tooltips="tooltips" setting-key="email" @edit="edit" />
<InfoTemplate
:label-tooltips="tooltips"
setting-title="邮件设置"
setting-key="email"
@edit="edit"
/>
<email-edit ref="editor" />
</template>

View File

@ -0,0 +1,494 @@
<template>
<el-row style="width: 100%">
<el-row style="display: table; width: 100%">
<el-col style="float: left" :class="state.asideActive ? 'aside-active' : 'aside-inActive'">
<el-icon v-show="!state.asideActive" class="insert" @click="asideActiveChange(true)"
><DArrowRight
/></el-icon>
<el-row v-show="state.asideActive" style="padding: 12px 12px 0">
<el-row style="align-items: center">
<el-icon class="insert" @click="closePreview()"><Close /></el-icon>
<span class="main-title">{{ t('visualization.template_preview') }}</span>
<el-icon class="insert" @click="asideActiveChange(false)"><DArrowLeft /></el-icon>
</el-row>
<el-row class="margin-top16 search-area">
<el-input
v-model="state.searchText"
size="small"
prefix-icon="Search"
class="title-name-search"
:placeholder="t('visualization.enter_template_name_tips')"
clearable="true"
/>
<el-icon
class="insert-filter filter-icon-span"
:class="state.extFilterActive ? 'filter-icon-active' : ''"
@click="extFilterActiveChange()"
><Filter
/></el-icon>
</el-row>
<el-row v-show="state.extFilterActive">
<el-select
v-model="state.marketActiveTab"
class="margin-top16"
size="small"
placeholder="请选择"
>
<el-option v-for="item in state.marketTabs" :key="item" :label="item" :value="item" />
</el-select>
</el-row>
<el-divider />
</el-row>
<el-row
v-show="state.asideActive"
class="aside-list"
:class="state.extFilterActive ? 'aside-list-filter-active' : ''"
>
<template-market-preview-item
v-for="templateItem in state.currentMarketTemplateShowList"
v-show="templateItem.showFlag"
:key="templateItem.id"
:template="templateItem"
:base-url="state.baseUrl"
:active="active(templateItem)"
@previewTemplate="previewTemplate"
/>
<el-row v-show="!state.hasResult" class="custom-position">
<div style="text-align: center">
<Icon name="no_result" style="margin-bottom: 16px; font-size: 75px"></Icon>
<br />
<span>{{ t('commons.no_result') }}</span>
</div>
</el-row>
</el-row>
</el-col>
<el-col
style="float: left"
class="main-area"
:class="state.asideActive ? 'main-area-active' : ''"
>
<el-row>
<span v-if="state.curTemplate" class="template-title">{{ state.curTemplate.title }}</span>
<div style="flex: 1; text-align: right">
<el-button
style="float: right"
type="primary"
size="small"
@click="templateApply(state.curTemplate)"
>{{ t('visualization.apply_this_template') }}</el-button
>
</div>
</el-row>
<el-row class="img-main">
<img style="height: 100%" :src="state.templatePreviewUrl" alt="" />
</el-row>
</el-col>
</el-row>
</el-row>
</template>
<script setup lang="ts">
import { searchMarket, getCategories } from '@/api/templateMarket'
import { onMounted, reactive, watch } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
import TemplateMarketPreviewItem from '@/views/template-market/component/TemplateMarketPreviewItem.vue'
const { t } = useI18n()
const props = defineProps({
previewId: {
type: String,
default: null
}
})
const emits = defineEmits(['templateApply', 'closeDialog', 'closePreview'])
const state = reactive({
hasResult: true,
extFilterActive: false,
asideActive: true,
previewVisible: false,
templatePreviewUrl: null,
marketTabs: null,
marketActiveTab: null,
searchText: null,
panelGroupList: [],
curApplyTemplate: null,
folderSelectShow: false,
baseUrl: 'https://dataease.io/templates',
currentMarketTemplateShowList: [],
networkStatus: true,
curTemplate: null
})
watch(
() => state.marketActiveTab,
value => {
initTemplateShow()
}
)
watch(
() => state.searchText,
value => {
initTemplateShow()
}
)
watch(
() => props.previewId,
value => {
state.currentMarketTemplateShowList.forEach(template => {
if (value === template.id) {
previewTemplate(template)
}
})
}
)
const initMarketTemplate = () => {
searchMarket()
.then(rsp => {
state.baseUrl = rsp.data.baseUrl
state.currentMarketTemplateShowList = rsp.data.contents
state.hasResult = true
})
.catch(() => {
state.networkStatus = false
})
getCategories()
.then(rsp => {
state.marketTabs = rsp.data
state.marketActiveTab = state.marketTabs[0]
})
.catch(() => {
state.networkStatus = false
})
if (props.previewId) {
state.currentMarketTemplateShowList.forEach(template => {
if (props.previewId === template.id) {
previewTemplate(template)
}
})
}
}
const getGroupTree = () => {
// do getGroupTree
}
const normalizer = node => {
// children=null
if (node.children === null || node.children === 'null') {
delete node.children
}
}
const templateApply = template => {
emits('templateApply', template)
}
const closeDialog = () => {
emits('closeDialog')
}
const handleClick = item => {
//handleClick
}
const initTemplateShow = () => {
state.hasResult = false
state.currentMarketTemplateShowList.forEach(template => {
template.showFlag = templateShow(template)
if (template.showFlag) {
state.hasResult = true
}
})
}
const templateShow = templateItem => {
let categoryMarch = false
let searchMarch = false
templateItem.categories.forEach(category => {
if (category.name === state.marketActiveTab) {
categoryMarch = true
}
})
if (!state.searchText || templateItem.title.indexOf(state.searchText) > -1) {
searchMarch = true
}
return categoryMarch && searchMarch
}
const previewTemplate = template => {
state.curTemplate = template
if (template.thumbnail.indexOf('http') > -1) {
state.templatePreviewUrl = template.thumbnail
} else {
state.templatePreviewUrl = state.baseUrl + template.thumbnail
}
}
const asideActiveChange = prop => {
state.asideActive = prop
}
const extFilterActiveChange = () => {
state.extFilterActive = !state.extFilterActive
state.marketActiveTab = state.marketTabs[0]
}
const closePreview = () => {
emits('closePreview')
}
const active = template => {
return state.curTemplate && state.curTemplate.id === template.id
}
onMounted(() => {
initMarketTemplate()
getGroupTree()
})
</script>
<style lang="less" scoped>
.aside-list {
padding: 0px 12px 12px 12px;
width: 100%;
height: calc(100vh - 200px);
overflow-x: hidden;
overflow-y: auto;
}
.aside-list-filter-active {
height: calc(100vh - 250px);
}
.template-main {
border-radius: 4px;
box-shadow: 0 0 2px 0 rgba(31, 31, 31, 0.15), 0 1px 2px 0 rgba(31, 31, 31, 0.15);
border: solid 2px #fff;
padding-bottom: 24px;
min-height: calc(100vh - 190px);
}
.market-main {
padding: 24px;
}
.title-left {
float: left;
font-size: 20px;
font-weight: 500;
line-height: 28px;
}
.title-right {
float: right;
width: 320px;
}
.dialog-footer-self {
text-align: center;
}
.search-button-self {
text-align: left;
padding-left: 10px;
}
.topbar-icon-active {
cursor: pointer;
transition: 0.1s;
border-radius: 3px;
font-size: 22px;
background-color: rgb(245, 245, 245);
&:active {
color: #000;
border-color: #3a8ee6;
background-color: red;
outline: 0;
}
&:hover {
background-color: rgba(31, 35, 41, 0.1);
color: #3a8ee6;
}
}
.custom-position {
height: 70vh;
flex: 1;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
flex-flow: row nowrap;
color: #646a73;
font-weight: 400;
}
.aside-active {
width: 206px;
height: calc(100vh - 56px);
overflow-x: hidden;
overflow-y: auto;
background-color: var(--ContentBG, #ffffff);
}
.aside-inActive {
position: relative;
width: 0px;
}
.main-area-active {
width: calc(100% - 206px) !important;
}
.main-area {
width: 100%;
padding: 24px;
text-align: center;
height: calc(100vh - 56px);
transition: 0.5s;
}
.title-name-search {
width: 140px;
float: left;
}
.icon20 {
font-size: 20px !important;
}
.main-title {
width: 135px;
margin-left: 8px;
font-weight: 500;
font-size: 16px;
line-height: 24px;
color: var(--TextPrimary, #1f2329);
}
.insert-filter {
display: inline-block;
font-weight: 400 !important;
font-family: PingFang SC;
line-height: 1;
white-space: nowrap;
cursor: pointer;
color: var(--TextPrimary, #1f2329);
-webkit-appearance: none;
text-align: center;
box-sizing: border-box;
outline: 0;
margin: 0;
transition: 0.1s;
border-radius: 3px;
&:active {
color: #000;
border-color: #3a8ee6;
background-color: red;
outline: 0;
}
&:hover {
background-color: rgba(31, 35, 41, 0.1);
color: #3a8ee6;
}
}
.insert {
display: inline-block;
font-weight: 400 !important;
font-family: PingFang SC;
line-height: 1;
white-space: nowrap;
cursor: pointer;
color: #646a73;
-webkit-appearance: none;
text-align: center;
box-sizing: border-box;
outline: 0;
margin: 0;
transition: 0.1s;
border-radius: 3px;
&:active {
color: #000;
border-color: #3a8ee6;
background-color: red;
outline: 0;
}
&:hover {
background-color: rgba(31, 35, 41, 0.1);
color: #3a8ee6;
}
}
.template-title {
float: left;
font-weight: 500;
font-size: 20px;
line-height: 28px;
margin-bottom: 24px;
color: var(--TextPrimary, #1f2329);
}
.margin-top16 {
margin-top: 16px;
}
.img-main {
display: inherit;
border-radius: 4px;
background: #0f1114;
overflow-x: auto;
overflow-y: hidden;
height: calc(100% - 50px) !important;
}
.open-button {
cursor: pointer;
font-size: 30px;
position: absolute;
left: 0;
top: 16px;
z-index: 2;
}
//.open-button:hover{
// transition: 0.5s;
// width: 50px;
//}
.open-button:hover {
color: #3a8ee6;
}
.filter-icon-span {
float: left;
border: 1px solid #dcdfe6;
width: 32px;
height: 32px;
border-radius: 4px;
padding: 7px;
margin-left: 8px;
}
.filter-icon-active {
border: 1px solid #3370ff;
color: #3370ff;
}
.filter-icon-active {
border: 1px solid #3370ff;
color: #3370ff;
}
.search-area {
width: 100%;
position: relative;
}
</style>

View File

@ -0,0 +1,122 @@
<template>
<div class="testcase-template">
<div class="template-img" :style="classBackground" @click.stop="templateInnerPreview" />
<el-row class="bottom-area">
<el-row>
<span class="demonstration">{{ template.title }}</span>
</el-row>
</el-row>
<el-row class="template-button">
<el-button size="mini" style="width: 141px" @click="templateInnerPreview">{{
t('visualization.preview')
}}</el-button>
<el-button size="mini" style="width: 141px" type="primary" @click="apply">{{
t('visualization.apply')
}}</el-button>
</el-row>
</div>
</template>
<script setup lang="ts">
import { useI18n } from '@/hooks/web/useI18n'
import { computed } from 'vue'
import { imgUrlTrans } from '@/utils/imgUtils'
const { t } = useI18n()
const emits = defineEmits(['templateApply', 'templatePreview'])
const props = defineProps({
template: {
type: Object,
default() {
return {}
}
},
baseUrl: {
type: String
},
width: {
type: Number
}
})
const classBackground = computed(() => {
return {
width: props.width + 'px',
height: props.width * 0.58 + 'px',
background: `url(${imgUrlTrans(thumbnailUrl.value)}) no-repeat`,
'background-size': `100% 100%`
}
})
const thumbnailUrl = computed(() => {
if (props.template.thumbnail.indexOf('http') > -1) {
return props.template.thumbnail
} else {
return props.baseUrl + props.template.thumbnail
}
})
const apply = () => {
emits('templateApply', props.template)
}
const templateInnerPreview = e => {
emits('templatePreview', props.template.id)
}
</script>
<style scoped lang="less">
.testcase-template {
position: relative;
display: inline-block;
margin: 0;
box-shadow: 0 0 2px 0 rgba(31, 31, 31, 0.15), 0 1px 2px 0 rgba(31, 31, 31, 0.15);
border: solid 2px #fff;
box-sizing: border-box;
border-radius: 4px;
width: 100%;
}
.demonstration {
display: block;
font-size: 16px;
text-align: left;
margin-left: 12px;
margin-top: 12px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: var(--TextPrimary, #1f2329);
}
.template-img {
background-size: 100% 100%;
margin: 0 auto;
border: solid 2px #fff;
box-sizing: border-box;
}
.template-img:hover {
border: solid 1px #4b8fdf;
border-radius: 4px;
color: deepskyblue;
cursor: pointer;
}
.testcase-template:hover ::v-deep .template-button {
display: inline;
}
.template-button {
display: none;
text-align: center;
position: absolute;
bottom: 5px;
left: 0px;
width: 100%;
}
.bottom-area {
height: 75px;
}
</style>

View File

@ -0,0 +1,105 @@
<template>
<div
:class="[
{
['template-item-main-active']: active
},
'template-item-main'
]"
@click.stop="previewTemplate"
>
<div class="template-item-img" :style="classBackground" />
<span class="demonstration">{{ template.title }}</span>
</div>
</template>
<script lang="ts" setup>
import { imgUrlTrans } from '@/utils/imgUtils'
import { computed } from 'vue'
const emits = defineEmits(['previewTemplate'])
const props = defineProps({
template: {
type: Object,
default() {
return {}
}
},
baseUrl: {
type: String
},
active: {
type: Boolean,
required: false,
default: false
}
})
const classBackground = computed(() => {
return {
background: `url(${imgUrlTrans(thumbnailUrl.value)}) no-repeat`,
'background-size': `100% 100%`
}
})
const thumbnailUrl = computed(() => {
if (props.template.thumbnail.indexOf('http') > -1) {
return props.template.thumbnail
} else {
return props.baseUrl + props.template.thumbnail
}
})
const previewTemplate = () => {
emits('previewTemplate', props.template)
}
</script>
<style scoped lang="less">
.template-item-main {
margin: 0 0 12px 0;
position: relative;
box-sizing: border-box;
width: 182px;
height: 116px;
background-color: var(--ContentBG, #ffffff);
border: 1px solid #dee0e3;
border-radius: 4px;
flex: none;
order: 0;
flex-grow: 0;
cursor: pointer;
overflow: hidden;
}
.template-item-main-active {
border: 2px solid #3370ff !important;
}
.template-item-img {
position: absolute;
width: 182px;
height: 86px;
left: 0px;
top: 0px;
}
.demonstration {
position: absolute;
width: 166px;
height: 20px;
left: 8px;
top: 91px;
font-style: normal;
font-weight: 400;
font-size: 12px;
line-height: 20px;
display: block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.template-item-main:hover {
border: solid 1px #3370ff;
}
</style>

View File

@ -1,35 +1,382 @@
<template>
<el-row class="custom-position template-main">
{{ t('visualization.market_network_tips') }}
<el-row class="outer-body" v-loading="state.loading">
<!--预览模式-->
<market-preview
v-show="previewModel"
:preview-id="state.templatePreviewId"
@closePreview="closePreview"
@templateApply="templateApply"
/>
<!--列表模式-->
<el-row v-show="!previewModel" class="market-main">
<el-row>
<el-col :span="12">
<span class="title-left">{{ t('visualization.template_market') }}</span>
</el-col>
<el-col :span="12">
<el-input
v-model="state.searchText"
prefix-icon="el-icon-search"
size="small"
class="title-right"
:placeholder="t('visualization.enter_template_name_tips')"
:clearable="true"
/>
</el-col>
</el-row>
<el-row class="custom-tabs-head">
<el-tabs v-model="state.marketActiveTab" @tab-click="handleClick">
<el-tab-pane
v-for="tabItem in state.marketTabs"
:key="tabItem"
:label="tabItem"
:name="tabItem"
/>
</el-tabs>
</el-row>
<el-row
v-show="state.networkStatus && state.hasResult"
id="template-main"
class="template-main"
>
<el-col
v-for="templateItem in state.currentMarketTemplateShowList"
v-show="templateItem.showFlag"
:key="templateItem.id"
style="padding: 24px 12px 0; text-align: center; flex: 0"
:style="{ width: state.templateSpan }"
>
<template-market-item
:key="'outer-' + templateItem.id"
:template="templateItem"
:base-url="state.baseUrl"
:width="state.templateCurWidth"
@templateApply="templateApply"
@templatePreview="templatePreview"
/>
</el-col>
</el-row>
<el-row
v-show="state.networkStatus && !state.hasResult"
class="custom-position template-main"
>
<div style="text-align: center">
<Icon name="no_result" style="margin-bottom: 16px; font-size: 75px"></Icon>
<br />
<span>{{ t('commons.no_result') }}</span>
</div>
</el-row>
<el-row v-show="!state.networkStatus" class="custom-position template-main">
{{ t('visualization.market_network_tips') }}
</el-row>
</el-row>
</el-row>
</template>
<script setup lang="ts">
import { useI18n } from '@/hooks/web/useI18n'
<script setup lang="ts">
import { getCategories, searchMarket } from '@/api/templateMarket'
import elementResizeDetectorMaker from 'element-resize-detector'
import { nextTick, reactive, watch, onMounted, ref } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
import { ElMessage } from 'element-plus-secondary'
import MarketPreview from '@/views/template-market/component/MarketPreview.vue'
import TemplateMarketItem from '@/views/template-market/component/TemplateMarketItem.vue'
import { decompression } from '@/api/visualization/dataVisualization'
import { useCache } from '@/hooks/web/useCache'
const { t } = useI18n()
const { wsCache } = useCache()
const previewModel = ref(false)
const state = reactive({
loading: false,
hasResult: true,
templateMiniWidth: 330,
templateCurWidth: 310,
templateSpan: '25%',
previewVisible: false,
templatePreviewId: '',
marketTabs: null,
marketActiveTab: null,
searchText: null,
dvCreateForm: {
name: null,
pid: null,
nodeType: 'panel',
templateUrl: null,
newFrom: 'new_market_template',
panelType: 'self',
panelStyle: {},
panelData: '[]'
},
panelGroupList: [],
curApplyTemplate: null,
folderSelectShow: false,
baseUrl: 'https://dataease.io/templates',
currentMarketTemplateShowList: [],
networkStatus: true,
rule: {
name: [
{
required: true,
message: t('visualization.template_name_tips'),
trigger: 'blur'
}
],
pid: [
{
required: true,
message: '',
trigger: 'blur'
}
]
}
})
watch(
() => state.marketActiveTab,
value => {
initTemplateShow()
}
)
watch(
() => state.searchText,
value => {
initTemplateShow()
}
)
const closePreview = () => {
previewModel.value = false
}
const initMarketTemplate = () => {
searchMarket()
.then(rsp => {
state.baseUrl = rsp.data.baseUrl
state.currentMarketTemplateShowList = rsp.data.contents
})
.catch(() => {
state.networkStatus = false
})
getCategories()
.then(rsp => {
state.marketTabs = rsp.data
state.marketActiveTab = state.marketTabs[0]
})
.catch(() => {
state.networkStatus = false
})
}
const getGroupTree = () => {
// do getGroupTree
// groupTree({ nodeType: 'folder' }).then(res => {
// state.panelGroupList = res.data
// })
}
const normalizer = node => {
// children=null
if (node.children === null || node.children === 'null') {
delete node.children
}
}
const templateApply = template => {
state.curApplyTemplate = template
state.dvCreateForm.name = template.title
state.dvCreateForm.templateUrl = template.metas.theme_repo
apply()
}
const apply = () => {
if (!state.dvCreateForm.templateUrl) {
ElMessage.warning('未获取模板下载链接请联系模板市场官方')
return false
}
state.loading = true
decompression(state.dvCreateForm)
.then(response => {
state.loading = false
const templateData = response.data
// do create
wsCache.set(`de-template-data`, JSON.stringify(templateData))
const baseUrl =
templateData.type === 'dataV'
? '#/dvCanvas?opt=create&createType=template'
: '#/dashboard?opt=create&createType=template'
window.open(baseUrl, '_blank')
})
.catch(() => {
state.loading = false
})
}
const handleClick = item => {
// do handleClick
}
const initTemplateShow = () => {
let tempHasResult = false
state.currentMarketTemplateShowList.forEach(template => {
template.showFlag = templateShow(template)
if (template.showFlag) {
tempHasResult = true
}
})
if (state.currentMarketTemplateShowList.length > 0) {
state.hasResult = tempHasResult
}
}
const templateShow = templateItem => {
let categoryMarch = false
let searchMarch = false
templateItem.categories.forEach(category => {
if (category.name === state.marketActiveTab) {
categoryMarch = true
}
})
if (!state.searchText || templateItem.title.indexOf(state.searchText) > -1) {
searchMarch = true
}
return categoryMarch && searchMarch
}
const templatePreview = previewId => {
state.templatePreviewId = previewId
previewModel.value = true
}
const newPanel = () => {
// do newPanel
}
onMounted(() => {
initMarketTemplate()
getGroupTree()
const erd = elementResizeDetectorMaker()
const templateMainDom = document.getElementById('template-main')
// div
if (templateMainDom) {
erd.listenTo(templateMainDom, element => {
nextTick(() => {
const curSeparator = Math.trunc(templateMainDom.offsetWidth / state.templateMiniWidth)
state.templateSpan =
100 / Math.trunc(templateMainDom.offsetWidth / state.templateMiniWidth) + '%'
state.templateCurWidth = Math.trunc(templateMainDom.offsetWidth / curSeparator) - 33
})
})
}
})
</script>
<style lang="less" scoped>
.custom-position {
height: 80vh;
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 14px;
flex-flow: row nowrap;
color: #646a73;
font-weight: 400;
.custom-tabs-head {
display: inherit;
padding-bottom: 15px;
::v-deep(.ed-tabs__item) {
font-weight: 400;
font-size: 14px;
}
}
.template-main {
text-align: center;
border-radius: 4px;
padding: 0 12px 24px 12px;
height: calc(100vh - 56px) !important;
height: calc(100vh - 190px) !important;
overflow-x: hidden;
overflow-y: auto;
background-color: var(--ContentBG, #ffffff);
}
.market-main {
padding: 24px;
display: inherit;
}
.title-left {
float: left;
font-size: 20px;
font-weight: 500;
line-height: 28px;
color: var(--TextPrimary, #1f2329);
}
.title-right {
float: right;
width: 320px;
}
.dialog-footer-self {
text-align: right;
}
.search-button-self {
text-align: left;
padding-left: 10px;
}
.topbar-icon-active {
cursor: pointer;
transition: 0.1s;
border-radius: 3px;
font-size: 22px;
background-color: rgb(245, 245, 245);
&:active {
color: #000;
border-color: #3a8ee6;
background-color: red;
outline: 0;
}
&:hover {
background-color: rgba(31, 35, 41, 0.1);
color: #3a8ee6;
}
}
.custom-position {
height: 80vh;
flex: 1;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
flex-flow: row nowrap;
color: #646a73;
font-weight: 400;
}
.outer-body {
display: inherit;
width: 100%;
height: calc(100vh - 56px);
background-color: var(--MainBG, #f5f6f7);
}
.market-dialog-css {
::v-deep(.ed-form-item__label) {
width: 100% !important;
text-align: left;
}
::v-deep(.ed-form-item.is-required:not(.is-no-asterisk) > .ed-form-item__label:before) {
display: none;
}
::v-deep(.ed-form-item.is-required:not(.is-no-asterisk) > .ed-form-item__label::after) {
content: '*';
color: #f54a45;
margin-left: 2px;
}
::v-deep(.ed-form-item__content) {
margin-left: 0 !important;
}
::v-deep(.vue-treeselect__input) {
vertical-align: middle;
}
}
</style>

@ -1 +1 @@
Subproject commit a6f7aa3f644fe258d9ed14cca420c3f6c4aeb1cd
Subproject commit 91e4019972ec96f7f672bb5da2156f54ca9d72ea

View File

@ -1,5 +1,7 @@
package io.dataease.api.chart.dto;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
/**
@ -7,6 +9,7 @@ import lombok.Data;
*/
@Data
public class ChartDimensionDTO {
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
private String value;
}

View File

@ -0,0 +1,21 @@
package io.dataease.api.lark.api;
import io.dataease.api.lark.dto.LarkTokenRequest;
import io.dataease.api.lark.vo.LarkInfoVO;
import io.dataease.api.lark.dto.LarkSettingCreator;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
public interface LarkApi {
@GetMapping("/info")
LarkInfoVO info();
@PostMapping("/create")
void save(@RequestBody LarkSettingCreator creator);
@PostMapping("/token")
String larkToken(@RequestBody LarkTokenRequest request);
}

View File

@ -0,0 +1,17 @@
package io.dataease.api.lark.dto;
import lombok.Data;
import java.io.Serializable;
@Data
public class LarkSettingCreator implements Serializable {
private String appId;
private String appSecret;
private String callBack;
private Boolean enable;
}

View File

@ -0,0 +1,13 @@
package io.dataease.api.lark.dto;
import lombok.Data;
import java.io.Serializable;
@Data
public class LarkTokenRequest implements Serializable {
private String code;
private String state;
}

View File

@ -0,0 +1,19 @@
package io.dataease.api.lark.vo;
import lombok.Data;
import java.io.Serializable;
@Data
public class LarkInfoVO implements Serializable {
private String appId;
private String appSecret;
private String callBack;
private Boolean enable;
}

View File

@ -14,8 +14,8 @@ import java.util.List;
*/
public interface TemplateMarketApi {
@PostMapping("/search")
MarketBaseResponse searchTemplate(@RequestBody TemplateMarketSearchRequest request);
@GetMapping("/search")
MarketBaseResponse searchTemplate();
@GetMapping("/categories")
List<String> categories();

View File

@ -3,10 +3,15 @@ package io.dataease.api.template.dto;
import io.dataease.api.template.vo.MarketCategoryVO;
import io.dataease.api.template.vo.MarketMetasVO;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.ArrayUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@Data
@NoArgsConstructor
public class TemplateMarketDTO {
private String id;
private String title;
@ -18,4 +23,12 @@ public class TemplateMarketDTO {
private Boolean showFlag = true;
private List<MarketCategoryVO> categories;
private MarketMetasVO metas;
public TemplateMarketDTO(String id, String title,String themeRepo,String templateUrl,String categoryName) {
this.id = id;
this.title = title;
this.categories = Arrays.asList(new MarketCategoryVO(categoryName),new MarketCategoryVO("全部"));
this.metas = new MarketMetasVO(templateUrl);
this.thumbnail = themeRepo;
}
}

View File

@ -1,6 +1,7 @@
package io.dataease.api.template.response;
import io.dataease.api.template.dto.TemplateMarketDTO;
import lombok.Data;
import java.util.List;
@ -8,6 +9,7 @@ import java.util.List;
* @author : WangJiaHao
* @date : 2023/11/6 17:43
*/
@Data
public class MarketBaseResponse {
private String baseUrl;
@ -21,4 +23,6 @@ public class MarketBaseResponse {
this.contents = contents;
}
}

View File

@ -0,0 +1,16 @@
package io.dataease.api.template.response;
import io.dataease.api.template.vo.TemplateCategoryVO;
import lombok.Data;
import java.util.List;
/**
* Author: wangjiahao
* Date: 2022/7/15
* Description:
*/
@Data
public class MarketCategoryBaseResponse {
private List<TemplateCategoryVO> data;
}

View File

@ -0,0 +1,21 @@
package io.dataease.api.template.response;
import io.dataease.api.template.vo.MarketMetaDataVO;
import lombok.Data;
import java.util.List;
/**
* Author: wangjiahao
* Date: 2022/7/15
* Description:
*/
@Data
public class MarketMetaDataBaseResponse {
private List<MarketMetaDataVO> deVersion;
private List<MarketMetaDataVO> templateTypes;
private List<MarketMetaDataVO> labels;
}

View File

@ -0,0 +1,17 @@
package io.dataease.api.template.response;
import io.dataease.api.template.dto.TemplateMarketDTO;
import lombok.Data;
import java.util.List;
/**
* @author : WangJiaHao
* @date : 2023/11/17 13:41
*/
@Data
public class MarketTemplateBaseResponse {
private MarketTemplateInnerResult data;
}

View File

@ -0,0 +1,17 @@
package io.dataease.api.template.response;
import io.dataease.api.template.dto.TemplateMarketDTO;
import lombok.Data;
import java.util.List;
/**
* @author : WangJiaHao
* @date : 2023/11/17 13:41
*/
@Data
public class MarketTemplateInnerResult {
private List<TemplateMarketDTO> content;
}

View File

@ -0,0 +1,16 @@
package io.dataease.api.template.response;
import lombok.Data;
import java.util.List;
/**
* @author : WangJiaHao
* @date : 2023/11/17 13:41
*/
@Data
public class MarketTemplateV2BaseResponse {
private List<MarketTemplateV2ItemResult> items;
}

View File

@ -0,0 +1,15 @@
package io.dataease.api.template.response;
import io.dataease.api.template.vo.MarketApplicationVO;
import lombok.Data;
/**
* @author : WangJiaHao
* @date : 2023/11/17 13:41
*/
@Data
public class MarketTemplateV2ItemResult {
private MarketApplicationVO application;
}

View File

@ -0,0 +1,16 @@
package io.dataease.api.template.vo;
import lombok.Data;
/**
* Author: wangjiahao
* Date: 2022/7/15
* Description:
*/
@Data
public class MarketApplicationSpecLinkVO {
private String name;
private String url;
}

View File

@ -0,0 +1,13 @@
package io.dataease.api.template.vo;
import lombok.Data;
/**
* Author: wangjiahao
* Date: 2022/7/15
* Description:
*/
@Data
public class MarketApplicationSpecScreenshotBaseVO {
private String url;
}

View File

@ -0,0 +1,30 @@
package io.dataease.api.template.vo;
import lombok.Data;
import java.util.List;
/**
* Author: wangjiahao
* Date: 2022/7/15
* Description:
*/
@Data
public class MarketApplicationSpecVO {
private String displayName;
private String type;
private String deVersion;
private String templateType;
private String label;
private String readmeName;
private List<MarketApplicationSpecScreenshotBaseVO> screenshots;
private List<MarketApplicationSpecLinkVO> links;
}

View File

@ -0,0 +1,13 @@
package io.dataease.api.template.vo;
import lombok.Data;
/**
* Author: wangjiahao
* Date: 2022/7/15
* Description:
*/
@Data
public class MarketApplicationVO {
private MarketApplicationSpecVO spec;
}

View File

@ -1,6 +1,7 @@
package io.dataease.api.template.vo;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* Author: wangjiahao
@ -8,8 +9,13 @@ import lombok.Data;
* Description:
*/
@Data
@NoArgsConstructor
public class MarketCategoryVO {
private String id;
private String name;
private String slug;
public MarketCategoryVO(String name) {
this.name = name;
}
}

View File

@ -0,0 +1,15 @@
package io.dataease.api.template.vo;
import lombok.Data;
/**
* Author: wangjiahao
* Date: 2022/7/15
* Description:
*/
@Data
public class MarketMetaDataVO {
private String slug;
private String value;
private String label;
}

View File

@ -1,6 +1,7 @@
package io.dataease.api.template.vo;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* Author: wangjiahao
@ -8,6 +9,11 @@ import lombok.Data;
* Description:
*/
@Data
@NoArgsConstructor
public class MarketMetasVO {
private String theme_repo;
public MarketMetasVO(String theme_repo) {
this.theme_repo = theme_repo;
}
}

View File

@ -0,0 +1,33 @@
package io.dataease.api.permissions.embedded.api;
import io.dataease.api.permissions.embedded.dto.EmbeddedCreator;
import io.dataease.api.permissions.embedded.dto.EmbeddedEditor;
import io.dataease.api.permissions.embedded.dto.EmbeddedResetRequest;
import io.dataease.api.permissions.embedded.vo.EmbeddedGridVO;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import java.util.List;
public interface EmbeddedApi {
@GetMapping("/queryGrid")
List<EmbeddedGridVO> queryGrid();
@PostMapping("/create")
void create(@RequestBody EmbeddedCreator creator);
@PostMapping("/edit")
void edit(@RequestBody EmbeddedEditor editor);
@PostMapping("/delete/{id}")
void delete(@PathVariable("id") Long id);
@PostMapping("/reset")
void reset(@RequestBody EmbeddedResetRequest request);
@GetMapping("/domainList")
List<String> domainList();
}

View File

@ -0,0 +1,13 @@
package io.dataease.api.permissions.embedded.dto;
import lombok.Data;
import java.io.Serializable;
@Data
public class EmbeddedCreator implements Serializable {
private String name;
private String domain;
}

View File

@ -0,0 +1,15 @@
package io.dataease.api.permissions.embedded.dto;
import lombok.Data;
import java.io.Serializable;
@Data
public class EmbeddedEditor implements Serializable {
private Long id;
private String name;
private String domain;
}

View File

@ -0,0 +1,13 @@
package io.dataease.api.permissions.embedded.dto;
import lombok.Data;
import java.io.Serializable;
@Data
public class EmbeddedResetRequest implements Serializable {
private Long id;
private String appSecret;
}

View File

@ -0,0 +1,22 @@
package io.dataease.api.permissions.embedded.vo;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;
import java.io.Serializable;
@Data
public class EmbeddedGridVO implements Serializable {
@JsonSerialize(using= ToStringSerializer.class)
private Long id;
private String name;
private String appId;
private String appSecret;
private String domain;
}

View File

@ -1,6 +1,7 @@
package io.dataease.auth.interceptor;
import io.dataease.constant.AuthConstant;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.RestController;
@ -13,16 +14,20 @@ import java.util.List;
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Resource(name = "deCorsInterceptor")
private CorsInterceptor corsInterceptor;
@Value("#{'${dataease.origin-list}'.split(',')}")
private List<String> originList;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new CorsInterceptor(originList)).addPathPatterns("/**");
corsInterceptor.addOriginList(originList);
registry.addInterceptor(corsInterceptor).addPathPatterns("/**");
}
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.addPathPrefix(AuthConstant.DE_API_PREFIX, c -> c.isAnnotationPresent(RestController.class));
configurer.addPathPrefix(AuthConstant.DE_API_PREFIX, c -> c.isAnnotationPresent(RestController.class) && c.getPackageName().startsWith("io.dataease"));
}
}

View File

@ -1,27 +1,73 @@
package io.dataease.auth.interceptor;
import cn.hutool.core.util.ReflectUtil;
import io.dataease.utils.CommonBeanFactory;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import java.util.ArrayList;
import java.util.List;
@Component("deCorsInterceptor")
public class CorsInterceptor implements HandlerInterceptor {
private List<String> originList;
private final List<String> originList;
private final List<String> busiOriginList = new ArrayList<>();
private Class<?> aClass;
private Object bean;
public CorsInterceptor(List<String> originList) {
this.originList = originList;
}
public void addOriginList(List<String> list) {
List<String> strings = list.stream().filter(item -> !originList.contains(item)).toList();
originList.addAll(strings);
}
public void addOriginList() {
String className = "io.dataease.api.permissions.embedded.api.EmbeddedApi";
String methodName = "domainList";
if (ObjectUtils.isEmpty(aClass)) {
try {
aClass = Class.forName(className);
} catch (ClassNotFoundException e) {
return;
}
}
if (ObjectUtils.isEmpty(bean)) {
bean = CommonBeanFactory.getBean(aClass);
}
if (ObjectUtils.isNotEmpty(bean)) {
Object result = ReflectUtil.invoke(bean, methodName);
if (ObjectUtils.isNotEmpty(result)) {
List<String> list = (List<String>) result;
if (CollectionUtils.isNotEmpty(list)) {
List<String> strings = list.stream().filter(item -> !busiOriginList.contains(item)).toList();
busiOriginList.addAll(strings);
}
}
}
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
addOriginList();
String origin = request.getHeader("Origin");
boolean embedded = StringUtils.startsWithAny(request.getRequestURI(), "/assets/", "/js/");
if ((StringUtils.isNotBlank(origin) && originList.contains(origin)) || embedded) {
if ((StringUtils.isNotBlank(origin) && originList.contains(origin)) || busiOriginList.contains(origin) || embedded) {
response.setHeader("Access-Control-Allow-Origin", embedded ? "*" : origin);
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS");

View File

@ -148,7 +148,7 @@ public class HttpClientUtil {
public static String post(String url, String json, HttpClientConfig config) {
CloseableHttpClient httpClient = null;
try {
buildHttpClient(url);
httpClient = buildHttpClient(url);
HttpPost httpPost = new HttpPost(url);
if (config == null) {
config = new HttpClientConfig();

View File

@ -14,10 +14,13 @@ public class WhitelistUtils {
"/dekey",
"/index.html",
"/model",
"/deApi",
"/demo.html",
"/swagger-resources",
"/doc.html",
"/panel.html",
"/lark/info",
"/lark/token",
"/setting/authentication/status",
"/");
@ -33,6 +36,7 @@ public class WhitelistUtils {
|| StringUtils.startsWithAny(requestURI, "/static-resource/")
|| StringUtils.startsWithAny(requestURI, "/share/proxyInfo")
|| StringUtils.startsWithAny(requestURI, "/xpackComponent/content/")
|| StringUtils.startsWithAny(requestURI, "/platform/")
|| StringUtils.startsWithAny(requestURI, "/map/");
}
}