refactor: 模版市场UI优化

This commit is contained in:
wangjiahao 2023-11-27 16:33:55 +08:00
parent 296c6de279
commit 8d68fb916b
29 changed files with 1700 additions and 339 deletions

View File

@ -14,18 +14,18 @@ public class MybatisPlusGenerator {
* 第一 我嫌麻烦
* 第二 后面配置会放到nacos读起来更麻烦了
*/
private static final String url = "jdbc:mysql://127.0.0.1:3306/de_standalone?autoReconnect=false&useUnicode=true&characterEncoding=UTF-8&characterSetResults=UTF-8&zeroDateTimeBehavior=convertToNull&useSSL=false";
private static final String url = "jdbc:mysql://localhost:3306/dataease?autoReconnect=false&useUnicode=true&characterEncoding=UTF-8&characterSetResults=UTF-8&zeroDateTimeBehavior=convertToNull&useSSL=false";
private static final String username = "root";
private static final String password = "Password123@mysql";
private static final String password = "123456";
/**
* 业务模块例如datasource,dataset,panel等
*/
private static final String busi = "template";
private static final String busi = "operation";
/**
* 这是要生成代码的表名称
*/
private static final String TABLE_NAME = "visualization_template_extend_data";
private static final String TABLE_NAME = "core_opt_recent";
/**
* 下面两个配置基本上不用动

View File

@ -9,7 +9,7 @@ public class OptConstants {
public static final class OPT_TYPE {
//新建
public static final int NEW = 1;
//
//
public static final int UPDATE = 2;
//删除
public static final int DELETE = 3;
@ -26,6 +26,8 @@ public class OptConstants {
public static final int DATASET = 4;
//数据源
public static final int DATASOURCE = 5;
//模版
public static final int TEMPLATE = 6;
}
}

View File

@ -9,7 +9,7 @@ import java.io.Serializable;
* </p>
*
* @author fit2cloud
* @since 2023-10-08
* @since 2023-11-26
*/
@TableName("core_opt_recent")
public class CoreOptRecent implements Serializable {
@ -26,6 +26,11 @@ public class CoreOptRecent implements Serializable {
*/
private Long resourceId;
/**
* 资源名称
*/
private String resourceName;
/**
* 用户ID
*/
@ -62,6 +67,14 @@ public class CoreOptRecent implements Serializable {
this.resourceId = resourceId;
}
public String getResourceName() {
return resourceName;
}
public void setResourceName(String resourceName) {
this.resourceName = resourceName;
}
public Long getUid() {
return uid;
}
@ -99,6 +112,7 @@ public class CoreOptRecent implements Serializable {
return "CoreOptRecent{" +
"id = " + id +
", resourceId = " + resourceId +
", resourceName = " + resourceName +
", uid = " + uid +
", resourceType = " + resourceType +
", optType = " + optType +

View File

@ -10,7 +10,7 @@ import org.apache.ibatis.annotations.Mapper;
* </p>
*
* @author fit2cloud
* @since 2023-10-08
* @since 2023-11-26
*/
@Mapper
public interface CoreOptRecentMapper extends BaseMapper<CoreOptRecent> {

View File

@ -1,13 +1,21 @@
package io.dataease.operation.manage;
import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import io.dataease.commons.constants.OptConstants;
import io.dataease.operation.dao.auto.entity.CoreOptRecent;
import io.dataease.operation.dao.auto.mapper.CoreOptRecentMapper;
import io.dataease.utils.AuthUtils;
import io.dataease.utils.IDUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Component
public class CoreOptRecentManage {
@ -15,19 +23,33 @@ public class CoreOptRecentManage {
@Autowired
private CoreOptRecentMapper coreStoreMapper;
public void saveOpt(Long resourceId,int resourceType,int optType) {
public void saveOpt(Long resourceId, int resourceType, int optType) {
saveOpt(resourceId, null, resourceType, optType);
}
public void saveOpt(String resourceName, int resourceType, int optType) {
saveOpt(null, resourceName, resourceType, optType);
}
public void saveOpt(Long resourceId, String resourceName, int resourceType, int optType) {
Long uid = AuthUtils.getUser().getUserId();
QueryWrapper<CoreOptRecent> updateWrapper = new QueryWrapper<>();
updateWrapper.eq("resource_id",resourceId);
updateWrapper.eq("resource_type",resourceType);
updateWrapper.eq("uid",uid);
if (resourceId != null) {
updateWrapper.eq("resource_id", resourceId);
}
if (StringUtils.isNotEmpty(resourceName)) {
updateWrapper.eq("resource_name", resourceName);
}
updateWrapper.eq("resource_type", resourceType);
updateWrapper.eq("uid", uid);
CoreOptRecent updateParam = new CoreOptRecent();
updateParam.setOptType(optType);
updateParam.setTime(System.currentTimeMillis());
if(coreStoreMapper.update(updateParam,updateWrapper)==0){
if (coreStoreMapper.update(updateParam, updateWrapper) == 0) {
CoreOptRecent optRecent = new CoreOptRecent();
optRecent.setId(IDUtils.snowID());
optRecent.setResourceId(resourceId);
optRecent.setResourceName(resourceName);
optRecent.setResourceType(resourceType);
optRecent.setOptType(optType);
optRecent.setTime(System.currentTimeMillis());
@ -36,4 +58,17 @@ public class CoreOptRecentManage {
}
}
public Map<String, Long> findTemplateRecentUseTime() {
Long uid = AuthUtils.getUser().getUserId();
QueryWrapper<CoreOptRecent> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("resource_type", OptConstants.OPT_RESOURCE_TYPE.TEMPLATE);
queryWrapper.eq("uid", uid);
List<CoreOptRecent> result = coreStoreMapper.selectList(queryWrapper);
if (CollectionUtil.isNotEmpty(result)) {
return result.stream().collect(Collectors.toMap(CoreOptRecent::getResourceName, CoreOptRecent::getTime));
} else {
return new HashMap<>();
}
}
}

View File

@ -2,11 +2,13 @@ package io.dataease.template.manage;
import io.dataease.api.template.dto.TemplateManageFileDTO;
import io.dataease.api.template.dto.TemplateMarketDTO;
import io.dataease.api.template.dto.TemplateMarketPreviewInfoDTO;
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.operation.manage.CoreOptRecentManage;
import io.dataease.system.manage.SysParameterManage;
import io.dataease.utils.HttpClientConfig;
import io.dataease.utils.HttpClientUtil;
@ -16,10 +18,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;
import java.util.*;
import java.util.stream.Collectors;
/**
@ -38,6 +37,9 @@ public class TemplateMarketManage {
@Resource
private SysParameterManage sysParameterManage;
@Resource
private CoreOptRecentManage coreOptRecentManage;
/**
* @param templateUrl template url
* @Description Get template file from template market
@ -62,26 +64,91 @@ public class TemplateMarketManage {
return HttpClientUtil.get(url, config);
}
private MarketTemplateV2BaseResponse templateQuery(Map<String, String> templateParams){
String result = marketGet(templateParams.get("template.url") + POSTS_API_V2, null);
MarketTemplateV2BaseResponse postsResult = JsonUtil.parseObject(result, MarketTemplateV2BaseResponse.class);
return postsResult;
}
public MarketBaseResponse searchTemplate() {
try {
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"));
return baseResponseV2Trans(templateQuery(templateParams), templateParams.get("template.url"));
} catch (Exception e) {
DEException.throwException(e);
}
return null;
}
private MarketBaseResponse baseResponseV2Trans(MarketTemplateV2BaseResponse v2BaseResponse, String url) {
public MarketBaseResponse searchTemplateRecommend() {
try {
Map<String, String> templateParams = sysParameterManage.groupVal("template.");
return baseResponseV2TransRecommend(templateQuery(templateParams), templateParams.get("template.url"));
} catch (Exception e) {
DEException.throwException(e);
}
return null;
}
public MarketPreviewBaseResponse searchTemplatePreview() {
try {
Map<String, String> templateParams = sysParameterManage.groupVal("template.");
return basePreviewResponseV2Trans(templateQuery(templateParams), templateParams.get("template.url"));
} catch (Exception e) {
DEException.throwException(e);
}
return null;
}
private MarketBaseResponse baseResponseV2TransRecommend(MarketTemplateV2BaseResponse v2BaseResponse, String url) {
Map<String, Long> useTime = coreOptRecentManage.findTemplateRecentUseTime();
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())));
if("Y".equalsIgnoreCase(spec.getSuggest())){
contents.add(new TemplateMarketDTO(spec.getReadmeName(), spec.getDisplayName(), spec.getScreenshots().get(0).getUrl(), spec.getLinks().get(0).getUrl(), categoriesMap.get(spec.getLabel()), spec.getTemplateType(), useTime.get(spec.getReadmeName()),"Y"));
}
});
return new MarketBaseResponse(url, contents);
// 最近使用排序
Collections.sort(contents);
return new MarketBaseResponse(url,contents);
}
private MarketBaseResponse baseResponseV2Trans(MarketTemplateV2BaseResponse v2BaseResponse, String url) {
Map<String, Long> useTime = coreOptRecentManage.findTemplateRecentUseTime();
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()), spec.getTemplateType(), useTime.get(spec.getReadmeName()),spec.getSuggest()));
});
// 最近使用排序
Collections.sort(contents);
return new MarketBaseResponse(url,contents);
}
private MarketPreviewBaseResponse basePreviewResponseV2Trans(MarketTemplateV2BaseResponse v2BaseResponse, String url) {
Map<String, Long> useTime = coreOptRecentManage.findTemplateRecentUseTime();
Map<String, String> categoriesMap = getCategoriesBaseV2();
List<String> categories = new ArrayList<>();
List<TemplateMarketPreviewInfoDTO> result = new ArrayList<>();
categoriesMap.forEach((key,value)->{
if(!"全部".equalsIgnoreCase(value)){
categories.add(value);
List<TemplateMarketDTO> contents = new ArrayList<>();
v2BaseResponse.getItems().stream().forEach(marketTemplateV2ItemResult -> {
MarketApplicationSpecVO spec = marketTemplateV2ItemResult.getApplication().getSpec();
if(key.equalsIgnoreCase(spec.getLabel())){
contents.add(new TemplateMarketDTO(spec.getReadmeName(), spec.getDisplayName(), spec.getScreenshots().get(0).getUrl(), spec.getLinks().get(0).getUrl(), categoriesMap.get(spec.getLabel()), spec.getTemplateType(), useTime.get(spec.getReadmeName()),spec.getSuggest()));
}
});
Collections.sort(contents);
result.add(new TemplateMarketPreviewInfoDTO(value,contents));
}
});
// 最近使用排序
return new MarketPreviewBaseResponse(url,categories,result);
}
public MarketBaseResponse searchTemplateV1() {
@ -115,6 +182,13 @@ public class TemplateMarketManage {
.collect(Collectors.toList());
}
public List<MarketMetaDataVO> getCategoriesObject() {
List<MarketMetaDataVO> result = getCategoriesV2();
result.add(0, new MarketMetaDataVO("suggest", "推荐"));
result.add(0, new MarketMetaDataVO("recent", "最近使用"));
return result;
}
public Map<String, String> getCategoriesBaseV2() {
Map<String, String> categories = getCategoriesV2().stream()
.collect(Collectors.toMap(MarketMetaDataVO::getSlug, MarketMetaDataVO::getLabel));

View File

@ -2,6 +2,8 @@ package io.dataease.template.service;
import io.dataease.api.template.TemplateMarketApi;
import io.dataease.api.template.response.MarketBaseResponse;
import io.dataease.api.template.response.MarketPreviewBaseResponse;
import io.dataease.api.template.vo.MarketMetaDataVO;
import io.dataease.template.manage.TemplateMarketManage;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.RequestMapping;
@ -23,9 +25,23 @@ public class TemplateMarketService implements TemplateMarketApi {
public MarketBaseResponse searchTemplate() {
return templateMarketManage.searchTemplate();
}
@Override
public MarketBaseResponse searchTemplateRecommend() {
return templateMarketManage.searchTemplateRecommend();
}
@Override
public MarketPreviewBaseResponse searchTemplatePreview() {
return templateMarketManage.searchTemplatePreview();
}
@Override
public List<String> categories() {
return templateMarketManage.getCategories();
}
@Override
public List<MarketMetaDataVO> categoriesObject() {
return templateMarketManage.getCategoriesObject();
}
}

View File

@ -15,11 +15,13 @@ import io.dataease.chart.dao.auto.mapper.CoreChartViewMapper;
import io.dataease.chart.manage.ChartDataManage;
import io.dataease.chart.manage.ChartViewManege;
import io.dataease.commons.constants.DataVisualizationConstants;
import io.dataease.commons.constants.OptConstants;
import io.dataease.constant.CommonConstants;
import io.dataease.exception.DEException;
import io.dataease.license.config.XpackInteract;
import io.dataease.model.BusiNodeRequest;
import io.dataease.model.BusiNodeVO;
import io.dataease.operation.manage.CoreOptRecentManage;
import io.dataease.template.dao.auto.entity.VisualizationTemplate;
import io.dataease.template.dao.auto.entity.VisualizationTemplateExtendData;
import io.dataease.template.dao.auto.mapper.VisualizationTemplateExtendDataMapper;
@ -79,6 +81,8 @@ public class DataVisualizationServer implements DataVisualizationApi {
@Resource
private VisualizationTemplateExtendDataMapper templateExtendDataMapper;
@Resource
private CoreOptRecentManage coreOptRecentManage;
@Override
@XpackInteract(value = "dataVisualizationServer", original = true)
@ -257,6 +261,8 @@ public class DataVisualizationServer implements DataVisualizationApi {
staticResource = templateFileInfo.getStaticResource();
name = templateFileInfo.getName();
dvType = templateFileInfo.getDvType();
// 模版市场记录
coreOptRecentManage.saveOpt(request.getResourceName(), OptConstants.OPT_RESOURCE_TYPE.TEMPLATE,OptConstants.OPT_TYPE.NEW);
}
// 解析动态数据
Map<String, String> dynamicDataMap = JsonUtil.parseObject(dynamicData, Map.class);

View File

@ -18,7 +18,7 @@ CREATE TABLE `visualization_template` (
BEGIN;
INSERT INTO `core_menu`
VALUES (19, 0, 2, 'template-market', 'template-market', 4, NULL, '/template-market', 0, 1, 0);
VALUES (19, 0, 2, 'template-market', 'template-market', 4, NULL, '/template-market', 0, 1, 1);
INSERT INTO `core_menu`
VALUES (20, 15, 2, 'template-setting', 'system/template-setting', 4, 'icon_template', '/template-setting', 0, 1, 1);
COMMIT;
@ -33,3 +33,7 @@ CREATE TABLE `visualization_template_extend_data` (
`copy_id` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
);
ALTER TABLE `core_opt_recent`
MODIFY COLUMN `resource_id` bigint NULL COMMENT '资源ID' AFTER `id`,
ADD COLUMN `resource_name` varchar(255) NULL COMMENT '资源名称' AFTER `resource_id`;

View File

@ -20,7 +20,7 @@ CREATE TABLE `visualization_template`
BEGIN;
INSERT INTO `core_menu`
VALUES (19, 0, 2, 'template-market', 'template-market', 4, NULL, '/template-market', 0, 1, 0);
VALUES (19, 0, 2, 'template-market', 'template-market', 4, NULL, '/template-market', 0, 1, 1);
INSERT INTO `core_menu`
VALUES (20, 15, 2, 'template-setting', 'system/template-setting', 4, 'icon_template', '/template-setting', 0, 1, 1);
COMMIT;
@ -35,3 +35,8 @@ CREATE TABLE `visualization_template_extend_data` (
`copy_id` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
);
ALTER TABLE `core_opt_recent`
MODIFY COLUMN `resource_id` bigint NULL COMMENT '资源ID' AFTER `id`,
ADD COLUMN `resource_name` varchar(255) NULL COMMENT '资源名称' AFTER `resource_id`;

View File

@ -6,8 +6,26 @@ export function searchMarket() {
})
}
export function searchMarketRecommend() {
return request.get({
url: '/templateMarket/searchRecommend'
})
}
export function searchMarketPreview() {
return request.get({
url: '/templateMarket/searchPreview'
})
}
export function getCategories() {
return request.get({
url: '/templateMarket/categories'
})
}
export function getCategoriesObject() {
return request.get({
url: '/templateMarket/categoriesObject'
})
}

View File

@ -0,0 +1,31 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_2346_358803)">
<path d="M8 4H20C26.6274 4 32 9.37258 32 16C32 22.6274 26.6274 28 20 28H8V4Z" fill="white"/>
</g>
<g filter="url(#filter1_d_2346_358803)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M20 5H8V27H20C26.0751 27 31 22.0751 31 16C31 9.92487 26.0751 5 20 5ZM8 4V28H20C26.6274 28 32 22.6274 32 16C32 9.37258 26.6274 4 20 4H8Z" fill="#DEE0E3"/>
</g>
<path d="M20.1391 16.0005L16.0732 20.0663C15.9756 20.164 15.9756 20.3223 16.0732 20.4199L16.4268 20.7735C16.5244 20.8711 16.6827 20.8711 16.7803 20.7735L21.1997 16.354C21.395 16.1588 21.395 15.8422 21.1997 15.6469L16.7803 11.2275C16.6827 11.1299 16.5244 11.1299 16.4268 11.2275L16.0732 11.5811C15.9756 11.6787 15.9756 11.837 16.0732 11.9346L20.1391 16.0005Z" fill="#3370FF"/>
<defs>
<filter id="filter0_d_2346_358803" x="0" y="0" width="40" height="40" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="4"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.121569 0 0 0 0 0.137255 0 0 0 0 0.160784 0 0 0 0.1 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_2346_358803"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_2346_358803" result="shape"/>
</filter>
<filter id="filter1_d_2346_358803" x="0" y="0" width="40" height="40" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="4"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_2346_358803"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_2346_358803" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1,19 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_2356_358817)">
<rect width="24" height="24" rx="12" transform="matrix(-1 0 0 1 32 4)" fill="white"/>
<rect x="-0.5" y="0.5" width="23" height="23" rx="11.5" transform="matrix(-1 0 0 1 31 4)" stroke="#DEE0E3"/>
</g>
<path d="M17.8181 16.0005L21.8839 11.9346C21.9816 11.837 21.9816 11.6787 21.8839 11.5811L21.5304 11.2275C21.4327 11.1299 21.2745 11.1299 21.1768 11.2275L16.7574 15.6469C16.5621 15.8422 16.5621 16.1588 16.7574 16.354L21.1768 20.7735C21.2745 20.8711 21.4327 20.8711 21.5304 20.7735L21.8839 20.4199C21.9816 20.3223 21.9816 20.164 21.8839 20.0663L17.8181 16.0005Z" fill="#1F2329"/>
<defs>
<filter id="filter0_d_2356_358817" x="0" y="0" width="40" height="40" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="4"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_2356_358817"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_2356_358817" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -1,5 +1,14 @@
<svg width="125" height="125" viewBox="0 0 125 125" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="125" height="125" rx="62.5" fill="#EBEBEB"/>
<path d="M82.625 48.1813H71.125V36.6252H42.375V88.3752H56.75V94.1252H39.5C38.7375 94.1252 38.0062 93.8223 37.4671 93.2831C36.9279 92.744 36.625 92.0127 36.625 91.2502V33.7502C36.625 32.9877 36.9279 32.2564 37.4671 31.7173C38.0062 31.1781 38.7375 30.8752 39.5 30.8752H75.4375L88.375 44.4294V56.7502H82.625V48.1813Z" fill="#BBBFC4"/>
<path d="M75.4375 91.25C78.4004 91.25 81.154 90.3536 83.4417 88.8172L88.8204 94.1958C89.3817 94.7572 90.2919 94.7572 90.8533 94.1958L93.2928 91.7563C93.8542 91.1949 93.8542 90.2847 93.2928 89.7233L87.7941 84.2247C89.0761 82.0741 89.8125 79.5605 89.8125 76.875C89.8125 68.9359 83.3766 62.5 75.4375 62.5C67.4984 62.5 61.0625 68.9359 61.0625 76.875C61.0625 84.8141 67.4984 91.25 75.4375 91.25ZM84.0625 76.875C84.0625 81.6384 80.201 85.5 75.4375 85.5C70.674 85.5 66.8125 81.6384 66.8125 76.875C66.8125 72.1115 70.674 68.25 75.4375 68.25C80.201 68.25 84.0625 72.1115 84.0625 76.875Z" fill="#BBBFC4"/>
<svg width="80" height="80" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.0705 8.95996H65.4094C67.4587 8.95996 69.12 10.6118 69.12 12.6494V53.7236L53.296 70.0591H19.0705C17.0213 70.0591 15.36 68.4073 15.36 66.3697V12.6494C15.36 10.6118 17.0213 8.95996 19.0705 8.95996Z" fill="#D6DAE1"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M53.2959 70.0327V54.9271C53.2959 54.2479 53.8497 53.6973 54.5328 53.6973H69.1199L53.2959 70.0327Z" fill="#B5BEC8"/>
<rect x="48.4399" y="14.0796" width="16" height="20.48" rx="2" fill="#F4F5F9"/>
<rect x="20.2241" y="14.1152" width="20.64" height="3.2" rx="1" fill="#F4F5F9"/>
<rect x="20.2241" y="20.2505" width="20.64" height="3.2" rx="1" fill="#F4F5F9"/>
<rect x="20.2241" y="26.6504" width="10.32" height="3.2" rx="1" fill="#F4F5F9"/>
<rect x="20.2241" y="44.1108" width="44.032" height="3.2" rx="1" fill="#F4F5F9"/>
<rect x="20.2241" y="48.8833" width="44.032" height="3.2" rx="1" fill="#F4F5F9"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M31.8181 34.6489C24.496 34.6489 18.5602 40.6141 18.5602 47.9725C18.5602 55.3309 24.496 61.296 31.8181 61.296C33.6008 61.296 35.3013 60.9425 36.8539 60.3013L43.7463 70.637L47.2995 68.2432L40.4981 58.0439C43.3024 55.6009 45.0761 51.9949 45.0761 47.9725C45.0761 40.6141 39.1403 34.6489 31.8181 34.6489ZM31.8181 38.9472C36.7782 38.9472 40.7993 42.9881 40.7993 47.9728C40.7993 52.9575 36.7782 56.9985 31.8181 56.9985C26.8579 56.9985 22.8369 52.9575 22.8369 47.9728C22.8369 42.9881 26.8579 38.9472 31.8181 38.9472Z" fill="white"/>
<path d="M15.7495 38.3938L15.4811 39.0205L9.58404 36.4913L9.85248 35.8646L15.7495 38.3938Z" fill="#1F2329"/>
<path d="M18.9606 36.2086L18.4367 36.6449L13.3637 30.5493L13.8876 30.113L18.9606 36.2086Z" fill="#1F2329"/>
<path d="M15.8575 42.0183L15.882 42.7005L7.80575 42.9754L7.78133 42.2932L15.8575 42.0183Z" fill="#1F2329"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -0,0 +1,94 @@
<script lang="ts" setup>
import { ElHeader } from 'element-plus-secondary'
import { useRouter } from 'vue-router'
import AccountOperator from '@/layout/components/AccountOperator.vue'
const { push } = useRouter()
const backToMain = () => {
push('/workbranch/index')
}
</script>
<template>
<el-header class="header-flex system-header">
<Icon className="logo" name="logo"></Icon>
<el-divider direction="vertical" />
<span class="system">模版市场</span>
<div class="operate-setting">
<span @click="backToMain" class="work-bar flex-align-center">
<el-icon>
<Icon name="icon_left_outlined"></Icon>
</el-icon>
<span class="work">返回工作台</span>
</span>
<AccountOperator />
</div>
</el-header>
</template>
<style lang="less" scoped>
.system-header {
font-family: PingFang SC;
.logo {
width: 134px;
height: 34px;
}
.ed-divider {
margin: 0 24px;
border-color: rgba(255, 255, 255, 0.3);
}
.system {
color: #fff;
font-size: 16px;
font-style: normal;
font-weight: 500;
line-height: 24px;
}
.work-bar {
color: rgba(255, 255, 255, 0.8);
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 22px;
cursor: pointer;
.ed-icon {
margin-right: 4px;
font-size: 16px;
}
}
.avatar {
margin: 0 -7px 0 20px !important;
}
}
.header-flex {
display: flex;
align-items: center;
height: 56px;
background-color: #050e21;
padding: 0 24px;
.operate-setting {
margin-left: auto;
display: flex;
align-items: center;
&:focus {
outline: none;
}
}
}
</style>
<style lang="less">
.header-flex {
.operate-setting {
.ed-icon {
cursor: pointer;
color: rgba(255, 255, 255, 0.8);
font-size: 18px;
}
}
}
</style>

View File

@ -7,14 +7,17 @@ import Menu from './components/Menu.vue'
import Main from './components/Main.vue'
import { ElContainer } from 'element-plus-secondary'
import { useRoute } from 'vue-router'
import HeaderTemplateMarket from '@/layout/components/HeaderTemplateMarket.vue'
const route = useRoute()
const systemMenu = computed(() => route.path.includes('system'))
const settingMenu = computed(() => route.path.includes('sys-setting'))
const templateMarketMenu = computed(() => route.path.includes('template-market'))
</script>
<template>
<div class="common-layout">
<HeaderSystem v-if="settingMenu"></HeaderSystem>
<header-template-market v-if="templateMarketMenu"></header-template-market>
<HeaderSystem v-else-if="settingMenu"></HeaderSystem>
<Header v-else></Header>
<el-container class="layout-container">
<Sidebar v-if="systemMenu || settingMenu" class="layout-sidebar">

View File

@ -147,12 +147,19 @@ watch(
}
)
const initMarketTemplate = () => {
searchMarket()
const initMarketTemplate = async () => {
await searchMarket()
.then(rsp => {
state.baseUrl = rsp.data.baseUrl
state.currentMarketTemplateShowList = rsp.data.contents
state.hasResult = true
if (props.previewId) {
state.currentMarketTemplateShowList.forEach(template => {
if (props.previewId === template.id) {
previewTemplate(template)
}
})
}
})
.catch(() => {
state.networkStatus = false
@ -165,17 +172,6 @@ const initMarketTemplate = () => {
.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 => {
@ -248,7 +244,6 @@ const active = template => {
onMounted(() => {
initMarketTemplate()
getGroupTree()
})
</script>

View File

@ -0,0 +1,579 @@
<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-tooltip class="box-item" effect="dark" content="展开" placement="right">
<el-icon v-show="!state.asideActive" class="insert" @click="asideActiveChange(true)">
<Icon name="market-expand"></Icon>
</el-icon>
</el-tooltip>
<el-row v-show="state.asideActive" style="padding: 24px 12px 0">
<el-row style="align-items: center">
<el-breadcrumb separator-icon="ArrowRight">
<el-breadcrumb-item class="custom-breadcrumb-item" @click="closePreview()">{{
t('visualization.template_preview')
}}</el-breadcrumb-item>
<el-breadcrumb-item>预览</el-breadcrumb-item>
</el-breadcrumb>
<el-tooltip class="box-item" effect="dark" content="收起" placement="right">
<el-icon class="insert-retract" @click="asideActiveChange(false)">
<Icon name="market-retract"></Icon>
</el-icon>
</el-tooltip>
</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.templateType"
class="margin-top16"
size="small"
placeholder="请选择"
>
<el-option
v-for="item in state.templateTypeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-row>
<el-divider />
</el-row>
<el-row
v-show="state.asideActive"
class="aside-list"
:class="state.extFilterActive ? 'aside-list-filter-active' : ''"
>
<el-collapse v-show="state.hasResult" v-model="activeCategories" class="market-collapse">
<el-collapse-item
themes="light"
v-for="(categoryTemplate, index) in state.marketTemplatePreviewShowList"
:name="categoryTemplate['categoryType']"
:key="index"
:title="categoryTemplate['categoryType']"
>
<template-market-preview-item
v-for="templateItem in categoryTemplate['contents']"
v-show="templateItem.showFlag"
:key="templateItem.id"
:template="templateItem"
:base-url="state.baseUrl"
:active="active(templateItem)"
@previewTemplate="previewTemplate"
/>
</el-collapse-item>
</el-collapse>
<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>没有找到相关模版</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 { searchMarketPreview } from '@/api/templateMarket'
import { onMounted, reactive, watch, ref } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
import TemplateMarketPreviewItem from '@/views/template-market/component/TemplateMarketPreviewItem.vue'
import { deepCopy } from '@/utils/utils'
const { t } = useI18n()
const props = defineProps({
previewId: {
type: String,
default: null
}
})
const emits = defineEmits(['templateApply', 'closeDialog', 'closePreview'])
const activeCategories = ref([])
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',
marketTemplatePreviewShowList: [],
categories: [],
networkStatus: true,
curTemplate: null,
templateType: 'all',
templateTypeOptions: [
{
value: 'all',
label: '全部类型'
},
{
value: 'PANEL',
label: '仪表板'
},
{
value: 'SCREEN',
label: '大屏'
}
]
})
watch(
() => state.templateType,
value => {
initTemplateShow()
}
)
watch(
() => state.searchText,
value => {
initTemplateShow()
}
)
watch(
() => props.previewId,
value => {
state.marketTemplatePreviewShowList.forEach(categoryTemplates => {
categoryTemplates.contents.forEach(template => {
if (props.previewId === template.id) {
previewTemplate(template)
}
})
})
}
)
const initMarketTemplate = () => {
searchMarketPreview()
.then(rsp => {
state.baseUrl = rsp.data.baseUrl
state.marketTemplatePreviewShowList = rsp.data.contents
state.hasResult = true
state.categories = rsp.data.categories
activeCategories.value = deepCopy(state.categories)
if (props.previewId) {
state.marketTemplatePreviewShowList.forEach(categoryTemplates => {
categoryTemplates.contents.forEach(template => {
if (props.previewId === template.id) {
previewTemplate(template)
}
})
})
}
})
.catch(() => {
state.networkStatus = false
})
}
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.marketTemplatePreviewShowList.forEach(categoryTemplates => {
categoryTemplates.contents.forEach(template => {
template.showFlag = templateShow(template)
if (template.showFlag) {
state.hasResult = true
}
})
})
activeCategories.value = deepCopy(state.categories)
}
const templateShow = templateItem => {
let templateTypeMarch = false
let searchMarch = false
if (state.templateType === 'all' || templateItem.templateType === state.templateType) {
templateTypeMarch = true
}
if (!state.searchText || templateItem.title.indexOf(state.searchText) > -1) {
searchMarch = true
}
return templateTypeMarch && 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()
})
</script>
<style lang="less" scoped>
.market-collapse {
width: 100%;
border: 0;
::v-deep(.ed-collapse-item__content) {
padding: 8px 0;
border: 0;
}
::v-deep(.ed-collapse-item__header) {
border: 0;
}
::v-deep(.ed-collapse-item__wrap) {
border: 0;
background-color: rgba(245, 246, 247, 1);
}
}
.aside-list {
padding: 0px 12px 12px 12px;
width: 100%;
height: calc(100vh - 200px);
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;
width: 100%;
}
.aside-active {
width: 206px;
height: calc(100vh - 56px);
background-color: rgba(245, 246, 247, 1);
}
.aside-inActive {
position: relative;
width: 0px;
}
.main-area-active {
width: calc(100% - 206px) !important;
background: #ffffff;
}
.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-retract {
position: absolute;
left: 176px;
top: 2px;
display: inline-block;
font-size: 34px;
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;
outline: 0;
}
&:hover {
color: #3a8ee6;
}
}
.insert {
font-size: 34px;
margin-top: 24px;
margin-left: -8px;
display: inline-block;
font-weight: 400 !important;
cursor: pointer;
color: #646a73;
transition: 0.1s;
&:active {
color: #000;
border-color: #3a8ee6;
outline: 0;
}
&:hover {
margin-left: -6px;
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;
}
.custom-breadcrumb-item {
cursor: pointer;
::v-deep(.ed-breadcrumb__inner) {
color: rgba(100, 106, 115, 1);
}
}
</style>

View File

@ -0,0 +1,130 @@
<template>
<div class="testcase-template">
<div class="template-img" :style="classBackground" @click.stop="templateInnerPreview" />
<el-row class="bottom-area"> </el-row>
<el-row class="bottom-area-show">
<el-row class="demonstration">
{{ template.title }}
</el-row>
<el-row class="template-button">
<el-button size="mini" style="width: 100px" @click="templateInnerPreview">{{
t('visualization.preview')
}}</el-button>
<el-button size="mini" style="width: 100px" type="primary" @click="apply">{{
t('visualization.apply')
}}</el-button>
</el-row>
</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;
border: 1px solid rgba(222, 224, 227, 1);
box-sizing: border-box;
border-radius: 4px;
width: 100%;
background: #fff;
overflow: hidden;
}
.demonstration {
height: 34px;
font-weight: 500;
display: block;
font-size: 14px;
text-align: left;
padding: 8px 12px 4px 12px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: var(--TextPrimary, #1f2329);
}
.template-img {
background-size: 100% 100%;
margin: 4px 4px 0 4px;
border-radius: 4px 4px 0 0;
}
.template-button {
justify-content: center;
width: 100%;
}
.bottom-area {
height: 38px;
}
.bottom-area-show {
width: 100%;
border-top: 1px solid rgba(222, 224, 227, 1);
position: absolute;
height: 75px;
bottom: -38px;
background: #ffffff;
}
.testcase-template:hover ::v-deep(.bottom-area-show) {
transition: 0.3s;
bottom: 0px;
}
.testcase-template:hover ::v-deep(.template-img) {
outline: solid 1px #4b8fdf;
color: deepskyblue;
cursor: pointer;
}
</style>

View File

@ -1,97 +1,130 @@
<template>
<el-row class="outer-body" v-loading="state.loading">
<!--预览模式-->
<market-preview
<el-row
class="template-outer-body"
:class="{ 'template-outer-body-padding': !previewModel }"
v-loading="state.loading"
>
<market-preview-v2
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">
></market-preview-v2>
<el-row v-show="!previewModel" class="main-container">
<el-row class="market-head">
<span>模版市场 </span>
<el-row class="head-right">
<el-input
class="title-search"
v-model="state.searchText"
prefix-icon="el-icon-search"
prefix-icon="Search"
size="small"
class="title-right"
:placeholder="t('visualization.enter_template_name_tips')"
:clearable="true"
/>
</el-col>
<el-select
class="title-type"
v-model="state.templateType"
size="small"
placeholder="Select"
>
<el-option
v-for="item in state.templateTypeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-row>
</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-row class="template-area">
<div class="template-left">
<el-tree
menu
:data="state.marketTabs"
:props="state.treeProps"
node-key="label"
default-expand-all
highlight-current
:current-node-key="state.marketActiveTab"
@node-click="nodeClick"
/>
</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') }}
<div
v-show="state.networkStatus && state.hasResult"
id="template-show-area"
class="template-right"
>
<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-v2-item
:key="'outer-' + templateItem.id"
:template="templateItem"
:base-url="state.baseUrl"
:width="state.templateCurWidth"
@templateApply="templateApply"
@templatePreview="templatePreview"
/>
</el-col>
</div>
<el-row v-show="state.networkStatus && !state.hasResult" class="template-empty">
<div style="text-align: center">
<Icon name="no_result" style="margin-bottom: 16px; font-size: 75px"></Icon>
<br />
<span>没有找到相关模版</span>
</div>
</el-row>
<el-row v-show="!state.networkStatus" class="template-empty">
{{ t('visualization.market_network_tips') }}
</el-row>
</el-row>
</el-row>
</el-row>
</template>
<script setup lang="ts">
import { getCategories, searchMarket } from '@/api/templateMarket'
import { getCategoriesObject, 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'
import TemplateMarketV2Item from '@/views/template-market/component/TemplateMarketV2Item.vue'
import MarketPreviewV2 from '@/views/template-market/component/MarketPreviewV2.vue'
const { t } = useI18n()
const { wsCache } = useCache()
const previewModel = ref(false)
const state = reactive({
treeProps: {
value: 'label',
label: 'label'
},
templateType: 'all',
templateTypeOptions: [
{
value: 'all',
label: '全部类型'
},
{
value: 'PANEL',
label: '仪表板'
},
{
value: 'SCREEN',
label: '大屏'
}
],
loading: false,
hasResult: true,
templateMiniWidth: 330,
templateMiniWidth: 270,
templateCurWidth: 310,
templateSpan: '25%',
previewVisible: false,
@ -100,6 +133,7 @@ const state = reactive({
marketActiveTab: null,
searchText: null,
dvCreateForm: {
resourceName: null,
name: null,
pid: null,
nodeType: 'panel',
@ -133,13 +167,6 @@ const state = reactive({
}
})
watch(
() => state.marketActiveTab,
value => {
initTemplateShow()
}
)
watch(
() => state.searchText,
value => {
@ -147,12 +174,22 @@ watch(
}
)
watch(
() => state.templateType,
value => {
initTemplateShow()
}
)
const nodeClick = data => {
state.marketActiveTab = data.label
initTemplateShow()
}
const closePreview = () => {
previewModel.value = false
}
const initMarketTemplate = () => {
searchMarket()
const initMarketTemplate = async () => {
await searchMarket()
.then(rsp => {
state.baseUrl = rsp.data.baseUrl
state.currentMarketTemplateShowList = rsp.data.contents
@ -160,21 +197,28 @@ const initMarketTemplate = () => {
.catch(() => {
state.networkStatus = false
})
getCategories()
getCategoriesObject()
.then(rsp => {
state.marketTabs = rsp.data
state.marketActiveTab = state.marketTabs[0]
state.marketActiveTab = state.marketTabs[0].label
initStyle()
initTemplateShow()
})
.catch(() => {
state.networkStatus = false
})
}
const getGroupTree = () => {
// do getGroupTree
// groupTree({ nodeType: 'folder' }).then(res => {
// state.panelGroupList = res.data
// })
const initStyle = () => {
nextTick(() => {
const tree = document.querySelector('.ed-tree')
// 线
const line = document.createElement('hr')
line.classList.add('custom-line')
// 线
tree.firstElementChild.appendChild(line)
})
}
const normalizer = node => {
// children=null
@ -187,6 +231,7 @@ const templateApply = template => {
state.curApplyTemplate = template
state.dvCreateForm.name = template.title
state.dvCreateForm.templateUrl = template.metas.theme_repo
state.dvCreateForm.resourceName = template.id
apply()
}
@ -231,30 +276,43 @@ const initTemplateShow = () => {
const templateShow = templateItem => {
let categoryMarch = false
let searchMarch = false
templateItem.categories.forEach(category => {
if (category.name === state.marketActiveTab) {
let templateTypeMarch = false
if (state.marketActiveTab === '最近使用') {
if (templateItem.recentUseTime) {
categoryMarch = true
}
})
} else if (state.marketActiveTab === '推荐') {
if (templateItem.suggest === 'Y') {
categoryMarch = true
}
} else {
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
if (state.templateType === 'all' || templateItem.templateType === state.templateType) {
templateTypeMarch = true
}
return categoryMarch && searchMarch && templateTypeMarch
}
const templatePreview = previewId => {
state.templatePreviewId = previewId
previewModel.value = true
}
const newPanel = () => {
// do newPanel
}
onMounted(() => {
previewInit()
initMarketTemplate()
getGroupTree()
const erd = elementResizeDetectorMaker()
const templateMainDom = document.getElementById('template-main')
const templateMainDom = document.getElementById('template-show-area')
// div
if (templateMainDom) {
erd.listenTo(templateMainDom, element => {
@ -267,116 +325,86 @@ onMounted(() => {
})
}
})
const previewInit = () => {
const previewId = wsCache.get('template-preview-id')
if (previewId) {
templatePreview(previewId)
wsCache.delete('template-preview-id')
}
}
</script>
<style lang="less" scoped>
.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 - 190px) !important;
overflow-x: hidden;
overflow-y: auto;
background-color: var(--ContentBG, #ffffff);
}
.market-main {
.template-outer-body-padding {
padding: 24px;
display: inherit;
}
.template-outer-body {
.main-container {
display: flex;
flex-wrap: nowrap;
flex-direction: column;
width: 100%;
height: 100%;
.market-head {
height: 56px;
background: #ffffff;
align-items: center;
padding: 12px 24px;
border-bottom: 1px solid rgba(31, 35, 41, 0.15);
span {
font-size: 16px;
font-color: #1f2329;
font-weight: 500;
}
.head-right {
flex: 1;
justify-content: right;
.title-search {
width: 320px;
}
.title-type {
margin-left: 12px;
width: 104px;
}
}
}
.template-area {
height: calc(100vh - 135px);
.template-left {
padding: 8px;
width: 204px;
height: 100%;
overflow-y: auto;
background: #ffffff;
}
.template-right {
flex: 1;
display: inherit;
height: 100%;
background: rgba(239, 240, 241, 1);
overflow-y: auto;
}
.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;
.template-empty {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
height: 100%;
background: rgba(239, 240, 241, 1);
overflow-y: auto;
}
}
}
}
</style>
<style lang="less">
.custom-line {
margin: 4px;
background: rgba(31, 35, 41, 0.15);
border: 0;
height: 1px;
}
</style>

View File

@ -0,0 +1,136 @@
<template>
<div class="template">
<div class="photo">
<div class="img" :style="classBackground"></div>
</div>
<div class="apply">
<span :title="template.title" class="name ellipsis"> {{ template.title }} </span>
<el-button class="flex-center" secondary @click="templateInnerPreview">{{
t('dataset.preview')
}}</el-button>
<el-button class="flex-center" type="primary" @click="apply">{{
t('commons.apply')
}}</el-button>
</div>
</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
}
})
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 apply = () => {
emits('templateApply', props.template)
}
const templateInnerPreview = e => {
emits('templatePreview', props.template.id)
}
</script>
<style scoped lang="less">
.template {
overflow: hidden;
border: 1px solid #d9d9d9;
border-radius: 4px;
display: flex;
flex-wrap: wrap;
width: 181px;
height: 141px;
margin-left: 16px;
position: relative;
.photo {
padding: 4px;
padding-bottom: 0;
height: 101px;
width: 100%;
.img {
width: 100%;
height: 100%;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
}
.apply {
padding: 8px 12px;
background: #fff;
border-top: 1px solid #d9d9d9;
position: absolute;
width: 100%;
left: 0;
bottom: 0;
height: 39px;
display: flex;
flex-wrap: wrap;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
justify-content: space-between;
.ed-button {
min-width: 73px;
height: 28px;
display: none;
font-size: 12px;
line-height: 20px;
padding: 0;
margin-top: 8px;
& + .ed-button {
margin-left: 8px;
}
}
.name {
color: #1f2329;
font-family: PingFang SC;
font-size: 14px;
font-style: normal;
font-weight: 500;
line-height: 22px;
width: 100%;
}
}
&:hover {
box-shadow: 0px 6px 24px 0px rgba(31, 35, 41, 0.08);
.apply {
transition: 0.3s;
height: 73px;
}
.ed-button {
display: block;
}
}
}
</style>

View File

@ -1,14 +1,18 @@
<script lang="ts" setup>
import { useI18n } from '@/hooks/web/useI18n'
import { ref, shallowRef, computed } from 'vue'
import { ref, shallowRef, computed, reactive, watch, nextTick } from 'vue'
import imgtest from '@/assets/img/dataease-10000Star.jpg'
import { usePermissionStoreWithOut } from '@/store/modules/permission'
import { useRequestStoreWithOut } from '@/store/modules/request'
import { interactiveStoreWithOut } from '@/store/modules/interactive'
import ShortcutTable from './ShortcutTable.vue'
import { useUserStoreWithOut } from '@/store/modules/user'
import { useRouter } from 'vue-router'
import { searchMarketRecommend } from '@/api/templateMarket'
import TemplateBranchItem from '@/views/workbranch/TemplateBranchItem.vue'
import { ElMessage } from 'element-plus-secondary'
import { decompression } from '@/api/visualization/dataVisualization'
import { useCache } from '@/hooks/web/useCache'
const userStore = useUserStoreWithOut()
const interactiveStore = interactiveStoreWithOut()
const permissionStore = usePermissionStoreWithOut()
@ -16,9 +20,8 @@ const requestStore = useRequestStoreWithOut()
const { t } = useI18n()
const busiDataMap = computed(() => interactiveStore.getData)
const busiCountCardList = ref([])
const showTemplate = ref(false)
const { wsCache } = useCache()
const { push } = useRouter()
const router = useRouter()
const quickCreationList = shallowRef([
@ -45,11 +48,89 @@ const handleExpandFold = () => {
expandFold.value = expandFold.value === 'expand' ? 'fold' : 'expand'
}
const tabBtnList = ['推荐仪表板', t('auth.screen'), '应用模版']
const activeTabBtn = ref('推荐仪表板')
const showTemplate = computed(() => {
return state.networkStatus && state.hasResult
})
const activeTabChange = value => {
activeTabBtn.value = value
}
const tabBtnList = [
{
name: '推荐仪表板',
value: 'PANEL'
},
{
name: t('auth.screen'),
value: 'SCREEN'
},
{
name: '应用模版',
value: 'APP'
}
]
const activeTabBtn = ref('PANEL')
const typeList = quickCreationList.value.map(ele => ele.name)
typeList.unshift('all_types')
const state = reactive({
templateType: 'PANEL',
baseUrl: null,
marketTemplatePreviewShowList: [],
hasResult: false,
networkStatus: true,
loading: false,
dvCreateForm: {
resourceName: null,
name: null,
pid: null,
nodeType: 'panel',
templateUrl: null,
newFrom: 'new_market_template',
panelType: 'self',
panelStyle: {},
panelData: '[]'
}
})
watch(
() => activeTabBtn.value,
value => {
initTemplateShow()
}
)
const initMarketTemplate = async () => {
await searchMarketRecommend()
.then(rsp => {
state.baseUrl = rsp.data.baseUrl
state.marketTemplatePreviewShowList = rsp.data.contents
state.hasResult = true
initTemplateShow()
})
.catch(() => {
state.networkStatus = false
})
}
const initTemplateShow = () => {
state.hasResult = false
state.marketTemplatePreviewShowList.forEach(template => {
template.showFlag = templateShowCur(template)
if (template.showFlag) {
state.hasResult = true
}
})
}
const templateShowCur = templateItem => {
let templateTypeMarch = false
if (activeTabBtn.value === templateItem.templateType) {
templateTypeMarch = true
}
return templateTypeMarch
}
const fillCardInfo = () => {
for (const key in busiDataMap.value) {
if (key !== '3') {
@ -99,7 +180,48 @@ const createDatasource = () => {
const baseUrl = '#/data/datasource?opt=create'
window.open(baseUrl, '_blank')
}
const templatePreview = previewId => {
wsCache.set(`template-preview-id`, previewId)
toTemplateMarket()
}
const templateApply = template => {
state.dvCreateForm.name = template.title
state.dvCreateForm.templateUrl = template.metas.theme_repo
state.dvCreateForm.resourceName = template.id
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 toTemplateMarket = () => {
push('/template-market/index')
}
fillCardInfo()
initMarketTemplate()
</script>
<template>
@ -161,11 +283,11 @@ fillCardInfo()
</div>
</div>
<div class="template-market-dashboard">
<div v-if="showTemplate" class="template-market">
<div class="template-market">
<div class="label">
模版市场
<div class="expand-all">
<button class="all flex-center">查看全部</button>
<button class="all flex-center" @click="toTemplateMarket">查看全部</button>
<el-divider direction="vertical" />
<button @click="handleExpandFold" class="expand flex-center">
{{ t(`visualization.${expandFold}`) }}
@ -175,37 +297,37 @@ fillCardInfo()
<template v-if="expandFold === 'fold'">
<div class="tab-btn">
<div
@click="activeTabBtn = ele"
v-for="ele in tabBtnList"
:key="ele"
:class="activeTabBtn === ele && 'active'"
:key="ele.value"
:class="activeTabBtn === ele.value && 'active'"
@click="activeTabChange(ele.value)"
class="main-btn"
>
{{ ele }}
{{ ele.name }}
</div>
</div>
<div class="template-list">
<div class="template">
<div class="photo">
<img :src="imgtest" alt="" />
</div>
<div class="apply">
<span title="电子银行业务分析" class="name ellipsis"> 电子银行业务分析 </span>
<el-button class="flex-center" secondary>{{ t('dataset.preview') }}</el-button>
<el-button class="flex-center" type="primary">{{ t('commons.apply') }}</el-button>
</div>
</div>
<div class="template">
<div class="photo">
<img :src="imgtest" alt="" />
</div>
<div class="apply">
<span title="电子银行业务分析" class="name ellipsis"> 电子银行业务分析 </span>
<el-button class="flex-center" secondary>{{ t('dataset.preview') }}</el-button>
<el-button class="flex-center" type="primary">{{ t('commons.apply') }}</el-button>
</div>
</div>
<div class="template-list" v-show="state.networkStatus && state.hasResult">
<template-branch-item
v-for="(template, index) in state.marketTemplatePreviewShowList"
v-show="template['showFlag']"
:key="index"
:template="template"
:base-url="state.baseUrl"
@templateApply="templateApply"
@templatePreview="templatePreview"
>
</template-branch-item>
</div>
<el-row v-show="state.networkStatus && !state.hasResult" class="template-empty">
<div style="text-align: center">
<Icon name="no_result" class="no-result"></Icon>
<br />
<span class="no-result-tips">没有找到相关模版</span>
</div>
</el-row>
<el-row v-show="!state.networkStatus" class="template-empty">
{{ t('visualization.market_network_tips') }}
</el-row>
</template>
</div>
<shortcut-table :expand="expandFold === 'expand'" />
@ -473,78 +595,25 @@ fillCardInfo()
.template-list {
display: flex;
margin-left: -16px;
.template {
border: 1px solid #d9d9d9;
border-radius: 4px;
display: flex;
flex-wrap: wrap;
width: 181px;
height: 141px;
margin-left: 16px;
position: relative;
.photo {
padding: 4px;
padding-bottom: 0;
height: 101px;
img {
width: 100%;
height: 100%;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
}
.apply {
padding: 8px 12px;
background: #fff;
border-top: 1px solid #d9d9d9;
position: absolute;
width: 100%;
left: 0;
bottom: 0;
height: 39px;
display: flex;
flex-wrap: wrap;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
justify-content: space-between;
.ed-button {
min-width: 73px;
height: 28px;
display: none;
font-size: 12px;
line-height: 20px;
padding: 0;
margin-top: 8px;
& + .ed-button {
margin-left: 8px;
}
}
.name {
color: #1f2329;
font-family: PingFang SC;
font-size: 14px;
font-style: normal;
font-weight: 500;
line-height: 22px;
width: 100%;
}
}
&:hover {
box-shadow: 0px 6px 24px 0px rgba(31, 35, 41, 0.08);
.apply {
height: 73px;
}
.ed-button {
display: block;
}
}
}
}
}
}
}
.template-empty {
display: flex;
align-items: center;
justify-content: center;
background: rgba(239, 240, 241, 1);
color: rgba(100, 106, 115, 1);
min-height: 60px;
}
.no-result {
width: 74px;
height: 74px;
}
.no-result-tips {
font-size: 14px;
color: rgba(100, 106, 115, 1);
}
</style>

View File

@ -2,6 +2,8 @@ package io.dataease.api.template;
import io.dataease.api.template.request.TemplateMarketSearchRequest;
import io.dataease.api.template.response.MarketBaseResponse;
import io.dataease.api.template.response.MarketPreviewBaseResponse;
import io.dataease.api.template.vo.MarketMetaDataVO;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
@ -16,8 +18,16 @@ public interface TemplateMarketApi {
@GetMapping("/search")
MarketBaseResponse searchTemplate();
@GetMapping("/searchRecommend")
MarketBaseResponse searchTemplateRecommend();
@GetMapping("/searchPreview")
MarketPreviewBaseResponse searchTemplatePreview();
@GetMapping("/categories")
List<String> categories();
@GetMapping("/categoriesObject")
List<MarketMetaDataVO> categoriesObject() ;
}

View File

@ -12,7 +12,7 @@ import java.util.List;
@Data
@NoArgsConstructor
public class TemplateMarketDTO {
public class TemplateMarketDTO implements Comparable<TemplateMarketDTO> {
private String id;
private String title;
private String status;
@ -20,15 +20,35 @@ public class TemplateMarketDTO {
private String editorType;
private String summary;
private String thumbnail;
private Boolean showFlag = true;
private String suggest = "N";
private Long recentUseTime = 0L;
private String templateType;
private List<MarketCategoryVO> categories;
private MarketMetasVO metas;
public TemplateMarketDTO(String id, String title,String themeRepo,String templateUrl,String categoryName) {
public TemplateMarketDTO(String id, String title, String themeRepo, String templateUrl, String categoryName, String templateType, Long recentUseTime,String suggest) {
this.id = id;
this.title = title;
this.categories = Arrays.asList(new MarketCategoryVO(categoryName),new MarketCategoryVO("全部"));
this.categories = Arrays.asList(new MarketCategoryVO(categoryName), new MarketCategoryVO("全部"));
this.metas = new MarketMetasVO(templateUrl);
this.thumbnail = themeRepo;
this.templateType = templateType;
if (recentUseTime != null) {
this.recentUseTime = recentUseTime;
}
if("Y".equalsIgnoreCase(suggest)){
this.suggest="Y";
}
}
@Override
public int compareTo(TemplateMarketDTO other) {
return Long.compare(other.recentUseTime, this.recentUseTime);
}
}

View File

@ -0,0 +1,24 @@
package io.dataease.api.template.dto;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* @author : WangJiaHao
* @date : 2023/11/27
*/
@Data
@NoArgsConstructor
public class TemplateMarketPreviewInfoDTO {
private String categoryType;
List<TemplateMarketDTO> contents;
public TemplateMarketPreviewInfoDTO(String categoryType, List<TemplateMarketDTO> contents) {
this.categoryType = categoryType;
this.contents = contents;
}
}

View File

@ -0,0 +1,29 @@
package io.dataease.api.template.response;
import io.dataease.api.template.dto.TemplateMarketDTO;
import io.dataease.api.template.dto.TemplateMarketPreviewInfoDTO;
import lombok.Data;
import java.util.List;
/**
* @author : WangJiaHao
* @date : 2023/11/6 17:43
*/
@Data
public class MarketPreviewBaseResponse {
private String baseUrl;
private List<String> categories;
private List<TemplateMarketPreviewInfoDTO> contents;
public MarketPreviewBaseResponse() {
}
public MarketPreviewBaseResponse(String baseUrl, List<String> categories, List<TemplateMarketPreviewInfoDTO> contents) {
this.baseUrl = baseUrl;
this.categories = categories;
this.contents = contents;
}
}

View File

@ -24,6 +24,9 @@ public class MarketApplicationSpecVO {
private String readmeName;
// 是否推荐
private String suggest;
private List<MarketApplicationSpecScreenshotBaseVO> screenshots;
private List<MarketApplicationSpecLinkVO> links;

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 MarketMetaDataVO {
private String slug;
private String value;
private String label;
public MarketMetaDataVO(String value, String label) {
this.label = label;
}
}

View File

@ -8,6 +8,8 @@ public class DataVisualizationBaseRequest extends DataVisualizationVO {
private String opt;
private String resourceName;
private Boolean moveFromUpdate = false;
private String optType;