diff --git a/backend/alembic/env.py b/backend/alembic/env.py index ce655b09..5318f8e5 100755 --- a/backend/alembic/env.py +++ b/backend/alembic/env.py @@ -26,7 +26,8 @@ fileConfig(config.config_file_name) # from apps.system.models.user import SQLModel # noqa # from apps.settings.models.setting_models import SQLModel -from apps.chat.models.chat_model import SQLModel +#from apps.chat.models.chat_model import SQLModel +from apps.dashboard.models.dashboard import SQLModel from common.core.config import settings # noqa target_metadata = SQLModel.metadata diff --git a/backend/alembic/versions/804b08ac329d_009_add_core_dashboard.py b/backend/alembic/versions/804b08ac329d_009_add_core_dashboard.py new file mode 100644 index 00000000..be6243a8 --- /dev/null +++ b/backend/alembic/versions/804b08ac329d_009_add_core_dashboard.py @@ -0,0 +1,56 @@ +"""core_dashboard + +Revision ID: 804b08ac329d +Revises: 1f077c30e476 +Create Date: 2025-06-03 14:40:25.419409 + +""" +from alembic import op +import sqlalchemy as sa +import sqlmodel.sql.sqltypes +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = '804b08ac329d' +down_revision = '1f077c30e476' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('core_dashboard', + sa.Column('id', sa.String(length=50), nullable=False), + sa.Column('name', sa.String(length=255), nullable=True), + sa.Column('pid', sa.String(length=50), nullable=True), + sa.Column('workspace_id', sa.String(length=50), nullable=True), + sa.Column('org_id', sa.String(length=50), nullable=True), + sa.Column('level', sa.Integer(), nullable=True), + sa.Column('node_type', sa.String(length=255), nullable=True), + sa.Column('type', sa.String(length=50), nullable=True), + sa.Column('canvas_style_data', sa.Text(), nullable=True), + sa.Column('component_data', sa.Text(), nullable=True), + sa.Column('mobile_layout', sa.SmallInteger(), nullable=True), + sa.Column('status', sa.Integer(), nullable=True), + sa.Column('self_watermark_status', sa.Integer(), nullable=True), + sa.Column('sort', sa.Integer(), nullable=True), + sa.Column('create_time', sa.BigInteger(), nullable=True), + sa.Column('create_by', sa.String(length=255), nullable=True), + sa.Column('update_time', sa.BigInteger(), nullable=True), + sa.Column('update_by', sa.String(length=255), nullable=True), + sa.Column('remark', sa.String(length=255), nullable=True), + sa.Column('source', sa.String(length=255), nullable=True), + sa.Column('delete_flag', sa.SmallInteger(), nullable=True), + sa.Column('delete_time', sa.BigInteger(), nullable=True), + sa.Column('delete_by', sa.String(length=255), nullable=True), + sa.Column('version', sa.Integer(), nullable=True), + sa.Column('content_id', sa.String(length=50), nullable=True), + sa.Column('check_version', sa.String(length=50), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + # ### end Alembic commands ### + + +def downgrade(): + op.drop_table('core_dashboard') + # ### end Alembic commands ### diff --git a/backend/apps/dashboard/__init__.py b/backend/apps/dashboard/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/apps/dashboard/api/__init__.py b/backend/apps/dashboard/api/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/apps/dashboard/api/dashboard_api.py b/backend/apps/dashboard/api/dashboard_api.py new file mode 100644 index 00000000..57c1fc0f --- /dev/null +++ b/backend/apps/dashboard/api/dashboard_api.py @@ -0,0 +1,18 @@ +import hashlib +import os +import uuid +from typing import List +from fastapi import APIRouter, File, UploadFile, HTTPException + +from apps.dashboard.crud.dashboard_service import get_dashboard_list, preview_with_id +from common.core.deps import SessionDep + +router = APIRouter(tags=["dashboard"], prefix="/dashboard") + +@router.get("/list") +async def datasource_list(session: SessionDep): + return get_dashboard_list(session=session) + +@router.get("/preview_dashboard/{id}") +async def preview_dashboard(session: SessionDep,id:str): + return preview_with_id(session=session,dashboard_id=id) \ No newline at end of file diff --git a/backend/apps/dashboard/crud/__init__.py b/backend/apps/dashboard/crud/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/apps/dashboard/crud/dashboard_service.py b/backend/apps/dashboard/crud/dashboard_service.py new file mode 100644 index 00000000..278600ea --- /dev/null +++ b/backend/apps/dashboard/crud/dashboard_service.py @@ -0,0 +1,13 @@ +from select import select + +from apps.dashboard.models.dashboard_model import CoreDashboard +from common.core.deps import SessionDep + + +def get_dashboard_list(session: SessionDep): + statement = select(CoreDashboard).order_by(CoreDashboard.create_time.desc()) + dashboard_list = session.exec(statement).fetchall() + return dashboard_list + +def preview_with_id(session: SessionDep, dashboard_id: str): + return session.query(CoreDashboard).filter(CoreDashboard.id == id).first() \ No newline at end of file diff --git a/backend/apps/dashboard/models/__init__.py b/backend/apps/dashboard/models/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/backend/apps/dashboard/models/dashboard_model.py b/backend/apps/dashboard/models/dashboard_model.py new file mode 100644 index 00000000..183f728b --- /dev/null +++ b/backend/apps/dashboard/models/dashboard_model.py @@ -0,0 +1,123 @@ +from sqlmodel import SQLModel, Field +from sqlalchemy import String, Column, Text, SmallInteger, BigInteger, Integer + + +class CoreDashboard(SQLModel, table=True): + __tablename__ = "core_dashboard" + + id: str = Field( + sa_column=Column(String(50), nullable=False, primary_key=True) + ) + name: str = Field( + default=None, + max_length=255, + sa_column=Column(String(255), nullable=True) + ) + pid: str = Field( + default=None, + max_length=50, + sa_column=Column(String(50), nullable=True) + ) + workspace_id: str = Field( + default=None, + max_length=50, + sa_column=Column(String(50), nullable=True) + ) + org_id: str = Field( + default=None, + max_length=50, + sa_column=Column(String(50), nullable=True) + ) + level: int = Field( + default=None, + sa_column=Column(Integer, nullable=True) + ) + node_type: str = Field( + default=None, + max_length=255, + sa_column=Column(String(255), nullable=True) + ) + type: str = Field( + default=None, + max_length=50, + sa_column=Column(String(50), nullable=True) + ) + canvas_style_data: str = Field( + default=None, + sa_column=Column(Text, nullable=True) + ) + component_data: str = Field( + default=None, + sa_column=Column(Text, nullable=True) + ) + mobile_layout: int = Field( + default=0, + sa_column=Column(SmallInteger, nullable=True) + ) + status: int = Field( + default=1, + sa_column=Column(Integer, nullable=True) + ) + self_watermark_status: int = Field( + default=0, + sa_column=Column(Integer, nullable=True) + ) + sort: int = Field( + default=0, + sa_column=Column(Integer, nullable=True) + ) + create_time: int = Field( + default=None, + sa_column=Column(BigInteger, nullable=True) + ) + create_by: str = Field( + default=None, + max_length=255, + sa_column=Column(String(255), nullable=True) + ) + update_time: int = Field( + default=None, + sa_column=Column(BigInteger, nullable=True) + ) + update_by: str = Field( + default=None, + max_length=255, + sa_column=Column(String(255), nullable=True) + ) + remark: str = Field( + default=None, + max_length=255, + sa_column=Column(String(255), nullable=True) + ) + source: str = Field( + default=None, + max_length=255, + sa_column=Column(String(255), nullable=True) + ) + delete_flag: int = Field( + default=0, + sa_column=Column(SmallInteger, nullable=True) + ) + delete_time: int = Field( + default=None, + sa_column=Column(BigInteger, nullable=True) + ) + delete_by: str = Field( + default=None, + max_length=255, + sa_column=Column(String(255), nullable=True) + ) + version: int = Field( + default=3, + sa_column=Column(Integer, nullable=True) + ) + content_id: str = Field( + default='0', + max_length=50, + sa_column=Column(String(50), nullable=True) + ) + check_version: str = Field( + default='1', + max_length=50, + sa_column=Column(String(50), nullable=True) + ) diff --git a/frontend/src/api/dashboard.ts b/frontend/src/api/dashboard.ts new file mode 100644 index 00000000..a861821d --- /dev/null +++ b/frontend/src/api/dashboard.ts @@ -0,0 +1,8 @@ +import { request } from '@/utils/request' + +export const dashboardApi = { + list: () => request.get('/dashboard/list'), + getDashboardInfo: (params:any) => request.post(`/dashboard/get_dashboard/${params.id}`,params), + sqNameCheck: (data: any) => request.post('/dashboard/name_check',data), + moveResource: (data: any) => request.post('/dashboard/move',data), +} \ No newline at end of file diff --git a/frontend/src/assets/svg/icon_add_outlined.svg b/frontend/src/assets/svg/icon_add_outlined.svg new file mode 100644 index 00000000..f3364bbc --- /dev/null +++ b/frontend/src/assets/svg/icon_add_outlined.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/stores/dashboard/dashboard.ts b/frontend/src/stores/dashboard/dashboard.ts index 5d94e5f8..e6062664 100644 --- a/frontend/src/stores/dashboard/dashboard.ts +++ b/frontend/src/stores/dashboard/dashboard.ts @@ -10,15 +10,19 @@ export const dashboardStore = defineStore('dashboard', { curComponentId: null, canvasStyle: {}, componentData: [], + fullscreenFlag: false, dashboardInfo: { id: null, - name: 'New Dashboard', + name: null, pid: null, - dataState: null, - optType: null, + workspaceId: null, status: null, type: null, - mobileLayout: false + creatorName: null, + updateName: null, + createTime: null, + updateTime: null, + contentId: null } } }, @@ -32,6 +36,9 @@ export const dashboardStore = defineStore('dashboard', { this.curComponent = value this.curComponentId = value && value.id ? value.id : null }, + setDashboardInfo(value: any) { + this.dashboardInfo = value + }, setComponentData(value: any) { this.componentData = value }, @@ -44,6 +51,9 @@ export const dashboardStore = defineStore('dashboard', { setTabMoveInActiveId(tabId: any) { this.tabMoveInActiveId = tabId }, + canvasDataInit(){ + + } } }) diff --git a/frontend/src/views/dashboard/common/DashboardDetailInfo.vue b/frontend/src/views/dashboard/common/DashboardDetailInfo.vue new file mode 100644 index 00000000..5b82e301 --- /dev/null +++ b/frontend/src/views/dashboard/common/DashboardDetailInfo.vue @@ -0,0 +1,58 @@ + + + + + diff --git a/frontend/src/views/dashboard/common/EmptyBackground.vue b/frontend/src/views/dashboard/common/EmptyBackground.vue new file mode 100644 index 00000000..06e1e507 --- /dev/null +++ b/frontend/src/views/dashboard/common/EmptyBackground.vue @@ -0,0 +1,62 @@ + + + + + diff --git a/frontend/src/views/dashboard/common/HandleMore.vue b/frontend/src/views/dashboard/common/HandleMore.vue new file mode 100644 index 00000000..89f359bd --- /dev/null +++ b/frontend/src/views/dashboard/common/HandleMore.vue @@ -0,0 +1,80 @@ + + + + + diff --git a/frontend/src/views/dashboard/common/ResourceTree.vue b/frontend/src/views/dashboard/common/ResourceTree.vue new file mode 100644 index 00000000..1c1317c0 --- /dev/null +++ b/frontend/src/views/dashboard/common/ResourceTree.vue @@ -0,0 +1,478 @@ + + + + + + diff --git a/frontend/src/views/dashboard/preview/SQPreview.vue b/frontend/src/views/dashboard/preview/SQPreview.vue new file mode 100644 index 00000000..2f86b473 --- /dev/null +++ b/frontend/src/views/dashboard/preview/SQPreview.vue @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/views/dashboard/preview/SQPreviewHead.vue b/frontend/src/views/dashboard/preview/SQPreviewHead.vue new file mode 100644 index 00000000..08eddf12 --- /dev/null +++ b/frontend/src/views/dashboard/preview/SQPreviewHead.vue @@ -0,0 +1,134 @@ + + + + + diff --git a/frontend/src/views/dashboard/preview/SQPreviewShow.vue b/frontend/src/views/dashboard/preview/SQPreviewShow.vue new file mode 100644 index 00000000..c7752720 --- /dev/null +++ b/frontend/src/views/dashboard/preview/SQPreviewShow.vue @@ -0,0 +1,246 @@ + + + + + diff --git a/frontend/src/views/dashboard/utils/canvasUtils.ts b/frontend/src/views/dashboard/utils/canvasUtils.ts new file mode 100644 index 00000000..a7fe846e --- /dev/null +++ b/frontend/src/views/dashboard/utils/canvasUtils.ts @@ -0,0 +1,24 @@ +import {dashboardApi} from "@/api/dashboard.ts"; + +export const initCanvasData = (params: any, callBack: Function) => { + dashboardApi.getDashboardInfo(params).then((res: any) => { + const canvasInfo = res.data; + const dashboardInfo = { + id: canvasInfo.id, + name: canvasInfo.name, + pid: canvasInfo.pid, + workspaceId: canvasInfo.workspaceId, + status: canvasInfo.status, + type: canvasInfo.type, + creatorName: canvasInfo.creatorName, + updateName: canvasInfo.updateName, + createTime: canvasInfo.createTime, + updateTime: canvasInfo.updateTime, + contentId: canvasInfo.contentId + } + const canvasDataResult = JSON.parse(canvasInfo.componentData) + const canvasStyleResult = JSON.parse(canvasInfo.canvasStyleData) + const canvasViewInfoPreview = canvasInfo.canvasViewInfo + callBack({dashboardInfo, canvasDataResult, canvasStyleResult, canvasViewInfoPreview}) + }) +} \ No newline at end of file diff --git a/frontend/src/views/dashboard/utils/treeDraggableChart.ts b/frontend/src/views/dashboard/utils/treeDraggableChart.ts new file mode 100644 index 00000000..83e2eff7 --- /dev/null +++ b/frontend/src/views/dashboard/utils/treeDraggableChart.ts @@ -0,0 +1,139 @@ +import { cloneDeep } from 'lodash-es' +import {dashboardApi} from "@/api/dashboard.ts"; +const treeDraggableChart = (state:any, key:any, type:any) => { + let dragNodeParentId = '' + let dragNodeId = '' + let dragNodeIndex = 0 + + let rawData:any[] = [] + + const dfsTreeNode = (arr:any, parentId:any) => { + arr.forEach((element:any, index:any) => { + if (element.id === dragNodeId) { + dragNodeIndex = index + dragNodeParentId = parentId + } + if (element.children?.length) { + dfsTreeNode(element.children, element.id) + } + }) + } + + const dfsTreeNodeSaveLevel = (arr:any[], idArr:any) => { + arr.forEach((element:any) => { + const index = idArr.findIndex((ele:any) => { + return ele === element.id + }) + if (index !== -1) { + idArr.splice(index, 1) + } + if (element.children?.length && idArr.length === 2) { + dfsTreeNodeSaveLevel(element.children, idArr) + } + }) + } + + const dfsTreeNodeBack = (arr:any, parentId:any, params:any) => { + arr.forEach((element:any) => { + if (element.id === params.id) { + params.pid = parentId + } + if (element.children?.length) { + dfsTreeNodeBack(element.children, element.id, params) + } + }) + } + + const dfsTreeNodeReset = (arr:any, node:any) => { + arr.forEach((element:any) => { + if (element.id === dragNodeParentId) { + element.children.splice(dragNodeIndex, 0, node) + } + if (element.children?.length) { + dfsTreeNodeReset(element.children, node) + } + }) + } + + const dfsTreeNodeDel = (arr:any, node:any) => { + arr.forEach((element:any, index:any) => { + if (element.id === node.id) { + arr.splice(index, 1) + } + if (element.children?.length) { + dfsTreeNodeDel(element.children, node) + } + }) + } + + const handleDragStart = (node:any) => { + dragNodeId = node.data.id + rawData = cloneDeep(state[key]) + dfsTreeNode(state[key], '0') + } + + const allowDrop = (dropNode:any) => { + return !dropNode.data?.leaf + } + + const handleDrop = async (draggingNode:any, dropNode:any, dropType:any) => { + const params = { + id: draggingNode.data?.id, + name: draggingNode.data?.name, + nodeType: draggingNode.data?.leaf ? 'leaf' : 'folder', + pid: '0', + opt: 'move', + type + } + if (dropType !== 'inner') { + const idArr = [params.id, dropNode.data?.id] + dfsTreeNodeSaveLevel(rawData, idArr) + if (idArr.length === 0) { + dfsTreeNodeDel(state[key], draggingNode.data) + setTimeout(() => { + if (dragNodeParentId === '0') { + state[key].splice(dragNodeIndex, 0, draggingNode.data) + } else { + dfsTreeNodeReset(state[key], draggingNode.data) + } + }, 0) + return + } + } + + try { + await dashboardApi.sqNameCheck(params) + } catch (error) { + console.error(error) + } + // @ts-ignore + delete params.opt + + if (dropType === 'inner') { + params.pid = dropNode.data?.id + } else { + dfsTreeNodeBack(state[key], '0', params) + } + + dashboardApi.moveResource(params) + .then(() => { + state.originResourceTree = cloneDeep(state[key]) + }) + .catch(() => { + if (dragNodeParentId === '0') { + state[key].splice(dragNodeIndex, 0, draggingNode.data) + return + } + + dfsTreeNodeReset(state[key], draggingNode.data) + }) + } + + return { + handleDrop, + allowDrop, + handleDragStart + } +} + +export { treeDraggableChart } diff --git a/frontend/src/views/dashboard/utils/treeNode.ts b/frontend/src/views/dashboard/utils/treeNode.ts new file mode 100644 index 00000000..aa58c3b2 --- /dev/null +++ b/frontend/src/views/dashboard/utils/treeNode.ts @@ -0,0 +1,19 @@ +export interface SQTreeNode { + id: string | number + pid: string | number + name: string + leaf?: boolean + weight: number + ext?: number + extraFlag: number + extraFlag1: number + children?: SQTreeNode[] +} + +export interface SQTreeRequest { + treeFlag?: string + leaf?: boolean + weight?: number + sortType?: string + resourceTable?: string +} diff --git a/frontend/src/views/dashboard/utils/treeSortUtils.ts b/frontend/src/views/dashboard/utils/treeSortUtils.ts new file mode 100644 index 00000000..d732ae3b --- /dev/null +++ b/frontend/src/views/dashboard/utils/treeSortUtils.ts @@ -0,0 +1,42 @@ +import _ from 'lodash' +import type {SQTreeNode} from "@/views/dashboard/utils/treeNode"; + +export function treeParentWeight(tree: SQTreeNode[], pWeight:any) { + const pWeightResult = {} + weightCheckCircle(tree, pWeightResult, pWeight) + return pWeightResult +} + +export function weightCheckCircle(tree: SQTreeNode[], pWeightResult:any, pWeight:any) { + _.forEach(tree, node => { + pWeightResult[node.id] = pWeight + if (node.children && node.children.length > 0) { + weightCheckCircle(node.children, pWeightResult, node.weight) + } + }) +} + +export default function treeSort(tree: SQTreeNode[], sortType: string) { + const result = _.cloneDeep(tree) + sortCircle(result, sortType) + return result +} + +export function sortCircle(tree: SQTreeNode[], sortType: string) { + sortPer(tree, sortType) + _.forEach(tree, node => { + if (node.children && node.children.length > 0) { + sortCircle(node.children, sortType) + } + }) +} + +export const sortPer = (subTree: SQTreeNode[], sortType: string) => { + if (sortType === 'name_desc') { + subTree.sort((a, b) => b.name.localeCompare(a.name, 'zh-Hans-CN', { sensitivity: 'accent' })) + } else if (sortType === 'name_asc') { + subTree.sort((a, b) => a.name.localeCompare(b.name, 'zh-Hans-CN', { sensitivity: 'accent' })) + } else if (sortType === 'time_asc') { + subTree.reverse() + } +}