feat: 添加功能底栏

This commit is contained in:
SikongJueluo 2025-07-09 20:48:11 +08:00
parent cbb3543c4a
commit bbad7388d8
No known key found for this signature in database
5 changed files with 163 additions and 73 deletions

11
components.d.ts vendored
View File

@ -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']

View File

@ -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>

View File

@ -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" />

View File

@ -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 },
], ],
}); });

View File

@ -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");
} }
} }