forked from github/dataease
refactor: 对接V2模版市场正式环境优化
This commit is contained in:
parent
4b14332163
commit
372b747bd2
@ -1,24 +1,22 @@
|
|||||||
package io.dataease.template.manage;
|
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.TemplateManageFileDTO;
|
||||||
import io.dataease.api.template.dto.TemplateMarketDTO;
|
import io.dataease.api.template.dto.TemplateMarketDTO;
|
||||||
import io.dataease.api.template.request.TemplateMarketSearchRequest;
|
import io.dataease.api.template.response.*;
|
||||||
import io.dataease.api.template.response.MarketBaseResponse;
|
import io.dataease.api.template.vo.MarketApplicationSpecVO;
|
||||||
import io.dataease.api.template.response.MarketCategoryBaseResponse;
|
import io.dataease.api.template.vo.MarketMetaDataVO;
|
||||||
import io.dataease.api.template.response.MarketTemplateBaseResponse;
|
|
||||||
import io.dataease.api.template.vo.TemplateCategoryVO;
|
import io.dataease.api.template.vo.TemplateCategoryVO;
|
||||||
import io.dataease.exception.DEException;
|
import io.dataease.exception.DEException;
|
||||||
import io.dataease.system.manage.SysParameterManage;
|
import io.dataease.system.manage.SysParameterManage;
|
||||||
import io.dataease.utils.HttpClientConfig;
|
import io.dataease.utils.HttpClientConfig;
|
||||||
import io.dataease.utils.HttpClientUtil;
|
import io.dataease.utils.HttpClientUtil;
|
||||||
import io.dataease.utils.JsonUtil;
|
import io.dataease.utils.JsonUtil;
|
||||||
import io.swagger.v3.core.util.Json;
|
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import org.apache.commons.collections4.CollectionUtils;
|
import org.apache.commons.collections4.CollectionUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -31,8 +29,12 @@ import java.util.stream.Collectors;
|
|||||||
public class TemplateMarketManage {
|
public class TemplateMarketManage {
|
||||||
|
|
||||||
private final static String POSTS_API = "/api/content/posts?page=0&size=2000";
|
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 CATEGORIES_API = "/api/content/categories";
|
||||||
|
|
||||||
|
private final static String TEMPLATE_META_DATA_URL = "/upload/meta_data.json";
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private SysParameterManage sysParameterManage;
|
private SysParameterManage sysParameterManage;
|
||||||
|
|
||||||
@ -44,7 +46,7 @@ public class TemplateMarketManage {
|
|||||||
if (StringUtils.isNotEmpty(templateUrl)) {
|
if (StringUtils.isNotEmpty(templateUrl)) {
|
||||||
String sufUrl = sysParameterManage.groupVal("template.").get("template.url");
|
String sufUrl = sysParameterManage.groupVal("template.").get("template.url");
|
||||||
String templateInfo = HttpClientUtil.get(sufUrl + templateUrl, null);
|
String templateInfo = HttpClientUtil.get(sufUrl + templateUrl, null);
|
||||||
return JsonUtil.parseObject(templateInfo,TemplateManageFileDTO.class);
|
return JsonUtil.parseObject(templateInfo, TemplateManageFileDTO.class);
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -62,7 +64,29 @@ public class TemplateMarketManage {
|
|||||||
|
|
||||||
public MarketBaseResponse searchTemplate() {
|
public MarketBaseResponse searchTemplate() {
|
||||||
try {
|
try {
|
||||||
Map<String,String> templateParams = sysParameterManage.groupVal("template.");
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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"));
|
String result = marketGet(templateParams.get("template.url") + POSTS_API, templateParams.get("template.accessKey"));
|
||||||
MarketTemplateBaseResponse postsResult = JsonUtil.parseObject(result, MarketTemplateBaseResponse.class);
|
MarketTemplateBaseResponse postsResult = JsonUtil.parseObject(result, MarketTemplateBaseResponse.class);
|
||||||
MarketBaseResponse response = new MarketBaseResponse(templateParams.get("template.url"), postsResult.getData().getContent());
|
MarketBaseResponse response = new MarketBaseResponse(templateParams.get("template.url"), postsResult.getData().getContent());
|
||||||
@ -73,8 +97,8 @@ public class TemplateMarketManage {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<String> getCategories() {
|
public List<String> getCategoriesV1() {
|
||||||
Map<String,String> templateParams = sysParameterManage.groupVal("template.");
|
Map<String, String> templateParams = sysParameterManage.groupVal("template.");
|
||||||
String resultStr = marketGet(templateParams.get("template.url") + CATEGORIES_API, templateParams.get("template.accessKey"));
|
String resultStr = marketGet(templateParams.get("template.url") + CATEGORIES_API, templateParams.get("template.accessKey"));
|
||||||
MarketCategoryBaseResponse categoryBaseResponse = JsonUtil.parseObject(resultStr, MarketCategoryBaseResponse.class);
|
MarketCategoryBaseResponse categoryBaseResponse = JsonUtil.parseObject(resultStr, MarketCategoryBaseResponse.class);
|
||||||
List<TemplateCategoryVO> categories = categoryBaseResponse.getData();
|
List<TemplateCategoryVO> categories = categoryBaseResponse.getData();
|
||||||
@ -85,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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -255,8 +255,8 @@ public class DataVisualizationServer implements DataVisualizationApi {
|
|||||||
templateData = templateFileInfo.getComponentData();
|
templateData = templateFileInfo.getComponentData();
|
||||||
dynamicData = templateFileInfo.getDynamicData();
|
dynamicData = templateFileInfo.getDynamicData();
|
||||||
staticResource = templateFileInfo.getStaticResource();
|
staticResource = templateFileInfo.getStaticResource();
|
||||||
name = request.getName();
|
name = templateFileInfo.getName();
|
||||||
dvType = request.getType();
|
dvType = templateFileInfo.getDvType();
|
||||||
}
|
}
|
||||||
// 解析动态数据
|
// 解析动态数据
|
||||||
Map<String, String> dynamicDataMap = JsonUtil.parseObject(dynamicData, Map.class);
|
Map<String, String> dynamicDataMap = JsonUtil.parseObject(dynamicData, Map.class);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
spring:
|
spring:
|
||||||
datasource:
|
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
|
username: root
|
||||||
password: 123456
|
password: 123456
|
||||||
messages:
|
messages:
|
||||||
|
3
core/core-frontend/src/assets/svg/dv-use-template.svg
Normal file
3
core/core-frontend/src/assets/svg/dv-use-template.svg
Normal 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 |
@ -101,15 +101,19 @@ const state = reactive({
|
|||||||
templateCreatePid: 0
|
templateCreatePid: 0
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const dvSvgType = computed(() =>
|
||||||
|
curCanvasType.value === 'dashboard' ? 'dv-dashboard-spine' : 'dv-screen-spine'
|
||||||
|
)
|
||||||
|
|
||||||
state.resourceTypeList = [
|
state.resourceTypeList = [
|
||||||
{
|
{
|
||||||
label: newResourceLabel,
|
label: '空白新建',
|
||||||
svgName: curCanvasType.value === 'dashboard' ? 'dv-dashboard-spine' : 'dv-screen-spine',
|
svgName: dvSvgType.value,
|
||||||
command: 'newLeaf'
|
command: 'newLeaf'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '从模版新建',
|
label: '使用模版新建',
|
||||||
svgName: curCanvasType.value === 'dashboard' ? 'dv-dashboard-spine' : 'dv-screen-spine',
|
svgName: 'dv-use-template',
|
||||||
command: 'newFromTemplate'
|
command: 'newFromTemplate'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -247,7 +251,7 @@ const addOperation = (
|
|||||||
window.open(baseUrl, '_blank')
|
window.open(baseUrl, '_blank')
|
||||||
}
|
}
|
||||||
} else if (cmd === 'newFromTemplate') {
|
} else if (cmd === 'newFromTemplate') {
|
||||||
state.templateCreatePid = data.id
|
state.templateCreatePid = data?.id
|
||||||
// newFromTemplate
|
// newFromTemplate
|
||||||
resourceCreateOpt.value.optInit()
|
resourceCreateOpt.value.optInit()
|
||||||
} else {
|
} else {
|
||||||
@ -337,11 +341,28 @@ defineExpose({
|
|||||||
<Icon name="dv-new-folder" />
|
<Icon name="dv-new-folder" />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</el-tooltip>
|
</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" />
|
<Icon name="icon_file-add_outlined" />
|
||||||
</el-icon>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
<el-input
|
<el-input
|
||||||
@ -459,6 +480,7 @@ defineExpose({
|
|||||||
line-height: 24px;
|
line-height: 24px;
|
||||||
}
|
}
|
||||||
.custom-icon {
|
.custom-icon {
|
||||||
|
font-size: 20px;
|
||||||
&.btn {
|
&.btn {
|
||||||
color: #3370ff;
|
color: #3370ff;
|
||||||
}
|
}
|
||||||
@ -539,3 +561,10 @@ defineExpose({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
.menu-outer-dv_popper {
|
||||||
|
width: 140px;
|
||||||
|
margin-top: -2px !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@ -1,96 +1,98 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-row style="display: inherit">
|
<el-row style="width: 100%">
|
||||||
<el-col :class="state.asideActive ? 'aside-active' : 'aside-inActive'">
|
<el-row style="display: table; width: 100%">
|
||||||
<svg-icon
|
<el-col style="float: left" :class="state.asideActive ? 'aside-active' : 'aside-inActive'">
|
||||||
v-show="!state.asideActive"
|
<el-icon v-show="!state.asideActive" class="insert" @click="asideActiveChange(true)"
|
||||||
icon-class="button_right"
|
><DArrowRight
|
||||||
class="open-button"
|
/></el-icon>
|
||||||
@click="asideActiveChange(true)"
|
<el-row v-show="state.asideActive" style="padding: 12px 12px 0">
|
||||||
/>
|
<el-row style="align-items: center">
|
||||||
<el-row v-show="state.asideActive" style="padding: 12px 12px 0">
|
<el-icon class="insert" @click="closePreview()"><Close /></el-icon>
|
||||||
<el-row>
|
<span class="main-title">{{ t('visualization.template_preview') }}</span>
|
||||||
<span class="icon iconfont icon-close icon20 insert" @click="closePreview()" />
|
<el-icon class="insert" @click="asideActiveChange(false)"><DArrowLeft /></el-icon>
|
||||||
<span class="main-title">{{ t('visualization.template_preview') }}</span>
|
</el-row>
|
||||||
<span
|
<el-row class="margin-top16 search-area">
|
||||||
style="float: right"
|
<el-input
|
||||||
class="icon iconfont icon-icon_up-left_outlined insert icon20"
|
v-model="state.searchText"
|
||||||
@click="asideActiveChange(false)"
|
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>
|
||||||
<el-row class="margin-top16 search-area">
|
|
||||||
<el-input
|
|
||||||
v-model="state.searchText"
|
|
||||||
size="small"
|
|
||||||
prefix-icon="el-icon-search"
|
|
||||||
class="title-name-search"
|
|
||||||
:placeholder="t('visualization.enter_template_name_tips')"
|
|
||||||
clearable="true"
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
class="icon iconfont icon-icon-filter insert-filter filter-icon-span"
|
|
||||||
:class="state.extFilterActive ? 'filter-icon-active' : ''"
|
|
||||||
@click="extFilterActiveChange()"
|
|
||||||
/>
|
|
||||||
</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
|
<el-row
|
||||||
v-show="state.asideActive"
|
v-show="state.asideActive"
|
||||||
class="aside-list"
|
class="aside-list"
|
||||||
:class="state.extFilterActive ? 'aside-list-filter-active' : ''"
|
: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' : ''"
|
||||||
>
|
>
|
||||||
<template-market-preview-item
|
<el-row>
|
||||||
v-for="templateItem in state.currentMarketTemplateShowList"
|
<span v-if="state.curTemplate" class="template-title">{{ state.curTemplate.title }}</span>
|
||||||
v-show="templateItem.showFlag"
|
<div style="flex: 1; text-align: right">
|
||||||
:key="templateItem.id"
|
<el-button
|
||||||
:template="templateItem"
|
style="float: right"
|
||||||
:base-url="state.baseUrl"
|
type="primary"
|
||||||
:active="active(templateItem)"
|
size="small"
|
||||||
@previewTemplate="previewTemplate"
|
@click="templateApply(state.curTemplate)"
|
||||||
/>
|
>{{ t('visualization.apply_this_template') }}</el-button
|
||||||
<el-row v-show="!state.hasResult" class="custom-position">
|
>
|
||||||
<div style="text-align: center">
|
|
||||||
<svg-icon icon-class="no_result" style="margin-bottom: 16px; font-size: 75px" />
|
|
||||||
<br />
|
|
||||||
<span>{{ t('commons.no_result') }}</span>
|
|
||||||
</div>
|
</div>
|
||||||
</el-row>
|
</el-row>
|
||||||
</el-row>
|
<el-row class="img-main">
|
||||||
</el-col>
|
<img style="height: 100%" :src="state.templatePreviewUrl" alt="" />
|
||||||
<el-col class="main-area" :class="state.asideActive ? 'main-area-active' : ''">
|
</el-row>
|
||||||
<el-row>
|
</el-col>
|
||||||
<span v-if="state.curTemplate" class="template-title">{{ state.curTemplate.title }}</span>
|
</el-row>
|
||||||
<el-button
|
|
||||||
style="float: right"
|
|
||||||
type="primary"
|
|
||||||
size="small"
|
|
||||||
@click="templateApply(state.curTemplate)"
|
|
||||||
>{{ t('visualization.apply_this_template') }}</el-button
|
|
||||||
>
|
|
||||||
</el-row>
|
|
||||||
<el-row class="img-main">
|
|
||||||
<img height="100%" :src="state.templatePreviewUrl" alt="" />
|
|
||||||
</el-row>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
</el-row>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { searchMarket, getCategories } from '@/api/templateMarket'
|
import { searchMarket, getCategories } from '@/api/templateMarket'
|
||||||
import TemplateMarketPreviewItem from '@/views/template-market/component/TemplateMarketPreviewItem.vue'
|
|
||||||
import { onMounted, reactive, watch } from 'vue'
|
import { onMounted, reactive, watch } from 'vue'
|
||||||
import { useI18n } from '@/hooks/web/useI18n'
|
import { useI18n } from '@/hooks/web/useI18n'
|
||||||
|
import TemplateMarketPreviewItem from '@/views/template-market/component/TemplateMarketPreviewItem.vue'
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@ -129,6 +131,13 @@ watch(
|
|||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => state.searchText,
|
() => state.searchText,
|
||||||
|
value => {
|
||||||
|
initTemplateShow()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.previewId,
|
||||||
value => {
|
value => {
|
||||||
state.currentMarketTemplateShowList.forEach(template => {
|
state.currentMarketTemplateShowList.forEach(template => {
|
||||||
if (value === template.id) {
|
if (value === template.id) {
|
||||||
@ -314,7 +323,7 @@ onMounted(() => {
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: center;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
flex-flow: row nowrap;
|
flex-flow: row nowrap;
|
||||||
color: #646a73;
|
color: #646a73;
|
||||||
@ -356,6 +365,7 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.main-title {
|
.main-title {
|
||||||
|
width: 135px;
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
@ -434,6 +444,7 @@ onMounted(() => {
|
|||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
}
|
}
|
||||||
.img-main {
|
.img-main {
|
||||||
|
display: inherit;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
background: #0f1114;
|
background: #0f1114;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="testcase-template">
|
<div class="testcase-template">
|
||||||
<div class="template-img" :style="classBackground" @click.stop="templatePreview" />
|
<div class="template-img" :style="classBackground" @click.stop="templateInnerPreview" />
|
||||||
<el-row class="bottom-area">
|
<el-row class="bottom-area">
|
||||||
<el-row>
|
<el-row>
|
||||||
<span class="demonstration">{{ template.title }}</span>
|
<span class="demonstration">{{ template.title }}</span>
|
||||||
</el-row>
|
</el-row>
|
||||||
</el-row>
|
</el-row>
|
||||||
<el-row class="template-button">
|
<el-row class="template-button">
|
||||||
<el-button size="mini" style="width: 141px" @click="templatePreview">{{
|
<el-button size="mini" style="width: 141px" @click="templateInnerPreview">{{
|
||||||
t('visualization.preview')
|
t('visualization.preview')
|
||||||
}}</el-button>
|
}}</el-button>
|
||||||
<el-button size="mini" style="width: 141px" type="primary" @click="apply">{{
|
<el-button size="mini" style="width: 141px" type="primary" @click="apply">{{
|
||||||
@ -61,7 +61,7 @@ const apply = () => {
|
|||||||
emits('templateApply', props.template)
|
emits('templateApply', props.template)
|
||||||
}
|
}
|
||||||
|
|
||||||
const templatePreview = () => {
|
const templateInnerPreview = e => {
|
||||||
emits('templatePreview', props.template.id)
|
emits('templatePreview', props.template.id)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,35 +1,382 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-row class="custom-position template-main">
|
<el-row class="outer-body" v-loading="state.loading">
|
||||||
{{ t('visualization.market_network_tips') }}
|
<!--预览模式-->
|
||||||
|
<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>
|
</el-row>
|
||||||
</template>
|
</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 { 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>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.custom-position {
|
.custom-tabs-head {
|
||||||
height: 80vh;
|
display: inherit;
|
||||||
flex: 1;
|
padding-bottom: 15px;
|
||||||
display: flex;
|
::v-deep(.ed-tabs__item) {
|
||||||
align-items: center;
|
font-weight: 400;
|
||||||
justify-content: space-between;
|
font-size: 14px;
|
||||||
font-size: 14px;
|
}
|
||||||
flex-flow: row nowrap;
|
|
||||||
color: #646a73;
|
|
||||||
font-weight: 400;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.template-main {
|
.template-main {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
padding: 0 12px 24px 12px;
|
padding: 0 12px 24px 12px;
|
||||||
height: calc(100vh - 56px) !important;
|
height: calc(100vh - 190px) !important;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
background-color: var(--ContentBG, #ffffff);
|
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;
|
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>
|
</style>
|
||||||
|
@ -1,414 +0,0 @@
|
|||||||
<template>
|
|
||||||
<el-row class="outer-body">
|
|
||||||
<!--预览模式-->
|
|
||||||
<market-preview
|
|
||||||
v-show="state.previewModel"
|
|
||||||
:preview-id="state.templatePreviewId"
|
|
||||||
@closePreview="state.previewModel = false"
|
|
||||||
@templateApply="templateApply"
|
|
||||||
/>
|
|
||||||
<!--列表模式-->
|
|
||||||
<el-row v-show="!state.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 style="display: inherit">
|
|
||||||
<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"
|
|
||||||
:style="{ width: state.templateSpan }"
|
|
||||||
>
|
|
||||||
<template-market-item
|
|
||||||
: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">
|
|
||||||
<svg-icon icon-class="no_result" style="margin-bottom: 16px; font-size: 75px" />
|
|
||||||
<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-dialog
|
|
||||||
:title="t('visualization.apply_template')"
|
|
||||||
v-model="state.folderSelectShow"
|
|
||||||
width="600px"
|
|
||||||
class="market-dialog-css"
|
|
||||||
:append-to-body="true"
|
|
||||||
:destroy-on-close="true"
|
|
||||||
>
|
|
||||||
<el-form ref="panelForm" :model="state.panelForm" :rules="rule" label-width="80px">
|
|
||||||
<el-form-item :label="t('visualization.name')" prop="name">
|
|
||||||
<el-input
|
|
||||||
v-model="state.panelForm.name"
|
|
||||||
:clearable="true"
|
|
||||||
:placeholder="t('visualization.enter_name_tips')"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item :label="t('commons.folder')" prop="pid">
|
|
||||||
<treeselect
|
|
||||||
v-model="state.panelForm.pid"
|
|
||||||
:clearable="false"
|
|
||||||
:options="state.panelGroupList"
|
|
||||||
:normalizer="normalizer"
|
|
||||||
:placeholder="t('chart.select_group')"
|
|
||||||
:no-children-text="t('commons.treeselect.no_children_text')"
|
|
||||||
:no-options-text="t('commons.treeselect.no_options_text')"
|
|
||||||
:no-results-text="t('commons.treeselect.no_results_text')"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
<template #footer>
|
|
||||||
<div class="dialog-footer dialog-footer-self">
|
|
||||||
<el-button size="mini" @click="state.folderSelectShow = false"
|
|
||||||
>{{ t('commons.cancel') }}
|
|
||||||
</el-button>
|
|
||||||
<el-button
|
|
||||||
size="mini"
|
|
||||||
type="primary"
|
|
||||||
:disabled="!state.panelForm.name || !state.panelForm.pid"
|
|
||||||
@click="apply"
|
|
||||||
>{{ t('commons.confirm') }}
|
|
||||||
</el-button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</el-dialog>
|
|
||||||
</el-row>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { getCategories, searchMarket } from '@/api/templateMarket'
|
|
||||||
import elementResizeDetectorMaker from 'element-resize-detector'
|
|
||||||
import { nextTick, reactive, watch, onMounted } 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'
|
|
||||||
const { t } = useI18n()
|
|
||||||
|
|
||||||
const emits = defineEmits(['closeDialog'])
|
|
||||||
|
|
||||||
const state = reactive({
|
|
||||||
hasResult: true,
|
|
||||||
templateMiniWidth: 330,
|
|
||||||
templateCurWidth: 310,
|
|
||||||
templateSpan: '25%',
|
|
||||||
previewModel: false,
|
|
||||||
previewVisible: false,
|
|
||||||
templatePreviewId: '',
|
|
||||||
marketTabs: null,
|
|
||||||
marketActiveTab: null,
|
|
||||||
searchText: null,
|
|
||||||
panelForm: {
|
|
||||||
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 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.panelForm.name = template.title
|
|
||||||
state.panelForm.templateUrl = state.baseUrl + template.metas.theme_repo
|
|
||||||
state.folderSelectShow = true
|
|
||||||
}
|
|
||||||
|
|
||||||
const apply = () => {
|
|
||||||
if (state.panelForm.name.length > 50) {
|
|
||||||
ElMessage.warning(t('commons.char_can_not_more_50'))
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!state.panelForm.templateUrl) {
|
|
||||||
ElMessage.warning('未获取模板下载链接请联系模板市场官方')
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// panelSave(state.panelForm)
|
|
||||||
// .then(response => {
|
|
||||||
// state.$message({
|
|
||||||
// message: state.t('commons.save_success'),
|
|
||||||
// type: 'success',
|
|
||||||
// showClose: true
|
|
||||||
// })
|
|
||||||
// state.folderSelectShow = false
|
|
||||||
// state.$router.push({ name: 'panel', params: response.data })
|
|
||||||
// })
|
|
||||||
// .catch(() => {
|
|
||||||
// state.loading = false
|
|
||||||
// })
|
|
||||||
}
|
|
||||||
const closeDialog = () => {
|
|
||||||
emits('closeDialog')
|
|
||||||
}
|
|
||||||
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
|
|
||||||
state.previewModel = 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>
|
|
||||||
.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 {
|
|
||||||
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: space-between;
|
|
||||||
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>
|
|
@ -3,10 +3,15 @@ package io.dataease.api.template.dto;
|
|||||||
import io.dataease.api.template.vo.MarketCategoryVO;
|
import io.dataease.api.template.vo.MarketCategoryVO;
|
||||||
import io.dataease.api.template.vo.MarketMetasVO;
|
import io.dataease.api.template.vo.MarketMetasVO;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.apache.commons.lang3.ArrayUtils;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
public class TemplateMarketDTO {
|
public class TemplateMarketDTO {
|
||||||
private String id;
|
private String id;
|
||||||
private String title;
|
private String title;
|
||||||
@ -18,4 +23,12 @@ public class TemplateMarketDTO {
|
|||||||
private Boolean showFlag = true;
|
private Boolean showFlag = true;
|
||||||
private List<MarketCategoryVO> categories;
|
private List<MarketCategoryVO> categories;
|
||||||
private MarketMetasVO metas;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,4 +23,6 @@ public class MarketBaseResponse {
|
|||||||
this.contents = contents;
|
this.contents = contents;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
@ -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;
|
||||||
|
|
||||||
|
}
|
@ -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;
|
||||||
|
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package io.dataease.api.template.vo;
|
package io.dataease.api.template.vo;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Author: wangjiahao
|
* Author: wangjiahao
|
||||||
@ -8,8 +9,13 @@ import lombok.Data;
|
|||||||
* Description:
|
* Description:
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
public class MarketCategoryVO {
|
public class MarketCategoryVO {
|
||||||
private String id;
|
private String id;
|
||||||
private String name;
|
private String name;
|
||||||
private String slug;
|
private String slug;
|
||||||
|
|
||||||
|
public MarketCategoryVO(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package io.dataease.api.template.vo;
|
package io.dataease.api.template.vo;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Author: wangjiahao
|
* Author: wangjiahao
|
||||||
@ -8,6 +9,11 @@ import lombok.Data;
|
|||||||
* Description:
|
* Description:
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
public class MarketMetasVO {
|
public class MarketMetasVO {
|
||||||
private String theme_repo;
|
private String theme_repo;
|
||||||
|
|
||||||
|
public MarketMetasVO(String theme_repo) {
|
||||||
|
this.theme_repo = theme_repo;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user