From 344027c8331b454b613bc84312661ebeb2e37dd9 Mon Sep 17 00:00:00 2001 From: wangjiahao <1522128093@qq.com> Date: Tue, 10 Jun 2025 17:58:37 +0800 Subject: [PATCH] refactor: snapshot --- backend/apps/dashboard/api/dashboard_api.py | 41 ++--- .../apps/dashboard/crud/dashboard_service.py | 57 +++++-- .../apps/dashboard/models/dashboard_model.py | 5 +- backend/common/utils/tree_utils.py | 22 +++ frontend/package.json | 1 + frontend/src/api/dashboard.ts | 18 +-- frontend/src/assets/svg/folder.svg | 2 +- frontend/src/assets/svg/icon_delete.svg | 3 + .../src/assets/svg/icon_edit_outlined.svg | 2 +- .../src/assets/svg/icon_more_outlined.svg | 2 +- .../src/assets/svg/icon_redo_outlined.svg | 3 + frontend/src/assets/svg/icon_rename.svg | 4 + .../src/assets/svg/icon_undo_outlined.svg | 3 + frontend/src/i18n/en.json | 12 ++ frontend/src/i18n/zh-CN.json | 12 ++ frontend/src/stores/dashboard/dashboard.ts | 11 +- frontend/src/stores/dashboard/snapshot.ts | 149 +++++++++++++++++ .../dashboard/common/ResourceGroupOpt.vue | 46 ++++-- .../views/dashboard/common/ResourceTree.vue | 78 ++++++--- .../src/views/dashboard/editor/Toolbar.vue | 151 +++++++++++++++++- frontend/src/views/dashboard/editor/index.vue | 2 +- .../views/dashboard/preview/SQPreviewShow.vue | 17 +- .../src/views/dashboard/utils/canvasUtils.ts | 28 ++-- .../dashboard/utils/treeDraggableChart.ts | 4 +- .../src/views/dashboard/utils/treeNode.ts | 5 +- 25 files changed, 566 insertions(+), 112 deletions(-) create mode 100644 backend/common/utils/tree_utils.py create mode 100644 frontend/src/assets/svg/icon_delete.svg create mode 100644 frontend/src/assets/svg/icon_redo_outlined.svg create mode 100644 frontend/src/assets/svg/icon_rename.svg create mode 100644 frontend/src/assets/svg/icon_undo_outlined.svg create mode 100644 frontend/src/stores/dashboard/snapshot.ts diff --git a/backend/apps/dashboard/api/dashboard_api.py b/backend/apps/dashboard/api/dashboard_api.py index 4d392db4..4ed81b25 100644 --- a/backend/apps/dashboard/api/dashboard_api.py +++ b/backend/apps/dashboard/api/dashboard_api.py @@ -1,39 +1,40 @@ -from xmlrpc.client import Boolean - from fastapi import APIRouter, File, UploadFile, HTTPException -from apps.dashboard.crud.dashboard_service import get_dashboard_list, preview_with_id, \ - create_resource, create_canvas, validate_name +from apps.dashboard.crud.dashboard_service import list_resource, load_resource, \ + create_resource, create_canvas, validate_name,delete_resource,update_resource from apps.dashboard.models.dashboard_model import CreateDashboard, BaseDashboard, QueryDashboard from common.core.deps import SessionDep, CurrentUser router = APIRouter(tags=["dashboard"], prefix="/dashboard") -@router.post("/list") -async def datasource_list(session: SessionDep,dashboard: QueryDashboard): - return get_dashboard_list(session=session,dashboard=dashboard) +@router.post("/list_resource") +async def list_resource_api(session: SessionDep,dashboard: QueryDashboard): + return list_resource(session=session,dashboard=dashboard) -@router.get("/preview_dashboard/{id}") -async def preview_dashboard(session: SessionDep,id:str): - return preview_with_id(session=session,dashboard_id=id) +@router.post("/load_resource") +async def load_resource_api(session: SessionDep,dashboard: QueryDashboard): + return load_resource(session=session,dashboard=dashboard) -@router.post("/add", response_model=BaseDashboard) -async def add(session: SessionDep, user: CurrentUser, dashboard: CreateDashboard): +@router.post("/create_resource", response_model=BaseDashboard) +async def create_resource_api(session: SessionDep, user: CurrentUser, dashboard: CreateDashboard): return create_resource(session, user, dashboard) -@router.post("/update", response_model=BaseDashboard) -async def update(session: SessionDep, user: CurrentUser, dashboard: CreateDashboard): - return create_resource(session, user, dashboard) - -@router.post("/add_canvas", response_model=BaseDashboard) -async def add_canvas(session: SessionDep, user: CurrentUser, dashboard: CreateDashboard): +@router.post("/update_resource", response_model=BaseDashboard) +async def update_resource_api(session: SessionDep, user: CurrentUser, dashboard: QueryDashboard): + return update_resource(session=session, user=user, dashboard=dashboard) +@router.delete("/delete_resource/{resource_id}") +async def delete_resource_api(session: SessionDep,resource_id:str): + return delete_resource(session,resource_id) +@router.post("/create_canvas", response_model=BaseDashboard) +async def create_canvas_api(session: SessionDep, user: CurrentUser, dashboard: CreateDashboard): return create_canvas(session, user, dashboard) @router.post("/update_canvas", response_model=BaseDashboard) -async def update(session: SessionDep, user: CurrentUser, dashboard: CreateDashboard): +async def update_canvas_api(session: SessionDep, user: CurrentUser, dashboard: CreateDashboard): return @router.post("/check_name") -async def check_name(session: SessionDep, dashboard: QueryDashboard): +async def check_name_api(session: SessionDep, dashboard: QueryDashboard): return validate_name(session,dashboard) + diff --git a/backend/apps/dashboard/crud/dashboard_service.py b/backend/apps/dashboard/crud/dashboard_service.py index fe7c703d..41e762c0 100644 --- a/backend/apps/dashboard/crud/dashboard_service.py +++ b/backend/apps/dashboard/crud/dashboard_service.py @@ -1,23 +1,42 @@ from sqlalchemy import select, and_, text -from apps.dashboard.models.dashboard_model import CoreDashboard, CreateDashboard, QueryDashboard +from apps.dashboard.models.dashboard_model import CoreDashboard, CreateDashboard, QueryDashboard,DashboardBaseResponse from common.core.deps import SessionDep, CurrentUser import uuid -import datetime -def get_dashboard_list(session: SessionDep,dashboard: QueryDashboard): - sql = text("SELECT id, name, type,node_type, pid, create_time FROM core_dashboard") - for row in session.exec(sql).mappings(): - yield CoreDashboard(**row) +import time + +from common.utils.tree_utils import build_tree_generic + + +def list_resource(session: SessionDep, dashboard: QueryDashboard): + sql = "SELECT id, name, type, node_type, pid, create_time FROM core_dashboard" + filters = [] + params = {} + if dashboard.node_type is not None and dashboard.node_type != "": + filters.append("node_type = :node_type") + params["node_type"] = dashboard.node_type + + if filters: + sql += " WHERE " + " AND ".join(filters) + + result = session.execute(text(sql), params) + nodes = [DashboardBaseResponse(**row) for row in result.mappings()] + tree = build_tree_generic(nodes, root_pid="root") + return tree + + +def load_resource(session: SessionDep,dashboard: QueryDashboard): + return session.query(CoreDashboard).filter(CoreDashboard.id == dashboard.id).first() -def preview_with_id(session: SessionDep, dashboard_id: str): - return session.query(CoreDashboard).filter(CoreDashboard.id == id).first() def get_create_base_info(user: CurrentUser, dashboard: CreateDashboard): new_id = uuid.uuid4().hex record = CoreDashboard(**dashboard.model_dump()) record.id = new_id record.create_by = user.id + record.create_time = int(time.time()) return record + def create_resource(session: SessionDep, user: CurrentUser, dashboard: CreateDashboard): record = get_create_base_info(user, dashboard) session.add(record) @@ -26,13 +45,15 @@ def create_resource(session: SessionDep, user: CurrentUser, dashboard: CreateDas session.commit() return record -def update_resource(session: SessionDep, user: CurrentUser, dashboard: CreateDashboard): + +def update_resource(session: SessionDep, user: CurrentUser, dashboard: QueryDashboard): record = session.query(CoreDashboard).filter(CoreDashboard.id == dashboard.id).first() record.name = dashboard.name record.update_by = user.id - record.update_time = datetime.datetime.now() + record.update_time = int(time.time()) session.add(record) session.commit() + return record def create_canvas(session: SessionDep, user: CurrentUser, dashboard: CreateDashboard): record = get_create_base_info(user, dashboard) @@ -41,13 +62,16 @@ def create_canvas(session: SessionDep, user: CurrentUser, dashboard: CreateDashb record.canvas_style_data = dashboard.canvas_style_data session.add(record) session.flush() + session.refresh(record) + session.commit() return record + def update_canvas(session: SessionDep, user: CurrentUser, dashboard: CreateDashboard): record = session.query(CoreDashboard).filter(CoreDashboard.id == dashboard.id).first() -def validate_name( session: SessionDep, dashboard: QueryDashboard) -> bool: +def validate_name(session: SessionDep, dashboard: QueryDashboard) -> bool: if not dashboard.workspace_id: raise ValueError("workspace_id is required") if not dashboard.pid: @@ -63,7 +87,7 @@ def validate_name( session: SessionDep, dashboard: QueryDashboard) -> bool: CoreDashboard.name == dashboard.name ) ) - elif dashboard.opt in ('updateLeaf', 'updateFolder'): + elif dashboard.opt in ('updateLeaf', 'updateFolder', 'rename'): if not dashboard.id: raise ValueError("id is required for update operation") query = session.query(CoreDashboard).filter( @@ -76,4 +100,11 @@ def validate_name( session: SessionDep, dashboard: QueryDashboard) -> bool: ) else: raise ValueError(f"Invalid opt value: {dashboard.opt}") - return not session.query(query.exists()).scalar() \ No newline at end of file + return not session.query(query.exists()).scalar() + + +def delete_resource(session: SessionDep, resource_id: str): + sql = text("DELETE FROM core_dashboard WHERE id = :resource_id") + result = session.execute(sql, {"resource_id": resource_id}) + session.commit() + return result.rowcount > 0 diff --git a/backend/apps/dashboard/models/dashboard_model.py b/backend/apps/dashboard/models/dashboard_model.py index 80015ee2..20e14e4f 100644 --- a/backend/apps/dashboard/models/dashboard_model.py +++ b/backend/apps/dashboard/models/dashboard_model.py @@ -1,6 +1,6 @@ from sqlmodel import SQLModel, Field from sqlalchemy import String, Column, Text, SmallInteger, BigInteger, Integer,DateTime -from typing import Optional +from typing import Optional, List from pydantic import BaseModel class CoreDashboard(SQLModel, table=True): @@ -127,9 +127,11 @@ class DashboardBaseResponse(BaseModel): name: Optional[str] = None pid: Optional[str] = None node_type: Optional[str] = None + leaf: Optional[bool] = False type: Optional[str] = None create_time: Optional[int] = None update_time: Optional[int] = None + children: List['DashboardBaseResponse'] = [] class BaseDashboard(BaseModel): id: str = '' @@ -139,6 +141,7 @@ class BaseDashboard(BaseModel): org_id: str = '' type: str = '' node_type: str = '' + level: int = 0 create_by: int = 0 class QueryDashboard(BaseDashboard): diff --git a/backend/common/utils/tree_utils.py b/backend/common/utils/tree_utils.py new file mode 100644 index 00000000..8398d65b --- /dev/null +++ b/backend/common/utils/tree_utils.py @@ -0,0 +1,22 @@ +from typing import List, Optional, Dict, TypeVar, Protocol, Any +from pydantic import BaseModel + + +class ITreeNode(Protocol): + id: Optional[str] + pid: Optional[str] + children: List['ITreeNode'] + +T = TypeVar('T', bound=ITreeNode) + +def build_tree_generic(nodes: List[T], root_pid: Any = None) -> List[T]: + node_dict: Dict[str, T] = {node.id: node for node in nodes if node.id is not None} + tree: List[T] = [] + + for node in nodes: + if node.pid == root_pid: + tree.append(node) + elif node.pid in node_dict: + node_dict[node.pid].children.append(node) + + return tree \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json index ae6836ec..69e4e4b4 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -13,6 +13,7 @@ "@npkg/tinymce-plugins": "^0.0.7", "@tinymce/tinymce-vue": "^5.1.0", "dayjs": "^1.11.13", + "element-plus": "^2.10.1", "element-plus-secondary": "^1.0.0", "lodash": "^4.17.21", "snowflake-id": "^1.1.0", diff --git a/frontend/src/api/dashboard.ts b/frontend/src/api/dashboard.ts index 8c1eb1e2..a7fe8fe2 100644 --- a/frontend/src/api/dashboard.ts +++ b/frontend/src/api/dashboard.ts @@ -1,13 +1,13 @@ import { request } from '@/utils/request' export const dashboardApi = { - list: (params: any) => request.post('/dashboard/list',params), - 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), - addResource: (data: any) => request.post('/dashboard/add',data), - updateResource: (data: any) => request.post('/dashboard/update',data), - saveCanvas: (data: any) => request.post('/dashboard/add_canvas',data), - updateCanvas: (data: any) => request.post('/dashboard/update_',data), - check_name: (data: any) => request.post('/dashboard/check_name',data) + list_resource: (params: any) => request.post('/dashboard/list_resource',params), + load_resource: (params:any) => request.post('/dashboard/load_resource',params), + create_resource: (params: any) => request.post('/dashboard/create_resource',params), + update_resource: (params: any) => request.post('/dashboard/update_resource',params), + create_canvas: (params: any) => request.post('/dashboard/create_canvas',params), + update_canvas: (params: any) => request.post('/dashboard/update_canvas',params), + check_name: (params: any) => request.post('/dashboard/check_name',params), + delete_resource: (params: any) => request.delete(`/dashboard/delete_resource/${params.id}`,params), + move_resource: (params: any) => request.delete(`/dashboard/move_resource/${params.id}`,params), } \ No newline at end of file diff --git a/frontend/src/assets/svg/folder.svg b/frontend/src/assets/svg/folder.svg index 8216001f..3af52c36 100644 --- a/frontend/src/assets/svg/folder.svg +++ b/frontend/src/assets/svg/folder.svg @@ -1 +1 @@ - + diff --git a/frontend/src/assets/svg/icon_delete.svg b/frontend/src/assets/svg/icon_delete.svg new file mode 100644 index 00000000..a1a1a600 --- /dev/null +++ b/frontend/src/assets/svg/icon_delete.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/assets/svg/icon_edit_outlined.svg b/frontend/src/assets/svg/icon_edit_outlined.svg index 817c15c2..608bbed0 100644 --- a/frontend/src/assets/svg/icon_edit_outlined.svg +++ b/frontend/src/assets/svg/icon_edit_outlined.svg @@ -1,3 +1,3 @@ - + diff --git a/frontend/src/assets/svg/icon_more_outlined.svg b/frontend/src/assets/svg/icon_more_outlined.svg index f6fec929..74f82392 100644 --- a/frontend/src/assets/svg/icon_more_outlined.svg +++ b/frontend/src/assets/svg/icon_more_outlined.svg @@ -1,3 +1,3 @@ - + diff --git a/frontend/src/assets/svg/icon_redo_outlined.svg b/frontend/src/assets/svg/icon_redo_outlined.svg new file mode 100644 index 00000000..62cbf370 --- /dev/null +++ b/frontend/src/assets/svg/icon_redo_outlined.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/assets/svg/icon_rename.svg b/frontend/src/assets/svg/icon_rename.svg new file mode 100644 index 00000000..df2e7ad3 --- /dev/null +++ b/frontend/src/assets/svg/icon_rename.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/src/assets/svg/icon_undo_outlined.svg b/frontend/src/assets/svg/icon_undo_outlined.svg new file mode 100644 index 00000000..0cf76745 --- /dev/null +++ b/frontend/src/assets/svg/icon_undo_outlined.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/i18n/en.json b/frontend/src/i18n/en.json index fb51d1e9..affc2b46 100644 --- a/frontend/src/i18n/en.json +++ b/frontend/src/i18n/en.json @@ -10,5 +10,17 @@ "confirm": "Confirm", "cancel": "Cancel", "system_manage": "System Management" + }, + "dashboard": { + "length_1_64_characters": "Name field length must be 1-64 characters", + "rename": "Rename", + "delete": "Delete", + "delete_tips": "All resources in this folder will be deleted after deletion. Please proceed with caution.", + "undo": "Undo", + "reduction": "Restore", + "folder": "Folder", + "dashboard": "Dashboard", + "delete_warning": "Are you sure you want to delete?", + "delete_success": "Successfully deleted" } } \ No newline at end of file diff --git a/frontend/src/i18n/zh-CN.json b/frontend/src/i18n/zh-CN.json index 7369b96a..3207616a 100644 --- a/frontend/src/i18n/zh-CN.json +++ b/frontend/src/i18n/zh-CN.json @@ -10,5 +10,17 @@ "confirm": "确认", "cancel": "取消", "system_manage": "系统管理" + }, + "dashboard": { + "length_1_64_characters": "名称字段长度1-64个字符", + "rename": "重命名", + "delete": "删除", + "delete_tips": "删除后,此目录的所有资源都会被删除,请谨慎操作。", + "undo": "撤销", + "reduction": "恢复", + "folder": "目录", + "dashboard": "仪表板", + "delete_warning": "确定要删除吗?", + "delete_success": "删除成功" } } \ No newline at end of file diff --git a/frontend/src/stores/dashboard/dashboard.ts b/frontend/src/stores/dashboard/dashboard.ts index 9d244e30..2bf9cb7d 100644 --- a/frontend/src/stores/dashboard/dashboard.ts +++ b/frontend/src/stores/dashboard/dashboard.ts @@ -8,9 +8,11 @@ export const dashboardStore = defineStore('dashboard', { tabMoveInActiveId: null, curComponent: null, curComponentId: null, - canvasStyle: {}, + canvasStyleData: {}, componentData: [], + canvasViewInfo: {}, fullscreenFlag: false, + dataPrepareState: false, dashboardInfo: { id: null, name: null, @@ -43,8 +45,8 @@ export const dashboardStore = defineStore('dashboard', { setComponentData(value: any) { this.componentData = value }, - setCanvasStyle(value: any) { - this.canvasStyle = value + setCanvasStyleData(value: any) { + this.canvasStyleData = value }, setTabCollisionActiveId(tabId: any) { this.tabCollisionActiveId = tabId @@ -58,6 +60,9 @@ export const dashboardStore = defineStore('dashboard', { this.dashboardInfo[key] = params[key] }) }, + setCanvasViewInfo(params: any) { + this.canvasViewInfo = params + }, canvasDataInit() { } diff --git a/frontend/src/stores/dashboard/snapshot.ts b/frontend/src/stores/dashboard/snapshot.ts new file mode 100644 index 00000000..10846322 --- /dev/null +++ b/frontend/src/stores/dashboard/snapshot.ts @@ -0,0 +1,149 @@ +import {defineStore, storeToRefs} from 'pinia' +import _ from 'lodash' +import {dashboardStoreWithOut} from "@/stores/dashboard/dashboard.ts"; +import {store} from "@/stores"; + +const dashboardStore = dashboardStoreWithOut() +const { + dashboardInfo, + componentData, + canvasStyleData, + canvasViewInfo, + dataPrepareState +} = storeToRefs(dashboardStore) + +let defaultCanvasInfo = { + componentData: [], + canvasStyleData: {}, + canvasViewInfo: {}, + cacheViewIdInfo: { + snapshotCacheViewCalc: [], + snapshotCacheViewRender: [] + } +} + +// Storage snapshot structure {componentData:[],canvasStyleData:{},canvasViewInfo:{}} +export const snapshotStore = defineStore('snapshot', { + state: () => { + return { + snapshotDisableTime: 1, // Snapshot disable time (resolves style changes caused by redo/undo) + styleChangeTimes: -1, // Component style modification count + cacheStyleChangeTimes: 0, // Unsaved component style modification count in the dashboard + snapshotCacheTimes: 0, // Current unsnapshot modification count (timed cache, cached every 5 seconds; used for minor changes like style adjustments) + snapshotData: [], // Editor snapshot data + snapshotIndex: -1 // Snapshot index + } + }, + actions: { + initSnapShot() { + this.styleChangeTimes = -1 + this.cacheStyleChangeTimes = 0 + this.snapshotCacheTimes = 0 + this.snapshotData = [] + this.snapshotIndex = -1 + }, + //Regularly check the number of changes, and if there are any changes, perform mirroring processing + snapshotCatchToStore() { + if (this.snapshotCacheTimes) { + this.recordSnapshot() + } + }, + recordSnapshotCache() { + if (dataPrepareState.value) { + this.snapshotCacheTimes++ + } + }, + undo() { + if (this.snapshotIndex > 0) { + this.snapshotIndex-- + const componentSnapshot = _.cloneDeep(this.snapshotData[this.snapshotIndex]) || getDefaultCanvasInfo() + // @ts-ignore + componentSnapshot.dashboardInfo.id = dashboardInfo.value.id + // @ts-ignore + componentSnapshot.dashboardInfo.pid = dashboardInfo.value.pid + // @ts-ignore + componentSnapshot.dashboardInfo.dataState = dashboardInfo.value.dataState + // @ts-ignore + componentSnapshot.dashboardInfo.contentId = dashboardInfo.value.contentId + this.snapshotPublish(componentSnapshot) + this.styleChangeTimes++ + this.snapshotDisableTime = Date.now() + 3000 + dashboardStore.setCurComponent(null) + } + }, + + redo() { + if (this.snapshotIndex < this.snapshotData.length - 1) { + this.snapshotIndex++ + const snapshotInfo = _.cloneDeep(this.snapshotData[this.snapshotIndex]) + // @ts-ignore + snapshotInfo.dashboardInfo.id = dashboardInfo.value.id + // @ts-ignore + snapshotInfo.dashboardInfo.pid = dashboardInfo.value.pid + // @ts-ignore + snapshotInfo.dashboardInfo.dataState = dashboardInfo.value.dataState + // @ts-ignore + snapshotInfo.dashboardInfo.contentId = dashboardInfo.value.contentId + this.snapshotPublish(snapshotInfo) + this.styleChangeTimes++ + this.snapshotDisableTime = Date.now() + 3000 + dashboardStore.setCurComponent(null) + } + }, + snapshotPublish(snapshotInfo: any) { + dashboardStore.updateDashboardInfo(snapshotInfo.dashboardInfo) + dashboardStore.setComponentData(snapshotInfo.componentData) + dashboardStore.setCanvasStyleData(snapshotInfo.canvasStyleData) + dashboardStore.setCanvasViewInfo(snapshotInfo.canvasViewInfo) + }, + + resetStyleChangeTimes() { + this.styleChangeTimes = 0 + this.snapshotCacheTimes = 0 + }, + resetSnapshot() { + this.styleChangeTimes = -1 + this.cacheStyleChangeTimes = 0 + this.snapshotCacheTimes = 0 + this.snapshotData = [] + this.snapshotIndex = -1 + this.recordSnapshot() + }, + + recordSnapshot() { + if (dataPrepareState.value) { + this.styleChangeTimes = ++this.styleChangeTimes + // Add a new snapshot + // @ts-ignore + this.snapshotData[++this.snapshotIndex] = { + componentData: _.cloneDeep(componentData.value), + canvasStyleData: _.cloneDeep(canvasStyleData.value), + canvasViewInfo: _.cloneDeep(canvasViewInfo.value), + dashboardInfo: _.cloneDeep(dashboardInfo.value) + } + // During the undo process, when adding a new snapshot, the snapshots following it should be cleaned up + if (this.snapshotIndex < this.snapshotData.length - 1) { + this.snapshotData = this.snapshotData.slice(0, this.snapshotIndex + 1) + } + // Clean up cache counters + this.snapshotCacheTimes = 0 + } + } + } +}) + +export function setDefaultComponentData(data = []) { + defaultCanvasInfo.componentData = data +} + +function getDefaultCanvasInfo() { + return defaultCanvasInfo +} + +export function setDefaultCanvasInfo(data: any) { + defaultCanvasInfo = data +} + +export const snapshotStoreWithOut = () => { + return snapshotStore(store) +} diff --git a/frontend/src/views/dashboard/common/ResourceGroupOpt.vue b/frontend/src/views/dashboard/common/ResourceGroupOpt.vue index 5b6697bd..479a80e5 100644 --- a/frontend/src/views/dashboard/common/ResourceGroupOpt.vue +++ b/frontend/src/views/dashboard/common/ResourceGroupOpt.vue @@ -3,8 +3,12 @@ import folder from '@/assets/svg/folder.svg' import {type SQTreeNode} from '@/views/dashboard/utils/treeNode' import {ref, reactive, computed} from 'vue' import {saveDashboardResource} from "@/views/dashboard/utils/canvasUtils.ts"; +import {dashboardApi} from "@/api/dashboard.ts"; +import _ from "lodash"; + const emits = defineEmits(['finish']) const state = reactive({ + id: null, opt: null, placeholder: '', nodeType: 'folder', @@ -12,19 +16,20 @@ const state = reactive({ resourceFormNameLabel: 'Name', dialogTitle: '', tData: [], + tDataSource: [], nameList: [], targetInfo: null, attachParams: null }) -let tData: any = [] - const getTitle = (opt: string) => { switch (opt) { case 'newLeaf': return 'New Dashboard' case 'newFolder': return 'New Folder' + case 'rename': + return 'Rename' default: return } @@ -41,10 +46,19 @@ const getResourceNewName = (opt: string) => { } } +const getTree = async () => { + const params = {node_type: 'folder'} + dashboardApi.list_resource(params).then((res) => { + state.tData = res || [] + state.tDataSource = [...state.tData] + }) +} + const optInit = (params: any) => { // @ts-ignore state.dialogTitle = getTitle(params.opt) state.opt = params.opt + state.id = params.id state.parentSelect = params.parentSelect state.targetInfo = params.data state.nodeType = params.nodeType || 'folder' @@ -52,11 +66,15 @@ const optInit = (params: any) => { // @ts-ignore resourceForm.name = params.name || getResourceNewName(params.opt) resourceForm.pid = params.pid || 'root' + if (params.parentSelect) { + getTree() + } } const resourceDialogShow = ref(false) const loading = ref(false) const resourceForm = reactive({ + id: null, pid: '', pName: '', name: 'New Dashboard' @@ -100,6 +118,7 @@ const resetForm = () => { } const propsTree = { + value: 'id', label: 'name', children: 'children', // @ts-ignore @@ -112,20 +131,21 @@ const showPid = computed(() => { const saveResource = () => { const params = { - nodeType: state.nodeType, + id: state.id, + node_type: state.nodeType, name: resourceForm.name, opt: state.opt, pid: resourceForm.pid, - type: 'dashboard' + type: 'dashboard', + level: state.nodeType === 'folder' ? 0 : 1, } - saveDashboardResource(params, function (rsp:any) { + saveDashboardResource(params, function (rsp: any) { + const messageTips = state.opt === 'rename' ? 'Update Success' : 'Save Success' ElMessage({ type: 'success', - message: 'Save Success', + message: messageTips, }) - if (state.opt === 'newLeaf') { - emits('finish', {opt: state.opt, resourceId: rsp.id}) - } + emits('finish', {opt: state.opt, resourceId: rsp.id}) resetForm() }) } @@ -137,7 +157,7 @@ const nodeClick = (data: SQTreeNode) => { const filterMethod = (value: any) => { // @ts-ignore - state.tData = [...tData].filter(item => item.name.includes(value)) + state.tData = state.tDataSource.filter(item => item.name.includes(value)) } defineExpose({ @@ -187,7 +207,7 @@ defineExpose({ - + {{ name }} @@ -246,4 +266,8 @@ defineExpose({ overflow: hidden; } } + +.custom-tree-folder { + color: rgb(255, 198, 10) +} diff --git a/frontend/src/views/dashboard/common/ResourceTree.vue b/frontend/src/views/dashboard/common/ResourceTree.vue index 9ef95446..baede223 100644 --- a/frontend/src/views/dashboard/common/ResourceTree.vue +++ b/frontend/src/views/dashboard/common/ResourceTree.vue @@ -7,6 +7,9 @@ import icon_folder from '@/assets/svg/icon_folder.svg' import icon_fileAdd_outlined from '@/assets/svg/icon_file-add_outlined.svg' import icon_dashboard from '@/assets/svg/icon_dashboard.svg' import icon_edit_outlined from '@/assets/svg/icon_edit_outlined.svg' +import icon_rename from '@/assets/svg/icon_rename.svg' +import icon_delete from '@/assets/svg/icon_delete.svg' +import icon_more_outlined from '@/assets/svg/icon_more_outlined.svg' import {onMounted, reactive, ref, watch, nextTick, computed} from 'vue' import {ElIcon, ElScrollbar} from 'element-plus-secondary' import {Icon} from '@/components/icon-custom' @@ -16,7 +19,10 @@ import router from '@/router' import {dashboardStoreWithOut} from "@/stores/dashboard/dashboard.ts"; import ResourceGroupOpt from "@/views/dashboard/common/ResourceGroupOpt.vue"; import {dashboardApi} from "@/api/dashboard.ts"; +import HandleMore from "@/views/dashboard/common/HandleMore.vue"; +import {useI18n} from "vue-i18n"; +const {t} = useI18n() const dashboardStore = dashboardStoreWithOut() const resourceGroupOptRef = ref(null) @@ -38,8 +44,7 @@ defineProps({ }) const defaultProps = { children: 'children', - label: 'name', - disabled: (data: any) => data.extraFlag1 === 0 + label: 'name' } const mounted = ref(false) const selectedNodeKey: any = ref(null) @@ -51,9 +56,21 @@ const state = reactive({ curSortType: 'time_desc', resourceTree: [] as SQTreeNode[], originResourceTree: [] as SQTreeNode[], - folderMenuList: [], sortType: [], - templateCreatePid: 0 + templateCreatePid: 0, + folderMenuList: [ + { + label: t('dashboard.rename'), + command: 'rename', + svgName: icon_rename + }, + { + label: t('dashboard.delete'), + command: 'delete', + svgName: icon_delete, + divided: true + } + ], }) @@ -101,7 +118,7 @@ const nodeClick = (data: SQTreeNode, node: any) => { }) } else { selectedNodeKey.value = data.id - if (data.leaf) { + if (data.node_type === 'leaf') { emit('nodeClick', data) } else { resourceListTree.value.setCurrentKey(null) @@ -112,7 +129,7 @@ const nodeClick = (data: SQTreeNode, node: any) => { const getTree = async () => { state.originResourceTree = [] const params = {} - dashboardApi.list(params).then((res: SQTreeNode[]) => { + dashboardApi.list_resource(params).then((res: SQTreeNode[]) => { state.originResourceTree = res || [] state.resourceTree = _.cloneDeep(state.originResourceTree) afterTreeInit() @@ -214,6 +231,32 @@ const addOperation = (params: any) => { } } +const operation = (opt: string, data: SQTreeNode) => { + if (opt === 'delete') { + const msg = data.node_type === 'leaf' ? '' : t('dashboard.delete_tips') + const tips_label = data.node_type === 'leaf' ? 'Dashboard' : t('dashboard.folder') + ElMessageBox.confirm(t('dashboard.delete_warn', [tips_label]), { + confirmButtonType: 'danger', + type: 'warning', + tip: msg, + autofocus: false, + showClose: false + }).then(() => { + dashboardApi.delete_resource({id: data.id}).then(() => { + ElMessage.success(t('dashboard.delete_success')) + getTree() + }) + }) + } else if (opt === 'rename') { + //@ts-ignore + resourceGroupOptRef.value?.optInit({opt: 'rename',id: data.id,name:data.name}) + } +} + +const baseInfoChangeFinish = () => { + getTree() +} + defineExpose({ hasData, @@ -289,7 +332,7 @@ defineExpose({ > - + @@ -301,7 +344,7 @@ defineExpose({ @@ -309,26 +352,25 @@ defineExpose({ - - - - - - - - + operation(opt,data)" + :menu-list="state.folderMenuList" + :icon-name="icon_more_outlined" + placement="bottom-start" + v-if="data.node_type !== 'leaf'" + > - +