refactor: dashboard view

This commit is contained in:
wangjiahao
2025-06-18 18:26:53 +08:00
parent aebcace782
commit 0c98f3c20f
15 changed files with 322 additions and 44 deletions
@@ -0,0 +1,28 @@
"""msg...
Revision ID: 941e2355a94d
Revises: 8dc3b1bdbfef
Create Date: 2025-06-18 14:58:21.977676
"""
from alembic import op
import sqlalchemy as sa
import sqlmodel.sql.sqltypes
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = '941e2355a94d'
down_revision = '8dc3b1bdbfef'
branch_labels = None
depends_on = None
def upgrade():
op.add_column('core_dashboard', sa.Column('canvas_views', sa.Text(), nullable=True))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('core_dashboard', 'canvas_views')
# ### end Alembic commands ###
@@ -71,6 +71,7 @@ def create_canvas(session: SessionDep, user: CurrentUser, dashboard: CreateDashb
record.node_type = dashboard.node_type
record.component_data = dashboard.component_data
record.canvas_style_data = dashboard.canvas_style_data
record.canvas_views = dashboard.canvas_views
session.add(record)
session.flush()
session.refresh(record)
@@ -85,6 +86,7 @@ def update_canvas(session: SessionDep, user: CurrentUser, dashboard: CreateDashb
record.update_time = int(time.time())
record.component_data = dashboard.component_data
record.canvas_style_data = dashboard.canvas_style_data
record.canvas_views = dashboard.canvas_views
session.add(record)
session.commit()
return record
@@ -50,6 +50,10 @@ class CoreDashboard(SQLModel, table=True):
default=None,
sa_column=Column(Text, nullable=True)
)
canvas_views: str = Field(
default=None,
sa_column=Column(Text, nullable=True)
)
mobile_layout: int = Field(
default=0,
sa_column=Column(SmallInteger, nullable=True)
@@ -156,4 +160,5 @@ class QueryDashboard(BaseDashboard):
class CreateDashboard(QueryDashboard):
canvas_style_data: str =''
component_data: str = ''
canvas_views: str = ''
description: str = ''
+5 -2
View File
@@ -56,14 +56,17 @@ export const dashboardStore = defineStore('dashboard', {
},
updateDashboardInfo(params: any) {
Object.keys(params).forEach((key: string) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
// @ts-expect-error eslint-disable-next-line @typescript-eslint/ban-ts-comment
this.dashboardInfo[key] = params[key]
})
},
setCanvasViewInfo(params: any) {
this.canvasViewInfo = params
},
addCanvasViewInfo(params: any) {
// @ts-expect-error eslint-disable-next-line @typescript-eslint/ban-ts-comment
this.canvasViewInfo[params.id] = params
},
canvasDataInit() {},
},
})
+89
View File
@@ -0,0 +1,89 @@
import mitt from 'mitt'
import { onBeforeUnmount } from 'vue'
type EventCallback = (...args: any[]) => void
interface Option {
name: string
callback: EventCallback
}
const emitter = mitt()
// Map to store debounce information
const lazyDebounceMap = new Map<
string,
{
timer: any | null
isPending: boolean
}
>()
/**
* Basic event emitter hook
* @param option - Optional configuration with event name and callback
* @returns Object containing the emitter instance
*/
export const useEmitt = (option?: Option) => {
if (option) {
emitter.on(option.name, option.callback)
onBeforeUnmount(() => {
emitter.off(option.name, option.callback)
})
}
return {
emitter,
}
}
/**
* Debounced event emitter
* @param eventName - Name of the event to emit
* @param params - Parameters to pass with the event
* @param delay - Debounce delay in milliseconds (default: 300ms)
*/
export const useEmittLazy = (eventName: string, params: any = null, delay = 500) => {
// If there's already a pending execution, skip this call
if (lazyDebounceMap.has(eventName)) {
const entry = lazyDebounceMap.get(eventName)!
if (entry.isPending) {
return
}
}
// Clear existing timer if present
if (lazyDebounceMap.has(eventName)) {
const { timer } = lazyDebounceMap.get(eventName)!
if (timer) {
clearTimeout(timer)
}
}
// Set up a new timer
const timer = setTimeout(() => {
emitter.emit(eventName, params)
// Mark execution as complete
if (lazyDebounceMap.has(eventName)) {
lazyDebounceMap.get(eventName)!.isPending = false
}
}, delay)
// Store timer information and mark as pending
lazyDebounceMap.set(eventName, {
timer,
isPending: true,
})
// Clean up on component unmount
onBeforeUnmount(() => {
if (lazyDebounceMap.has(eventName)) {
const { timer } = lazyDebounceMap.get(eventName)!
if (timer) {
clearTimeout(timer)
}
lazyDebounceMap.delete(eventName)
}
})
}
@@ -2,6 +2,7 @@
import { computed, onMounted, onUnmounted } from 'vue'
import { getChartInstance } from '@/views/chat/component/index.ts'
import type { BaseChart, ChartAxis, ChartData } from '@/views/chat/component/BaseChart.ts'
import { useEmitt } from '@/utils/useEmitt.ts'
const params = withDefaults(
defineProps<{
@@ -61,6 +62,16 @@ function destroyChart() {
}
}
useEmitt({
name: 'view-render-all',
callback: renderChart,
})
useEmitt({
name: `view-render-${params.id}`,
callback: renderChart,
})
defineExpose({
renderChart,
destroyChart,
@@ -83,7 +94,5 @@ onUnmounted(() => {
.chart-container {
height: 100%;
width: 100%;
min-height: 360px;
min-width: 360px;
}
</style>
@@ -6,6 +6,7 @@ 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'
import { useEmittLazy } from '@/utils/useEmitt.ts'
const dashboardStore = dashboardStoreWithOut()
const canvasLocked = ref(false) // Is the canvas movement locked, Default false
@@ -25,12 +26,17 @@ const props = defineProps({
dashboardInfo: {
type: Object,
required: false,
default: null,
default: () => {},
},
canvasStyleData: {
type: Object,
required: false,
default: null,
default: () => {},
},
canvasViewInfo: {
type: Object,
required: false,
default: () => {},
},
canvasComponentData: {
type: Array as PropType<CanvasItem[]>,
@@ -317,6 +323,9 @@ function resizePlayer(item: CanvasItem, newSize: any) {
if (canGoUpRows > 0) {
moveItemUp(item, canGoUpRows)
}
if (item.component === 'SQView') {
useEmittLazy(`view-render-${item.id}`)
}
}
/**
@@ -1167,8 +1176,9 @@ defineExpose({
<component
:is="findComponent(item.component)"
:ref="'shape_component_' + item.id"
class="sql-component slot-component dragHandle"
class="sql-component dragHandle"
:config-item="item"
:view-info="canvasViewInfo[item.id]"
@parent-add-item-box="(subItem: any) => addItemBox(subItem)"
>
</component>
@@ -1,34 +1,39 @@
<script setup lang="ts">
import ChartComponent from '@/views/chat/component/ChartComponent.vue'
import { reactive } from 'vue'
import type { ChartAxis, ChartData } from '@/views/chat/component/BaseChart.ts'
const state = reactive({
chartInfo: {
id: null as string | null,
type: '' as string,
columns: [] as ChartAxis[],
xAxis: [] as ChartAxis[],
yAxis: [] as ChartAxis[],
series: [] as ChartAxis[],
data: [] as ChartData[],
defineProps({
viewInfo: {
type: Object,
required: true,
},
})
import { ref } from 'vue'
const chartRef = ref(null)
const renderChart = () => {
//@ts-expect-error eslint-disable-next-line @typescript-eslint/no-unused-expressions
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
chartRef.value?.renderChart
}
defineExpose({
renderChart,
})
</script>
<template>
<div class="chart-base-container">
<div>
<div style="height: 100%; width: 100%">
<ChartComponent
v-if="state.chartInfo.id"
:id="state.chartInfo.id"
v-if="viewInfo.id"
:id="viewInfo.id"
ref="chartRef"
:type="state.chartInfo.type"
:columns="state.chartInfo.columns"
:x="state.chartInfo.xAxis"
:y="state.chartInfo.yAxis"
:series="state.chartInfo.series"
:data="state.chartInfo.data"
:type="viewInfo.chart.type"
:columns="viewInfo.chart.columns"
:x="viewInfo.chart?.xAxis"
:y="viewInfo.chart?.yAxis"
:series="viewInfo.chart?.series"
:data="viewInfo.data?.data"
/>
</div>
</div>
@@ -36,7 +41,5 @@ const state = reactive({
<style scoped lang="less">
.chart-base-container {
padding: 20px;
background: rgba(224, 224, 226, 0.29);
}
</style>
@@ -0,0 +1,45 @@
<script setup lang="ts">
import { ref } from 'vue'
import SQView from '@/views/dashboard/components/sq-view/index.vue'
const isSelected = ref(false)
const props = defineProps({
viewInfo: {
type: Object,
required: true,
},
selectChange: {
type: Function,
default: () => {
return {}
},
},
})
const curSelectChange = (value: boolean) => {
props.selectChange(value)
}
</script>
<template>
<div class="chart-selection-container">
<el-checkbox class="select-area" :value="isSelected" @change="curSelectChange"></el-checkbox>
<SQView :view-info="viewInfo"></SQView>
</div>
</template>
<style scoped lang="less">
.chart-selection-container {
width: 33%;
height: 250px;
position: relative;
padding: 15px;
float: left;
.select-area {
position: absolute;
top: 20px;
right: 20px;
}
}
</style>
@@ -6,6 +6,7 @@
trigger="click"
:title="t('dashboard.add_chart')"
modal-class="custom-drawer"
:destroy-on-close="true"
@closed="handleClose()"
>
<el-container class="chat-container">
@@ -22,8 +23,17 @@
</el-container>
</el-aside>
<el-container :loading="loading">
<el-main class="chat-record-list">
<el-scrollbar ref="chatListRef"> </el-scrollbar>
<el-main v-if="!loading" class="chat-record-list">
<el-scrollbar ref="chatListRef">
<chart-selection
v-for="(viewInfo, index) in chartInfoList"
:key="index"
:view-info="viewInfo"
:select-change="(value: boolean) => selectChange(value, viewInfo)"
style="width: 33%"
>
</chart-selection>
</el-scrollbar>
</el-main>
</el-container>
</el-container>
@@ -33,16 +43,16 @@
<el-col class="adapt-count">
<span>{{ t('dashboard.chart_selected', [selectComponentCount]) }} </span>
</el-col>
<el-button class="close-button" @click="dialogShow = false">{{
t('common.cancel')
}}</el-button>
<el-button class="close-button" @click="dialogShow = false"
>{{ t('common.cancel') }}
</el-button>
<el-button
type="primary"
:disabled="!selectComponentCount"
class="confirm-button"
@click="saveMultiplexing"
>{{ t('common.save') }}</el-button
>
>{{ t('common.save') }}
</el-button>
</el-row>
</template>
</el-drawer>
@@ -53,11 +63,13 @@ import { computed, onMounted, reactive, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { Chat, chatApi, ChatInfo } from '@/api/chat.ts'
import DashboardChatList from '@/views/dashboard/editor/DashboardChatList.vue'
import ChartSelection from '@/views/dashboard/editor/ChartSelection.vue'
const dialogShow = ref(false)
const { t } = useI18n()
const selectComponentCount = computed(() => Object.keys(state.curMultiplexingComponents).length)
const selectComponentCount = computed(() => state.curMultiplexingComponents.length)
const state = reactive({
curMultiplexingComponents: {},
curMultiplexingComponents: [],
})
const loading = ref<boolean>(false)
@@ -65,11 +77,49 @@ const chatList = ref<Array<ChatInfo>>([])
const currentChatId = ref<number | undefined>()
const currentChat = ref<ChatInfo>(new ChatInfo())
const chartInfoList = ref<Array<any>>([])
const emits = defineEmits(['addChatChart'])
onMounted(() => {
getChatList()
})
function selectChange(value: boolean, viewInfo: any) {
if (value) {
// @ts-expect-error eslint-disable-next-line @typescript-eslint/ban-ts-comment
state.curMultiplexingComponents.push(viewInfo)
} else {
state.curMultiplexingComponents = state.curMultiplexingComponents.filter(
// @ts-expect-error eslint-disable-next-line @typescript-eslint/ban-ts-comment
(component) => component.id !== viewInfo.id
)
}
}
function adaptorChartInfoList(chatInfo: ChatInfo) {
chartInfoList.value = []
if (chatInfo && chatInfo.records) {
chatInfo.records.forEach((record: any) => {
const recordeInfo = { id: chatInfo.id + '_' + record.id, data: null, chart: {} }
if (record?.data) {
recordeInfo['data'] = JSON.parse(record.data)
}
if (record?.chart) {
const chartBaseInfo = JSON.parse(record.chart)
recordeInfo['chart'] = {
type: chartBaseInfo.type,
title: chartBaseInfo.title,
columns: chartBaseInfo.columns,
xAxis: chartBaseInfo.axis?.x ? [chartBaseInfo.axis.x] : [],
yAxis: chartBaseInfo.axis?.y ? [chartBaseInfo.axis.y] : [],
series: chartBaseInfo.axis?.series ? [chartBaseInfo.axis.series] : [],
}
}
chartInfoList.value.push(recordeInfo)
})
}
}
function onClickHistory(chat: Chat) {
currentChat.value = new ChatInfo(chat)
if (chat !== undefined && chat.id !== undefined && !loading.value) {
@@ -81,6 +131,8 @@ function onClickHistory(chat: Chat) {
const info = chatApi.toChatInfo(res)
if (info) {
currentChat.value = info
adaptorChartInfoList(info)
state.curMultiplexingComponents = []
}
})
.finally(() => {
@@ -103,11 +155,14 @@ function getChatList() {
const dialogInit = () => {
dialogShow.value = true
state.curMultiplexingComponents = {}
state.curMultiplexingComponents = []
}
const saveMultiplexing = () => {
dialogShow.value = false
if (state.curMultiplexingComponents.length > 0) {
emits('addChatChart', state.curMultiplexingComponents)
}
}
const handleClose = () => {}
defineExpose({
@@ -121,15 +176,18 @@ defineExpose({
top: 18px;
right: 120px;
}
.confirm-button {
position: absolute;
top: 18px;
right: 20px;
}
.multiplexing-area {
width: 100%;
height: 100%;
}
.multiplexing-footer {
position: relative;
}
@@ -149,6 +207,7 @@ defineExpose({
top: 18px;
right: 220px;
}
.adapt-text {
font-size: 14px;
font-weight: 400;
@@ -199,6 +258,7 @@ defineExpose({
.chat-record-list {
padding: 0 0 20px 0;
background: rgba(224, 224, 226, 0.29);
}
.chat-footer {
@@ -2,6 +2,7 @@
import CanvasCore from '@/views/dashboard/canvas/CanvasCore.vue'
import { nextTick, onMounted, type PropType, ref } from 'vue'
import type { CanvasItem } from '@/utils/canvas.ts'
import { useEmittLazy } from '@/utils/useEmitt.ts'
const canvasCoreRef = ref(null)
const dashboardEditorRef = ref(null)
@@ -26,6 +27,11 @@ const props = defineProps({
required: false,
default: null,
},
canvasViewInfo: {
type: Object,
required: false,
default: () => {},
},
canvasStyleData: {
type: Object,
required: false,
@@ -70,6 +76,7 @@ const sizeInit = () => {
(screenWidth - baseMarginLeft.value) / props.baseMatrixCount.x - baseMarginLeft.value
baseHeight.value =
(screenHeight - baseMarginTop.value) / props.baseMatrixCount.y - baseMarginTop.value
useEmittLazy('view-render-all')
}
}
@@ -114,6 +121,7 @@ const emits = defineEmits(['parentAddItemBox'])
:base-margin-top="baseMarginTop"
:canvas-component-data="canvasComponentData"
:canvas-style-data="canvasStyleData"
:canvas-view-info="canvasViewInfo"
:dashboard-info="dashboardInfo"
:parent-config-item="parentConfigItem"
:canvas-id="canvasId"
@@ -29,6 +29,8 @@ const openViewDialog = () => {
chatChartSelectionRef.value?.dialogInit()
}
import cloneDeep from 'lodash/cloneDeep'
let nameEdit = ref(false)
let inputName = ref('')
let nameInput = ref(null)
@@ -128,6 +130,12 @@ const backToMain = () => {
window.open(url, '_self')
}
}
const addChatChart = (views: any) => {
views.forEach((view: any) => {
emits('addComponent', 'SQView', cloneDeep(view))
})
}
</script>
<template>
@@ -214,6 +222,7 @@ const backToMain = () => {
<ResourceGroupOpt ref="resourceGroupOptRef" @finish="groupOptFinish"></ResourceGroupOpt>
<ChatChartSelection
ref="chatChartSelectionRef"
@add-chat-chart="addChatChart"
@finish="chartSelectionFinish"
></ChatChartSelection>
</div>
@@ -11,7 +11,7 @@ import router from '@/router'
import { initCanvasData } from '@/views/dashboard/utils/canvasUtils.ts'
const dashboardStore = dashboardStoreWithOut()
const { componentData } = storeToRefs(dashboardStore)
const { componentData, canvasViewInfo } = storeToRefs(dashboardStore)
const dataInitState = ref(false)
const state = reactive({
@@ -21,10 +21,16 @@ const state = reactive({
})
const dashboardEditorRef = ref(null)
const addComponent = (componentType: string) => {
const addComponent = (componentType: string, viewInfo?: any) => {
const component = cloneDeep(findNewComponentFromList(componentType))
if (component && dashboardEditorRef.value) {
component.id = guid()
// add view
if (component?.component === 'SQView' && !!viewInfo) {
viewInfo['sourceId'] = viewInfo['id']
viewInfo['id'] = component.id
dashboardStore.addCanvasViewInfo(viewInfo)
}
if (component.component === 'SQTab') {
const subTabName = guid('tab')
// @ts-expect-error eslint-disable-next-line @typescript-eslint/ban-ts-comment
@@ -73,6 +79,7 @@ const baseParams = computed(() => {
v-if="dataInitState"
ref="dashboardEditorRef"
:canvas-component-data="componentData"
:canvas-view-info="canvasViewInfo"
>
</DashboardEditor>
</div>
@@ -42,7 +42,7 @@ const viewDemoInnerId = computed(() => 'enlarge-inner-content-' + configItem.val
ref="component"
class="component"
:canvas-view-info="canvasViewInfo"
:view="canvasViewInfo[configItem.id]"
:view-info="canvasViewInfo[configItem.id]"
:config-item="configItem"
:show-position="showPosition"
:active="active"
@@ -23,7 +23,7 @@ export const load_resource_prepare = (params: any, callBack: (obj: any) => void)
}
const canvasDataResult = JSON.parse(canvasInfo.component_data)
const canvasStyleResult = JSON.parse(canvasInfo.canvas_style_data)
const canvasViewInfoPreview = canvasInfo.canvas_view_Info
const canvasViewInfoPreview = JSON.parse(canvasInfo.canvas_view_Info || '{}')
callBack({ dashboardInfo, canvasDataResult, canvasStyleResult, canvasViewInfoPreview })
})
}