feat: 添加功能底栏
This commit is contained in:
parent
cbb3543c4a
commit
bbad7388d8
|
@ -21,6 +21,7 @@ declare module 'vue' {
|
||||||
DiagramCanvas: typeof import('./src/components/LabCanvas/DiagramCanvas.vue')['default']
|
DiagramCanvas: typeof import('./src/components/LabCanvas/DiagramCanvas.vue')['default']
|
||||||
Dialog: typeof import('./src/components/Dialog.vue')['default']
|
Dialog: typeof import('./src/components/Dialog.vue')['default']
|
||||||
ETH: typeof import('./src/components/equipments/ETH.vue')['default']
|
ETH: typeof import('./src/components/equipments/ETH.vue')['default']
|
||||||
|
FunctionBar: typeof import('./src/components/FunctionBar.vue')['default']
|
||||||
HDMI: typeof import('./src/components/equipments/HDMI.vue')['default']
|
HDMI: typeof import('./src/components/equipments/HDMI.vue')['default']
|
||||||
IpInputField: typeof import('./src/components/InputField/IpInputField.vue')['default']
|
IpInputField: typeof import('./src/components/InputField/IpInputField.vue')['default']
|
||||||
LabCanvas: typeof import('./src/components/LabCanvasNew/LabCanvas.vue')['default']
|
LabCanvas: typeof import('./src/components/LabCanvasNew/LabCanvas.vue')['default']
|
||||||
|
@ -42,6 +43,11 @@ declare module 'vue' {
|
||||||
RekaSplitterGroup: typeof import('reka-ui')['SplitterGroup']
|
RekaSplitterGroup: typeof import('reka-ui')['SplitterGroup']
|
||||||
RouterLink: typeof import('vue-router')['RouterLink']
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
RouterView: typeof import('vue-router')['RouterView']
|
RouterView: typeof import('vue-router')['RouterView']
|
||||||
|
ScrollAreaCorner: typeof import('reka-ui')['ScrollAreaCorner']
|
||||||
|
ScrollAreaRoot: typeof import('reka-ui')['ScrollAreaRoot']
|
||||||
|
ScrollAreaScrollbar: typeof import('reka-ui')['ScrollAreaScrollbar']
|
||||||
|
ScrollAreaThumb: typeof import('reka-ui')['ScrollAreaThumb']
|
||||||
|
ScrollAreaViewport: typeof import('reka-ui')['ScrollAreaViewport']
|
||||||
SD: typeof import('./src/components/equipments/SD.vue')['default']
|
SD: typeof import('./src/components/equipments/SD.vue')['default']
|
||||||
SevenSegmentDisplay: typeof import('./src/components/equipments/SevenSegmentDisplay.vue')['default']
|
SevenSegmentDisplay: typeof import('./src/components/equipments/SevenSegmentDisplay.vue')['default']
|
||||||
SFP: typeof import('./src/components/equipments/SFP.vue')['default']
|
SFP: typeof import('./src/components/equipments/SFP.vue')['default']
|
||||||
|
@ -52,6 +58,11 @@ declare module 'vue' {
|
||||||
SplitterPanel: typeof import('reka-ui')['SplitterPanel']
|
SplitterPanel: typeof import('reka-ui')['SplitterPanel']
|
||||||
SplitterResizeHandle: typeof import('reka-ui')['SplitterResizeHandle']
|
SplitterResizeHandle: typeof import('reka-ui')['SplitterResizeHandle']
|
||||||
Switch: typeof import('./src/components/equipments/Switch.vue')['default']
|
Switch: typeof import('./src/components/equipments/Switch.vue')['default']
|
||||||
|
TabsContent: typeof import('reka-ui')['TabsContent']
|
||||||
|
TabsIndicator: typeof import('reka-ui')['TabsIndicator']
|
||||||
|
TabsList: typeof import('reka-ui')['TabsList']
|
||||||
|
TabsRoot: typeof import('reka-ui')['TabsRoot']
|
||||||
|
TabsTrigger: typeof import('reka-ui')['TabsTrigger']
|
||||||
ThemeControlButton: typeof import('./src/components/ThemeControlButton.vue')['default']
|
ThemeControlButton: typeof import('./src/components/ThemeControlButton.vue')['default']
|
||||||
ThemeControlToggle: typeof import('./src/components/ThemeControlToggle.vue')['default']
|
ThemeControlToggle: typeof import('./src/components/ThemeControlToggle.vue')['default']
|
||||||
TutorialCarousel: typeof import('./src/components/TutorialCarousel.vue')['default']
|
TutorialCarousel: typeof import('./src/components/TutorialCarousel.vue')['default']
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
<template>
|
||||||
|
<div class="h-full flex flex-col">
|
||||||
|
<div class="tabs tabs-box flex-shrink-0">
|
||||||
|
<label class="tab">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="function-bar"
|
||||||
|
id="1"
|
||||||
|
checked
|
||||||
|
@change="handleTabChange"
|
||||||
|
/>
|
||||||
|
<TerminalIcon class="icon" />
|
||||||
|
日志终端
|
||||||
|
</label>
|
||||||
|
<label class="tab">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="function-bar"
|
||||||
|
id="2"
|
||||||
|
@change="handleTabChange"
|
||||||
|
/>
|
||||||
|
<VideoIcon class="icon" />
|
||||||
|
HTTP视频流
|
||||||
|
</label>
|
||||||
|
<label class="tab">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="function-bar"
|
||||||
|
id="3"
|
||||||
|
@change="handleTabChange"
|
||||||
|
/>
|
||||||
|
<SquareActivityIcon class="icon" />
|
||||||
|
示波器
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 overflow-hidden">
|
||||||
|
<div v-if="checkID === 1" class="h-full overflow-y-auto"></div>
|
||||||
|
<div v-else-if="checkID === 2" class="h-full overflow-y-auto">
|
||||||
|
<VideoStreamView />
|
||||||
|
</div>
|
||||||
|
<div v-else-if="checkID === 3" class="h-full overflow-y-auto">
|
||||||
|
<OscilloscopeView />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { VideoIcon, SquareActivityIcon, TerminalIcon } from "lucide-vue-next";
|
||||||
|
import VideoStreamView from "@/views/VideoStreamView.vue";
|
||||||
|
import OscilloscopeView from "@/views/OscilloscopeView.vue";
|
||||||
|
import { isNull, toNumber } from "lodash";
|
||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
|
const checkID = ref(1);
|
||||||
|
|
||||||
|
function handleTabChange(event: Event) {
|
||||||
|
const target = event.currentTarget as HTMLInputElement;
|
||||||
|
if (isNull(target)) return;
|
||||||
|
|
||||||
|
checkID.value = toNumber(target.id);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
@import "../assets/main.css";
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
@apply h-4 w-4 opacity-70 mr-1.5;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -50,18 +50,6 @@
|
||||||
测试功能
|
测试功能
|
||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li class="my-1 hover:translate-x-1 transition-all duration-300">
|
|
||||||
<router-link to="/video-stream" class="text-base font-medium">
|
|
||||||
<Video class="icon" />
|
|
||||||
HTTP视频流
|
|
||||||
</router-link>
|
|
||||||
</li>
|
|
||||||
<li class="my-1 hover:translate-x-1 transition-all duration-300">
|
|
||||||
<router-link to="/oscilloscope" class="text-base font-medium">
|
|
||||||
<SquareActivity class="icon" />
|
|
||||||
示波器
|
|
||||||
</router-link>
|
|
||||||
</li>
|
|
||||||
<li class="my-1 hover:translate-x-1 transition-all duration-300">
|
<li class="my-1 hover:translate-x-1 transition-all duration-300">
|
||||||
<router-link to="/markdown-test" class="text-base font-medium">
|
<router-link to="/markdown-test" class="text-base font-medium">
|
||||||
<FileText class="icon" />
|
<FileText class="icon" />
|
||||||
|
|
|
@ -19,8 +19,6 @@ const router = createRouter({
|
||||||
{ path: "/test", name: "test", component: TestView },
|
{ path: "/test", name: "test", component: TestView },
|
||||||
{ path: "/user", name: "user", component: UserView },
|
{ path: "/user", name: "user", component: UserView },
|
||||||
{ path: "/admin", name: "admin", component: AdminView },
|
{ path: "/admin", name: "admin", component: AdminView },
|
||||||
{ path: "/video-stream", name: "videoStream", component: VideoStreamView },
|
|
||||||
{ path: "/oscilloscope", name: "oscilloscope", component: OscilloscopeView },
|
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,41 +1,89 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="h-screen flex flex-col overflow-hidden">
|
<div class="h-screen flex flex-col overflow-hidden">
|
||||||
<div class="flex flex-1 overflow-hidden relative">
|
<div class="flex flex-1 overflow-hidden relative">
|
||||||
<SplitterGroup id="splitter-group" direction="horizontal" class="w-full h-full">
|
<SplitterGroup
|
||||||
<!-- 左侧图形化区域 -->
|
id="splitter-group-v"
|
||||||
<SplitterPanel
|
direction="vertical"
|
||||||
id="splitter-group-panel-canvas"
|
class="w-full h-full"
|
||||||
:default-size="60"
|
>
|
||||||
:min-size="30"
|
<SplitterPanel id="splitter-group-v-panel-project">
|
||||||
class="relative bg-base-200 overflow-hidden h-full"
|
<SplitterGroup
|
||||||
>
|
id="splitter-group-h"
|
||||||
<DiagramCanvas ref="diagramCanvas" :showDocPanel="showDocPanel"
|
direction="horizontal"
|
||||||
@diagram-updated="handleDiagramUpdated" @open-components="openComponentsMenu"
|
class="w-full h-full"
|
||||||
@toggle-doc-panel="toggleDocPanel" />
|
>
|
||||||
|
<!-- 左侧图形化区域 -->
|
||||||
|
<SplitterPanel
|
||||||
|
id="splitter-group-h-panel-canvas"
|
||||||
|
:default-size="60"
|
||||||
|
:min-size="30"
|
||||||
|
class="relative bg-base-200 overflow-hidden h-full"
|
||||||
|
>
|
||||||
|
<DiagramCanvas
|
||||||
|
ref="diagramCanvas"
|
||||||
|
:showDocPanel="showDocPanel"
|
||||||
|
@diagram-updated="handleDiagramUpdated"
|
||||||
|
@open-components="openComponentsMenu"
|
||||||
|
@toggle-doc-panel="toggleDocPanel"
|
||||||
|
/>
|
||||||
|
</SplitterPanel>
|
||||||
|
<!-- 拖拽分割线 -->
|
||||||
|
<SplitterResizeHandle
|
||||||
|
id="splitter-group-h-resize-handle"
|
||||||
|
class="w-2 bg-base-100 hover:bg-primary hover:opacity-70 transition-colors"
|
||||||
|
/>
|
||||||
|
<!-- 右侧编辑区域 -->
|
||||||
|
<SplitterPanel
|
||||||
|
id="splitter-group-h-panel-properties"
|
||||||
|
:min-size="20"
|
||||||
|
class="bg-base-200 h-full overflow-hidden flex flex-col"
|
||||||
|
>
|
||||||
|
<div class="overflow-y-auto flex-1">
|
||||||
|
<!-- 使用条件渲染显示不同的面板 -->
|
||||||
|
<PropertyPanel
|
||||||
|
v-show="!showDocPanel"
|
||||||
|
:componentData="componentManager.selectedComponentData.value"
|
||||||
|
:componentConfig="
|
||||||
|
componentManager.selectedComponentConfig.value
|
||||||
|
"
|
||||||
|
@updateProp="updateComponentProp"
|
||||||
|
@updateDirectProp="updateComponentDirectProp"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
v-show="showDocPanel"
|
||||||
|
class="doc-panel overflow-y-auto h-full"
|
||||||
|
>
|
||||||
|
<MarkdownRenderer :content="documentContent" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</SplitterPanel>
|
||||||
|
</SplitterGroup>
|
||||||
</SplitterPanel>
|
</SplitterPanel>
|
||||||
<!-- 拖拽分割线 -->
|
|
||||||
<SplitterResizeHandle id="splitter-group-resize-handle" class="w-2 bg-base-100 hover:bg-primary hover:opacity-70 transition-colors" />
|
<SplitterResizeHandle
|
||||||
<!-- 右侧编辑区域 -->
|
id="splitter-group-v-resize-handle"
|
||||||
|
class="h-2 bg-base-100 hover:bg-primary hover:opacity-70 transition-colors"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 功能底栏 -->
|
||||||
<SplitterPanel
|
<SplitterPanel
|
||||||
id="splitter-group-panel-properties"
|
id="splitter-group-v-panel-bar"
|
||||||
:min-size="20"
|
:default-size="20"
|
||||||
class="bg-base-200 h-full overflow-hidden flex flex-col"
|
:min-size="15"
|
||||||
|
class="w-full overflow-hidden"
|
||||||
>
|
>
|
||||||
<div class="overflow-y-auto flex-1">
|
<FunctionBar class="mx-4 mt-1" />
|
||||||
<!-- 使用条件渲染显示不同的面板 -->
|
|
||||||
<PropertyPanel v-show="!showDocPanel" :componentData="componentManager.selectedComponentData.value"
|
|
||||||
:componentConfig="componentManager.selectedComponentConfig.value" @updateProp="updateComponentProp"
|
|
||||||
@updateDirectProp="updateComponentDirectProp" />
|
|
||||||
<div v-show="showDocPanel" class="doc-panel overflow-y-auto h-full">
|
|
||||||
<MarkdownRenderer :content="documentContent" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</SplitterPanel>
|
</SplitterPanel>
|
||||||
</SplitterGroup>
|
</SplitterGroup>
|
||||||
</div>
|
</div>
|
||||||
<!-- 元器件选择组件 -->
|
<!-- 元器件选择组件 -->
|
||||||
<ComponentSelector :open="showComponentsMenu" @update:open="showComponentsMenu = $event"
|
<ComponentSelector
|
||||||
@add-component="handleAddComponent" @add-template="handleAddTemplate" @close="showComponentsMenu = false" />
|
:open="showComponentsMenu"
|
||||||
|
@update:open="showComponentsMenu = $event"
|
||||||
|
@add-component="handleAddComponent"
|
||||||
|
@add-template="handleAddTemplate"
|
||||||
|
@close="showComponentsMenu = false"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -46,16 +94,19 @@ import DiagramCanvas from "@/components/LabCanvas/DiagramCanvas.vue";
|
||||||
import ComponentSelector from "@/components/LabCanvas/ComponentSelector.vue";
|
import ComponentSelector from "@/components/LabCanvas/ComponentSelector.vue";
|
||||||
import PropertyPanel from "@/components/PropertyPanel.vue";
|
import PropertyPanel from "@/components/PropertyPanel.vue";
|
||||||
import MarkdownRenderer from "@/components/MarkdownRenderer.vue";
|
import MarkdownRenderer from "@/components/MarkdownRenderer.vue";
|
||||||
|
import FunctionBar from "@/components/FunctionBar.vue";
|
||||||
import { useProvideComponentManager } from "@/components/LabCanvas";
|
import { useProvideComponentManager } from "@/components/LabCanvas";
|
||||||
import type { DiagramData, DiagramPart } from "@/components/LabCanvas";
|
import type { DiagramData } from "@/components/LabCanvas";
|
||||||
|
import { useAlertStore } from "@/components/Alert";
|
||||||
|
|
||||||
// 获取路由参数
|
|
||||||
import { useRoute } from "vue-router";
|
import { useRoute } from "vue-router";
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|
||||||
// 提供组件管理服务
|
// 提供组件管理服务
|
||||||
const componentManager = useProvideComponentManager();
|
const componentManager = useProvideComponentManager();
|
||||||
|
|
||||||
|
const alert = useAlertStore();
|
||||||
|
|
||||||
// --- 文档面板控制 ---
|
// --- 文档面板控制 ---
|
||||||
const showDocPanel = ref(false);
|
const showDocPanel = ref(false);
|
||||||
const documentContent = ref("");
|
const documentContent = ref("");
|
||||||
|
@ -106,34 +157,6 @@ async function loadDocumentContent() {
|
||||||
const showComponentsMenu = ref(false);
|
const showComponentsMenu = ref(false);
|
||||||
const diagramCanvas = ref(null);
|
const diagramCanvas = ref(null);
|
||||||
|
|
||||||
// --- 页面动画和通知 ---
|
|
||||||
const showNotification = ref(false);
|
|
||||||
const notificationMessage = ref("");
|
|
||||||
const notificationType = ref<"success" | "error" | "info">("info");
|
|
||||||
|
|
||||||
function showToast(
|
|
||||||
message: string,
|
|
||||||
type: "success" | "error" | "info" = "info",
|
|
||||||
duration = 3000,
|
|
||||||
) {
|
|
||||||
const canvasInstance = diagramCanvas.value as any;
|
|
||||||
if (canvasInstance && canvasInstance.showToast) {
|
|
||||||
canvasInstance.showToast(message, type, duration);
|
|
||||||
} else {
|
|
||||||
// 后备方案:使用原来的通知系统
|
|
||||||
notificationMessage.value = message;
|
|
||||||
notificationType.value = type;
|
|
||||||
showNotification.value = true;
|
|
||||||
|
|
||||||
// 设置自动消失
|
|
||||||
setTimeout(() => {
|
|
||||||
showNotification.value = false;
|
|
||||||
}, duration);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- 事件处理器(委托给组件管理器) ---
|
|
||||||
|
|
||||||
function openComponentsMenu() {
|
function openComponentsMenu() {
|
||||||
showComponentsMenu.value = true;
|
showComponentsMenu.value = true;
|
||||||
}
|
}
|
||||||
|
@ -155,7 +178,7 @@ async function handleAddTemplate(templateData: {
|
||||||
}) {
|
}) {
|
||||||
const result = await componentManager.addTemplate(templateData);
|
const result = await componentManager.addTemplate(templateData);
|
||||||
if (result) {
|
if (result) {
|
||||||
showToast(result.message, result.success ? "success" : "error");
|
alert?.show(result.message, result.success ? "success" : "error");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue