feat: 仪表板选择视图

This commit is contained in:
fit2cloud-chenyw 2021-03-23 13:58:49 +08:00
parent 621e92a06d
commit 3b87239aa2
11 changed files with 686 additions and 314 deletions

View File

@ -0,0 +1,13 @@
package io.dataease.base.mapper.ext;
import io.dataease.base.mapper.ext.query.GridExample;
import io.dataease.dto.panel.po.PanelViewPo;
import java.util.List;
public interface ExtPanelViewMapper {
List<PanelViewPo> groups(GridExample example);
List<PanelViewPo> views(GridExample example);
}

View File

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="io.dataease.base.mapper.ext.ExtPanelViewMapper">
<resultMap id="treeNodeMap" type="io.dataease.dto.panel.po.PanelViewPo">
<id column="id" property="id" />
<result column="name" property="name" />
<result column="pid" property="pid" />
<result column="type" property="type" />
</resultMap>
<select id="groups" parameterType="io.dataease.base.mapper.ext.query.GridExample" resultMap="treeNodeMap">
select id, pid, name, `type`
from chart_group
<if test="_parameter != null">
<include refid="io.dataease.base.mapper.ext.query.GridSql.gridCondition" />
</if>
<if test="orderByClause != null">
order by ${orderByClause}
</if>
<if test="orderByClause == null">
order by create_time desc
</if>
</select>
<select id="views" parameterType="io.dataease.base.mapper.ext.query.GridExample" resultMap="treeNodeMap">
select id, scene_id as pid ,title as name, `type`
from chart_view
<if test="_parameter != null">
<include refid="io.dataease.base.mapper.ext.query.GridSql.gridCondition" />
</if>
<if test="orderByClause != null">
order by ${orderByClause}
</if>
<if test="orderByClause == null">
order by create_time desc
</if>
</select>
</mapper>

View File

@ -0,0 +1,21 @@
package io.dataease.controller.panel.api;
import io.dataease.controller.sys.base.BaseGridRequest;
import io.dataease.dto.panel.PanelViewDto;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.List;
@Api(tags = "仪表板:视图管理")
@RequestMapping("/api/panelView")
public interface ViewApi {
@ApiOperation("视图树")
@PostMapping("/tree")
List<PanelViewDto> tree(BaseGridRequest request);
}

View File

@ -0,0 +1,43 @@
package io.dataease.controller.panel.server;
import io.dataease.commons.utils.AuthUtils;
import io.dataease.controller.panel.api.ViewApi;
import io.dataease.controller.sys.base.BaseGridRequest;
import io.dataease.controller.sys.base.ConditionEntity;
import io.dataease.dto.panel.PanelViewDto;
import io.dataease.dto.panel.po.PanelViewPo;
import io.dataease.service.panel.PanelViewService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
@RestController
public class ViewServer implements ViewApi {
@Autowired
private PanelViewService panelViewService;
/**
* 为什么查两次
* 因为left join 会导致全表扫描
* 查两次在索引合理情况下 效率比查询一次高
* @return
*/
@Override
public List<PanelViewDto> tree(@RequestBody BaseGridRequest request) {
List<ConditionEntity> conditions = new ArrayList<>();
ConditionEntity condition = new ConditionEntity();
condition.setField("create_by");
condition.setOperator("eq");
condition.setValue(AuthUtils.getUser().getUsername());
conditions.add(condition);
request.setConditions(conditions);
List<PanelViewPo> groups = panelViewService.groups(request);
List<PanelViewPo> views = panelViewService.views(request);
List<PanelViewDto> panelViewDtos = panelViewService.buildTree(groups, views);
return panelViewDtos;
}
}

View File

@ -0,0 +1,26 @@
package io.dataease.dto.panel;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@Data
public class PanelViewDto {
private String id;
private String pid;
private String type;
private String name;
private List<PanelViewDto> children;
public void addChild(PanelViewDto dto){
children = Optional.ofNullable(children).orElse(new ArrayList<>());
children.add(dto);
}
}

View File

@ -0,0 +1,15 @@
package io.dataease.dto.panel.po;
import lombok.Data;
@Data
public class PanelViewPo {
private String id;
private String pid;
private String type;
private String name;
}

View File

@ -0,0 +1,69 @@
package io.dataease.service.panel;
import io.dataease.base.mapper.ext.ExtPanelViewMapper;
import io.dataease.base.mapper.ext.query.GridExample;
import io.dataease.commons.utils.BeanUtils;
import io.dataease.controller.sys.base.BaseGridRequest;
import io.dataease.dto.panel.PanelViewDto;
import io.dataease.dto.panel.po.PanelViewPo;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
@Service
public class PanelViewService {
@Autowired(required = false)
private ExtPanelViewMapper extPanelViewMapper;
private final static String SCENE_TYPE = "scene";
public List<PanelViewPo> groups(BaseGridRequest request){
GridExample example = request.convertExample();
return extPanelViewMapper.groups(example);
}
public List<PanelViewPo> views(BaseGridRequest request){
GridExample example = request.convertExample();
return extPanelViewMapper.views(example);
}
public List<PanelViewDto> buildTree(List<PanelViewPo> groups, List<PanelViewPo> views){
if (CollectionUtils.isEmpty(groups) || CollectionUtils.isEmpty(views)) return null;
Map<String, List<PanelViewPo>> viewsMap = views.stream().collect(Collectors.groupingBy(PanelViewPo::getPid));
List<PanelViewDto> dtos = groups.stream().map(group -> BeanUtils.copyBean(new PanelViewDto(), group)).collect(Collectors.toList());
List<PanelViewDto> roots = new ArrayList<>();
dtos.forEach(group -> {
// 查找跟节点
if (ObjectUtils.isEmpty(group.getPid())){
roots.add(group);
}
// 查找当前节点的子节点
// 当前group是场景
if (StringUtils.equals(group.getType(), SCENE_TYPE)){
Optional.ofNullable(viewsMap.get(group.getId())).ifPresent(lists -> lists.forEach(view -> {
PanelViewDto dto = BeanUtils.copyBean(new PanelViewDto(), view);
group.addChild(dto);
}));
return;
}
// 当前group是分组
dtos.forEach(item -> {
if (StringUtils.equals(item.getPid(), group.getId())){
group.addChild(item);
}
});
});
// 最后 没有孩子的老东西淘汰
return roots.stream().filter(item -> CollectionUtils.isNotEmpty(item.getChildren())).collect(Collectors.toList());
}
}

View File

@ -0,0 +1,10 @@
import request from '@/utils/request'
export function tree(data) {
return request({
url: '/api/panelView/tree',
method: 'post',
loading: true,
data
})
}

View File

@ -0,0 +1,122 @@
<template>
<div>
<div class="my-top">
<el-input
v-model="filterText"
placeholder="按名称搜索"
/>
<el-tree
ref="tree"
class="filter-tree"
:data="data"
:props="defaultProps"
:render-content="renderNode"
default-expand-all
:filter-node-method="filterNode"
/>
</div>
<div v-if="showdetail" class="detail-class">
<el-card class="filter-card-class">
<div slot="header" class="button-div-class">
<span>{{ detailItem.name }}</span>
<div style="float: right; padding: 1px 5px; cursor:pointer; " @click="closeDetail">
<i class="el-icon-close" />
</div>
</div>
<img class="view-list-thumbnails" :src="'/common-files/images/'+detailItem.id+'/VIEW_DEFAULT_IMAGE'" alt="">
</el-card>
</div>
</div>
</template>
<script>
import { tree } from '@/api/panel/view'
import { addClass, removeClass } from '@/utils'
export default {
name: 'ViewSelect',
data() {
return {
filterText: null,
defaultProps: {
children: 'children',
label: 'name'
},
data: [],
showdetail: false,
detailItem: null
}
},
watch: {
filterText(val) {
this.$refs.tree.filter(val)
},
showdetail(val) {
const dom = document.querySelector('.my-top')
if (val) {
addClass(dom, 'top-div-class')
} else {
removeClass(dom, 'top-div-class')
}
}
},
created() {
this.loadData()
},
methods: {
filterNode(value, data) {
if (!value) return true
return data.name.indexOf(value) !== -1
},
loadData() {
const param = {}
tree(param).then(res => {
let arr = []
for (let index = 0; index < 10; index++) {
arr = arr.concat(res.data)
}
this.data = arr
})
},
renderNode(h, { node, data, store }) {
return (
<div class='custom-tree-node' on-click={() => this.detail(data)} >
<span class='label-span'>{node.label}</span>
{data.type !== 'group' && data.type !== 'scene' ? (
<svg-icon icon-class={data.type} class='chart-icon' />
) : (
''
)}
</div>
)
},
detail(data) {
this.showdetail = true
this.detailItem = data
},
closeDetail() {
this.showdetail = false
this.detailItem = null
}
}
}
</script>
<style lang="scss" scoped>
.top-div-class {
max-height: calc(100vh - 335px);
width: 100%;
position: fixed;
overflow-y : auto
}
.detail-class {
width: 100%;
position: fixed;
bottom: 0px;
}
.view-list-thumbnails {
width: 100%;
height: 100%;
}
</style>

View File

@ -73,7 +73,8 @@
<div v-if="show" class="leftPanel">
<div class="leftPanel-items">
<filter-group />
<view-select v-if="show && showIndex===0" />
<filter-group v-if="show && showIndex===1" />
</div>
</div>
</div>
@ -92,6 +93,7 @@ import DeContainer from '@/components/dataease/DeContainer'
import DeAsideContainer from '@/components/dataease/DeAsideContainer'
import { addClass, removeClass } from '@/utils'
import FilterGroup from '../filter'
import ViewSelect from '../ViewSelect'
import DrawingBoard from '../DrawingBoard'
export default {
components: {
@ -99,13 +101,15 @@ export default {
DeContainer,
DeAsideContainer,
FilterGroup,
ViewSelect,
DrawingBoard
},
data() {
return {
show: false,
clickNotClose: false
clickNotClose: false,
showIndex: -1
}
},
watch: {
@ -136,6 +140,7 @@ export default {
},
showPanel(type) {
this.show = !this.show
this.showIndex = type
},
addEventClick() {
window.addEventListener('click', this.closeSidebar)
@ -146,6 +151,7 @@ export default {
if (!parent && !self) {
this.show = false
window.removeEventListener('click', this.closeSidebar)
this.showIndex = -1
}
},
insertToBody() {

View File

@ -1,348 +1,348 @@
<template xmlns:el-col="http://www.w3.org/1999/html">
<el-row style="height: 100%;overflow-y: hidden;width: 100%;">
<span>仪表盘名称{{panelName}}</span>
</el-row>
<el-row style="height: 100%;overflow-y: hidden;width: 100%;">
<span>仪表盘名称{{ panelName }}</span>
</el-row>
</template>
<script>
import {loadTable, getScene, addGroup, delGroup, addTable, delTable, groupTree} from '@/api/dataset/dataset'
import { loadTable, getScene, addGroup, delGroup, addTable, delTable, groupTree } from '@/api/dataset/dataset'
export default {
name: 'PanelView',
data() {
export default {
name: 'PanelView',
data() {
return {
sceneMode: false,
dialogTitle: '',
search: '',
editGroup: false,
editTable: false,
tData: [],
tableData: [],
currGroup: {},
expandedArray: [],
groupForm: {
name: '',
pid: null,
level: 0,
type: '',
children: [],
sort: 'type desc,name asc'
},
tableForm: {
name: '',
mode: '',
sort: 'type asc,create_time desc,name asc'
},
groupFormRules: {
name: [
{ required: true, message: this.$t('commons.input_content'), trigger: 'blur' }
]
},
tableFormRules: {
name: [
{ required: true, message: this.$t('commons.input_content'), trigger: 'blur' }
],
mode: [
{ required: true, message: this.$t('commons.input_content'), trigger: 'blur' }
]
}
}
},
computed: {
panelName: function() {
console.log(this.$store.state.panel.panelName)
return this.$store.state.panel.panelName
}
},
watch: {
// search(val){
// this.groupForm.name = val;
// this.tree(this.groupForm);
// }
},
mounted() {
this.tree(this.groupForm)
this.refresh()
this.tableTree()
// this.$router.push('/dataset');
},
methods: {
clickAdd(param) {
// console.log(param);
this.add(param.type)
this.groupForm.pid = param.data.id
this.groupForm.level = param.data.level + 1
},
beforeClickAdd(type, data, node) {
return {
sceneMode: false,
dialogTitle: '',
search: '',
editGroup: false,
editTable: false,
tData: [],
tableData: [],
currGroup: {},
expandedArray: [],
groupForm: {
name: '',
pid: null,
level: 0,
type: '',
children: [],
sort: 'type desc,name asc'
},
tableForm: {
name: '',
mode: '',
sort: 'type asc,create_time desc,name asc'
},
groupFormRules: {
name: [
{required: true, message: this.$t('commons.input_content'), trigger: 'blur'}
]
},
tableFormRules: {
name: [
{required: true, message: this.$t('commons.input_content'), trigger: 'blur'}
],
mode: [
{required: true, message: this.$t('commons.input_content'), trigger: 'blur'}
]
}
'type': type,
'data': data,
'node': node
}
},
computed: {
panelName: function () {
console.log(this.$store.state.panel.panelName)
return this.$store.state.panel.panelName
clickMore(param) {
console.log(param)
switch (param.type) {
case 'rename':
this.add(param.data.type)
this.groupForm = JSON.parse(JSON.stringify(param.data))
break
case 'move':
break
case 'delete':
this.delete(param.data)
break
case 'editTable':
this.editTable = true
this.tableForm = JSON.parse(JSON.stringify(param.data))
this.tableForm.mode = this.tableForm.mode + ''
break
case 'deleteTable':
this.deleteTable(param.data)
break
}
},
watch: {
// search(val){
// this.groupForm.name = val;
// this.tree(this.groupForm);
// }
beforeClickMore(type, data, node) {
return {
'type': type,
'data': data,
'node': node
}
},
mounted() {
this.tree(this.groupForm)
this.refresh()
this.tableTree()
// this.$router.push('/dataset');
add(type) {
switch (type) {
case 'group':
this.dialogTitle = this.$t('dataset.group')
break
case 'scene':
this.dialogTitle = this.$t('dataset.scene')
break
}
this.groupForm.type = type
this.editGroup = true
},
methods: {
clickAdd(param) {
// console.log(param);
this.add(param.type)
this.groupForm.pid = param.data.id
this.groupForm.level = param.data.level + 1
},
beforeClickAdd(type, data, node) {
return {
'type': type,
'data': data,
'node': node
}
},
clickMore(param) {
console.log(param)
switch (param.type) {
case 'rename':
this.add(param.data.type)
this.groupForm = JSON.parse(JSON.stringify(param.data))
break
case 'move':
break
case 'delete':
this.delete(param.data)
break
case 'editTable':
this.editTable = true
this.tableForm = JSON.parse(JSON.stringify(param.data))
this.tableForm.mode = this.tableForm.mode + ''
break
case 'deleteTable':
this.deleteTable(param.data)
break
}
},
beforeClickMore(type, data, node) {
return {
'type': type,
'data': data,
'node': node
}
},
add(type) {
switch (type) {
case 'group':
this.dialogTitle = this.$t('dataset.group')
break
case 'scene':
this.dialogTitle = this.$t('dataset.scene')
break
}
this.groupForm.type = type
this.editGroup = true
},
saveGroup(group) {
// console.log(group);
this.$refs['groupForm'].validate((valid) => {
if (valid) {
addGroup(group).then(res => {
this.close()
this.$message({
message: this.$t('commons.save_success'),
type: 'success',
showClose: true
})
this.tree(this.groupForm)
})
} else {
this.$message({
message: this.$t('commons.input_content'),
type: 'error',
showClose: true
})
return false
}
})
},
saveTable(table) {
// console.log(table)
table.mode = parseInt(table.mode)
this.$refs['tableForm'].validate((valid) => {
if (valid) {
addTable(table).then(response => {
this.closeTable()
this.$message({
message: this.$t('commons.save_success'),
type: 'success',
showClose: true
})
this.tableTree()
// this.$router.push('/dataset/home')
this.$emit('switchComponent', {name: ''})
this.$store.dispatch('dataset/setTable', null)
})
} else {
this.$message({
message: this.$t('commons.input_content'),
type: 'error',
showClose: true
})
return false
}
})
},
delete(data) {
this.$confirm(this.$t('dataset.confirm_delete'), this.$t('dataset.tips'), {
confirmButtonText: this.$t('dataset.confirm'),
cancelButtonText: this.$t('dataset.cancel'),
type: 'warning'
}).then(() => {
delGroup(data.id).then(response => {
saveGroup(group) {
// console.log(group);
this.$refs['groupForm'].validate((valid) => {
if (valid) {
addGroup(group).then(res => {
this.close()
this.$message({
message: this.$t('commons.save_success'),
type: 'success',
message: this.$t('dataset.delete_success'),
showClose: true
})
this.tree(this.groupForm)
})
}).catch(() => {
})
},
} else {
this.$message({
message: this.$t('commons.input_content'),
type: 'error',
showClose: true
})
return false
}
})
},
deleteTable(data) {
this.$confirm(this.$t('dataset.confirm_delete'), this.$t('dataset.tips'), {
confirmButtonText: this.$t('dataset.confirm'),
cancelButtonText: this.$t('dataset.cancel'),
type: 'warning'
}).then(() => {
delTable(data.id).then(response => {
saveTable(table) {
// console.log(table)
table.mode = parseInt(table.mode)
this.$refs['tableForm'].validate((valid) => {
if (valid) {
addTable(table).then(response => {
this.closeTable()
this.$message({
message: this.$t('commons.save_success'),
type: 'success',
message: this.$t('dataset.delete_success'),
showClose: true
})
this.tableTree()
// this.$router.push('/dataset/home')
this.$emit('switchComponent', {name: ''})
this.$emit('switchComponent', { name: '' })
this.$store.dispatch('dataset/setTable', null)
})
}).catch(() => {
})
},
close() {
this.editGroup = false
this.groupForm = {
name: '',
pid: null,
level: 0,
type: '',
children: [],
sort: 'type desc,name asc'
}
},
closeTable() {
this.editTable = false
this.tableForm = {
name: ''
}
},
tree(group) {
groupTree(group).then(res => {
this.tData = res.data
})
},
tableTree() {
this.tableData = []
if (this.currGroup.id) {
loadTable({
sort: 'type asc,create_time desc,name asc',
sceneId: this.currGroup.id
}).then(res => {
this.tableData = res.data
})
}
},
nodeClick(data, node) {
// console.log(data);
// console.log(node);
if (data.type === 'scene') {
this.sceneMode = true
this.currGroup = data
this.$store.dispatch('dataset/setSceneData', this.currGroup.id)
}
if (node.expanded) {
this.expandedArray.push(data.id)
} else {
const index = this.expandedArray.indexOf(data.id)
if (index > -1) {
this.expandedArray.splice(index, 1)
}
}
// console.log(this.expandedArray);
},
back() {
this.sceneMode = false
// const route = this.$store.state.permission.currentRoutes
// console.log(route)
// this.$router.push('/dataset/index')
this.$store.dispatch('dataset/setSceneData', null)
this.$emit('switchComponent', {name: ''})
},
clickAddData(param) {
// console.log(param);
switch (param.type) {
case 'db':
this.addDB()
break
case 'sql':
this.$message(param.type)
break
case 'excel':
this.$message(param.type)
break
case 'custom':
this.$message(param.type)
break
}
},
beforeClickAddData(type) {
return {
'type': type
}
},
addDB() {
// this.$router.push({
// name: 'add_db',
// params: {
// scene: this.currGroup
// }
// })
this.$emit('switchComponent', {name: 'AddDB', param: this.currGroup})
},
sceneClick(data, node) {
// console.log(data);
this.$store.dispatch('dataset/setTable', null)
this.$store.dispatch('dataset/setTable', data.id)
// this.$router.push({
// name: 'table',
// params: {
// table: data
// }
// })
this.$emit('switchComponent', {name: 'ViewTable'})
},
refresh() {
const path = this.$route.path
if (path === '/dataset/table') {
this.sceneMode = true
const sceneId = this.$store.state.dataset.sceneData
getScene(sceneId).then(res => {
this.currGroup = res.data
this.$message({
message: this.$t('commons.input_content'),
type: 'error',
showClose: true
})
return false
}
},
panelDefaultClick(data, node) {
// console.log(data);
// console.log(node);
},
})
},
delete(data) {
this.$confirm(this.$t('dataset.confirm_delete'), this.$t('dataset.tips'), {
confirmButtonText: this.$t('dataset.confirm'),
cancelButtonText: this.$t('dataset.cancel'),
type: 'warning'
}).then(() => {
delGroup(data.id).then(response => {
this.$message({
type: 'success',
message: this.$t('dataset.delete_success'),
showClose: true
})
this.tree(this.groupForm)
})
}).catch(() => {
})
},
deleteTable(data) {
this.$confirm(this.$t('dataset.confirm_delete'), this.$t('dataset.tips'), {
confirmButtonText: this.$t('dataset.confirm'),
cancelButtonText: this.$t('dataset.cancel'),
type: 'warning'
}).then(() => {
delTable(data.id).then(response => {
this.$message({
type: 'success',
message: this.$t('dataset.delete_success'),
showClose: true
})
this.tableTree()
// this.$router.push('/dataset/home')
this.$emit('switchComponent', { name: '' })
this.$store.dispatch('dataset/setTable', null)
})
}).catch(() => {
})
},
close() {
this.editGroup = false
this.groupForm = {
name: '',
pid: null,
level: 0,
type: '',
children: [],
sort: 'type desc,name asc'
}
},
closeTable() {
this.editTable = false
this.tableForm = {
name: ''
}
},
tree(group) {
groupTree(group).then(res => {
this.tData = res.data
})
},
tableTree() {
this.tableData = []
if (this.currGroup.id) {
loadTable({
sort: 'type asc,create_time desc,name asc',
sceneId: this.currGroup.id
}).then(res => {
this.tableData = res.data
})
}
},
nodeClick(data, node) {
// console.log(data);
// console.log(node);
if (data.type === 'scene') {
this.sceneMode = true
this.currGroup = data
this.$store.dispatch('dataset/setSceneData', this.currGroup.id)
}
if (node.expanded) {
this.expandedArray.push(data.id)
} else {
const index = this.expandedArray.indexOf(data.id)
if (index > -1) {
this.expandedArray.splice(index, 1)
}
}
// console.log(this.expandedArray);
},
back() {
this.sceneMode = false
// const route = this.$store.state.permission.currentRoutes
// console.log(route)
// this.$router.push('/dataset/index')
this.$store.dispatch('dataset/setSceneData', null)
this.$emit('switchComponent', { name: '' })
},
clickAddData(param) {
// console.log(param);
switch (param.type) {
case 'db':
this.addDB()
break
case 'sql':
this.$message(param.type)
break
case 'excel':
this.$message(param.type)
break
case 'custom':
this.$message(param.type)
break
}
},
beforeClickAddData(type) {
return {
'type': type
}
},
addDB() {
// this.$router.push({
// name: 'add_db',
// params: {
// scene: this.currGroup
// }
// })
this.$emit('switchComponent', { name: 'AddDB', param: this.currGroup })
},
sceneClick(data, node) {
// console.log(data);
this.$store.dispatch('dataset/setTable', null)
this.$store.dispatch('dataset/setTable', data.id)
// this.$router.push({
// name: 'table',
// params: {
// table: data
// }
// })
this.$emit('switchComponent', { name: 'ViewTable' })
},
refresh() {
const path = this.$route.path
if (path === '/dataset/table') {
this.sceneMode = true
const sceneId = this.$store.state.dataset.sceneData
getScene(sceneId).then(res => {
this.currGroup = res.data
})
}
},
panelDefaultClick(data, node) {
// console.log(data);
// console.log(node);
}
}
}
</script>
<style scoped>