feat: 添加功能底栏
This commit is contained in:
		
							
								
								
									
										11
									
								
								components.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										11
									
								
								components.d.ts
									
									
									
									
										vendored
									
									
								
							@@ -21,6 +21,7 @@ declare module 'vue' {
 | 
			
		||||
    DiagramCanvas: typeof import('./src/components/LabCanvas/DiagramCanvas.vue')['default']
 | 
			
		||||
    Dialog: typeof import('./src/components/Dialog.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']
 | 
			
		||||
    IpInputField: typeof import('./src/components/InputField/IpInputField.vue')['default']
 | 
			
		||||
    LabCanvas: typeof import('./src/components/LabCanvasNew/LabCanvas.vue')['default']
 | 
			
		||||
@@ -42,6 +43,11 @@ declare module 'vue' {
 | 
			
		||||
    RekaSplitterGroup: typeof import('reka-ui')['SplitterGroup']
 | 
			
		||||
    RouterLink: typeof import('vue-router')['RouterLink']
 | 
			
		||||
    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']
 | 
			
		||||
    SevenSegmentDisplay: typeof import('./src/components/equipments/SevenSegmentDisplay.vue')['default']
 | 
			
		||||
    SFP: typeof import('./src/components/equipments/SFP.vue')['default']
 | 
			
		||||
@@ -52,6 +58,11 @@ declare module 'vue' {
 | 
			
		||||
    SplitterPanel: typeof import('reka-ui')['SplitterPanel']
 | 
			
		||||
    SplitterResizeHandle: typeof import('reka-ui')['SplitterResizeHandle']
 | 
			
		||||
    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']
 | 
			
		||||
    ThemeControlToggle: typeof import('./src/components/ThemeControlToggle.vue')['default']
 | 
			
		||||
    TutorialCarousel: typeof import('./src/components/TutorialCarousel.vue')['default']
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										70
									
								
								src/components/FunctionBar.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								src/components/FunctionBar.vue
									
									
									
									
									
										Normal 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>
 | 
			
		||||
@@ -50,18 +50,6 @@
 | 
			
		||||
              测试功能
 | 
			
		||||
            </router-link>
 | 
			
		||||
          </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">
 | 
			
		||||
            <router-link to="/markdown-test" class="text-base font-medium">
 | 
			
		||||
              <FileText class="icon" />
 | 
			
		||||
 
 | 
			
		||||
@@ -19,8 +19,6 @@ const router = createRouter({
 | 
			
		||||
    { path: "/test", name: "test", component: TestView },
 | 
			
		||||
    { path: "/user", name: "user", component: UserView },
 | 
			
		||||
    { path: "/admin", name: "admin", component: AdminView },
 | 
			
		||||
    { path: "/video-stream", name: "videoStream", component: VideoStreamView },
 | 
			
		||||
    { path: "/oscilloscope", name: "oscilloscope", component: OscilloscopeView },
 | 
			
		||||
  ],
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,41 +1,89 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="h-screen flex flex-col overflow-hidden">
 | 
			
		||||
    <div class="flex flex-1 overflow-hidden relative">
 | 
			
		||||
      <SplitterGroup id="splitter-group" direction="horizontal" class="w-full h-full">
 | 
			
		||||
        <!-- 左侧图形化区域 -->
 | 
			
		||||
        <SplitterPanel
 | 
			
		||||
          id="splitter-group-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" />
 | 
			
		||||
      <SplitterGroup
 | 
			
		||||
        id="splitter-group-v"
 | 
			
		||||
        direction="vertical"
 | 
			
		||||
        class="w-full h-full"
 | 
			
		||||
      >
 | 
			
		||||
        <SplitterPanel id="splitter-group-v-panel-project">
 | 
			
		||||
          <SplitterGroup
 | 
			
		||||
            id="splitter-group-h"
 | 
			
		||||
            direction="horizontal"
 | 
			
		||||
            class="w-full h-full"
 | 
			
		||||
          >
 | 
			
		||||
            <!-- 左侧图形化区域 -->
 | 
			
		||||
            <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>
 | 
			
		||||
        <!-- 拖拽分割线 -->
 | 
			
		||||
        <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
 | 
			
		||||
          id="splitter-group-panel-properties"
 | 
			
		||||
          :min-size="20"
 | 
			
		||||
          class="bg-base-200 h-full overflow-hidden flex flex-col"
 | 
			
		||||
          id="splitter-group-v-panel-bar"
 | 
			
		||||
          :default-size="20"
 | 
			
		||||
          :min-size="15"
 | 
			
		||||
          class="w-full overflow-hidden"
 | 
			
		||||
        >
 | 
			
		||||
          <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>
 | 
			
		||||
          <FunctionBar class="mx-4 mt-1" />
 | 
			
		||||
        </SplitterPanel>
 | 
			
		||||
      </SplitterGroup>
 | 
			
		||||
    </div>
 | 
			
		||||
    <!-- 元器件选择组件 -->
 | 
			
		||||
    <ComponentSelector :open="showComponentsMenu" @update:open="showComponentsMenu = $event"
 | 
			
		||||
      @add-component="handleAddComponent" @add-template="handleAddTemplate" @close="showComponentsMenu = false" />
 | 
			
		||||
    <ComponentSelector
 | 
			
		||||
      :open="showComponentsMenu"
 | 
			
		||||
      @update:open="showComponentsMenu = $event"
 | 
			
		||||
      @add-component="handleAddComponent"
 | 
			
		||||
      @add-template="handleAddTemplate"
 | 
			
		||||
      @close="showComponentsMenu = false"
 | 
			
		||||
    />
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
@@ -46,16 +94,19 @@ import DiagramCanvas from "@/components/LabCanvas/DiagramCanvas.vue";
 | 
			
		||||
import ComponentSelector from "@/components/LabCanvas/ComponentSelector.vue";
 | 
			
		||||
import PropertyPanel from "@/components/PropertyPanel.vue";
 | 
			
		||||
import MarkdownRenderer from "@/components/MarkdownRenderer.vue";
 | 
			
		||||
import FunctionBar from "@/components/FunctionBar.vue";
 | 
			
		||||
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";
 | 
			
		||||
const route = useRoute();
 | 
			
		||||
 | 
			
		||||
// 提供组件管理服务
 | 
			
		||||
const componentManager = useProvideComponentManager();
 | 
			
		||||
 | 
			
		||||
const alert = useAlertStore();
 | 
			
		||||
 | 
			
		||||
// --- 文档面板控制 ---
 | 
			
		||||
const showDocPanel = ref(false);
 | 
			
		||||
const documentContent = ref("");
 | 
			
		||||
@@ -106,34 +157,6 @@ async function loadDocumentContent() {
 | 
			
		||||
const showComponentsMenu = ref(false);
 | 
			
		||||
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() {
 | 
			
		||||
  showComponentsMenu.value = true;
 | 
			
		||||
}
 | 
			
		||||
@@ -155,7 +178,7 @@ async function handleAddTemplate(templateData: {
 | 
			
		||||
}) {
 | 
			
		||||
  const result = await componentManager.addTemplate(templateData);
 | 
			
		||||
  if (result) {
 | 
			
		||||
    showToast(result.message, result.success ? "success" : "error");
 | 
			
		||||
    alert?.show(result.message, result.success ? "success" : "error");
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user