This repository has been archived on 2025-10-29. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
FPGA_WebLab/src/views/Project/Index.vue

383 lines
11 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="h-screen flex flex-col overflow-hidden">
<div class="flex flex-1 overflow-hidden relative">
<SplitterGroup
id="splitter-group-v"
direction="vertical"
class="w-full h-full"
@layout="handleVerticalSplitterResize"
>
<!-- 使用 v-show 替代 v-if -->
<SplitterPanel
v-show="!isBottomBarFullscreen"
id="splitter-group-v-panel-project"
:default-size="verticalSplitterSize"
>
<SplitterGroup
id="splitter-group-h"
direction="horizontal"
class="w-full h-full"
@layout="handleHorizontalSplitterResize"
>
<!-- 左侧图形化区域 -->
<SplitterPanel
id="splitter-group-h-panel-canvas"
:default-size="horizontalSplitterSize"
:min-size="30"
class="relative bg-base-200 overflow-hidden h-full"
>
<DiagramCanvas
ref="diagramCanvas"
:showDocPanel="showDocPanel"
@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>
<!-- 分割线也使用 v-show -->
<SplitterResizeHandle
v-show="!isBottomBarFullscreen"
id="splitter-group-v-resize-handle"
class="h-2 bg-base-100 hover:bg-primary hover:opacity-70 transition-colors"
/>
<!-- 功能底栏 -->
<SplitterPanel
id="splitter-group-v-panel-bar"
:default-size="isBottomBarFullscreen ? 100 : (100 - verticalSplitterSize)"
:min-size="isBottomBarFullscreen ? 100 : 15"
class="w-full overflow-hidden px-5 pt-3"
>
<BottomBar
:isFullscreen="isBottomBarFullscreen"
@toggle-fullscreen="handleToggleBottomBarFullscreen"
/>
</SplitterPanel>
</SplitterGroup>
</div>
<!-- 元器件选择组件 -->
<ComponentSelector
:open="showComponentsMenu"
@update:open="showComponentsMenu = $event"
@close="showComponentsMenu = false"
/>
<!-- 实验板申请对话框 -->
<RequestBoardDialog
:open="showRequestBoardDialog"
@close="handleRequestBoardClose"
@success="handleRequestBoardSuccess"
/>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, watch } from "vue";
import { useRouter } from "vue-router";
import { useLocalStorage } from '@vueuse/core'; // 添加VueUse导入
import { SplitterGroup, SplitterPanel, SplitterResizeHandle } from "reka-ui";
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 BottomBar from "@/views/Project/BottomBar.vue";
import RequestBoardDialog from "@/views/Project/RequestBoardDialog.vue";
import { useProvideComponentManager } from "@/components/LabCanvas";
import type { DiagramData } from "@/components/LabCanvas";
import { useAlertStore } from "@/components/Alert";
import { AuthManager } from "@/utils/AuthManager";
import { useEquipments } from "@/stores/equipments";
import type { Board } from "@/APIClient";
import { useRoute } from "vue-router";
const route = useRoute();
const router = useRouter();
// 提供组件管理服务
const componentManager = useProvideComponentManager();
// 设备管理store
const equipments = useEquipments();
const alert = useAlertStore();
// --- 使用VueUse保存分栏状态 ---
// 左右分栏比例默认60%
const horizontalSplitterSize = useLocalStorage('project-horizontal-splitter-size', 60);
// 上下分栏比例默认80%
const verticalSplitterSize = useLocalStorage('project-vertical-splitter-size', 80);
// 底栏全屏状态
const isBottomBarFullscreen = useLocalStorage('project-bottom-bar-fullscreen', false);
// 文档面板显示状态
const showDocPanel = useLocalStorage('project-show-doc-panel', false);
function handleToggleBottomBarFullscreen() {
isBottomBarFullscreen.value = !isBottomBarFullscreen.value;
}
// --- 处理分栏大小变化 ---
function handleHorizontalSplitterResize(sizes: number[]) {
if (sizes && sizes.length > 0) {
horizontalSplitterSize.value = sizes[0];
}
}
function handleVerticalSplitterResize(sizes: number[]) {
if (sizes && sizes.length > 0) {
// 只在非全屏状态下保存分栏大小避免全屏时的100%被保存
if (!isBottomBarFullscreen.value) {
verticalSplitterSize.value = sizes[0];
}
}
}
// --- 实验板申请对话框 ---
const showRequestBoardDialog = ref(false);
// --- 文档面板控制 ---
const documentContent = ref("");
// 切换文档面板和属性面板
async function toggleDocPanel() {
showDocPanel.value = !showDocPanel.value;
// 如果切换到文档面板,则获取文档内容
if (showDocPanel.value) {
await loadDocumentContent();
}
}
// 加载文档内容
async function loadDocumentContent() {
try {
// 从路由参数中获取教程ID
const tutorialId = (route.query.tutorial as string) || "02"; // 默认加载02例程
// 构建文档路径
let docPath = `/doc/${tutorialId}/doc.md`;
// 检查当前路径是否包含下划线(例如 02_key 格式)
// 如果不包含,那么使用更新的命名格式
if (!tutorialId.includes("_")) {
docPath = `/doc/${tutorialId}/doc.md`;
}
// 获取文档内容
const response = await fetch(docPath);
if (!response.ok) {
throw new Error(`Failed to load document: ${response.status}`);
}
// 更新文档内容,并替换图片路径
documentContent.value = (await response.text()).replace(
/.\/images/gi,
`/doc/${tutorialId}/images`,
);
} catch (error) {
console.error("加载文档失败:", error);
documentContent.value = "# 文档加载失败\n\n无法加载请求的文档。";
}
}
// --- UI 状态管理 ---
const showComponentsMenu = ref(false);
const diagramCanvas = ref(null);
function openComponentsMenu() {
showComponentsMenu.value = true;
}
// 更新组件属性的方法 - 委托给componentManager
function updateComponentProp(
componentId: string,
propName: string,
value: any,
) {
componentManager.updateComponentProp(componentId, propName, value);
}
// 更新组件的直接属性 - 委托给componentManager
function updateComponentDirectProp(
componentId: string,
propName: string,
value: any,
) {
componentManager.updateComponentDirectProp(componentId, propName, value);
}
// --- 实验板管理 ---
// 检查并初始化用户实验板
async function checkAndInitializeBoard() {
try {
const client = AuthManager.createAuthenticatedDataClient();
const userInfo = await client.getUserInfo();
if (userInfo.boardID && userInfo.boardID.trim() !== '') {
// 用户已绑定实验板获取实验板信息并更新到equipment
try {
const board = await client.getBoardByID(userInfo.boardID);
updateEquipmentFromBoard(board);
alert?.show(`实验板 ${board.boardName} 已连接`, "success");
} catch (boardError) {
console.error('获取实验板信息失败:', boardError);
alert?.show("获取实验板信息失败", "error");
showRequestBoardDialog.value = true;
}
} else {
// 用户未绑定实验板,显示申请对话框
showRequestBoardDialog.value = true;
}
} catch (error) {
console.error('检查用户实验板失败:', error);
alert?.show("检查用户信息失败", "error");
showRequestBoardDialog.value = true;
}
}
// 根据实验板信息更新equipment store
function updateEquipmentFromBoard(board: Board) {
equipments.setAddr(board.ipAddr);
equipments.setPort(board.port);
console.log(`实验板信息已更新到equipment store:`, {
address: board.ipAddr,
port: board.port,
boardName: board.boardName,
boardId: board.id
});
}
// 处理申请实验板对话框关闭
function handleRequestBoardClose() {
showRequestBoardDialog.value = false;
// 如果用户取消申请,可以选择返回上一页或显示警告
router.push('/');
}
// 处理申请实验板成功
function handleRequestBoardSuccess(board: Board) {
showRequestBoardDialog.value = false;
updateEquipmentFromBoard(board);
alert?.show(`实验板 ${board.boardName} 申请成功!`, "success");
}
// --- 生命周期钩子 ---
onMounted(async () => {
// 验证用户身份
try {
const isAuthenticated = await AuthManager.isAuthenticated();
if (!isAuthenticated) {
// 验证失败,跳转到登录页面
router.push('/login');
return;
}
} catch (error) {
console.error('身份验证失败:', error);
router.push('/login');
return;
}
// 检查并初始化用户实验板
await checkAndInitializeBoard();
// 检查是否有例程参数,如果有则自动打开文档面板
if (route.query.tutorial) {
showDocPanel.value = true;
await loadDocumentContent();
}
// 设置画布引用并初始化组件管理器
componentManager.setCanvasRef(diagramCanvas.value);
await componentManager.initialize();
});
</script>
<style scoped lang="postcss">
/* 样式保持不变 */
@import "@/assets/main.css";
.animate-slideRight {
animation: slideRight 0.3s ease-out forwards;
}
@keyframes slideRight {
from {
opacity: 0;
transform: translateX(30px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
/* 确保滚动行为仅在需要时出现 */
html,
body {
overflow: hidden;
height: 100%;
margin: 0;
padding: 0;
}
/* 文档面板样式 */
.doc-panel {
padding: 1.5rem;
max-width: 100%;
margin: 0;
background-color: transparent;
/* 使用透明背景 */
border: none;
/* 确保没有边框 */
}
/* 文档切换按钮样式 */
.doc-toggle-btn {
position: absolute;
top: 10px;
right: 10px;
z-index: 50;
}
/* Markdown渲染样式调整 */
:deep(.markdown-content) {
padding: 1rem;
background-color: hsl(var(--b1));
border-radius: 0.5rem;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
</style>