feat: 完善用户界面,添加绑定与解除绑定的功能

This commit is contained in:
2025-07-12 17:46:23 +08:00
parent 0fb0c4e395
commit f253a33c83
11 changed files with 1654 additions and 185 deletions

View File

@@ -84,6 +84,13 @@
@add-template="handleAddTemplate"
@close="showComponentsMenu = false"
/>
<!-- 实验板申请对话框 -->
<RequestBoardDialog
:open="showRequestBoardDialog"
@close="handleRequestBoardClose"
@success="handleRequestBoardSuccess"
/>
</div>
</template>
@@ -96,10 +103,13 @@ 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();
@@ -108,8 +118,14 @@ const router = useRouter();
// 提供组件管理服务
const componentManager = useProvideComponentManager();
// 设备管理store
const equipments = useEquipments();
const alert = useAlertStore();
// --- 实验板申请对话框 ---
const showRequestBoardDialog = ref(false);
// --- 文档面板控制 ---
const showDocPanel = ref(false);
const documentContent = ref("");
@@ -208,6 +224,62 @@ function updateComponentDirectProp(
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 () => {
// 验证用户身份
@@ -224,6 +296,9 @@ onMounted(async () => {
return;
}
// 检查并初始化用户实验板
await checkAndInitializeBoard();
// 检查是否有例程参数,如果有则自动打开文档面板
if (route.query.tutorial) {
showDocPanel.value = true;

View File

@@ -0,0 +1,179 @@
<template>
<div
v-if="open"
class="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50"
>
<div class="bg-base-100 rounded-lg shadow-xl max-w-md w-full mx-4">
<div class="p-6">
<h2 class="text-xl font-bold mb-4">申请实验板</h2>
<div v-if="!loading && !hasBoard" class="space-y-4">
<p class="text-base-content">
检测到您尚未绑定实验板请申请一个可用的实验板以继续实验
</p>
<div class="flex justify-end space-x-2">
<button
@click="$emit('close')"
class="btn btn-ghost"
:disabled="requesting"
>
取消
</button>
<button
@click="requestBoard"
class="btn btn-primary"
:disabled="requesting"
>
<span
v-if="requesting"
class="loading loading-spinner loading-sm"
></span>
{{ requesting ? "申请中..." : "申请实验板" }}
</button>
</div>
</div>
<div v-else-if="loading" class="text-center py-8">
<span class="loading loading-spinner loading-lg"></span>
<p class="mt-2">检查实验板状态中...</p>
</div>
<div v-else-if="hasBoard" class="space-y-4">
<div class="alert alert-success">
<CheckCircle class="shrink-0 h-6 w-6" />
<span>实验板绑定成功</span>
</div>
<div class="bg-base-200 p-4 rounded">
<h3 class="font-semibold mb-2">实验板信息</h3>
<div class="space-y-1 text-sm">
<p>
<span class="font-medium">名称:</span>
{{ boardInfo?.boardName }}
</p>
<p><span class="font-medium">ID:</span> {{ boardInfo?.id }}</p>
<p>
<span class="font-medium">地址:</span>
{{ boardInfo?.ipAddr }}:{{ boardInfo?.port }}
</p>
<p>
<span class="font-medium">状态:</span>
<span class="badge badge-success">{{
boardInfo?.status === 1 ? "可用" : "忙碌"
}}</span>
</p>
</div>
</div>
<div class="flex justify-end">
<button
v-if="boardInfo"
@click="$emit('success', boardInfo)"
class="btn btn-primary"
>
开始实验
</button>
</div>
</div>
<div v-if="error" class="alert alert-error mt-4">
<XCircle class="shrink-0 h-6 w-6" />
<span>{{ error }}</span>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, watch } from "vue";
import { CheckCircle, XCircle } from "lucide-vue-next";
import { AuthManager } from "@/utils/AuthManager";
import type { Board } from "@/APIClient";
interface Props {
open: boolean;
}
interface Emits {
(e: "close"): void;
(e: "success", board: Board): void;
}
const props = defineProps<Props>();
const emit = defineEmits<Emits>();
const loading = ref(false);
const requesting = ref(false);
const hasBoard = ref(false);
const boardInfo = ref<Board | null>(null);
const error = ref<string>("");
// 监听对话框打开状态,自动检查用户信息
watch(
() => props.open,
async (newOpen) => {
if (newOpen) {
await checkUserBoard();
}
},
);
// 检查用户是否已绑定实验板
async function checkUserBoard() {
loading.value = true;
error.value = "";
hasBoard.value = false;
boardInfo.value = null;
try {
const client = AuthManager.createAuthenticatedDataClient();
const userInfo = await client.getUserInfo();
if (userInfo.boardID && userInfo.boardID.trim() !== "") {
// 用户已绑定实验板,获取实验板信息
try {
const board = await client.getBoardByID(userInfo.boardID);
boardInfo.value = board;
hasBoard.value = true;
} catch (boardError) {
console.error("获取实验板信息失败:", boardError);
error.value = "获取实验板信息失败,请重试";
}
}
} catch (err) {
console.error("检查用户信息失败:", err);
error.value = "检查用户信息失败,请重试";
} finally {
loading.value = false;
}
}
// 申请实验板
async function requestBoard() {
requesting.value = true;
error.value = "";
try {
const client = AuthManager.createAuthenticatedDataClient();
const board = await client.getAvailableBoard();
if (board) {
boardInfo.value = board;
hasBoard.value = true;
} else {
error.value = "当前没有可用的实验板,请稍后重试";
}
} catch (err: any) {
console.error("申请实验板失败:", err);
if (err.status === 404) {
error.value = "当前没有可用的实验板,请稍后重试";
} else {
error.value = "申请实验板失败,请重试";
}
} finally {
requesting.value = false;
}
}
</script>