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

This commit is contained in:
dataeaseShu
2023-11-10 14:03:44 +08:00
30 changed files with 791 additions and 93 deletions

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@@ -361,3 +361,9 @@ em {
background-color: #1F232999;
}
}
.de-icon-sense {
margin-right: 9px;
width: 16px !important;
height: 12px !important;
}

View File

@@ -1,6 +1,11 @@
import html2canvas from 'html2canvas'
import JsPDF from 'jspdf'
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
import { storeToRefs } from 'pinia'
import { findResourceAsBase64 } from '@/api/staticResource'
import FileSaver from 'file-saver'
const dvMainStore = dvMainStoreWithOut()
const { canvasStyleData, componentData, canvasViewInfo, dvInfo } = storeToRefs(dvMainStore)
const basePath = import.meta.env.VITE_API_BASEPATH
export function imgUrlTrans(url) {
@@ -20,6 +25,37 @@ export function imgUrlTrans(url) {
}
}
export function download2AppTemplate(downloadType, canvasDom, name, callBack?) {
try {
findStaticSource(function (staticResource) {
html2canvas(canvasDom).then(canvas => {
const snapshot = canvas.toDataURL('image/jpeg', 0.1) // 0.1是图片质量
if (snapshot !== '') {
const templateInfo = {
name: name,
templateType: 'self',
snapshot: snapshot,
dvType: dvInfo.value.type,
canvasStyleData: JSON.stringify(canvasStyleData.value),
componentData: JSON.stringify(componentData.value),
dynamicData: JSON.stringify(canvasViewInfo.value),
staticResource: JSON.stringify(staticResource || {})
}
const blob = new Blob([JSON.stringify(templateInfo)], { type: '' })
if (downloadType === 'template') {
FileSaver.saveAs(blob, name + '-TEMPLATE.DET2')
}
}
if (callBack) {
callBack()
}
})
})
} catch (e) {
console.error(e)
}
}
export function downloadCanvas(type, canvasDom, name, callBack?) {
// const canvasDom = document.getElementById(canvasId)
if (canvasDom) {
@@ -56,15 +92,57 @@ export function downloadCanvas(type, canvasDom, name, callBack?) {
}
}
export function dataURLToBlob(dataurl) {
export function dataURLToBlob(dataUrl) {
// ie 图片转格式
const arr = dataurl.split(',')
const arr = dataUrl.split(',')
const mime = arr[0].match(/:(.*?);/)[1]
const bstr = atob(arr[1])
let n = bstr.length
const bStr = atob(arr[1])
let n = bStr.length
const u8arr = new Uint8Array(n)
while (n--) {
u8arr[n] = bstr.charCodeAt(n)
u8arr[n] = bStr.charCodeAt(n)
}
return new Blob([u8arr], { type: mime })
}
// 解析静态文件
export function findStaticSource(callBack) {
const staticResource = []
// 系统背景文件
if (
typeof canvasStyleData.value.background === 'string' &&
canvasStyleData.value.background.indexOf('static-resource') > -1
) {
staticResource.push(canvasStyleData.value.background)
}
componentData.value.forEach(item => {
if (
typeof item.commonBackground.outerImage === 'string' &&
item.commonBackground.outerImage.indexOf('static-resource') > -1
) {
staticResource.push(item.commonBackground.outerImage)
}
if (
item.component === 'Picture' &&
item.propValue['url'] &&
typeof item.propValue['url'] === 'string' &&
item.propValue['url'].indexOf('static-resource') > -1
) {
staticResource.push(item.propValue)
}
})
if (staticResource.length > 0) {
try {
findResourceAsBase64({ resourcePathList: staticResource }).then(rsp => {
callBack(rsp.data)
})
} catch (e) {
console.error('findResourceAsBase64 error', e)
callBack()
}
} else {
setTimeout(() => {
callBack()
}, 0)
}
}

View File

@@ -10,7 +10,7 @@ import { useRequestStoreWithOut } from '@/store/modules/request'
import { usePermissionStoreWithOut } from '@/store/modules/permission'
import { useMoveLine } from '@/hooks/web/useMoveLine'
import { Icon } from '@/components/icon-custom'
import { downloadCanvas } from '@/utils/imgUtils'
import { download2AppTemplate, downloadCanvas } from '@/utils/imgUtils'
const dvMainStore = dvMainStoreWithOut()
const previewCanvasContainer = ref(null)
@@ -89,7 +89,16 @@ const downloadH2 = type => {
downloadCanvas(type, vueDom, state.dvInfo.name, () => {
downloadStatus.value = false
})
}, 200)
})
}
const downloadAsAppTemplate = downloadType => {
nextTick(() => {
const vueDom = previewCanvasContainer.value.querySelector('.canvas-container')
download2AppTemplate(downloadType, vueDom, state.dvInfo.name, () => {
downloadStatus.value = false
})
})
}
const slideOpenChange = () => {
@@ -155,7 +164,12 @@ defineExpose({
</div>
<!--从store中判断当前是否有点击仪表板 复用时也符合-->
<template v-if="previewShowFlag">
<preview-head v-if="showPosition === 'preview'" @reload="reload" @download="downloadH2" />
<preview-head
v-if="showPosition === 'preview'"
@reload="reload"
@download="downloadH2"
@downloadAsAppTemplate="downloadAsAppTemplate"
/>
<div ref="previewCanvasContainer" class="content">
<de-preview
ref="dashboardPreview"

View File

@@ -8,7 +8,7 @@ import { ref, watch } from 'vue'
import { XpackComponent } from '@/components/plugin'
const dvMainStore = dvMainStoreWithOut()
const { dvInfo } = storeToRefs(dvMainStore)
const emit = defineEmits(['reload', 'download'])
const emit = defineEmits(['reload', 'download', 'downloadAsAppTemplate'])
const { t } = useI18n()
const favorited = ref(false)
@@ -24,6 +24,9 @@ const reload = () => {
const download = type => {
emit('download', type)
}
const downloadAsAppTemplate = downloadType => {
emit('downloadAsAppTemplate', downloadType)
}
const dvEdit = () => {
const baseUrl = dvInfo.value.type === 'dataV' ? '#/dvCanvas?dvId=' : '#/dashboard?resourceId='
@@ -124,6 +127,9 @@ watch(
<el-dropdown-item style="width: 118px" @click="download('pdf')"
>PDF</el-dropdown-item
>
<el-dropdown-item style="width: 118px" @click="downloadAsAppTemplate('template')"
>模版</el-dropdown-item
>
<el-dropdown-item @click="download('img')">{{
t('chart.image')
}}</el-dropdown-item>

View File

@@ -1,3 +1,48 @@
<template>
<h2>template setting page for jiahao.wang</h2>
<p class="router-title">模版管理</p>
<div class="sys-setting-p">
<div class="container-sys-param">
<template-manage></template-manage>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
import TemplateManage from '@/views/template/index.vue'
const { t } = useI18n()
const activeName = ref('basic')
const handleClick = (tab, event: Event) => {
console.log(tab, event)
}
</script>
<style lang="less">
.router-title {
color: #1f2329;
font-feature-settings: 'clig' off, 'liga' off;
font-family: PingFang SC;
font-size: 20px;
font-style: normal;
font-weight: 500;
line-height: 28px;
}
.sys-setting-p {
width: 100%;
background: var(--ContentBG, #ffffff);
height: calc(100vh - 176px);
box-sizing: border-box;
margin-top: 12px;
}
.setting-auto-h {
height: auto !important;
}
.container-sys-param {
height: 100%;
overflow-y: auto;
}
</style>
<style lang="less" scoped></style>

View File

@@ -1,3 +1,6 @@
<template>
<h2>template marget page for jiahao.wang</h2>
<template-manage></template-manage>
</template>
<script setup lang="ts">
import TemplateManage from '@/views/template/index.vue'
</script>

View File

@@ -6,17 +6,17 @@
:model="state.templateInfo"
:rules="state.templateInfoRules"
>
<el-form-item :label="t('system_parameter_setting.template_name')" prop="name">
<el-form-item :label="'模版名称'" 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')
t('visualization.upload_template')
}}</el-button>
<input
id="input"
ref="filesRef"
type="file"
accept=".DET"
accept=".DET2"
hidden
@change="handleFileChange"
/>
@@ -25,8 +25,8 @@
</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-button secondary @click="cancel()">{{ t('commons.cancel') }}</el-button>
<el-button type="primary" @click="saveTemplate()">{{ t('commons.confirm') }}</el-button>
</el-row>
</div>
</template>
@@ -65,6 +65,7 @@ const state = reactive({
templateInfo: {
level: '1',
pid: props.pid,
dvType: 'dashboard',
name: '',
templateStyle: null,
templateData: null,
@@ -115,7 +116,7 @@ const saveTemplate = () => {
type: 'primary',
cb: () =>
save(state.templateInfo).then(response => {
ElMessage.success(t('system_parameter_setting.import_succeeded'))
ElMessage.success('导入成功')
emits('refresh')
emits('closeEditTemplateDialog')
}),
@@ -124,7 +125,7 @@ const saveTemplate = () => {
handlerConfirm(options)
} else {
save(state.templateInfo).then(response => {
ElMessage.success(t('system_parameter_setting.import_succeeded'))
ElMessage.success(t('导入成功'))
emits('refresh')
emits('closeEditTemplateDialog')
})
@@ -143,8 +144,9 @@ const handleFileChange = e => {
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.dvType = state.importTemplateInfo['dvType']
state.templateInfo.templateStyle = state.importTemplateInfo['canvasStyleData']
state.templateInfo.templateData = state.importTemplateInfo['componentData']
state.templateInfo.snapshot = state.importTemplateInfo.snapshot
state.templateInfo.dynamicData = state.importTemplateInfo['dynamicData']
state.templateInfo.staticResource = state.importTemplateInfo['staticResource']
@@ -161,7 +163,7 @@ onMounted(() => {
})
</script>
<style scoped>
<style scoped lang="less">
.my_table :deep(.el-table__row > td) {
/* 去除表格线 */
border: none;
@@ -178,7 +180,7 @@ onMounted(() => {
.de-root-class {
margin-top: 24px;
text-align: right;
justify-content: flex-end;
}
.preview {
margin-top: -12px;

View File

@@ -1,23 +1,35 @@
<template>
<div :style="classBackground" class="de-card-model">
<div class="card-img-model" :style="classImg">
<img :src="model.snapshot" alt="" />
<img :src="imgUrlTrans(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>
<div style="display: flex; align-items: center">
<el-tooltip class="item" effect="dark" :content="dvTypeName" placement="top">
<el-icon style="font-size: 18px" v-if="model.dvType === 'dashboard'">
<Icon name="dv-dashboard-spine"></Icon>
</el-icon>
<el-icon class="icon-screen-new" style="font-size: 18px" v-else>
<Icon name="icon_operation-analysis_outlined"></Icon>
</el-icon>
</el-tooltip>
<el-tooltip class="item" effect="dark" :content="model.name" placement="top">
<span class="de-model-text">{{ model.name }}</span>
</el-tooltip>
</div>
<el-dropdown size="medium" trigger="click" @command="handleCommand">
<i class="el-icon-more" />
<el-icon class="el-icon-more"><MoreFilled /></el-icon>
<template #dropdown>
<el-dropdown-menu class="de-card-dropdown">
<slot>
<el-dropdown-item command="rename">
<i class="el-icon-edit" />
<el-icon><EditPen /></el-icon>
{{ $t('chart.rename') }}
</el-dropdown-item>
<el-dropdown-item command="delete">
<i class="el-icon-delete" />
<el-icon><Delete /></el-icon>
{{ $t('chart.delete') }}
</el-dropdown-item>
</slot>
@@ -29,6 +41,7 @@
</template>
<script setup lang="ts">
import { imgUrlTrans } from '@/utils/imgUtils'
import { computed } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
const { t } = useI18n()
@@ -43,6 +56,10 @@ const props = defineProps({
}
})
const dvTypeName = computed(() => {
return props.model.dvType === 'dashboard' ? '仪表板' : '数据大屏'
})
const classBackground = computed(() => {
return {
width: props.width + 'px',
@@ -68,6 +85,7 @@ const handleCommand = key => {
border: 1px solid var(--deCardStrokeColor, #dee0e3);
border-radius: 4px;
margin: 0 24px 25px 0;
overflow: hidden;
.card-img-model {
border-bottom: 1px solid var(--deCardStrokeColor, #dee0e3);
height: 144px;
@@ -112,6 +130,7 @@ const handleCommand = key => {
}
.de-model-text {
margin-left: 8px;
font-family: 'PingFang SC';
font-style: normal;
font-weight: 400;
@@ -137,4 +156,11 @@ const handleCommand = key => {
display: none !important;
}
}
.icon-screen-new {
background: #3370ff;
border-radius: 4px;
color: #fff;
padding: 3px;
}
</style>

View File

@@ -2,24 +2,26 @@
<div class="de-template-list">
<el-input
v-model="state.templateFilterText"
:placeholder="t('system_parameter_setting.search_keywords')"
:placeholder="'搜索关键字'"
size="small"
class="de-input-search"
clearable
>
<template #prefix>
<svg-icon icon-class="de-search" />
<el-icon>
<Icon name="de-search" />
</el-icon>
</template>
</el-input>
<el-empty
v-if="!templateListComputed.length && state.templateFilterText === ''"
:image="state.noneImg"
:description="t('components.no_classification')"
:image="NoneImage"
:description="'当前无分类'"
/>
<el-empty
v-if="!templateListComputed.length && state.templateFilterText !== ''"
:image="state.nothingImg"
:description="t('components.relevant_content_found')"
:image="NothingImage"
:description="'没有找到相关内容'"
/>
<ul>
<li
@@ -28,7 +30,9 @@
:class="[{ select: state.activeTemplate === ele.id }]"
@click="nodeClick(ele)"
>
<svg-icon icon-class="scene" class="de-icon-sense" />
<el-icon class="de-icon-sense">
<Icon name="scene" />
</el-icon>
<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)">
@@ -38,13 +42,13 @@
<template #dropdown>
<el-dropdown-menu class="de-template-dropdown">
<el-dropdown-item icon="el-icon-upload2" command="import">
{{ t('panel.import') }}
{{ t('visualization.import') }}
</el-dropdown-item>
<el-dropdown-item icon="el-icon-edit" command="edit">
{{ t('panel.rename') }}
{{ t('visualization.rename') }}
</el-dropdown-item>
<el-dropdown-item icon="el-icon-delete" command="delete">
{{ t('panel.delete') }}
{{ t('visualization.delete') }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
@@ -59,7 +63,7 @@
secondary
@click="add()"
>
{{ t('panel.add_category') }}
{{ t('visualization.add_category') }}
</el-button>
</div>
</template>
@@ -67,6 +71,8 @@
<script setup lang="ts">
import { useI18n } from '@/hooks/web/useI18n'
import { computed, reactive } from 'vue'
import NoneImage from '@/assets/none.png'
import NothingImage from '@/assets/nothing.png'
const { t } = useI18n()
const emits = defineEmits([
@@ -93,7 +99,7 @@ const props = defineProps({
const state = reactive({
templateFilterText: '',
activeTemplate: '',
noneImg: '@/assets/None.png',
noneImg: '@/assets/none.png',
nothingImg: '@/assets/nothing.png'
})
@@ -141,6 +147,10 @@ const templateImport = template => {
const handlerConfirm = options => {
// do handlerConfirm
}
defineExpose({
nodeClick
})
</script>
<style scoped lang="less">
@@ -244,4 +254,9 @@ const handlerConfirm = options => {
display: none !important;
}
}
.sense {
width: 20px;
height: 20px;
}
</style>

View File

@@ -1,21 +1,21 @@
<template>
<div>
<div style="width: 100%; height: 100%">
<div class="de-template">
<el-tabs v-model="state.currentTemplateType" class="de-tabs" @tab-click="handleClick">
<el-tabs v-model="state.currentTemplateType" @tab-click="handleClick">
<el-tab-pane name="self">
<template #label>
<span>{{ t('panel.user_template') }}</span>
<span>{{ t('visualization.user_template') }}</span>
</template>
</el-tab-pane>
<el-tab-pane name="system">
<template #label>
<span>{{ t('panel.sys_template') }}</span>
<span>{{ t('visualization.sys_template') }}</span>
</template>
</el-tab-pane>
</el-tabs>
<div class="tabs-container flex-tabs">
<div class="de-tabs-left">
<template-list
<de-template-list
ref="templateListRef"
:template-type="state.currentTemplateType"
:template-list="state.templateList"
@@ -31,19 +31,19 @@
{{ state.currentTemplateLabel }}&nbsp;&nbsp;({{ state.currentTemplateShowList.length }})
<el-button
type="primary"
icon="el-icon-upload2"
icon="Upload"
@click="templateImport(state.currentTemplateId)"
>
{{ t('panel.import') }}
{{ t('visualization.import') }}
</el-button>
</div>
<el-empty
v-if="!state.currentTemplateShowList.length"
:image="state.noneImg"
:description="t('components.no_template')"
:image="NoneImage"
:description="'暂无模版'"
/>
<div v-show="state.currentTemplateId !== ''" id="template-box" class="template-box">
<template-item
<de-template-item
v-for="item in state.currentTemplateShowList"
:key="item.id"
:width="state.templateCurWidth"
@@ -56,7 +56,7 @@
</div>
<el-dialog
:title="state.dialogTitle"
v-model:visible="state.editTemplate"
v-model="state.editTemplate"
append-to-body
class="de-dialog-form"
width="600px"
@@ -80,16 +80,15 @@
</div>
</template>
</el-dialog>
<!--导入templatedialog-->
<!--导入templateDialog-->
<el-dialog
:title="state.templateDialog.title"
v-model:visible="state.templateDialog.visible"
v-model="state.templateDialog.visible"
:show-close="true"
class="de-dialog-form"
width="600px"
>
<template-import
<de-template-import
v-if="state.templateDialog.visible"
:pid="state.templateDialog.pid"
@refresh="showCurrentTemplate(state.currentTemplateId, state.currentTemplateLabel)"
@@ -105,10 +104,13 @@ 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'
import DeTemplateList from '@/views/template/component/DeTemplateList.vue'
const { t } = useI18n()
const templateEditFormRef = ref(null)
const templateListRef = ref(null)
import NoneImage from '@/assets/none.png'
import DeTemplateImport from '@/views/template/component/DeTemplateImport.vue'
import DeTemplateItem from '@/views/template/component/DeTemplateItem.vue'
const roleValidator = (rule, value, callback) => {
if (nameRepeat(value)) {
@@ -160,7 +162,7 @@ const state = reactive({
formType: '',
originName: '',
templateDialog: {
title: t('panel.import_template'),
title: t('visualization.import_template'),
visible: false,
pid: ''
}
@@ -251,14 +253,10 @@ const showTemplateEditDialog = (type, templateInfo) => {
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.dialogTitle = state.templateEditForm['nodeType'] === 'folder' ? '编辑分类' : '编辑模版'
state.originName = state.templateEditForm['label']
} else {
state.dialogTitle = t('panel.add_category')
state.dialogTitle = t('visualization.add_category')
state.templateEditForm = {
name: '',
nodeType: 'folder',
@@ -266,11 +264,7 @@ const showTemplateEditDialog = (type, templateInfo) => {
level: 0
}
}
state.dialogTitleLabel = t(
`system_parameter_setting.${
state.templateEditForm['nodeType'] === 'folder' ? 'classification_name' : 'template_name'
}`
)
state.dialogTitleLabel = state.templateEditForm['nodeType'] === 'folder' ? '分类名称' : '模版名称'
state.editTemplate = true
}
@@ -300,13 +294,15 @@ const close = () => {
state.editTemplate = false
}
const getTree = () => {
const request = {
templateType: state.currentTemplateType,
level: '0'
}
find(request).then(res => {
state.templateList = res.data
showFirst()
nextTick(() => {
const request = {
templateType: state.currentTemplateType,
level: '0'
}
find(request).then(res => {
state.templateList = res.data
showFirst()
})
})
}
const showFirst = () => {
@@ -367,6 +363,7 @@ onMounted(() => {
}
.flex-tabs {
margin-top: 16px;
display: flex;
background: #f5f6f7;
}