mirror of
https://github.com/dataease/dataease.git
synced 2025-02-24 19:42:56 +08:00
Merge pull request #13315 from dataease/pr@dev-v2@feat_tab-layer
feat(数据大屏): Tab组件每页支持展示在图层区域,并进行图层相关信息管理 #12118
This commit is contained in:
commit
6c2e6f04e7
1
core/core-frontend/src/assets/svg/tab-title.svg
Normal file
1
core/core-frontend/src/assets/svg/tab-title.svg
Normal file
@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1731562646817" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5554" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M256 640h512c46.933333 0 85.333333-38.4 85.333333-85.333333v-42.666667c0-46.933333-38.4-85.333333-85.333333-85.333333h-157.866667v-12.8c-4.266667-42.666667-42.666667-72.533333-85.333333-72.533334H256c-46.933333 0-85.333333 38.4-85.333333 85.333334v128c0 46.933333 38.4 85.333333 85.333333 85.333333z m512-128v42.666667h-140.8l-4.266667-42.666667H768zM256 426.666667h268.8-12.8l25.6 128H256v-128z" p-id="5555"></path><path d="M896 170.666667H128c-46.933333 0-85.333333 38.4-85.333333 85.333333v512c0 46.933333 38.4 85.333333 85.333333 85.333333h768c46.933333 0 85.333333-38.4 85.333333-85.333333V256c0-46.933333-38.4-85.333333-85.333333-85.333333z m0 597.333333H128V256h768v512z" p-id="5556"></path></svg>
|
After Width: | Height: | Size: 1.0 KiB |
@ -391,6 +391,7 @@ const handleContextMenu = e => {
|
||||
</el-row>
|
||||
<Teleport v-if="editComponentId && nameEdit" :to="editComponentId">
|
||||
<input
|
||||
class="custom-teleport"
|
||||
@keydown.stop
|
||||
@keyup.stop
|
||||
ref="nameInput"
|
||||
@ -541,6 +542,9 @@ const handleContextMenu = e => {
|
||||
color: #5f5f5f !important;
|
||||
}
|
||||
}
|
||||
.custom-teleport {
|
||||
background: #1a1a1a !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less">
|
||||
|
@ -78,6 +78,7 @@ import ComposeShow from '@/components/data-visualization/canvas/ComposeShow.vue'
|
||||
import { composeStoreWithOut } from '@/store/modules/data-visualization/compose'
|
||||
import RealTimeGroup from '@/components/data-visualization/RealTimeGroup.vue'
|
||||
import { contextmenuStoreWithOut } from '@/store/modules/data-visualization/contextmenu'
|
||||
import RealTimeTab from '@/components/data-visualization/RealTimeTab.vue'
|
||||
const dropdownMore = ref(null)
|
||||
const lockStore = lockStoreWithOut()
|
||||
|
||||
@ -504,7 +505,7 @@ const canvasChange = () => {
|
||||
>
|
||||
<div style="width: 22px; padding-left: 3px">
|
||||
<el-icon
|
||||
v-show="getComponent(index)?.component === 'Group'"
|
||||
v-show="['Group', 'DeTabs'].includes(getComponent(index)?.component)"
|
||||
class="component-expand"
|
||||
@click="expandClick(getComponent(index))"
|
||||
>
|
||||
@ -612,6 +613,14 @@ const canvasChange = () => {
|
||||
<div v-if="getComponent(index)?.component === 'Group' && getComponent(index)?.expand">
|
||||
<real-time-group :component-data="getComponent(index).propValue"></real-time-group>
|
||||
</div>
|
||||
<div
|
||||
v-if="getComponent(index)?.component === 'DeTabs' && getComponent(index)?.expand"
|
||||
>
|
||||
<real-time-tab
|
||||
:tab-element="getComponent(index)"
|
||||
:component-data="getComponent(index).propValue"
|
||||
></real-time-tab>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
|
@ -0,0 +1,370 @@
|
||||
<script lang="ts" setup>
|
||||
import tabTitle from '@/assets/svg/tab-title.svg'
|
||||
import dvExpandDown from '@/assets/svg/dv-expand-down.svg'
|
||||
import dvExpandRight from '@/assets/svg/dv-expand-right.svg'
|
||||
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
|
||||
import { snapshotStoreWithOut } from '@/store/modules/data-visualization/snapshot'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { ElIcon, ElRow } from 'element-plus-secondary'
|
||||
import Icon from '../icon-custom/src/Icon.vue'
|
||||
import { nextTick, ref, toRefs } from 'vue'
|
||||
import draggable from 'vuedraggable'
|
||||
import { composeStoreWithOut } from '@/store/modules/data-visualization/compose'
|
||||
import RealTimeGroup from '@/components/data-visualization/RealTimeGroup.vue'
|
||||
import eventBus from '@/utils/eventBus'
|
||||
|
||||
const dvMainStore = dvMainStoreWithOut()
|
||||
const snapshotStore = snapshotStoreWithOut()
|
||||
const composeStore = composeStoreWithOut()
|
||||
|
||||
const { areaData } = storeToRefs(composeStore)
|
||||
|
||||
const { curTabName } = storeToRefs(dvMainStore)
|
||||
|
||||
const props = defineProps({
|
||||
componentData: [],
|
||||
tabElement: {}
|
||||
})
|
||||
|
||||
const { componentData, tabElement } = toRefs(props)
|
||||
|
||||
const getComponent = index => {
|
||||
return componentData.value[index]
|
||||
}
|
||||
const transformIndex = index => {
|
||||
return componentData.value.length - 1 - index
|
||||
}
|
||||
|
||||
const onClick = item => {
|
||||
if (item) {
|
||||
dvMainStore.setCurTabName(item.name)
|
||||
} else {
|
||||
dvMainStore.setCurTabName(null)
|
||||
}
|
||||
//其他情况点击清理选择区域
|
||||
areaData.value.components.splice(0, areaData.value.components.length)
|
||||
}
|
||||
|
||||
let nameEdit = ref(false)
|
||||
let editComponentId = ref('')
|
||||
let inputName = ref('')
|
||||
let nameInput = ref(null)
|
||||
let curEditComponent = null
|
||||
const editComponentName = item => {
|
||||
curEditComponent = item
|
||||
editComponentId.value = `#component-label-${item.name}`
|
||||
nameEdit.value = true
|
||||
inputName.value = item.title
|
||||
nextTick(() => {
|
||||
nameInput.value.focus()
|
||||
})
|
||||
}
|
||||
const closeEditComponentName = () => {
|
||||
nameEdit.value = false
|
||||
if (!inputName.value || !inputName.value.trim()) {
|
||||
return
|
||||
}
|
||||
if (inputName.value.trim() === curEditComponent.title) {
|
||||
return
|
||||
}
|
||||
curEditComponent.title = inputName.value
|
||||
inputName.value = ''
|
||||
curEditComponent = null
|
||||
}
|
||||
|
||||
const dragOnEnd = ({ oldIndex, newIndex }) => {
|
||||
const source = componentData.value[newIndex]
|
||||
const comLength = componentData.value.length
|
||||
// 还原数组
|
||||
componentData.value.splice(newIndex, 1)
|
||||
componentData.value.splice(oldIndex, 0, source)
|
||||
const target = componentData.value[comLength - 1 - oldIndex]
|
||||
// 反向移动数组
|
||||
componentData.value.splice(comLength - 1 - oldIndex, 1)
|
||||
componentData.value.splice(comLength - 1 - newIndex, 0, target)
|
||||
dvMainStore.setCurTabName(source.title)
|
||||
eventBus.emit('onTabSortChange-' + tabElement.value?.id)
|
||||
snapshotStore.recordSnapshotCache()
|
||||
}
|
||||
|
||||
const menuAsideClose = (param, index) => {
|
||||
const iconDom = document.getElementById('close-button')
|
||||
if (iconDom) {
|
||||
iconDom.click()
|
||||
}
|
||||
if (param?.opt === 'rename') {
|
||||
setTimeout(() => {
|
||||
editComponentName(getComponent(index))
|
||||
}, 200)
|
||||
}
|
||||
}
|
||||
|
||||
const handleContextMenu = e => {
|
||||
e.preventDefault()
|
||||
// 获取鼠标点击位置
|
||||
const x = e.clientX
|
||||
const y = e.clientY
|
||||
const customContextMenu = document.createElement('div')
|
||||
customContextMenu.style.position = 'fixed'
|
||||
customContextMenu.style.left = x + 'px'
|
||||
customContextMenu.style.top = y + 'px'
|
||||
|
||||
// 将自定义菜单添加到页面
|
||||
document.body.appendChild(customContextMenu)
|
||||
|
||||
// 为自定义菜单添加事件监听器,例如,点击菜单项后执行的操作
|
||||
customContextMenu.addEventListener('click', () => {
|
||||
// 在这里执行菜单项点击后的操作
|
||||
// 例如,关闭菜单
|
||||
document.body.removeChild(customContextMenu)
|
||||
})
|
||||
}
|
||||
|
||||
const expandClick = component => {
|
||||
component['expand'] = !component['expand']
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!--为了保持图层视觉上的一致性 这里进行数组的倒序排列 相应的展示和移动按照倒序处理-->
|
||||
<div class="real-time-component-list">
|
||||
<button hidden="true" id="close-button"></button>
|
||||
<el-row class="list-wrap">
|
||||
<div class="list-container" @contextmenu="handleContextMenu">
|
||||
<draggable
|
||||
@end="dragOnEnd"
|
||||
:list="componentData"
|
||||
animation="100"
|
||||
class="drag-list"
|
||||
item-key="id"
|
||||
>
|
||||
<template #item="{ index }">
|
||||
<div>
|
||||
<div
|
||||
:title="getComponent(index)?.title"
|
||||
class="component-item"
|
||||
:class="{
|
||||
activated: curTabName === getComponent(index)?.name
|
||||
}"
|
||||
@click="onClick(getComponent(index))"
|
||||
>
|
||||
<div style="width: 22px">
|
||||
<el-icon class="component-expand" @click="expandClick(getComponent(index))">
|
||||
<Icon
|
||||
v-if="getComponent(index)?.expand"
|
||||
name="dv-expand-down"
|
||||
class="expand-icon"
|
||||
><dvExpandDown class="svg-icon expand-icon"
|
||||
/></Icon>
|
||||
<Icon
|
||||
v-if="!getComponent(index)?.expand"
|
||||
name="dv-expand-right"
|
||||
class="expand-icon"
|
||||
><dvExpandRight class="svg-icon expand-icon"
|
||||
/></Icon>
|
||||
</el-icon>
|
||||
</div>
|
||||
<el-icon class="component-icon">
|
||||
<Icon><component :is="tabTitle"></component></Icon>
|
||||
</el-icon>
|
||||
<span
|
||||
:id="`component-label-${getComponent(index)?.name}`"
|
||||
class="component-label"
|
||||
@dblclick="editComponentName(getComponent(index))"
|
||||
>
|
||||
{{ getComponent(index)?.title }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="getComponent(index)?.expand">
|
||||
<real-time-group
|
||||
:component-data="getComponent(index).componentData"
|
||||
></real-time-group>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
</div>
|
||||
</el-row>
|
||||
<Teleport v-if="editComponentId && nameEdit" :to="editComponentId">
|
||||
<input
|
||||
class="custom-teleport"
|
||||
@keydown.stop
|
||||
@keyup.stop
|
||||
ref="nameInput"
|
||||
v-model="inputName"
|
||||
@blur="closeEditComponentName"
|
||||
/>
|
||||
</Teleport>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.real-time-component-list {
|
||||
white-space: nowrap;
|
||||
.list-wrap {
|
||||
max-height: calc(100% - @component-toolbar-height);
|
||||
overflow-y: auto;
|
||||
width: 100%;
|
||||
.list-container {
|
||||
width: 100%;
|
||||
.component-item {
|
||||
position: relative;
|
||||
height: 30px;
|
||||
width: 100%;
|
||||
cursor: grab;
|
||||
color: @dv-canvas-main-font-color;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
font-size: 12px;
|
||||
padding: 0 2px 0 20px;
|
||||
user-select: none;
|
||||
|
||||
.component-icon {
|
||||
color: #a6a6a6;
|
||||
font-size: 14px;
|
||||
}
|
||||
.component-label {
|
||||
color: #ebebeb;
|
||||
}
|
||||
|
||||
> span.component-label {
|
||||
font-size: 12px;
|
||||
margin-left: 10px;
|
||||
position: relative;
|
||||
min-width: 43px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
input {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
background-color: white;
|
||||
outline: none;
|
||||
border: none;
|
||||
border-radius: 2px;
|
||||
padding: 0 4px;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(235, 235, 235, 0.1);
|
||||
|
||||
.icon-container {
|
||||
.component-base {
|
||||
opacity: 1;
|
||||
}
|
||||
width: 22px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.icon-container {
|
||||
.component-base {
|
||||
opacity: 0;
|
||||
}
|
||||
width: 0px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
flex-grow: 1;
|
||||
cursor: none;
|
||||
i {
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
.activated {
|
||||
background-color: var(--ed-color-primary-1a, rgba(51, 112, 255, 0.05)) !important;
|
||||
:deep(.component-icon) {
|
||||
color: var(--ed-color-primary);
|
||||
}
|
||||
:deep(.component-label) {
|
||||
color: var(--ed-color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.real-time-component-list :deep(.ed-popper) {
|
||||
background: #303133 !important;
|
||||
}
|
||||
|
||||
.component-base {
|
||||
cursor: pointer;
|
||||
height: 22px !important;
|
||||
width: 22px !important;
|
||||
border-radius: 4px;
|
||||
padding: 0 4px;
|
||||
|
||||
.opt-icon {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: rgba(235, 235, 235, 0.1);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: rgba(235, 235, 235, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.component-icon-display {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
.icon-container-show {
|
||||
width: 70px !important;
|
||||
}
|
||||
|
||||
.icon-container-lock {
|
||||
width: 45px !important;
|
||||
}
|
||||
|
||||
.container-item-not-show {
|
||||
color: #5f5f5f !important;
|
||||
:deep(.component-icon) {
|
||||
color: #5f5f5f !important;
|
||||
}
|
||||
:deep(.component-label) {
|
||||
color: #5f5f5f !important;
|
||||
}
|
||||
}
|
||||
|
||||
.component-expand {
|
||||
cursor: pointer;
|
||||
height: 16px !important;
|
||||
width: 16px !important;
|
||||
border-radius: 2px;
|
||||
padding: 0 2px;
|
||||
|
||||
.expand-icon {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: rgba(235, 235, 235, 0.1);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: rgba(235, 235, 235, 0.1);
|
||||
}
|
||||
}
|
||||
.custom-teleport {
|
||||
background: #1a1a1a !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less">
|
||||
.compose-dropdown {
|
||||
position: initial !important;
|
||||
}
|
||||
</style>
|
@ -1,7 +0,0 @@
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<div>ts</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less"></style>
|
@ -34,7 +34,7 @@
|
||||
<script setup lang="ts">
|
||||
import drag from '@/assets/svg/drag.svg'
|
||||
import draggable from 'vuedraggable'
|
||||
import { onMounted, ref, toRefs } from 'vue'
|
||||
import { ref } from 'vue'
|
||||
import { deepCopy } from '@/utils/utils'
|
||||
import { ElButton } from 'element-plus-secondary'
|
||||
import { snapshotStoreWithOut } from '@/store/modules/data-visualization/snapshot'
|
||||
|
@ -59,6 +59,7 @@ export const dvMainStore = defineStore('dataVisualization', {
|
||||
isInEditor: false, // 是否在编辑器中,用于判断复制、粘贴组件时是否生效,如果在编辑器外,则无视这些操作
|
||||
componentData: [], // 画布组件数据
|
||||
curComponent: null,
|
||||
curTabName: null, // 当前选中的tabName 大屏图层区域使用
|
||||
curComponentIndex: null,
|
||||
curCanvasScaleMap: {},
|
||||
// 预览仪表板缩放信息
|
||||
@ -264,7 +265,11 @@ export const dvMainStore = defineStore('dataVisualization', {
|
||||
setCurComponentMobileConfig(component) {
|
||||
this.curComponent = component
|
||||
},
|
||||
setCurTabName(val) {
|
||||
this.curTabName = val
|
||||
},
|
||||
setCurComponent({ component, index }) {
|
||||
this.setCurTabName(null)
|
||||
if (!component && this.curComponent) {
|
||||
this.curComponent['editing'] = false
|
||||
this.curComponent['resizing'] = false
|
||||
|
Loading…
Reference in New Issue
Block a user