feat: 添加全局alert,并替换原先是toast
This commit is contained in:
parent
53027470fe
commit
cbb3543c4a
|
@ -9,6 +9,7 @@ export {}
|
|||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
Alert: typeof import('./src/components/Alert/Alert.vue')['default']
|
||||
AlertDemo: typeof import('./src/components/AlertDemo.vue')['default']
|
||||
BaseBoard: typeof import('./src/components/equipments/BaseBoard.vue')['default']
|
||||
BaseInputField: typeof import('./src/components/InputField/BaseInputField.vue')['default']
|
||||
Canvas: typeof import('./src/components/Canvas.vue')['default']
|
||||
|
|
14
src/App.vue
14
src/App.vue
|
@ -1,6 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import Navbar from "./components/Navbar.vue";
|
||||
import Dialog from "./components/Dialog.vue";
|
||||
import { Alert, useAlertProvider } from "./components/Alert";
|
||||
import { ref, provide, computed, onMounted } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
|
@ -49,19 +50,26 @@ provide("theme", {
|
|||
const currentRoutePath = computed(() => {
|
||||
return router.currentRoute.value.path;
|
||||
});
|
||||
|
||||
useAlertProvider();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<header class="relative">
|
||||
<Navbar></Navbar>
|
||||
<Dialog></Dialog>
|
||||
<Navbar />
|
||||
<Dialog />
|
||||
<Alert />
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<RouterView />
|
||||
</main>
|
||||
<footer v-if="currentRoutePath != '/project'" class="footer footer-center p-4 bg-base-300 text-base-content">
|
||||
|
||||
<footer
|
||||
v-if="currentRoutePath != '/project'"
|
||||
class="footer footer-center p-4 bg-base-300 text-base-content"
|
||||
>
|
||||
<div>
|
||||
<p>Copyright © 2023 - All right reserved by OurEDA</p>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
<template>
|
||||
<div class="fixed left-1/2 top-30 z-50 -translate-x-1/2">
|
||||
<transition
|
||||
name="alert"
|
||||
enter-active-class="alert-enter-active"
|
||||
leave-active-class="alert-leave-active"
|
||||
enter-from-class="alert-enter-from"
|
||||
enter-to-class="alert-enter-to"
|
||||
leave-from-class="alert-leave-from"
|
||||
leave-to-class="alert-leave-to"
|
||||
>
|
||||
<div
|
||||
v-if="alertStore?.alertState.value.visible"
|
||||
:class="alertClasses"
|
||||
class="alert"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<!-- Icons for different alert types -->
|
||||
<CheckCircle
|
||||
v-if="alertStore?.alertState.value.type === 'success'"
|
||||
class="h-6 w-6 shrink-0 stroke-current"
|
||||
/>
|
||||
<XCircle
|
||||
v-else-if="alertStore?.alertState.value.type === 'error'"
|
||||
class="h-6 w-6 shrink-0 stroke-current"
|
||||
/>
|
||||
<AlertTriangle
|
||||
v-else-if="alertStore?.alertState.value.type === 'warning'"
|
||||
class="h-6 w-6 shrink-0 stroke-current"
|
||||
/>
|
||||
<Info v-else class="h-6 w-6 shrink-0 stroke-current" />
|
||||
<span>{{ alertStore?.alertState.value.message }}</span>
|
||||
</div>
|
||||
<div class="flex-none">
|
||||
<button
|
||||
class="btn btn-sm btn-circle btn-ghost"
|
||||
@click="alertStore?.hide"
|
||||
>
|
||||
<X class="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from "vue";
|
||||
import { CheckCircle, XCircle, AlertTriangle, Info, X } from "lucide-vue-next";
|
||||
import { useAlertStore } from ".";
|
||||
|
||||
const alertStore = useAlertStore();
|
||||
|
||||
// Computed classes for different alert types
|
||||
const alertClasses = computed(() => {
|
||||
const baseClasses = "shadow-lg max-w-sm";
|
||||
|
||||
switch (alertStore?.alertState.value.type) {
|
||||
case "success":
|
||||
return `${baseClasses} alert-success`;
|
||||
case "error":
|
||||
return `${baseClasses} alert-error`;
|
||||
case "warning":
|
||||
return `${baseClasses} alert-warning`;
|
||||
case "info":
|
||||
default:
|
||||
return `${baseClasses} alert-info`;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 进入和离开的过渡动画持续时间 */
|
||||
.alert-enter-active,
|
||||
.alert-leave-active {
|
||||
transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
|
||||
}
|
||||
|
||||
/* 进入的起始状态 */
|
||||
.alert-enter-from {
|
||||
opacity: 0;
|
||||
transform: translateY(-20px) scale(0.95);
|
||||
}
|
||||
|
||||
/* 进入的结束状态 */
|
||||
.alert-enter-to {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
|
||||
/* 离开的起始状态 */
|
||||
.alert-leave-from {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
|
||||
/* 离开的结束状态 */
|
||||
.alert-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateY(-10px) scale(0.98);
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,61 @@
|
|||
import { ref, computed } from "vue";
|
||||
import Alert from "./Alert.vue";
|
||||
import { createInjectionState } from "@vueuse/core";
|
||||
|
||||
export interface AlertState {
|
||||
visible: boolean;
|
||||
message: string;
|
||||
type: "success" | "error" | "warning" | "info";
|
||||
}
|
||||
|
||||
// create injectivon state using vueuse
|
||||
const [useAlertProvider, useAlertStore] = createInjectionState(() => {
|
||||
const alertState = ref<AlertState>({
|
||||
visible: false,
|
||||
message: "",
|
||||
type: "info",
|
||||
});
|
||||
|
||||
let timeoutId: number | null = null;
|
||||
|
||||
function show(
|
||||
message: string,
|
||||
type: AlertState["type"] = "info",
|
||||
duration = 2000,
|
||||
) {
|
||||
// Clear existing timeout
|
||||
if (timeoutId) {
|
||||
window.clearTimeout(timeoutId);
|
||||
}
|
||||
|
||||
alertState.value = {
|
||||
visible: true,
|
||||
message,
|
||||
type,
|
||||
};
|
||||
|
||||
// Auto hide after duration
|
||||
if (duration > 0) {
|
||||
timeoutId = window.setTimeout(() => {
|
||||
hide();
|
||||
}, duration);
|
||||
}
|
||||
}
|
||||
|
||||
function hide() {
|
||||
alertState.value.visible = false;
|
||||
if (timeoutId) {
|
||||
window.clearTimeout(timeoutId);
|
||||
timeoutId = null;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
alertState,
|
||||
show,
|
||||
hide,
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
export { Alert, useAlertProvider, useAlertStore };
|
|
@ -1,70 +1,148 @@
|
|||
<template>
|
||||
<div class="flex-1 h-full w-full bg-base-200 relative overflow-hidden diagram-container" ref="canvasContainer"
|
||||
@mousedown="handleCanvasMouseDown" @mousedown.middle.prevent="startMiddleDrag" @wheel.prevent="onZoom"
|
||||
@contextmenu.prevent="handleContextMenu">
|
||||
<div
|
||||
class="flex-1 h-full w-full bg-base-200 relative overflow-hidden diagram-container"
|
||||
ref="canvasContainer"
|
||||
@mousedown="handleCanvasMouseDown"
|
||||
@mousedown.middle.prevent="startMiddleDrag"
|
||||
@wheel.prevent="onZoom"
|
||||
@contextmenu.prevent="handleContextMenu"
|
||||
>
|
||||
<!-- 工具栏 -->
|
||||
<div class="absolute top-2 right-2 flex gap-2 z-30">
|
||||
<button class="btn btn-sm btn-primary" @click="openDiagramFileSelector">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M5 19a2 2 0 01-2-2V7a2 2 0 012-2h4l2 2h4a2 2 0 012 2v1M5 19h14a2 2 0 002-2v-5a2 2 0 00-2-2H9a2 2 0 00-2 2v5a2 2 0 01-2 2z" />
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-4 w-4 mr-1"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M5 19a2 2 0 01-2-2V7a2 2 0 012-2h4l2 2h4a2 2 0 012 2v1M5 19h14a2 2 0 002-2v-5a2 2 0 00-2-2H9a2 2 0 00-2 2v5a2 2 0 01-2 2z"
|
||||
/>
|
||||
</svg>
|
||||
导入
|
||||
</button>
|
||||
<button class="btn btn-sm btn-primary" @click="exportDiagram">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-4 w-4 mr-1"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
|
||||
/>
|
||||
</svg>
|
||||
导出
|
||||
</button>
|
||||
<button class="btn btn-sm btn-primary" @click="emit('open-components')">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-4 w-4 mr-1"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 4v16m8-8H4"
|
||||
/>
|
||||
</svg>
|
||||
添加组件
|
||||
</button>
|
||||
<button class="btn btn-sm btn-primary" @click="emit('toggle-doc-panel')">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-4 w-4 mr-1"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
|
||||
/>
|
||||
</svg>
|
||||
{{ props.showDocPanel ? "属性面板" : "文档" }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 隐藏的文件输入 -->
|
||||
<input type="file" ref="fileInput" class="hidden" accept=".json" @change="handleFileSelected" />
|
||||
<input
|
||||
type="file"
|
||||
ref="fileInput"
|
||||
class="hidden"
|
||||
accept=".json"
|
||||
@change="handleFileSelected"
|
||||
/>
|
||||
|
||||
<div ref="canvas" class="diagram-canvas" :style="{
|
||||
transform: `translate(${position.x}px, ${position.y}px) scale(${scale})`,
|
||||
}">
|
||||
<div
|
||||
ref="canvas"
|
||||
class="diagram-canvas"
|
||||
:style="{
|
||||
transform: `translate(${position.x}px, ${position.y}px) scale(${scale})`,
|
||||
}"
|
||||
>
|
||||
<!-- 渲染连线 -->
|
||||
<svg class="wires-layer" width="10000" height="10000">
|
||||
<!-- 已完成的连线 -->
|
||||
<WireComponent v-for="(wire, index) in wireItems" :key="wire.id" :id="wire.id" :start-x="wire.startX"
|
||||
:start-y="wire.startY" :end-x="wire.endX" :end-y="wire.endY" :stroke-color="wire.color || '#4a5568'"
|
||||
:stroke-width="wire.strokeWidth" :is-active="false" :start-component-id="wire.startComponentId"
|
||||
:start-pin-id="wire.startPinId" :end-component-id="wire.endComponentId" :end-pin-id="wire.endPinId"
|
||||
:routing-mode="wire.routingMode" :path-commands="wire.pathCommands" />
|
||||
<WireComponent
|
||||
v-for="(wire, index) in wireItems"
|
||||
:key="wire.id"
|
||||
:id="wire.id"
|
||||
:start-x="wire.startX"
|
||||
:start-y="wire.startY"
|
||||
:end-x="wire.endX"
|
||||
:end-y="wire.endY"
|
||||
:stroke-color="wire.color || '#4a5568'"
|
||||
:stroke-width="wire.strokeWidth"
|
||||
:is-active="false"
|
||||
:start-component-id="wire.startComponentId"
|
||||
:start-pin-id="wire.startPinId"
|
||||
:end-component-id="wire.endComponentId"
|
||||
:end-pin-id="wire.endPinId"
|
||||
:routing-mode="wire.routingMode"
|
||||
:path-commands="wire.pathCommands"
|
||||
/>
|
||||
|
||||
<!-- 正在创建的连线 -->
|
||||
<WireComponent v-if="isCreatingWire" id="temp-wire" :start-x="creatingWireStart.x"
|
||||
:start-y="creatingWireStart.y" :end-x="mousePosition.x" :end-y="mousePosition.y" stroke-color="#3182ce"
|
||||
:stroke-width="2" :is-active="true" />
|
||||
<WireComponent
|
||||
v-if="isCreatingWire"
|
||||
id="temp-wire"
|
||||
:start-x="creatingWireStart.x"
|
||||
:start-y="creatingWireStart.y"
|
||||
:end-x="mousePosition.x"
|
||||
:end-y="mousePosition.y"
|
||||
stroke-color="#3182ce"
|
||||
:stroke-width="2"
|
||||
:is-active="true"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<!-- 渲染画布上的组件 -->
|
||||
<div v-for="component in diagramParts" :key="component.id" class="component-wrapper" :class="{
|
||||
'component-hover': hoveredComponent === component.id,
|
||||
'component-selected': selectedComponentId === component.id,
|
||||
'component-disabled': !component.isOn,
|
||||
'component-hidepins': component.hidepins,
|
||||
}" :style="{
|
||||
<div
|
||||
v-for="component in diagramParts"
|
||||
:key="component.id"
|
||||
class="component-wrapper"
|
||||
:class="{
|
||||
'component-hover': hoveredComponent === component.id,
|
||||
'component-selected': selectedComponentId === component.id,
|
||||
'component-disabled': !component.isOn,
|
||||
'component-hidepins': component.hidepins,
|
||||
}"
|
||||
:style="{
|
||||
top: component.y + 'px',
|
||||
left: component.x + 'px',
|
||||
zIndex: component.index ?? 0,
|
||||
|
@ -73,56 +151,74 @@
|
|||
: 'none',
|
||||
opacity: component.isOn ? 1 : 0.6,
|
||||
display: 'block',
|
||||
}" @mousedown.left.stop="startComponentDrag($event, component)" @mouseover="
|
||||
}"
|
||||
@mousedown.left.stop="startComponentDrag($event, component)"
|
||||
@mouseover="
|
||||
(event) => {
|
||||
hoveredComponent = component.id;
|
||||
}
|
||||
" @mouseleave="
|
||||
"
|
||||
@mouseleave="
|
||||
(event) => {
|
||||
hoveredComponent = null;
|
||||
}
|
||||
">
|
||||
"
|
||||
>
|
||||
<!-- 动态渲染组件 -->
|
||||
<component :is="componentManager.getComponentDefinition(component.type)"
|
||||
v-if="componentManager.componentModules.value[component.type] && componentManager.getComponentDefinition(component.type)"
|
||||
v-bind="componentManager.prepareComponentProps(component.attrs || {}, component.id)" @update:bindKey="
|
||||
<component
|
||||
:is="componentManager.getComponentDefinition(component.type)"
|
||||
v-if="
|
||||
componentManager.componentModules.value[component.type] &&
|
||||
componentManager.getComponentDefinition(component.type)
|
||||
"
|
||||
v-bind="
|
||||
componentManager.prepareComponentProps(
|
||||
component.attrs || {},
|
||||
component.id,
|
||||
)
|
||||
"
|
||||
@update:bindKey="
|
||||
(value: string) =>
|
||||
updateComponentProp(component.id, 'bindKey', value)
|
||||
" @pin-click="
|
||||
"
|
||||
@pin-click="
|
||||
(pinInfo: any) =>
|
||||
handlePinClick(component.id, pinInfo, pinInfo.originalEvent)
|
||||
" :ref="(el: any) => componentManager.setComponentRef(component.id, el)" />
|
||||
"
|
||||
:ref="(el: any) => componentManager.setComponentRef(component.id, el)"
|
||||
/>
|
||||
|
||||
<!-- Fallback if component module not loaded yet -->
|
||||
<div v-else
|
||||
class="p-2 text-xs text-gray-400 border border-dashed border-gray-400 flex items-center justify-center">
|
||||
<div
|
||||
v-else
|
||||
class="p-2 text-xs text-gray-400 border border-dashed border-gray-400 flex items-center justify-center"
|
||||
>
|
||||
<div class="flex flex-col items-center">
|
||||
<div class="loading loading-spinner loading-xs mb-1"></div>
|
||||
<span>Loading {{ component.type }}...</span>
|
||||
<small class="mt-1 text-xs">{{ componentManager.componentModules.value[component.type] ? 'Module loaded but invalid' : 'Module not found' }}</small>
|
||||
<small class="mt-1 text-xs">{{
|
||||
componentManager.componentModules.value[component.type]
|
||||
? "Module loaded but invalid"
|
||||
: "Module not found"
|
||||
}}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 通知组件 -->
|
||||
<div v-if="showNotification" class="toast toast-top toast-center z-50 w-fit-content">
|
||||
<div :class="`alert ${notificationType === 'success'
|
||||
? 'alert-success'
|
||||
: notificationType === 'error'
|
||||
? 'alert-error'
|
||||
: 'alert-info'
|
||||
}`">
|
||||
<span>{{ notificationMessage }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 加载指示器 -->
|
||||
<div v-if="isLoading" class="absolute inset-0 bg-black bg-opacity-30 flex items-center justify-center z-50">
|
||||
<div
|
||||
v-if="isLoading"
|
||||
class="absolute inset-0 bg-black bg-opacity-30 flex items-center justify-center z-50"
|
||||
>
|
||||
<div class="loading loading-spinner loading-lg text-primary"></div>
|
||||
</div>
|
||||
|
||||
<!-- 缩放指示器 -->
|
||||
<div class="absolute bottom-4 right-4 bg-base-100 px-3 py-1.5 rounded-md shadow-md z-20" style="opacity: 0.9">
|
||||
<div
|
||||
class="absolute bottom-4 right-4 bg-base-100 px-3 py-1.5 rounded-md shadow-md z-20"
|
||||
style="opacity: 0.9"
|
||||
>
|
||||
<span class="text-sm font-medium">{{ Math.round(scale * 100) }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -140,6 +236,7 @@ import {
|
|||
} from "vue";
|
||||
import { useEventListener } from "@vueuse/core";
|
||||
import WireComponent from "@/components/equipments/Wire.vue";
|
||||
import { useAlertStore } from "@/components/Alert";
|
||||
|
||||
// 导入 diagram 管理器
|
||||
import {
|
||||
|
@ -186,9 +283,14 @@ const props = defineProps<{
|
|||
// 获取componentManager实例
|
||||
const componentManager = useComponentManager();
|
||||
if (!componentManager) {
|
||||
throw new Error("DiagramCanvas must be used within a component manager provider");
|
||||
throw new Error(
|
||||
"DiagramCanvas must be used within a component manager provider",
|
||||
);
|
||||
}
|
||||
|
||||
// 获取Alert store实例
|
||||
const alertStore = useAlertStore();
|
||||
|
||||
// --- 画布状态 ---
|
||||
const canvasContainer = ref<HTMLElement | null>(null);
|
||||
const canvas = ref<HTMLElement | null>(null);
|
||||
|
@ -215,7 +317,9 @@ const diagramData = ref<DiagramData>({
|
|||
});
|
||||
|
||||
// 组件引用跟踪(保留以便向后兼容)
|
||||
const componentRefs = computed(() => componentManager?.componentRefs.value || {});
|
||||
const componentRefs = computed(
|
||||
() => componentManager?.componentRefs.value || {},
|
||||
);
|
||||
|
||||
// 计算属性:从 diagramData 中提取组件列表,并按index属性排序
|
||||
const diagramParts = computed<DiagramPart[]>(() => {
|
||||
|
@ -320,13 +424,6 @@ const mousePosition = reactive({ x: 0, y: 0 });
|
|||
// 加载状态
|
||||
const isLoading = ref(false);
|
||||
|
||||
// 通知状态
|
||||
const showNotification = ref(false);
|
||||
const notificationMessage = ref("");
|
||||
const notificationType = ref<"success" | "error" | "info">("info");
|
||||
// 保存toast定时器ID
|
||||
const toastTimers: number[] = [];
|
||||
|
||||
// 文件选择引用
|
||||
const fileInput = ref<HTMLInputElement | null>(null);
|
||||
|
||||
|
@ -337,7 +434,7 @@ const isWireCreationEventActive = ref(false);
|
|||
|
||||
// 使用VueUse设置事件监听器
|
||||
// 画布拖拽事件
|
||||
useEventListener(document, 'mousemove', (e: MouseEvent) => {
|
||||
useEventListener(document, "mousemove", (e: MouseEvent) => {
|
||||
if (isDragEventActive.value) {
|
||||
onDrag(e);
|
||||
}
|
||||
|
@ -349,7 +446,7 @@ useEventListener(document, 'mousemove', (e: MouseEvent) => {
|
|||
}
|
||||
});
|
||||
|
||||
useEventListener(document, 'mouseup', () => {
|
||||
useEventListener(document, "mouseup", () => {
|
||||
if (isDragEventActive.value) {
|
||||
stopDrag();
|
||||
}
|
||||
|
@ -359,7 +456,7 @@ useEventListener(document, 'mouseup', () => {
|
|||
});
|
||||
|
||||
// 键盘事件
|
||||
useEventListener(window, 'keydown', handleKeyDown);
|
||||
useEventListener(window, "keydown", handleKeyDown);
|
||||
|
||||
// --- 缩放功能 ---
|
||||
const MIN_SCALE = 0.2;
|
||||
|
@ -893,7 +990,7 @@ function handleFileSelected(event: Event) {
|
|||
const validation = validateDiagramData(parsed);
|
||||
|
||||
if (!validation.isValid) {
|
||||
showToast(
|
||||
alertStore?.show(
|
||||
`不是有效的diagram.json格式: ${validation.errors.join("; ")}`,
|
||||
"error",
|
||||
);
|
||||
|
@ -910,11 +1007,11 @@ function handleFileSelected(event: Event) {
|
|||
// 发出更新事件
|
||||
emit("diagram-updated", diagramData.value);
|
||||
|
||||
showToast(`成功导入diagram文件`, "success");
|
||||
alertStore?.show(`成功导入diagram文件`, "success");
|
||||
} catch (error) {
|
||||
console.error("解析JSON文件出错:", error);
|
||||
if (document.body.contains(canvasContainer.value)) {
|
||||
showToast("解析文件出错,请确认是有效的JSON格式", "error");
|
||||
alertStore?.show("解析文件出错,请确认是有效的JSON格式", "error");
|
||||
}
|
||||
} finally {
|
||||
// 检查组件是否仍然挂载
|
||||
|
@ -930,7 +1027,7 @@ function handleFileSelected(event: Event) {
|
|||
reader.onerror = () => {
|
||||
// 检查组件是否仍然挂载
|
||||
if (document.body.contains(canvasContainer.value)) {
|
||||
showToast("读取文件时出错", "error");
|
||||
alertStore?.show("读取文件时出错", "error");
|
||||
isLoading.value = false;
|
||||
}
|
||||
// 清除文件输入
|
||||
|
@ -956,46 +1053,21 @@ function exportDiagram() {
|
|||
a.download = "diagram.json";
|
||||
a.click();
|
||||
// 释放URL对象
|
||||
const timerId = setTimeout(() => {
|
||||
setTimeout(() => {
|
||||
URL.revokeObjectURL(url);
|
||||
// 检查组件是否仍然挂载
|
||||
if (document.body.contains(canvasContainer.value)) {
|
||||
isLoading.value = false;
|
||||
showToast("成功导出diagram文件", "success");
|
||||
alertStore?.show("成功导出diagram文件", "success");
|
||||
}
|
||||
}, 100);
|
||||
|
||||
// 将定时器ID保存起来,以便在组件卸载时清除
|
||||
toastTimers.push(timerId);
|
||||
} catch (error) {
|
||||
console.error("导出diagram文件时出错:", error);
|
||||
showToast("导出diagram文件时出错", "error");
|
||||
alertStore?.show("导出diagram文件时出错", "error");
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 显示通知
|
||||
function showToast(
|
||||
message: string,
|
||||
type: "success" | "error" | "info" = "info",
|
||||
duration = 3000,
|
||||
) {
|
||||
notificationMessage.value = message;
|
||||
notificationType.value = type;
|
||||
showNotification.value = true;
|
||||
|
||||
// 保存定时器ID以便清除
|
||||
const timerId = setTimeout(() => {
|
||||
// 检查组件是否仍然挂载
|
||||
if (document.body.contains(canvasContainer.value)) {
|
||||
showNotification.value = false;
|
||||
}
|
||||
}, duration);
|
||||
|
||||
// 将定时器ID保存起来,以便在组件卸载时清除
|
||||
toastTimers.push(timerId);
|
||||
}
|
||||
|
||||
// --- 生命周期钩子 ---
|
||||
onMounted(async () => {
|
||||
// 设置componentManager的画布引用
|
||||
|
@ -1011,7 +1083,6 @@ onMounted(async () => {
|
|||
getCanvasPosition: () => ({ x: position.x, y: position.y }),
|
||||
getScale: () => scale.value,
|
||||
$el: canvasContainer.value,
|
||||
showToast
|
||||
};
|
||||
componentManager.setCanvasRef(canvasAPI);
|
||||
}
|
||||
|
@ -1033,7 +1104,9 @@ onMounted(async () => {
|
|||
|
||||
// 直接通过componentManager预加载组件模块
|
||||
if (componentManager) {
|
||||
await componentManager.preloadComponentModules(Array.from(componentTypes));
|
||||
await componentManager.preloadComponentModules(
|
||||
Array.from(componentTypes),
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("加载图表数据失败:", error);
|
||||
|
@ -1061,16 +1134,6 @@ function handleKeyDown(e: KeyboardEvent) {
|
|||
}
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
// 清除所有toast定时器
|
||||
toastTimers.forEach((timerId) => clearTimeout(timerId));
|
||||
|
||||
// 重置事件状态
|
||||
isDragEventActive.value = false;
|
||||
isComponentDragEventActive.value = false;
|
||||
isWireCreationEventActive.value = false;
|
||||
});
|
||||
|
||||
// 无加载动画的数据更新方法
|
||||
function updateDiagramDataDirectly(data: DiagramData) {
|
||||
// 检查组件是否仍然挂载
|
||||
|
@ -1085,7 +1148,7 @@ function updateDiagramDataDirectly(data: DiagramData) {
|
|||
emit("diagram-updated", data);
|
||||
}
|
||||
|
||||
// 暴露方法给父组件 - 简化版本,主要用于数据访问
|
||||
// 暴露方法给父组件
|
||||
defineExpose({
|
||||
// 基本数据操作
|
||||
getDiagramData: () => diagramData.value,
|
||||
|
@ -1112,24 +1175,18 @@ defineExpose({
|
|||
emit("diagram-updated", data);
|
||||
|
||||
// 短暂延迟后结束加载状态,以便UI能更新
|
||||
const timerId = setTimeout(() => {
|
||||
setTimeout(() => {
|
||||
// 检查组件是否仍然挂载
|
||||
if (document.body.contains(canvasContainer.value)) {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}, 200);
|
||||
|
||||
// 将定时器ID保存起来,以便在组件卸载时清除
|
||||
toastTimers.push(timerId);
|
||||
});
|
||||
},
|
||||
|
||||
// 画布状态
|
||||
getCanvasPosition: () => ({ x: position.x, y: position.y }),
|
||||
getScale: () => scale.value,
|
||||
|
||||
// 通知系统
|
||||
showToast,
|
||||
});
|
||||
|
||||
// 监视器 - 当图表数据更改时保存
|
||||
|
@ -1254,7 +1311,13 @@ watch(
|
|||
-ms-user-select: none;
|
||||
}
|
||||
|
||||
.component-wrapper :deep(svg *:not([class*="interactive"]):not(rect.glow):not(circle[fill-opacity]):not([fill-opacity])) {
|
||||
.component-wrapper
|
||||
:deep(
|
||||
svg
|
||||
*:not([class*="interactive"]):not(rect.glow):not(
|
||||
circle[fill-opacity]
|
||||
):not([fill-opacity])
|
||||
) {
|
||||
pointer-events: none;
|
||||
/* 非交互元素不接收鼠标事件 */
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue