From 0c98f3c20ff0b283922b7618416f7f9f2bba2a6d Mon Sep 17 00:00:00 2001 From: wangjiahao <1522128093@qq.com> Date: Wed, 18 Jun 2025 18:26:53 +0800 Subject: [PATCH] refactor: dashboard view --- .../941e2355a94d_011_update_dashboard.py | 28 ++++++ .../apps/dashboard/crud/dashboard_service.py | 2 + .../apps/dashboard/models/dashboard_model.py | 5 ++ frontend/src/stores/dashboard/dashboard.ts | 7 +- frontend/src/utils/useEmitt.ts | 89 +++++++++++++++++++ .../views/chat/component/ChartComponent.vue | 13 ++- .../src/views/dashboard/canvas/CanvasCore.vue | 16 +++- .../dashboard/components/sq-view/index.vue | 49 +++++----- .../views/dashboard/editor/ChartSelection.vue | 45 ++++++++++ .../dashboard/editor/ChatChartSelection.vue | 80 ++++++++++++++--- .../dashboard/editor/DashboardEditor.vue | 8 ++ .../src/views/dashboard/editor/Toolbar.vue | 9 ++ frontend/src/views/dashboard/editor/index.vue | 11 ++- .../dashboard/preview/SQComponentWrapper.vue | 2 +- .../src/views/dashboard/utils/canvasUtils.ts | 2 +- 15 files changed, 322 insertions(+), 44 deletions(-) create mode 100644 backend/alembic/versions/941e2355a94d_011_update_dashboard.py create mode 100644 frontend/src/utils/useEmitt.ts create mode 100644 frontend/src/views/dashboard/editor/ChartSelection.vue diff --git a/backend/alembic/versions/941e2355a94d_011_update_dashboard.py b/backend/alembic/versions/941e2355a94d_011_update_dashboard.py new file mode 100644 index 00000000..b1a27e34 --- /dev/null +++ b/backend/alembic/versions/941e2355a94d_011_update_dashboard.py @@ -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 ### diff --git a/backend/apps/dashboard/crud/dashboard_service.py b/backend/apps/dashboard/crud/dashboard_service.py index 8f6c6ceb..3824038f 100644 --- a/backend/apps/dashboard/crud/dashboard_service.py +++ b/backend/apps/dashboard/crud/dashboard_service.py @@ -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 diff --git a/backend/apps/dashboard/models/dashboard_model.py b/backend/apps/dashboard/models/dashboard_model.py index 6104c7da..e3824452 100644 --- a/backend/apps/dashboard/models/dashboard_model.py +++ b/backend/apps/dashboard/models/dashboard_model.py @@ -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 = '' diff --git a/frontend/src/stores/dashboard/dashboard.ts b/frontend/src/stores/dashboard/dashboard.ts index 918c3fd8..8f20dcf3 100644 --- a/frontend/src/stores/dashboard/dashboard.ts +++ b/frontend/src/stores/dashboard/dashboard.ts @@ -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() {}, }, }) diff --git a/frontend/src/utils/useEmitt.ts b/frontend/src/utils/useEmitt.ts new file mode 100644 index 00000000..dedd8136 --- /dev/null +++ b/frontend/src/utils/useEmitt.ts @@ -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) + } + }) +} diff --git a/frontend/src/views/chat/component/ChartComponent.vue b/frontend/src/views/chat/component/ChartComponent.vue index dc9124fa..a723b6f8 100644 --- a/frontend/src/views/chat/component/ChartComponent.vue +++ b/frontend/src/views/chat/component/ChartComponent.vue @@ -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; } diff --git a/frontend/src/views/dashboard/canvas/CanvasCore.vue b/frontend/src/views/dashboard/canvas/CanvasCore.vue index 62b81b63..17bf1c72 100644 --- a/frontend/src/views/dashboard/canvas/CanvasCore.vue +++ b/frontend/src/views/dashboard/canvas/CanvasCore.vue @@ -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, @@ -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({ diff --git a/frontend/src/views/dashboard/components/sq-view/index.vue b/frontend/src/views/dashboard/components/sq-view/index.vue index f6a0ed51..b0eee156 100644 --- a/frontend/src/views/dashboard/components/sq-view/index.vue +++ b/frontend/src/views/dashboard/components/sq-view/index.vue @@ -1,34 +1,39 @@ @@ -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(false) @@ -65,11 +77,49 @@ const chatList = ref>([]) const currentChatId = ref() const currentChat = ref(new ChatInfo()) +const chartInfoList = ref>([]) +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 { diff --git a/frontend/src/views/dashboard/editor/DashboardEditor.vue b/frontend/src/views/dashboard/editor/DashboardEditor.vue index f85311ab..99e11e08 100644 --- a/frontend/src/views/dashboard/editor/DashboardEditor.vue +++ b/frontend/src/views/dashboard/editor/DashboardEditor.vue @@ -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" diff --git a/frontend/src/views/dashboard/editor/Toolbar.vue b/frontend/src/views/dashboard/editor/Toolbar.vue index 2219d26e..c941b427 100644 --- a/frontend/src/views/dashboard/editor/Toolbar.vue +++ b/frontend/src/views/dashboard/editor/Toolbar.vue @@ -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)) + }) +}