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/User/AddBoardDialog.vue

441 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>
<dialog class="modal" :class="{ 'modal-open': visible }">
<div class="modal-box w-96 max-w-md">
<h3 class="text-lg font-bold mb-4">新增实验板</h3>
<!-- 步骤1: 输入板卡名称 -->
<div v-if="currentStep === 'input'" class="space-y-4">
<form @submit.prevent="handleSubmit">
<!-- 实验板名称 -->
<div class="form-control">
<label class="label">
<span class="label-text"
>实验板名称 <span class="text-error">*</span></span
>
</label>
<input
v-model="form.name"
type="text"
placeholder="请输入实验板名称"
class="input input-bordered"
:class="{ 'input-error': errors.name }"
required
/>
<label v-if="errors.name" class="label">
<span class="label-text-alt text-error">{{ errors.name }}</span>
</label>
</div>
<!-- 操作按钮 -->
<div class="modal-action">
<button
type="button"
class="btn btn-ghost"
@click="handleCancel"
:disabled="isSubmitting"
>
取消
</button>
<button
type="submit"
class="btn btn-primary"
:class="{ loading: isSubmitting }"
:disabled="isSubmitting"
>
{{ isSubmitting ? "添加中..." : "确认添加" }}
</button>
</div>
</form>
</div>
<!-- 步骤2: 等待配对 -->
<div v-else-if="currentStep === 'pairing'" class="space-y-4">
<div class="text-center">
<div class="alert alert-info">
<div class="flex items-center">
<svg
class="w-5 h-5 mr-2"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<span>请打开实验板配对模式</span>
</div>
</div>
</div>
<!-- 操作按钮 -->
<div class="modal-action">
<button
type="button"
class="btn btn-ghost"
@click="handleCancelPairing"
:disabled="isConfiguring"
>
取消
</button>
<button
type="button"
class="btn btn-primary"
@click="handlePairingConfirm"
:disabled="isConfiguring"
>
已开启
</button>
</div>
</div>
<!-- 步骤3: 配置网络 -->
<div v-else-if="currentStep === 'configuring'" class="space-y-4">
<div class="text-center">
<div class="alert alert-warning">
<div class="flex items-center">
<div class="loading loading-spinner loading-sm mr-2"></div>
<span>正在分配网络...</span>
</div>
</div>
</div>
</div>
<!-- 步骤4: 显示配置结果 -->
<div v-else-if="currentStep === 'result'" class="space-y-4">
<div class="text-center">
<div class="alert alert-success">
<div class="flex items-center">
<svg
class="w-5 h-5 mr-2"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M5 13l4 4L19 7"
/>
</svg>
<span>实验板配置成功</span>
</div>
</div>
</div>
<!-- 网络配置信息 -->
<div v-if="networkConfig" class="space-y-2">
<h4 class="font-semibold">网络配置信息</h4>
<div class="bg-base-200 p-3 rounded">
<div class="text-sm space-y-1">
<div>
<span class="font-medium">主机IP:</span>
{{ networkConfig.hostIP }}
</div>
<div>
<span class="font-medium">板卡IP:</span>
{{ networkConfig.boardIP }}
</div>
<div>
<span class="font-medium">主机MAC:</span>
{{ networkConfig.hostMAC }}
</div>
<div>
<span class="font-medium">板卡MAC:</span>
{{ networkConfig.boardMAC }}
</div>
</div>
</div>
</div>
<!-- 操作按钮 -->
<div class="modal-action">
<button type="button" class="btn btn-primary" @click="handleSuccess">
确认
</button>
</div>
</div>
</div>
<!-- 点击背景关闭 -->
<form method="dialog" class="modal-backdrop">
<button type="button" @click="handleCancel">close</button>
</form>
</dialog>
</template>
<script lang="ts" setup>
import { ref, reactive, watch } from "vue";
import { AuthManager } from "../../utils/AuthManager";
import { useAlertStore } from "../../components/Alert";
import {
BoardStatus,
DataClient,
NetConfigClient,
type NetworkConfigDto,
} from "../../APIClient";
import { useRequiredInjection } from "@/utils/Common";
import { useBoardManager } from "@/utils/BoardManager";
// Props 和 Emits
interface Props {
visible: boolean;
}
interface Emits {
(e: "update:visible", value: boolean): void;
(e: "success"): void;
}
const props = defineProps<Props>();
const emit = defineEmits<Emits>();
// 使用 Alert
const alertStore = useAlertStore();
const boardManager = useRequiredInjection(useBoardManager);
// 当前步骤
const currentStep = ref<"input" | "pairing" | "configuring" | "result">(
"input",
);
// 表单数据
const form = reactive({
name: "Board1",
});
// 表单错误
const errors = reactive({
name: "",
});
// 状态
const isSubmitting = ref(false);
const isConfiguring = ref(false);
// 添加的板卡信息
const addedBoardId = ref<string>("");
const networkConfig = ref<NetworkConfigDto | null>(null);
// 验证表单
function validateForm(): boolean {
// 清空之前的错误
errors.name = "";
let isValid = true;
// 验证名称
if (!form.name.trim()) {
errors.name = "请输入实验板名称";
isValid = false;
} else if (form.name.trim().length < 2) {
errors.name = "实验板名称至少需要2个字符";
isValid = false;
} else if (form.name.trim().length > 50) {
errors.name = "实验板名称不能超过50个字符";
isValid = false;
}
return isValid;
}
// 重置表单
function resetForm() {
form.name = "Board1";
errors.name = "";
currentStep.value = "input";
addedBoardId.value = "";
networkConfig.value = null;
}
// 处理取消
function handleCancel() {
if (!isSubmitting.value && !isConfiguring.value) {
emit("update:visible", false);
resetForm();
}
}
// 处理提交
async function handleSubmit() {
if (!validateForm()) {
return;
}
isSubmitting.value = true;
try {
const dataClient = AuthManager.createClient(DataClient);
// 添加板卡到数据库
const boardId = await dataClient.addBoard(form.name.trim());
if (boardId) {
addedBoardId.value = boardId;
currentStep.value = "pairing";
alertStore?.success("板卡添加成功,请开启配对模式");
} else {
alertStore?.error("板卡添加失败");
}
} catch (error) {
console.error("添加实验板失败:", error);
alertStore?.error("添加实验板失败");
} finally {
isSubmitting.value = false;
}
}
// 处理取消配对
async function handleCancelPairing() {
if (!addedBoardId.value) return;
try {
const dataClient = AuthManager.createClient(DataClient);
// 删除添加的板卡
await dataClient.deleteBoard(addedBoardId.value);
alertStore?.info("已取消添加实验板");
emit("update:visible", false);
resetForm();
} catch (error) {
console.error("删除板卡失败:", error);
alertStore?.error("删除板卡失败");
}
}
// 处理配对确认
async function handlePairingConfirm() {
if (!addedBoardId.value) return;
isConfiguring.value = true;
currentStep.value = "configuring";
try {
// 通过 AuthManager 获取认证的客户端
const dataClient = AuthManager.createClient(DataClient);
const netConfigClient = AuthManager.createClient(NetConfigClient);
// 获取数据库中对应分配的板卡信息
const boardInfo = await dataClient.getBoardByID(addedBoardId.value);
if (!boardInfo) {
throw new Error("无法获取板卡信息");
}
// 更新主机IP和主机MAC
await netConfigClient.updateHostIP();
await netConfigClient.updateHostMAC();
// 设置板卡IP和MAC
await netConfigClient.setBoardIP(boardInfo.ipAddr);
await netConfigClient.setBoardMAC(boardInfo.macAddr);
// 更新板卡状态为可用
if (
(await dataClient.updateBoardStatus(
boardInfo.id,
BoardStatus.Available,
)) != 1
) {
throw new Error("无法更新板卡状态");
}
if (!(await boardManager.getAllBoards()).success) {
alertStore?.error("无法获取板卡列表");
}
// 获取实验板网络信息
const networkInfo = await netConfigClient.getNetworkConfig();
if (networkInfo) {
networkConfig.value = networkInfo;
currentStep.value = "result";
alertStore?.success("实验板配置成功");
} else {
throw new Error("无法获取网络配置信息");
}
} catch (error) {
console.error("配置实验板失败:", error);
alertStore?.error("配置实验板失败");
// 配置失败,删除数据库中的板卡信息
try {
const dataClient = AuthManager.createClient(DataClient);
await dataClient.deleteBoard(addedBoardId.value);
} catch (deleteError) {
console.error("删除板卡失败:", deleteError);
}
// 返回输入步骤
currentStep.value = "input";
} finally {
isConfiguring.value = false;
}
}
// 处理成功
function handleSuccess() {
emit("success");
emit("update:visible", false);
resetForm();
}
// 监听对话框显示状态,重置表单
watch(
() => props.visible,
(newVisible) => {
if (newVisible) {
resetForm();
}
},
);
</script>
<style scoped lang="postcss">
@import "@/assets/main.css";
.form-control {
@apply w-full;
}
.label-text {
@apply font-medium;
}
.input-error {
@apply border-error;
}
.text-error {
@apply text-red-500;
}
.loading {
@apply opacity-50 cursor-not-allowed;
}
.alert {
@apply rounded-lg p-4;
}
.alert-info {
@apply bg-blue-50 text-blue-800 border border-blue-200;
}
.alert-warning {
@apply bg-yellow-50 text-yellow-800 border border-yellow-200;
}
.alert-success {
@apply bg-green-50 text-green-800 border border-green-200;
}
</style>