Merge pull request #6741 from dataease/pr@dev-v2@refactor_template

refactor: 优化模版样式应用逻辑等
This commit is contained in:
王嘉豪 2023-11-17 16:14:49 +08:00 committed by GitHub
commit 08cc003980
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 1232 additions and 25 deletions

View File

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

View File

@ -5,12 +5,15 @@ import io.dataease.api.template.dto.TemplateManageFileDTO;
import io.dataease.api.template.dto.TemplateMarketDTO;
import io.dataease.api.template.request.TemplateMarketSearchRequest;
import io.dataease.api.template.response.MarketBaseResponse;
import io.dataease.api.template.response.MarketCategoryBaseResponse;
import io.dataease.api.template.response.MarketTemplateBaseResponse;
import io.dataease.api.template.vo.TemplateCategoryVO;
import io.dataease.exception.DEException;
import io.dataease.system.manage.SysParameterManage;
import io.dataease.utils.HttpClientConfig;
import io.dataease.utils.HttpClientUtil;
import io.dataease.utils.JsonUtil;
import io.swagger.v3.core.util.Json;
import jakarta.annotation.Resource;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
@ -57,14 +60,13 @@ public class TemplateMarketManage {
return HttpClientUtil.get(url, config);
}
public MarketBaseResponse searchTemplate(TemplateMarketSearchRequest request) {
public MarketBaseResponse searchTemplate() {
try {
Map<String,String> templateParams = sysParameterManage.groupVal("template.");
String result = marketGet(templateParams.get("template.url") + POSTS_API, templateParams.get("template.accessKey"));
TypeReference<List<TemplateMarketDTO>> market = new TypeReference<>() {
};
List<TemplateMarketDTO> postsResult = JsonUtil.parseList(result,market);
return new MarketBaseResponse(templateParams.get("template.url"), postsResult);
MarketTemplateBaseResponse postsResult = JsonUtil.parseObject(result, MarketTemplateBaseResponse.class);
MarketBaseResponse response = new MarketBaseResponse(templateParams.get("template.url"), postsResult.getData().getContent());
return response;
} catch (Exception e) {
DEException.throwException(e);
}
@ -74,9 +76,8 @@ public class TemplateMarketManage {
public List<String> getCategories() {
Map<String,String> templateParams = sysParameterManage.groupVal("template.");
String resultStr = marketGet(templateParams.get("template.url") + CATEGORIES_API, templateParams.get("template.accessKey"));
TypeReference<List<TemplateCategoryVO>> market = new TypeReference<>() {
};
List<TemplateCategoryVO> categories = JsonUtil.parseList(resultStr,market);
MarketCategoryBaseResponse categoryBaseResponse = JsonUtil.parseObject(resultStr, MarketCategoryBaseResponse.class);
List<TemplateCategoryVO> categories = categoryBaseResponse.getData();
if (CollectionUtils.isNotEmpty(categories)) {
return categories.stream().filter(item -> !"应用系列".equals(item.getName())).sorted(Comparator.comparing(TemplateCategoryVO::getPriority)).map(TemplateCategoryVO::getName).collect(Collectors.toList());
} else {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,414 @@
<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>

View File

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

View File

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

View File

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

View File

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

View File

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