<template>
<el-drawer
:title="t('visualization.save_app')"
v-model="state.appApplyDrawer"
modal-class="de-app-drawer"
:show-close="false"
:close-on-click-modal="false"
:close-on-press-escape="false"
size="500px"
direction="rtl"
:z-index="1000"
>
<div class="app-export">
<el-form
ref="appSaveForm"
:model="state.form"
:rules="isDatasourceMatch ? state.ruleDatasource : state.ruleDataset"
class="de-form-item app-form create-dialog"
label-width="180px"
label-position="top"
>
<div class="de-row-rules" style="margin: 0 0 16px">
<span>{{ t('visualization.base_info') }}</span>
</div>
<el-form-item :label="dvPreName + t('visualization.name')" prop="name">
<el-input
v-model="state.form.name"
autocomplete="off"
:placeholder="t('visualization.input_tips')"
/>
</el-form-item>
<el-form-item :label="dvPreName + t('visualization.position')" prop="pid">
<el-tree-select
style="width: 100%"
@keydown.stop
@keyup.stop
v-model="state.form.pid"
:data="state.dvTree"
:props="state.propsTree"
@node-click="dvTreeSelect"
:render-after-expand="false"
filterable
>
<template #default="{ data: { name } }">
<span class="custom-tree-node">
<el-icon>
<Icon name="dv-folder"><dvFolder class="svg-icon" /></Icon>
</el-icon>
<span :title="name">{{ name }}</span>
</span>
</template>
</el-tree-select>
</el-form-item>
<el-form-item :label="t('visualization.data_match_type')" prop="dataType">
<el-select v-model="state.form.dataType" :placeholder="t('chart.pls_select_field')">
<el-option key="datasource" :label="t('datasource.datasource')" value="datasource" />
<el-option key="dataset" :label="t('dataset.datalist')" value="dataset" />
</el-select>
</el-form-item>
<template v-if="isDatasourceMatch">
<el-form-item :label="t('visualization.ds_group_name')" prop="datasetFolderName">
<el-input
v-model="state.form.datasetFolderName"
autocomplete="off"
:placeholder="t('visualization.input_tips')"
/>
</el-form-item>
<el-form-item :label="t('visualization.ds_group_position')" prop="datasetFolderPid">
<el-tree-select
style="width: 100%"
@keydown.stop
@keyup.stop
v-model="state.form.datasetFolderPid"
:data="state.dsTree"
:props="state.propsTree"
@node-click="dsTreeSelect"
:render-after-expand="false"
filterable
>
<template #default="{ data: { name } }">
<span class="custom-tree-node">
<el-icon>
<Icon name="dv-folder"><dvFolder class="svg-icon" /></Icon>
</el-icon>
<span :title="name">{{ name }}</span>
</span>
</template>
</el-tree-select>
</el-form-item>
<div class="de-row-rules" style="margin: 0 0 16px">
<span>{{ t('visualization.datasource_info') }}</span>
</div>
<el-row class="datasource-link">
<el-row class="head">
<el-col :span="11">{{ t('visualization.app_datasource') }}</el-col
><el-col :span="2"></el-col
><el-col :span="11">{{ t('visualization.sys_datasource') }}</el-col>
</el-row>
<el-row
:key="index"
class="content"
v-for="(appDatasource, index) in state.appData.datasourceInfo"
>
<el-col :span="11">
<el-select style="width: 100%" v-model="appDatasource.name" disabled>
<el-option
:key="appDatasource.name"
:label="appDatasource.name"
:value="appDatasource.name"
>
</el-option>
</el-select> </el-col
><el-col :span="2" class="icon-center">
<Icon name="dv-link-target"
><dvLinkTarget
class="svg-icon"
style="width: 20px; height: 20px" /></Icon></el-col
><el-col :span="11">
<dataset-select
ref="datasetSelector"
v-model="appDatasource.systemDatasourceId"
style="flex: 1"
:state-obj="state"
themes="light"
source-type="datasource"
@add-ds-window="addDatasourceWindow"
view-id="0"
/>
</el-col>
</el-row>
</el-row>
</template>
<template v-if="!isDatasourceMatch">
<div class="de-row-rules" style="margin: 0 0 16px">
<span>{{ t('visualization.dataset_info') }}</span>
</div>
<el-row class="datasource-link">
<el-row class="head">
<el-col :span="11">{{ t('visualization.app_dataset') }}</el-col
><el-col :span="2"></el-col
><el-col :span="11">{{ t('visualization.sys_dataset') }}</el-col>
</el-row>
<el-row
:key="index"
class="content"
v-for="(appDataset, index) in state.appData.datasetGroupsInfo"
>
<el-col :span="11">
<el-select style="width: 100%" v-model="appDataset.name" disabled>
<el-option
:key="appDataset.name"
:label="appDataset.name"
:value="appDataset.name"
>
</el-option>
</el-select> </el-col
><el-col :span="2" class="icon-center">
<Icon name="dv-link-target"
><dvLinkTarget
class="svg-icon"
style="width: 20px; height: 20px" /></Icon></el-col
><el-col :span="11">
<dataset-select
ref="datasetSelector"
v-model="appDataset.systemDatasetId"
style="flex: 1"
:state-obj="state"
themes="light"
@add-ds-window="addDatasetWindow"
view-id="0"
/>
</el-col>
</el-row>
</el-row>
</template>
</el-form>
</div>
<template #footer>
<div class="apply" style="width: 100%">
<el-button v-if="isDesktop() || openType === '_self'" @click="goBack">{{
t('visualization.back')
}}</el-button>
<el-button type="primary" @click="saveApp">{{ t('visualization.save') }}</el-button>
</div>
</template>
</el-drawer>
</template>
<script lang="ts" setup>
import dvFolder from '@/assets/svg/dv-folder.svg'
import dvLinkTarget from '@/assets/svg/dv-link-target.svg'
import {
ElButton,
ElDrawer,
ElForm,
ElFormItem,
ElInput,
ElMessage,
ElTreeSelect
} from 'element-plus-secondary'
import { computed, PropType, reactive, ref, toRefs } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
import { dvNameCheck, queryTreeApi } from '@/api/visualization/dataVisualization'
import { BusiTreeNode, BusiTreeRequest } from '@/models/tree/TreeNode'
import { getDatasetTree } from '@/api/dataset'
import DatasetSelect from '@/views/chart/components/editor/dataset-select/DatasetSelect.vue'
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
import { storeToRefs } from 'pinia'
import { deepCopy } from '@/utils/utils'
import { snapshotStoreWithOut } from '@/store/modules/data-visualization/snapshot'
import { useCache } from '@/hooks/web/useCache'
import { isDesktop } from '@/utils/ModelUtil'
import { filterFreeFolder } from '@/utils/utils'
const { wsCache } = useCache('localStorage')
const { t } = useI18n()
const emits = defineEmits(['closeDraw', 'saveAppCanvas'])
const appSaveForm = ref(null)
const dvMainStore = dvMainStoreWithOut()
const { dvInfo, appData } = storeToRefs(dvMainStore)
const snapshotStore = snapshotStoreWithOut()
const props = defineProps({
componentData: {
type: Object,
required: true
},
canvasViewInfo: {
type: Object,
required: true
},
curCanvasType: {
type: String,
required: true
},
themes: {
type: String as PropType<EditorTheme>,
default: 'dark'
}
})
const { curCanvasType } = toRefs(props)
const openType = wsCache.get('open-backend') === '1' ? '_self' : '_blank'
const dvPreName = computed(() =>
curCanvasType.value === 'dashboard'
? t('work_branch.dashboard')
: t('work_branch.big_data_screen')
)
const isDatasourceMatch = computed(() => state.form.dataType === 'datasource')
const addDatasourceWindow = () => {
// do addDsWindow
const url = '#/data/datasource?opt=create'
window.open(url, openType)
}
const addDatasetWindow = () => {
// do addDsWindow
const url = '#/data/dataset?opt=create'
window.open(url, openType)
}
const state = reactive({
appApplyDrawer: false,
dvTree: [],
dsTree: [],
propsTree: {
label: 'name',
children: 'children',
isLeaf: node => !node.children?.length
},
appData: {
datasourceInfo: [],
datasetGroupsInfo: []
},
form: {
pid: '',
name: t('visualization.new'),
datasetFolderPid: null,
datasetFolderName: null,
dataType: 'datasource'
},
ruleDataset: {
name: [
{
required: true,
min: 2,
max: 25,
message: t('datasource.input_limit_2_25', [2, 25]),
trigger: 'blur'
}
],
pid: [
{
required: true,
message: t('visualization.select_folder'),
trigger: 'blur'
}
],
dataType: [
{
required: true
}
]
},
ruleDatasource: {
name: [
{
required: true,
min: 2,
max: 25,
message: t('datasource.input_limit_2_25', [2, 25]),
trigger: 'blur'
}
],
pid: [
{
required: true,
message: t('visualization.select_folder'),
trigger: 'blur'
}
],
datasetFolderName: [
{
required: true,
min: 2,
max: 25,
message: t('datasource.input_limit_2_25', [2, 25]),
trigger: 'blur'
}
],
datasetFolderPid: [
{
required: true,
message: t('visualization.select_ds_group_folder'),
trigger: 'blur'
}
],
dataType: [
{
required: true
}
]
}
})
const goBack = () => {
window.history.back()
}
const initData = () => {
const request = { busiFlag: curCanvasType.value, resourceTable: 'core', leaf: false, weight: 7 }
queryTreeApi(request).then(res => {
filterFreeFolder(res, curCanvasType.value)
const resultTree = res || []
dfs(resultTree as unknown as BusiTreeNode[])
state.dvTree = (resultTree as unknown as BusiTreeNode[]) || []
if (state.dvTree.length && state.dvTree[0].name === 'root' && state.dvTree[0].id === '0') {
state.dvTree[0].name =
curCanvasType.value === 'dataV'
? t('work_branch.big_data_screen')
: t('work_branch.dashboard')
}
})
const requestDs = { leaf: false, weight: 7 } as BusiTreeRequest
getDatasetTree(requestDs).then(res => {
filterFreeFolder(res, 'dataset')
dfs(res as unknown as BusiTreeNode[])
state.dsTree = (res as unknown as BusiTreeNode[]) || []
if (state.dsTree.length && state.dsTree[0].name === 'root' && state.dsTree[0].id === '0') {
state.dsTree[0].name = t('visualization.dataset')
}
})
}
const dfs = (arr: BusiTreeNode[]) => {
arr.forEach(ele => {
ele['value'] = ele.id
if (ele.children?.length) {
dfs(ele.children)
}
})
}
const init = params => {
state.appApplyDrawer = true
state.form = params.base
state.form.dataType = 'datasource'
state.appData.datasourceInfo = deepCopy(appData.value?.datasourceInfo)
state.appData.datasetGroupsInfo = deepCopy(appData.value?.datasetGroupsInfo)
initData()
}
const dvTreeSelect = element => {
state.form.pid = element.id
}
const dsTreeSelect = element => {
state.form.datasetFolderPid = element.id
}
const close = () => {
emits('closeDraw')
snapshotStore.recordSnapshotCache('renderChart')
state.appApplyDrawer = false
}
const saveApp = () => {
let datasourceMatchReady = true
let datasetMatchReady = true
state.appData.datasourceInfo.forEach(datasource => {
if (!datasource.systemDatasourceId) {
datasourceMatchReady = false
}
})
state.appData.datasetGroupsInfo.forEach(dataset => {
if (!dataset.systemDatasetId) {
datasetMatchReady = false
}
})
if (!datasourceMatchReady && isDatasourceMatch.value) {
ElMessage.error(t('visualization.app_no_datasource_tips'))
return
}
if (!datasetMatchReady && !isDatasourceMatch.value) {
ElMessage.error(t('visualization.app_no_dataset_tips'))
return
}
appSaveForm.value?.validate(async valid => {
if (valid) {
// 还原datasource
appData.value['datasourceInfo'] = state.appData.datasourceInfo
appData.value['datasetGroupsInfo'] = state.appData.datasetGroupsInfo
dvInfo.value['pid'] = state.form.pid
dvInfo.value['name'] = state.form.name
dvInfo.value['datasetFolderPid'] = state.form.datasetFolderPid
dvInfo.value['datasetFolderName'] = state.form.datasetFolderName
dvInfo.value['dataType'] = state.form.dataType
dvInfo.value['dataState'] = 'ready'
await dvNameCheck({
opt: 'newLeaf',
nodeType: 'leaf',
name: dvInfo.value['name'],
type: curCanvasType.value,
pid: dvInfo.value['pid']
})
snapshotStore.recordSnapshotCache('renderChart')
emits('saveAppCanvas')
} else {
return false
}
})
}
defineExpose({
init,
close
})
</script>
<style lang="less" scoped>
.app-export {
width: 100%;
height: calc(100% - 56px);
}
.app-export-bottom {
width: 100%;
height: 56px;
text-align: right;
}
:deep(.ed-drawer__body) {
padding-bottom: 0 !important;
}
.de-row-rules {
display: flex;
align-items: center;
position: relative;
font-size: 14px;
font-weight: 500;
line-height: 22px;
padding-left: 10px;
margin: 24px 0 16px 0;
color: var(--ed-text-color-regular);
&::before {
content: '';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
height: 14px;
width: 2px;
background: var(--ed-color-primary, #3370ff);
}
}
.custom-tree-node {
display: flex;
align-items: center;
span {
margin-left: 8.75px;
width: 120px;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
}
.datasource-link {
color: var(--ed-text-color-regular);
font-size: 12px;
font-weight: 500;
width: 100%;
.head_type {
width: 100%;
margin-bottom: 16px;
}
.head {
width: 100%;
}
.content {
width: 100%;
margin-top: 8px;
}
}
.icon-center {
padding: 0 8px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.app-form {
padding-bottom: 95px;
}
</style>
<style lang="less">
.de-app-drawer {
z-index: 1000;
}
</style>