feat: dashboard tab

This commit is contained in:
wangjiahao
2025-05-27 18:35:14 +08:00
parent 72d7104c34
commit 8e85bd90eb
9 changed files with 257 additions and 165 deletions
+1
View File
@@ -41,6 +41,7 @@ declare module '@vue/runtime-core' {
ElTable: typeof import('element-plus-secondary/es')['ElTable']
ElTableColumn: typeof import('element-plus-secondary/es')['ElTableColumn']
ElTabPane: typeof import('element-plus-secondary/es')['ElTabPane']
ElTabPanel: typeof import('element-plus-secondary/es')['ElTabPanel']
ElTabs: typeof import('element-plus-secondary/es')['ElTabs']
ElTooltip: typeof import('element-plus-secondary/es')['ElTooltip']
ElUpload: typeof import('element-plus-secondary/es')['ElUpload']
+10 -5
View File
@@ -1,13 +1,17 @@
// @ts-ignore
// @ts-ignore
import SnowflakeID from 'snowflake-id';
const snowflake = new SnowflakeID({
mid: 42,
offset: (2010 - 1970) * 365 * 24 * 3600 * 1000
mid: 42,
offset: (2010 - 1970) * 365 * 24 * 3600 * 1000
});
export const guid = () => {
return snowflake.generate();
export const guid = (prefix?: string) => {
if (prefix) {
return `${prefix}_${snowflake.generate()}`;
} else {
return snowflake.generate()
}
}
export interface CanvasItem {
@@ -16,6 +20,7 @@ export interface CanvasItem {
y: number
sizeX: number
sizeY: number
[key: string]: any
}
@@ -1,69 +0,0 @@
<script setup lang="ts">
import CanvasCore from '@/views/dashboard/canvas/CanvasCore.vue'
import { nextTick, onMounted, ref } from 'vue'
const cyGridster = ref(null)
const baseWidth = ref(0)
const baseHeight = ref(0)
const baseMarginLeft = ref(0)
const baseMarginTop = ref(0)
const baseMatrixCount = {
x: 72,
y: 36
}
const componentData = ref([
{
id: 4,
x: 1,
y: 1,
sizeX: 20,
sizeY: 10,
_dragId: 0
},
{
id: 10,
x: 2,
y: 1,
sizeX: 20,
sizeY: 10,
_dragId: 1
},
{
id: 7,
x: 1,
y: 2,
sizeX: 20,
sizeY: 10,
_dragId: 2
}
])
onMounted(() => {
const screenWidth = window.innerWidth
const screenHeight = window.innerHeight
baseMarginLeft.value = 10
baseMarginTop.value = 10
baseWidth.value = (screenWidth - baseMarginLeft.value) / baseMatrixCount.x - baseMarginLeft.value
baseHeight.value = (screenHeight - baseMarginTop.value) / baseMatrixCount.y - baseMarginTop.value
nextTick(() => {
if (cyGridster.value) {
// @ts-ignore
cyGridster.value.init()
}
})
})
</script>
<template>
<CanvasCore
ref="cyGridster"
:base-width="baseWidth"
:base-height="baseHeight"
:base-margin-left="baseMarginLeft"
:base-margin-top="baseMarginTop"
:canvas-component-data="componentData"
></CanvasCore>
</template>
<style scoped lang="less"></style>
@@ -1,20 +1,25 @@
<script setup lang="ts">
import {ref, nextTick, toRefs, type PropType} from 'vue'
import {ref, nextTick, toRefs, type PropType, onMounted, getCurrentInstance} from 'vue'
import _ from 'lodash'
import {dashboardStoreWithOut} from '@/stores/dashboard/dashboard'
import {type CanvasCoord, type CanvasItem} from '@/utils/canvas.ts'
import CanvasShape from './CanvasShape.vue'
import {findComponent} from "@/views/dashboard/components/component-list.ts";
import {storeToRefs} from 'pinia'
const dashboardStore = dashboardStoreWithOut()
const {
tabCollisionActiveId,
tabMoveInActiveId
} = storeToRefs(dashboardStore)
const canvasLocked = ref(false) // Is the canvas movement locked, Default false
// @ts-ignore
let currentInstance
// Props
const props = defineProps({
canvasId: {
type: String,
default: 'canvas-main'
},
parentConfigItem: {
type: Object as PropType<CanvasItem>,
required: false
},
canvasComponentData: {
type: Array as PropType<CanvasItem[]>,
required: true
@@ -111,7 +116,9 @@ let itemMaxX = 0
let itemMaxY = 0
const moveTime = 80
const tabMoveInYOffset = 70
const tabMoveInYOffset = 30
const tabMoveOutXOffset = 30
const tabMoveOutYOffset = 30
// Effective area of collision depth
const collisionGap = 10
@@ -372,6 +379,13 @@ function movePlayer(item: CanvasItem, position: any) {
}
}
function removeItemById(id: number) {
const index = canvasComponentData.value.findIndex(item => item.id === id);
if (index >= 0) {
removeItem(index)
}
}
function removeItem(index: number) {
const item = canvasComponentData.value[index] as CanvasItem
removeItemFromPositionBox(item)
@@ -383,7 +397,7 @@ function removeItem(index: number) {
}
})
// @ts-ignore
canvasComponentData.value.splice(index, 1, {})
canvasComponentData.value.splice(index, 1)
}
function addItem(item: CanvasItem, index: any) {
@@ -679,6 +693,8 @@ function containerMouseDown(e: MouseEvent) {
}
infoBox.value.startX = e.pageX
infoBox.value.startY = e.pageY
e.preventDefault()
e.stopPropagation()
}
function getNowPosition(addSizeX: number, addSizeY: number, moveXSize: number, moveYSize: number) {
@@ -736,6 +752,7 @@ function getNowPosition(addSizeX: number, addSizeY: number, moveXSize: number, m
}
function startMove(e: MouseEvent, item: CanvasItem, index: number) {
canvasLocked.value = false // Reset canvas lock status
if (!draggable.value) return
dashboardStore.setCurComponent(item)
if (!infoBox.value) {
@@ -787,7 +804,6 @@ function startMove(e: MouseEvent, item: CanvasItem, index: number) {
infoBox.value.oldSizeY = item.sizeY
infoBox.value.originWidth = infoBox.value.cloneItem.offsetWidth
infoBox.value.originHeight = infoBox.value.cloneItem.offsetHeight
const itemMouseMove = (e: MouseEvent) => {
const moveItem = _.get(infoBox.value, 'moveItem')
const resizeItem = _.get(infoBox.value, 'resizeItem')
@@ -839,10 +855,17 @@ function startMove(e: MouseEvent, item: CanvasItem, index: number) {
let nowCloneItemX = infoBox.value.originX + moveXSize
let nowCloneItemY = infoBox.value.originY + moveYSize
infoBox.value.cloneItem.style.left = `${nowCloneItemX}px`
infoBox.value.cloneItem.style.top = `${nowCloneItemY}px`
tabMoveInCheckSQ()
tabMoveOutCheckSQ()
//If the current canvas is locked, no component movement will be performed
if (canvasLocked.value) return
// Adjust the accuracy of moving coordinate changes
const newX = Math.max(
Math.floor(
(nowCloneItemX + infoBox.value.cloneItem.offsetWidth / 96 - baseMarginLeft.value) /
(nowCloneItemX - 20 + infoBox.value.cloneItem.offsetWidth / 96 - baseMarginLeft.value) /
cellWidth.value +
1
),
@@ -857,7 +880,6 @@ function startMove(e: MouseEvent, item: CanvasItem, index: number) {
),
1
)
debounce(() => {
if (newX !== infoBox.value.oldX || newY !== infoBox.value.oldY) {
movePlayer(moveItem, {x: newX, y: newY})
@@ -865,18 +887,33 @@ function startMove(e: MouseEvent, item: CanvasItem, index: number) {
infoBox.value.oldY = newY
}
}, 10)
infoBox.value.cloneItem.style.left = `${nowCloneItemX}px`
infoBox.value.cloneItem.style.top = `${nowCloneItemY}px`
tabMoveInCheckSQ()
}
}
window.addEventListener('mousemove', itemMouseMove)
// Need to execute before mouseup
const itemCanvasChange = () => {
// The current canvas movement is in a locked state, indicating that there are components that require canvas switching
if (canvasLocked.value) {
const moveItem = infoBox.value.moveItem
// Get the SQTab currently being moved in
const curActiveSQTab = canvasComponentData?.value.find(item => item.component === 'SQTab' && item.moveInActive === true);
if (curActiveSQTab) {
// @ts-ignore
const refTabInstance = currentInstance.refs['shape_component_' + curActiveSQTab.id][0]
refTabInstance.addTabItem(moveItem)
removeItemById(moveItem.id)
curActiveSQTab.collisionActive = false
curActiveSQTab.moveInActive = false
}
}
canvasLocked.value = false
}
const itemMouseUp = () => {
if (_.isEmpty(infoBox.value)) return
if (infoBox.value.cloneItem) {
infoBox.value.cloneItem.remove()
}
@@ -889,6 +926,7 @@ function startMove(e: MouseEvent, item: CanvasItem, index: number) {
infoBox.value.moveItem.show = true
delete infoBox.value.moveItem.isPlayer
}
itemCanvasChange()
infoBox.value = {}
window.removeEventListener('mousemove', itemMouseMove)
@@ -960,12 +998,12 @@ function moving() {
// Obtain matrix position
function getItemMatrixPosition(item: CanvasItem) {
return {
tw: cellWidth.value * item.sizeX - baseMarginLeft.value,
th: cellHeight.value * item.sizeY - baseMarginTop.value,
tl: cellWidth.value * (item.x - 1) + baseMarginLeft.value,
tr: cellWidth.value * (item.sizeX + item.x - 1) + baseMarginLeft.value,
tt: cellHeight.value * (item.y - 1) + baseMarginTop.value,
tb: cellHeight.value * (item.sizeY + item.y - 1) + baseMarginLeft.value
tw: item.sizeX,
th: item.sizeY,
tl: item.x - 1,
tr: item.sizeX + item.x - 1,
tt: item.y - 1,
tb: item.sizeY + item.y - 1
}
}
@@ -973,14 +1011,15 @@ function getItemMatrixPosition(item: CanvasItem) {
function getItemStylePosition(item: CanvasItem) {
const {tw, th, tl, tr, tt, tb} = getItemMatrixPosition(item)
return {
tw: tw * (cellWidth.value + baseMarginLeft.value) - baseMarginLeft.value,
th: th * (cellHeight.value + baseMarginTop.value) - baseMarginTop.value,
tw: tw * cellWidth.value - baseMarginLeft.value,
tl: cellWidth.value * tl + baseMarginLeft.value,
tr: cellWidth.value * tr + baseMarginLeft.value,
th: th * cellHeight.value - baseMarginTop.value,
tt: cellHeight.value * tt + baseMarginTop.value,
tb: cellHeight.value * tb + baseMarginLeft.value
tb: cellHeight.value * tb + baseMarginTop.value
}
}
// @ts-ignore
function tabMoveInCheck(cloneRefItem) {
//1. If the current cloneItem type is not a tab,
@@ -998,8 +1037,19 @@ function tabMoveInCheck(cloneRefItem) {
}
}
function tabMoveOutCheckSQ() {
const {cloneItem, moveItem} = infoBox.value
if (cloneItem && moveItem && props.canvasId.includes('tab') && props.parentConfigItem) {
const left = cloneItem.offsetLeft
const width = cloneItem.offsetWidth
const top = cloneItem.offsetTop
const {tw} = getItemStylePosition(props.parentConfigItem)
props.parentConfigItem.moveOutActive = left < -tabMoveOutXOffset || top < -tabMoveOutYOffset || (left + width - tw) > tabMoveOutXOffset;
}
}
function tabMoveInCheckSQ() {
const {cloneItem,moveItem} = infoBox.value.cloneItem
const {cloneItem, moveItem} = infoBox.value
if (cloneItem && moveItem && moveItem !== 'SQTab') {
const width = cloneItem.offsetWidth
const height = cloneItem.offsetHeight
@@ -1022,12 +1072,8 @@ function tabMoveInCheckSQ() {
// Near the lower right corner area
const brAndBr =
collisionT + collisionH >= top + height && collisionL + collisionW >= left + width
if (tfAndTf && bfAndBf && trAndTr && brAndBr) {
dashboardStore.setTabCollisionActiveId(item.id)
} else if (tabCollisionActiveId.value === item.id) {
dashboardStore.setTabCollisionActiveId(null)
}
item.collisionActive = tfAndTf && bfAndBf && trAndTr && brAndBr;
canvasLocked.value = item.collisionActive // Contains collision move in operation, locking canvas
//Move into effective area for inspection
//Collision effective area inspection
@@ -1044,17 +1090,17 @@ function tabMoveInCheckSQ() {
const activeTrAndTr = activeT <= top && activeL + activeW >= left + width
// Near the lower right corner area
const activeBrAndBr = activeT + activeH >= top + height && activeL + activeW >= left + width
if (activeTfAndTf && activeBfAndBf && activeTrAndTr && activeBrAndBr) {
dashboardStore.setTabMoveInActiveId(item.id)
} else if (tabMoveInActiveId.value === item.id) {
dashboardStore.setTabMoveInActiveId(null)
}
item.moveInActive = activeTfAndTf && activeBfAndBf && activeTrAndTr && activeBrAndBr
}
})
}
}
onMounted(() => {
currentInstance = getCurrentInstance()
})
defineExpose({
getRenderState,
@@ -1086,7 +1132,11 @@ defineExpose({
:start-resize="startResize"
:key="'item' + index"
:style="nowItemStyle(item)">
<component class="slot-component" :is="findComponent(item.component)" :config-item="item"></component>
<component :ref="'shape_component_'+item.id"
class="sql-component slot-component dragHandle"
:is="findComponent(item.component)"
:config-item="item">
</component>
</CanvasShape>
</template>
</div>
@@ -11,6 +11,7 @@ const COMPONENT_LIST = [
propValue: '&nbsp;',
icon: 'icon_graphical',
innerType: 'bar',
locked: false,
x: 1,
y: 1,
sizeX: 36,
@@ -21,6 +22,11 @@ const COMPONENT_LIST = [
id: 1002,
component: 'SQTab',
name: 'new-tabs',
locked: false,
collisionActive: false,
moveInActive: false,
moveOutActive: false,
activeTabName: 'tab',
propValue: [
{
name: 'tab',
@@ -40,6 +46,7 @@ const COMPONENT_LIST = [
id: 1003,
component: 'SQText',
name: 'new text',
locked: false,
propValue: '&nbsp;',
x: 1,
y: 1,
@@ -1,12 +1,22 @@
<script setup lang="ts">
import {computed, type PropType, reactive, ref, toRefs, nextTick} from 'vue'
import {computed, type PropType, reactive, ref, toRefs, nextTick, getCurrentInstance, onMounted} from 'vue'
import CustomTab from "@/views/dashboard/components/sq-tab/CustomTab.vue";
import {guid, type CanvasItem,} from "@/utils/canvas.ts";
import DragHandle from "@/views/dashboard/canvas/DragHandle.vue";
import {ArrowDown} from "@element-plus/icons-vue";
import DashboardEditor from "@/views/dashboard/editor/DashboardEditor.vue";
const editableTabsValue = ref(null)
const showTabTitleFlag = ref(true)
// @ts-ignore
let currentInstance
import _ from 'lodash'
const tabBaseMatrixCount = {
x: 36,
y: 12
}
const props = defineProps({
configItem: {
type: Object as PropType<CanvasItem>,
@@ -30,19 +40,19 @@ const state = reactive({
})
function addTab() {
const newName = guid()
const newTab = {
name: newName,
name: guid('tab'),
title: 'New Tab',
componentData: [],
closable: true
}
configItem.value.propValue.push(newTab)
configItem.value.activeSubTabIndex = configItem.value.propValue.length - 1
// @ts-ignore
editableTabsValue.value = newTab.name
}
function deleteCur(param:any) {
function deleteCur(param: any) {
state.curItem = param
let len = configItem.value.propValue.length
while (len--) {
@@ -51,6 +61,7 @@ function deleteCur(param:any) {
const activeIndex =
(len - 1 + configItem.value.propValue.length) % configItem.value.propValue.length
editableTabsValue.value = configItem.value.propValue[activeIndex].name
configItem.value.activeSubTabIndex = configItem.value.propValue.length - 1
state.tabShow = false
nextTick(() => {
state.tabShow = true
@@ -59,14 +70,14 @@ function deleteCur(param:any) {
}
}
function editCurTitle(param:any) {
function editCurTitle(param: any) {
state.activeTabName = param.name
state.curItem = param
state.textarea = param.title
state.dialogVisible = true
}
function handleCommand(command:any) {
function handleCommand(command: any) {
switch (command.command) {
case 'editTitle':
editCurTitle(command.param)
@@ -77,7 +88,7 @@ function handleCommand(command:any) {
}
}
const beforeHandleCommand = (item : any, param : any) => {
const beforeHandleCommand = (item: any, param: any) => {
return {
command: item,
param: param
@@ -93,6 +104,29 @@ const titleStyle = (itemName: string) => {
}
const isEditMode = computed(() => true)
const addTabItem = (item: CanvasItem) => {
// do addTabItem
// @ts-ignore
const index = configItem.value.propValue.findIndex(tabItem => configItem.value.activeTabName === tabItem.name);
// @ts-ignore
const refInstance = currentInstance.refs['tabEditorRef_' + index][0]
const newTabItem = _.cloneDeep(item)
newTabItem.sizeX = 10
newTabItem.sizeY = 10
newTabItem.x = 1
newTabItem.y = 1
refInstance.addItemToBox(newTabItem)
}
onMounted(() => {
currentInstance = getCurrentInstance()
})
defineExpose({
addTabItem
})
</script>
<template>
@@ -108,6 +142,7 @@ const isEditMode = computed(() => true)
:border-active-color="state.headBorderActiveColor"
:hide-title="!showTabTitleFlag"
>
-------{{ configItem.collisionActive }} -- -- {{ configItem.moveInActive }}-- -- {{ configItem.moveOutActive }}
<template :key="tabItem.name" v-for="tabItem in configItem.propValue">
<el-tab-pane
class="el-tab-pane-custom"
@@ -151,8 +186,21 @@ const isEditMode = computed(() => true)
</template>
</el-tab-pane>
</template>
<div>
this is a tab
<div
class="tab-content-custom"
:key="tabItem.name + '-content'"
v-for="(tabItem, index) in configItem.propValue"
:class="{ 'switch-hidden': editableTabsValue !== tabItem.name }"
>
<DashboardEditor
:ref="'tabEditorRef_'+index"
:canvas-component-data="tabItem.componentData"
:move-in-active="configItem.moveInActive"
:base-matrix-count="tabBaseMatrixCount"
:canvas-id="tabItem.name"
:parent-config-item="configItem"
>
</DashboardEditor>
</div>
</custom-tab>
</div>
@@ -160,8 +208,14 @@ const isEditMode = computed(() => true)
</template>
<style scoped lang="less">
.ed-dropdown-link{
.ed-dropdown-link {
margin-top: 3px !important;
}
.tab-content-custom {
position: absolute;
width: 100%;
height: 100%;
}
</style>
@@ -15,7 +15,7 @@
width: 100px;
height: 100px;
cursor: move;
border: 1px solid #59c7f9;
border: 1px solid #59c7f9;
background-color: #fff;
.resizeHandle
{
@@ -27,6 +27,10 @@
border-radius: 50%;
z-index: 5;
}
.sql-component {
width: 100%;
height: 100%;
}
}
.moveAnimation
{
@@ -39,7 +43,7 @@
.cloneNode
{
z-index: 3;
z-index: 100;
transition: none;
@@ -98,4 +102,4 @@
border: 1px solid;
}
}
}
@@ -1,6 +1,6 @@
<script setup lang="ts">
import CanvasCore from '@/views/dashboard/canvas/CanvasCore.vue'
import {nextTick, onMounted, ref} from 'vue'
import {nextTick, onMounted, type PropType, ref} from 'vue'
import type {CanvasItem} from "@/utils/canvas.ts";
const canvasCoreRef = ref(null)
@@ -9,41 +9,38 @@ const baseWidth = ref(0)
const baseHeight = ref(0)
const baseMarginLeft = ref(0)
const baseMarginTop = ref(0)
const baseMatrixCount = {
x: 72,
y: 36
}
const componentData = ref([
{
id: 4,
x: 1,
y: 1,
sizeX: 20,
sizeY: 10,
_dragId: 0
// Props
const props = defineProps({
canvasId: {
type: String,
default: 'canvas-main'
},
{
id: 10,
x: 2,
y: 1,
sizeX: 20,
sizeY: 10,
_dragId: 1
parentConfigItem: {
type: Object as PropType<CanvasItem>,
required: false
},
{
id: 7,
x: 1,
y: 2,
sizeX: 20,
sizeY: 10,
_dragId: 2
canvasComponentData: {
type: Array as PropType<CanvasItem[]>,
required: true
},
baseMatrixCount: {
type: Object,
default: {
x: 72,
y: 36
}
},
moveInActive: {
type: Boolean,
default: false
}
])
})
const canvasSizeInit = () => {
sizeInit()
if (canvasCoreRef.value) {
//@ts-ignore
//@ts-ignore
canvasCoreRef.value.sizeInit()
}
}
@@ -56,14 +53,12 @@ const sizeInit = () => {
const screenHeight = dashboardEditorRef.value.offsetHeight
baseMarginLeft.value = 10
baseMarginTop.value = 10
baseWidth.value = (screenWidth - baseMarginLeft.value) / baseMatrixCount.x - baseMarginLeft.value
baseHeight.value = (screenHeight - baseMarginTop.value) / baseMatrixCount.y - baseMarginTop.value
baseWidth.value = (screenWidth - baseMarginLeft.value) / props.baseMatrixCount.x - baseMarginLeft.value
baseHeight.value = (screenHeight - baseMarginTop.value) / props.baseMatrixCount.y - baseMarginTop.value
}
}
const addItemToBox = (item: CanvasItem) => {
// @ts-ignore
console.log(componentData.value.length + '===' + JSON.stringify(componentData.value))
// @ts-ignore
canvasCoreRef.value.addItemBox(item)
}
@@ -91,14 +86,19 @@ onMounted(() => {
</script>
<template>
<div class='dashboard-editor-main' ref="dashboardEditorRef">
<div
class='dashboard-editor-main'
:class="{'move-in-active': moveInActive}"
ref="dashboardEditorRef">
<CanvasCore
ref="canvasCoreRef"
:base-width="baseWidth"
:base-height="baseHeight"
:base-margin-left="baseMarginLeft"
:base-margin-top="baseMarginTop"
:canvas-component-data="componentData"
:canvas-component-data="canvasComponentData"
:parent-config-item="parentConfigItem"
:canvas-id="canvasId"
></CanvasCore>
</div>
@@ -110,4 +110,9 @@ onMounted(() => {
height: calc(100% - 56px);
overflow-y: auto;
}
.move-in-active {
border: 2px dotted transparent;
border-color: blueviolet;
}
</style>
+37 -2
View File
@@ -8,20 +8,55 @@ import cloneDeep from 'lodash/cloneDeep';
const dashboardEditorRef = ref(null);
const addComponent = (componentType : string) => {
const addComponent = (componentType: string) => {
const component = cloneDeep(findNewComponentFromList(componentType))
if (component && dashboardEditorRef.value) {
// @ts-ignore
component.id = guid()
if (component.component === 'SQTab') {
const subTabName = guid('tab')
// @ts-ignore
component.propValue[0].name = subTabName
component.activeTabName = subTabName
}
//@ts-ignore
dashboardEditorRef.value.addItemToBox(component)
}
}
const componentData = ref([
{
id: 4,
x: 1,
y: 1,
sizeX: 20,
sizeY: 10,
_dragId: 0
},
{
id: 10,
x: 2,
y: 1,
sizeX: 20,
sizeY: 10,
_dragId: 1
},
{
id: 7,
x: 1,
y: 2,
sizeX: 20,
sizeY: 10,
_dragId: 2
}
])
</script>
<template>
<div class="editor-main">
<Toolbar @add-component="addComponent"></Toolbar>
<DashboardEditor ref="dashboardEditorRef"></DashboardEditor>
<DashboardEditor ref="dashboardEditorRef" :canvas-component-data="componentData"></DashboardEditor>
</div>
</template>