Merge pull request #6551 from dataease/pr@dev-v2@feat_template-manage

Pr@dev v2@feat template manage
This commit is contained in:
王嘉豪 2023-11-06 18:10:24 +08:00 committed by GitHub
commit 85c1ce9080
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 1228 additions and 13 deletions

View File

@ -92,7 +92,20 @@
</properties>
<dependencies>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>io.dataease</groupId>
<artifactId>xpack-permissions</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.dataease</groupId>
<artifactId>xpack-base</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
<build>

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://39.98.78.97: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";
/**
* 业务模块例如datasource,dataset,panel等
*/
private static final String busi = "system";
private static final String busi = "template";
/**
* 这是要生成代码的表名称
*/
private static final String TABLE_NAME = "core_sys_setting";
private static final String TABLE_NAME = "visualization_template";
/**
* 下面两个配置基本上不用动

View File

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

View File

@ -0,0 +1,42 @@
import request from '@/config/axios'
export function save(data) {
return request.post({
url: '/template/save',
data: data,
loading: true
})
}
export function templateDelete(id) {
return request.post({
url: '/template/delete/' + id
})
}
export function showTemplateList(data) {
return request.post({
url: '/template/templateList',
data: data
})
}
export function findOne(id) {
return request.get({
url: '/template/findOne/' + id
})
}
export function find(data) {
return request.post({
url: '/template/find',
data: data,
loading: true
})
}
export function nameCheck(data) {
return request.post({
url: '/template/nameCheck',
data: data
})
}

View File

@ -0,0 +1,14 @@
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,209 @@
<template>
<div class="template-import">
<el-form
ref="templateImportForm"
class="de-form-item"
:model="state.templateInfo"
:rules="state.templateInfoRules"
>
<el-form-item :label="t('system_parameter_setting.template_name')" prop="name">
<div class="flex-template">
<el-input v-model="state.templateInfo.name" clearable size="small" />
<el-button style="margin-left: 10px" class="el-icon-upload2" secondary @click="goFile">{{
t('panel.upload_template')
}}</el-button>
<input
id="input"
ref="filesRef"
type="file"
accept=".DET"
hidden
@change="handleFileChange"
/>
</div>
</el-form-item>
</el-form>
<el-row class="preview" :style="classBackground" />
<el-row class="de-root-class">
<deBtn secondary @click="cancel()">{{ t('commons.cancel') }}</deBtn>
<deBtn type="primary" @click="saveTemplate()">{{ t('commons.confirm') }}</deBtn>
</el-row>
</div>
</template>
<script lang="ts" setup>
import { save, nameCheck, find } from '@/api/template'
import { computed, onMounted, reactive, ref } from 'vue'
import { imgUrlTrans } from '@/utils/imgUtils'
import { ElMessage } from 'element-plus-secondary'
import { useI18n } from '@/hooks/web/useI18n'
const emits = defineEmits(['closeEditTemplateDialog', 'refresh'])
const { t } = useI18n()
const filesRef = ref(null)
const props = defineProps({
pid: {
type: String,
required: true
}
})
const state = reactive({
nameList: [],
importTemplateInfo: {
snapshot: ''
},
templateInfoRules: {
name: [
{
required: true,
message: t('commons.input_content'),
trigger: 'change'
}
]
},
recover: false,
templateInfo: {
level: '1',
pid: props.pid,
name: '',
templateStyle: null,
templateData: null,
dynamicData: null,
staticResource: null,
snapshot: ''
}
})
const classBackground = computed(() => {
if (state.importTemplateInfo.snapshot) {
return {
background: `url(${imgUrlTrans(state.importTemplateInfo.snapshot)}) no-repeat`
}
} else {
return {}
}
})
const showCurrentTemplate = pid => {
find({ pid }).then(response => {
state.nameList = response.data
})
}
const cancel = () => {
emits('closeEditTemplateDialog')
}
const saveTemplate = () => {
if (!state.templateInfo.name) {
ElMessage.warning(t('chart.name_can_not_empty'))
return false
}
if (!state.templateInfo.templateData) {
ElMessage.warning(t('chart.template_can_not_empty'))
return false
}
const nameCheckRequest = {
pid: state.templateInfo.pid,
name: state.templateInfo.name,
optType: 'insert'
}
nameCheck(nameCheckRequest).then(response => {
if (response.data.indexOf('exist') > -1) {
const options = {
title: 'commons.prompt',
content: 'system_parameter_setting.to_overwrite_them',
type: 'primary',
cb: () =>
save(state.templateInfo).then(response => {
ElMessage.success(t('system_parameter_setting.import_succeeded'))
emits('refresh')
emits('closeEditTemplateDialog')
}),
confirmButtonText: t('template.override')
}
handlerConfirm(options)
} else {
save(state.templateInfo).then(response => {
ElMessage.success(t('system_parameter_setting.import_succeeded'))
emits('refresh')
emits('closeEditTemplateDialog')
})
}
})
}
const handlerConfirm = option => {
// do handlerConfirm
}
const handleFileChange = e => {
const file = e.target.files[0]
const reader = new FileReader()
reader.onload = res => {
const result = res.target.result as string
state.importTemplateInfo = JSON.parse(result)
state.templateInfo.name = state.importTemplateInfo['name']
state.templateInfo.templateStyle = state.importTemplateInfo['panelStyle']
state.templateInfo.templateData = state.importTemplateInfo['panelData']
state.templateInfo.snapshot = state.importTemplateInfo.snapshot
state.templateInfo.dynamicData = state.importTemplateInfo['dynamicData']
state.templateInfo.staticResource = state.importTemplateInfo['staticResource']
state.templateInfo['nodeType'] = 'template'
}
reader.readAsText(file)
}
const goFile = () => {
filesRef.value.click()
}
onMounted(() => {
showCurrentTemplate(props.pid)
})
</script>
<style scoped>
.my_table :deep(.el-table__row > td) {
/* 去除表格线 */
border: none;
padding: 0 0;
}
.my_table :deep(.el-table th.is-leaf) {
/* 去除上边框 */
border: none;
}
.my_table :deep(.el-table::before) {
/* 去除下边框 */
height: 0;
}
.de-root-class {
margin-top: 24px;
text-align: right;
}
.preview {
margin-top: -12px;
border: 1px solid #e6e6e6;
height: 300px !important;
overflow: auto;
background-size: 100% 100% !important;
border-radius: 4px;
}
.preview-show {
border-left: 1px solid #e6e6e6;
height: 300px;
background-size: 100% 100% !important;
}
</style>
<style lang="scss">
.flex-template {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
.el-input {
margin-right: 2px;
flex: 1;
}
}
</style>

View File

@ -0,0 +1,140 @@
<template>
<div :style="classBackground" class="de-card-model">
<div class="card-img-model" :style="classImg">
<img :src="model.snapshot" alt="" />
</div>
<div class="card-info">
<el-tooltip class="item" effect="dark" :content="model.name" placement="top">
<span class="de-model-text">{{ model.name }}</span>
</el-tooltip>
<el-dropdown size="medium" trigger="click" @command="handleCommand">
<i class="el-icon-more" />
<template #dropdown>
<el-dropdown-menu class="de-card-dropdown">
<slot>
<el-dropdown-item command="rename">
<i class="el-icon-edit" />
{{ $t('chart.rename') }}
</el-dropdown-item>
<el-dropdown-item command="delete">
<i class="el-icon-delete" />
{{ $t('chart.delete') }}
</el-dropdown-item>
</slot>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
const { t } = useI18n()
const emits = defineEmits(['command'])
const props = defineProps({
model: {
type: Object
},
width: {
type: Number
}
})
const classBackground = computed(() => {
return {
width: props.width + 'px',
height: props.width * 0.714 + 'px'
}
})
const classImg = computed(() => {
return {
width: props.width + 'px',
height: props.width * 0.576 + 'px'
}
})
const handleCommand = key => {
emits('command', key)
}
</script>
<style lang="scss">
.de-card-model {
box-sizing: border-box;
background: #ffffff;
border: 1px solid var(--deCardStrokeColor, #dee0e3);
border-radius: 4px;
margin: 0 24px 25px 0;
.card-img-model {
border-bottom: 1px solid var(--deCardStrokeColor, #dee0e3);
height: 144px;
width: 100%;
overflow: hidden;
img {
width: 100%;
height: 100%;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
}
.card-info {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 12px 9px 12px;
box-sizing: border-box;
.el-icon-more {
width: 24px;
height: 24px;
line-height: 24px;
text-align: center;
font-size: 12px;
color: #646a73;
cursor: pointer;
}
.el-icon-more:hover {
background: rgba(31, 35, 41, 0.1);
border-radius: 4px;
}
.el-icon-more:active {
background: rgba(31, 35, 41, 0.2);
border-radius: 4px;
}
}
.de-model-text {
font-family: 'PingFang SC';
font-style: normal;
font-weight: 400;
font-size: 14px;
line-height: 22px;
color: #1f2329;
display: inline-block;
width: 90%;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
margin-right: 10px;
}
}
.de-card-model:hover {
box-shadow: 0px 6px 24px rgba(31, 35, 41, 0.08);
}
.de-card-dropdown {
margin-top: 0 !important;
.popper__arrow {
display: none !important;
}
}
</style>

View File

@ -0,0 +1,247 @@
<template xmlns:el-col="http://www.w3.org/1999/html">
<div class="de-template-list">
<el-input
v-model="state.templateFilterText"
:placeholder="t('system_parameter_setting.search_keywords')"
size="small"
class="de-input-search"
clearable
>
<template #prefix>
<svg-icon icon-class="de-search" />
</template>
</el-input>
<el-empty
v-if="!templateListComputed.length && state.templateFilterText === ''"
:image="state.noneImg"
:description="t('components.no_classification')"
/>
<el-empty
v-if="!templateListComputed.length && state.templateFilterText !== ''"
:image="state.nothingImg"
:description="t('components.relevant_content_found')"
/>
<ul>
<li
v-for="ele in templateListComputed"
:key="ele.name"
:class="[{ select: state.activeTemplate === ele.id }]"
@click="nodeClick(ele)"
>
<svg-icon icon-class="scene" class="de-icon-sense" />
<span class="text-template-overflow" :title="ele.name">{{ ele.name }}</span>
<span class="more" @click.stop>
<el-dropdown trigger="click" size="small" @command="type => clickMore(type, ele)">
<span class="el-dropdown-link">
<i class="el-icon-more" />
</span>
<template #dropdown>
<el-dropdown-menu class="de-template-dropdown">
<el-dropdown-item icon="el-icon-upload2" command="import">
{{ t('panel.import') }}
</el-dropdown-item>
<el-dropdown-item icon="el-icon-edit" command="edit">
{{ t('panel.rename') }}
</el-dropdown-item>
<el-dropdown-item icon="el-icon-delete" command="delete">
{{ t('panel.delete') }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</span>
</li>
</ul>
<el-button
v-if="state.templateFilterText === ''"
style="width: 100%"
icon="el-icon-plus"
secondary
@click="add()"
>
{{ t('panel.add_category') }}
</el-button>
</div>
</template>
<script setup lang="ts">
import { useI18n } from '@/hooks/web/useI18n'
import { computed, reactive } from 'vue'
const { t } = useI18n()
const emits = defineEmits([
'showCurrentTemplate',
'showTemplateEditDialog',
'templateDelete',
'templateEdit',
'templateImport'
])
const props = defineProps({
templateType: {
type: String,
default: ''
},
templateList: {
type: Array,
default: function () {
return []
}
}
})
const state = reactive({
templateFilterText: '',
activeTemplate: '',
noneImg: '@/assets/None.png',
nothingImg: '@/assets/nothing.png'
})
const templateListComputed = computed(() => {
if (!state.templateFilterText) return [...props.templateList]
return props.templateList.filter(ele => ele['name']?.includes(state.templateFilterText))
})
const clickMore = (type, data) => {
switch (type) {
case 'edit':
templateEdit(data)
break
case 'delete':
templateDelete(data)
break
case 'import':
templateImport(data)
break
}
}
const nodeClick = ({ id, label }) => {
state.activeTemplate = id
emits('showCurrentTemplate', id, label)
}
const add = () => {
emits('showTemplateEditDialog', 'new')
}
const templateDelete = template => {
const options = {
title: 'system_parameter_setting.delete_this_category',
content: 'system_parameter_setting.also_be_deleted',
type: 'primary',
cb: () => emits('templateDelete', template.id)
}
handlerConfirm(options)
}
const templateEdit = template => {
emits('templateEdit', template)
}
const templateImport = template => {
emits('templateImport', template.id)
}
const handlerConfirm = options => {
// do handlerConfirm
}
</script>
<style scoped lang="scss">
.de-template-list {
height: 100%;
position: relative;
ul {
margin: 16px 0 20px 0;
padding: 0;
overflow-y: auto;
max-height: calc(100% - 90px);
}
li {
list-style: none;
width: 100%;
box-sizing: border-box;
height: 40px;
padding: 0 30px 0 12px;
display: flex;
align-items: center;
border-radius: 4px;
color: var(--deTextPrimary, #1f2329);
font-family: 'PingFang SC';
font-style: normal;
font-weight: 500;
font-size: 14px;
cursor: pointer;
position: relative;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
.text-template-overflow {
display: inline-block;
max-width: 87%;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.folder {
color: #8f959e;
margin-right: 9px;
}
.more {
position: absolute;
top: 50%;
right: 10px;
transform: translateY(-50%);
display: none;
.el-icon-more {
width: 24px;
height: 24px;
line-height: 24px;
text-align: center;
font-size: 12px;
color: #646a73;
cursor: pointer;
}
.el-icon-more:hover {
background: rgba(31, 35, 41, 0.1);
border-radius: 4px;
}
.el-icon-more:active {
background: rgba(31, 35, 41, 0.2);
border-radius: 4px;
}
}
&:hover {
background: rgba(31, 35, 41, 0.1);
.more {
display: block;
}
}
}
li.select {
background: var(--deWhiteHover, #e0eaff) !important;
color: var(--TextActive, #3370ff) !important;
}
.de-btn-fix {
position: absolute;
bottom: 0;
left: 0;
}
}
.de-template-dropdown {
margin-top: 0 !important;
.popper__arrow {
display: none !important;
}
}
</style>

View File

@ -1,12 +1,412 @@
<script lang="ts" setup>
import { ref } from 'vue'
const wizard = ref('wizard')
</script>
<template>
<div>
{{ wizard }}
<div class="de-template">
<el-tabs v-model="state.currentTemplateType" class="de-tabs" @tab-click="handleClick">
<el-tab-pane name="self">
<template #label>
<span>{{ t('panel.user_template') }}</span>
</template>
</el-tab-pane>
<el-tab-pane name="system">
<template #label>
<span>{{ t('panel.sys_template') }}</span>
</template>
</el-tab-pane>
</el-tabs>
<div class="tabs-container flex-tabs">
<div class="de-tabs-left">
<template-list
ref="templateListRef"
:template-type="state.currentTemplateType"
:template-list="state.templateList"
@templateDelete="templateFolderDelete"
@templateEdit="templateEdit"
@showCurrentTemplate="showCurrentTemplate"
@templateImport="templateImport"
@showTemplateEditDialog="showTemplateEditDialog"
/>
</div>
<div class="de-tabs-right">
<div v-if="state.currentTemplateLabel" class="active-template">
{{ state.currentTemplateLabel }}&nbsp;&nbsp;({{ state.currentTemplateShowList.length }})
<el-button
type="primary"
icon="el-icon-upload2"
@click="templateImport(state.currentTemplateId)"
>
{{ t('panel.import') }}
</el-button>
</div>
<el-empty
v-if="!state.currentTemplateShowList.length"
:image="state.noneImg"
:description="t('components.no_template')"
/>
<div v-show="state.currentTemplateId !== ''" id="template-box" class="template-box">
<template-item
v-for="item in state.currentTemplateShowList"
:key="item.id"
:width="state.templateCurWidth"
:model="item"
@command="key => handleCommand(key, item)"
/>
</div>
</div>
</div>
</div>
<el-dialog
:title="state.dialogTitle"
v-model:visible="state.editTemplate"
append-to-body
class="de-dialog-form"
width="600px"
>
<el-form
ref="templateEditFormRef"
class="de-form-item"
:model="state.templateEditForm"
:rules="state.templateEditFormRules"
>
<el-form-item :label="state.dialogTitleLabel" prop="name">
<el-input v-model="state.templateEditForm.name" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button secondary @click="close()">{{ t('commons.cancel') }}</el-button>
<el-button type="primary" @click="saveTemplateEdit(state.templateEditForm)"
>{{ t('commons.confirm') }}
</el-button>
</div>
</template>
</el-dialog>
<!--导入templatedialog-->
<el-dialog
:title="state.templateDialog.title"
v-model:visible="state.templateDialog.visible"
:show-close="true"
class="de-dialog-form"
width="600px"
>
<template-import
v-if="state.templateDialog.visible"
:pid="state.templateDialog.pid"
@refresh="showCurrentTemplate(state.currentTemplateId, state.currentTemplateLabel)"
@closeEditTemplateDialog="closeEditTemplateDialog"
/>
</el-dialog>
</div>
</template>
<style lang="less" scoped></style>
<script lang="ts" setup>
import { save, templateDelete, find } from '@/api/template'
import elementResizeDetectorMaker from 'element-resize-detector'
import { computed, nextTick, onMounted, reactive, ref } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
import { ElMessage } from 'element-plus-secondary'
import TemplateList from '@/views/template/component/TemplateList.vue'
const { t } = useI18n()
const templateEditFormRef = ref(null)
const templateListRef = ref(null)
const roleValidator = (rule, value, callback) => {
if (nameRepeat(value)) {
const { nodeType } = state.templateEditForm || {}
callback(
new Error(
t(
`system_parameter_setting.${
nodeType === 'folder' ? 'name_already_exists_type' : 'the_same_category'
}`
)
)
)
} else {
callback()
}
}
const state = reactive({
showShare: false,
currentTemplateShowList: [],
noneImg: '@/assets/None.png',
currentPid: '',
currentTemplateType: 'self',
templateEditFormRules: {
name: [
{ required: true, trigger: 'blur', validator: roleValidator },
{
required: true,
message: t('commons.input_content'),
trigger: 'blur'
},
{
max: 50,
message: t('commons.char_can_not_more_50'),
trigger: 'change'
}
]
},
templateEditForm: {},
editTemplate: false,
dialogTitle: '',
dialogTitleLabel: '',
currentTemplateLabel: '',
currentTemplateId: '',
templateList: [],
templateMiniWidth: 286,
templateCurWidth: 286,
formType: '',
originName: '',
templateDialog: {
title: t('panel.import_template'),
visible: false,
pid: ''
}
})
const nameList = computed(() => {
const { nodeType } = state.templateEditForm || {}
if (nodeType === 'template') {
return state.currentTemplateShowList.map(ele => ele.label)
}
if (nodeType === 'folder') {
return state.templateList.map(ele => ele.label)
}
return []
})
const nameRepeat = value => {
if (!nameList.value) {
return false
}
//
if (state.formType === 'edit' && state.originName === value) {
return false
}
return nameList.value.some(name => name === value)
}
const handleCommand = (key, data) => {
switch (key) {
case 'rename':
templateEdit(data)
break
case 'delete':
templateDeleteConfirm(data)
break
default:
break
}
}
const handlerConfirm = option => {
//do handlerConfirm
}
const templateDeleteConfirm = template => {
const options = {
title: 'system_parameter_setting.delete_this_template',
type: 'primary',
cb: () => templateDelete(template.id)
}
handlerConfirm(options)
}
const handleClick = (tab, event) => {
getTree()
}
const showCurrentTemplate = (pid, label) => {
state.currentTemplateId = pid
state.currentTemplateLabel = label
if (state.currentTemplateId) {
find({ pid: state.currentTemplateId }).then(response => {
state.currentTemplateShowList = response.data
})
}
}
const templateFolderDelete = id => {
if (id) {
templateDelete(id).then(response => {
ElMessage.info(t('commons.delete_success'))
getTree()
})
}
}
const templateDelete = id => {
if (id) {
templateDelete(id).then(response => {
ElMessage.info(t('commons.delete_success'))
showCurrentTemplate(state.currentTemplateId, state.currentTemplateLabel)
})
}
}
const showTemplateEditDialog = (type, templateInfo) => {
state.templateEditForm = null
state.formType = type
if (type === 'edit') {
state.templateEditForm = JSON.parse(JSON.stringify(templateInfo))
state.dialogTitle = t(
`system_parameter_setting.${
state.templateEditForm['nodeType'] === 'folder' ? 'edit_classification' : 'edit_template'
}`
)
state.originName = state.templateEditForm['label']
} else {
state.dialogTitle = t('panel.add_category')
state.templateEditForm = {
name: '',
nodeType: 'folder',
templateType: state.currentTemplateType,
level: 0
}
}
state.dialogTitleLabel = t(
`system_parameter_setting.${
state.templateEditForm['nodeType'] === 'folder' ? 'classification_name' : 'template_name'
}`
)
state.editTemplate = true
}
const templateEdit = templateInfo => {
showTemplateEditDialog('edit', templateInfo)
}
const saveTemplateEdit = templateEditForm => {
templateEditFormRef.value.validate(valid => {
if (valid) {
save(templateEditForm).then(response => {
close()
// openMessageSuccess(
// `system_parameter_setting.${
// this.templateEditForm.id ? 'rename_succeeded' : 'added_successfully'
// }`
// )
getTree()
})
} else {
return false
}
})
}
const close = () => {
templateEditFormRef.value.resetFields()
state.editTemplate = false
}
const getTree = () => {
const request = {
templateType: state.currentTemplateType,
level: '0'
}
find(request).then(res => {
state.templateList = res.data
showFirst()
})
}
const showFirst = () => {
//
if (state.templateList && state.templateList.length > 0) {
let showFirst = true
state.templateList.forEach(template => {
if (template.id === state.currentTemplateId) {
showFirst = false
}
})
if (showFirst) {
nextTick().then(() => {
const [obj = {}] = state.templateList
templateListRef.value.nodeClick(obj)
})
} else {
showCurrentTemplate(state.currentTemplateId, state.currentTemplateLabel)
}
} else {
state.currentTemplateShowList = []
}
}
const closeEditTemplateDialog = () => {
state.templateDialog.visible = false
}
const templateImport = pid => {
state.templateDialog.visible = true
state.templateDialog.pid = pid
}
onMounted(() => {
getTree()
const erd = elementResizeDetectorMaker()
const templateMainDom = document.getElementById('template-box')
// div
erd.listenTo(templateMainDom, element => {
nextTick(() => {
const curSeparator = Math.trunc(templateMainDom.offsetWidth / state.templateMiniWidth)
state.templateCurWidth =
Math.trunc(templateMainDom.offsetWidth / curSeparator) - 24 - curSeparator
})
})
})
</script>
<style lang="scss" scoped>
.de-template {
height: 100%;
background-color: var(--MainBG, #f5f6f7);
.tabs-container {
height: calc(100% - 48px);
background: var(--ContentBG, #ffffff);
overflow-x: auto;
}
.flex-tabs {
display: flex;
background: #f5f6f7;
}
.de-tabs-left {
background: #fff;
width: 269px;
border-right: 1px solid rgba(31, 35, 41, 0.15);
padding: 24px;
}
.de-tabs-right {
flex: 1;
background: #fff;
padding: 24px 0 24px 24px;
overflow: hidden;
.template-box {
display: flex;
flex-wrap: wrap;
overflow-y: auto;
box-sizing: border-box;
align-content: flex-start;
height: calc(100% - 10px);
width: 100%;
padding-bottom: 24px;
}
.active-template {
margin: 4px 0 20px 0;
padding-right: 24px;
font-family: 'PingFang SC';
font-style: normal;
font-weight: 500;
font-size: 16px;
display: flex;
align-items: center;
justify-content: space-between;
color: var(--deTextPrimary, #1f2329);
}
}
}
</style>

@ -1 +1 @@
Subproject commit 912a648808e87bdf1e5a11f7cdc6467b89fd9347
Subproject commit e3143e3176ca189c35729fa7bb195264c061a824

View File

@ -0,0 +1,13 @@
package io.dataease.api.template;
import io.dataease.api.template.dto.TemplateManageDTO;
import org.springframework.web.bind.annotation.*;
import java.util.List;
public interface TemplateManageApi {
@PostMapping("/templateList")
List<TemplateManageDTO> templateList();
}

View File

@ -0,0 +1,23 @@
package io.dataease.api.template;
import io.dataease.api.template.request.TemplateMarketSearchRequest;
import io.dataease.api.template.response.MarketBaseResponse;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import java.util.List;
/**
* @author : WangJiaHao
* @date : 2023/11/6 17:23
*/
public interface TemplateMarketApi {
@PostMapping("/search")
MarketBaseResponse searchTemplate(@RequestBody TemplateMarketSearchRequest request);
@GetMapping("/categories")
List<String> categories();
}

View File

@ -0,0 +1,15 @@
package io.dataease.api.template.dto;
import lombok.Data;
@Data
public class TemplateManageDTO {
private String label;
private Integer childrenCount;
}

View File

@ -0,0 +1,21 @@
package io.dataease.api.template.dto;
import io.dataease.api.template.vo.MarketCategoryVO;
import io.dataease.api.template.vo.MarketMetasVO;
import lombok.Data;
import java.util.List;
@Data
public class TemplateMarketDTO {
private String id;
private String title;
private String status;
private String slug;
private String editorType;
private String summary;
private String thumbnail;
private Boolean showFlag = true;
private List<MarketCategoryVO> categories;
private MarketMetasVO metas;
}

View File

@ -0,0 +1,7 @@
package io.dataease.api.template.request;
import io.dataease.api.template.dto.TemplateMarketDTO;
public class TemplateMarketSearchRequest extends TemplateMarketDTO {
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,19 @@
package io.dataease.api.template.vo;
import lombok.Data;
/**
* Author: wangjiahao
* Date: 2022/7/18
* Description:
*/
@Data
public class TemplateCategoryVO {
private Integer id;
private String name;
private String slug;
private Integer priority;
}