fea: 前端完成动态ip与动态mac的适配
This commit is contained in:
		@@ -10,6 +10,7 @@ import {
 | 
			
		||||
  TutorialClient,
 | 
			
		||||
  UDPClient,
 | 
			
		||||
  LogicAnalyzerClient,
 | 
			
		||||
  NetConfigClient,
 | 
			
		||||
} from "@/APIClient";
 | 
			
		||||
 | 
			
		||||
// 支持的客户端类型联合类型
 | 
			
		||||
@@ -24,7 +25,8 @@ type SupportedClient =
 | 
			
		||||
  | RemoteUpdateClient
 | 
			
		||||
  | TutorialClient
 | 
			
		||||
  | LogicAnalyzerClient
 | 
			
		||||
  | UDPClient;
 | 
			
		||||
  | UDPClient
 | 
			
		||||
  | NetConfigClient;
 | 
			
		||||
 | 
			
		||||
export class AuthManager {
 | 
			
		||||
  // 存储token到localStorage
 | 
			
		||||
@@ -156,6 +158,10 @@ export class AuthManager {
 | 
			
		||||
  public static createAuthenticatedLogicAnalyzerClient(): LogicAnalyzerClient {
 | 
			
		||||
    return AuthManager.createAuthenticatedClient(LogicAnalyzerClient);
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  public static createAuthenticatedNetConfigClient(): NetConfigClient {
 | 
			
		||||
    return AuthManager.createAuthenticatedClient(NetConfigClient);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 登录函数
 | 
			
		||||
  public static async login(
 | 
			
		||||
 
 | 
			
		||||
@@ -74,8 +74,6 @@ const [useProvideBoardManager, useBoardManager] = createInjectionState(() => {
 | 
			
		||||
  // 新增板卡(管理员权限)
 | 
			
		||||
  async function addBoard(
 | 
			
		||||
    name: string,
 | 
			
		||||
    ipAddr: string,
 | 
			
		||||
    port: number,
 | 
			
		||||
  ): Promise<{ success: boolean; error?: string; boardId?: string }> {
 | 
			
		||||
    try {
 | 
			
		||||
      // 验证管理员权限
 | 
			
		||||
@@ -86,19 +84,19 @@ const [useProvideBoardManager, useBoardManager] = createInjectionState(() => {
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // 验证输入参数
 | 
			
		||||
      if (!name || !ipAddr || !port) {
 | 
			
		||||
        console.error("参数验证失败", { name, ipAddr, port });
 | 
			
		||||
      if (!name) {
 | 
			
		||||
        console.error("参数验证失败", { name });
 | 
			
		||||
        return { success: false, error: "参数不完整" };
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const client = AuthManager.createAuthenticatedDataClient();
 | 
			
		||||
      const boardId = await client.addBoard(name, ipAddr, port);
 | 
			
		||||
      const boardId = await client.addBoard(name);
 | 
			
		||||
 | 
			
		||||
      if (boardId) {
 | 
			
		||||
        console.log("新增板卡成功", { boardId, name, ipAddr, port });
 | 
			
		||||
        console.log("新增板卡成功", { boardId, name});
 | 
			
		||||
        // 刷新板卡列表
 | 
			
		||||
        await getAllBoards();
 | 
			
		||||
        return { success: true};
 | 
			
		||||
        return { success: true };
 | 
			
		||||
      } else {
 | 
			
		||||
        console.error("新增板卡失败:返回ID为空");
 | 
			
		||||
        return { success: false, error: "新增板卡失败" };
 | 
			
		||||
@@ -116,7 +114,9 @@ const [useProvideBoardManager, useBoardManager] = createInjectionState(() => {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 删除板卡(管理员权限)
 | 
			
		||||
  async function deleteBoard(boardId: string): Promise<{ success: boolean; error?: string }> {
 | 
			
		||||
  async function deleteBoard(
 | 
			
		||||
    boardId: string,
 | 
			
		||||
  ): Promise<{ success: boolean; error?: string }> {
 | 
			
		||||
    try {
 | 
			
		||||
      // 验证管理员权限
 | 
			
		||||
      const hasAdminAuth = await AuthManager.verifyAdminAuth();
 | 
			
		||||
@@ -167,7 +167,7 @@ const [useProvideBoardManager, useBoardManager] = createInjectionState(() => {
 | 
			
		||||
    if (!isUndefined(appBitstream1)) cnt++;
 | 
			
		||||
    if (!isUndefined(appBitstream2)) cnt++;
 | 
			
		||||
    if (!isUndefined(appBitstream3)) cnt++;
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    if (cnt === 0) {
 | 
			
		||||
      console.error("未选择比特流文件");
 | 
			
		||||
      return { success: false, error: "未选择比特流文件" };
 | 
			
		||||
@@ -175,7 +175,7 @@ const [useProvideBoardManager, useBoardManager] = createInjectionState(() => {
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      console.log("开始上传比特流", { boardIp: board.ipAddr, fileCount: cnt });
 | 
			
		||||
      
 | 
			
		||||
 | 
			
		||||
      const uploadResult = await remoteUpdater.uploadBitstreams(
 | 
			
		||||
        board.ipAddr,
 | 
			
		||||
        toFileParameterOrNull(goldBitstream),
 | 
			
		||||
@@ -198,7 +198,10 @@ const [useProvideBoardManager, useBoardManager] = createInjectionState(() => {
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
      if (downloadResult != cnt) {
 | 
			
		||||
        console.error("固化比特流失败", { expected: cnt, actual: downloadResult });
 | 
			
		||||
        console.error("固化比特流失败", {
 | 
			
		||||
          expected: cnt,
 | 
			
		||||
          actual: downloadResult,
 | 
			
		||||
        });
 | 
			
		||||
        return { success: false, error: "固化比特流失败" };
 | 
			
		||||
      } else {
 | 
			
		||||
        console.log("固化比特流成功", { count: downloadResult });
 | 
			
		||||
@@ -212,18 +215,18 @@ const [useProvideBoardManager, useBoardManager] = createInjectionState(() => {
 | 
			
		||||
 | 
			
		||||
  // 热启动位流
 | 
			
		||||
  async function hotresetBitstream(
 | 
			
		||||
    board: BoardData, 
 | 
			
		||||
    bitstreamNum: number
 | 
			
		||||
    board: BoardData,
 | 
			
		||||
    bitstreamNum: number,
 | 
			
		||||
  ): Promise<{ success: boolean; error?: string }> {
 | 
			
		||||
    try {
 | 
			
		||||
      console.log("开始热启动比特流", { boardIp: board.ipAddr, bitstreamNum });
 | 
			
		||||
      
 | 
			
		||||
 | 
			
		||||
      const ret = await remoteUpdater.hotResetBitstream(
 | 
			
		||||
        board.ipAddr,
 | 
			
		||||
        board.port,
 | 
			
		||||
        bitstreamNum,
 | 
			
		||||
      );
 | 
			
		||||
      
 | 
			
		||||
 | 
			
		||||
      if (ret) {
 | 
			
		||||
        console.log("热启动比特流成功");
 | 
			
		||||
        return { success: true };
 | 
			
		||||
@@ -253,7 +256,11 @@ const [useProvideBoardManager, useBoardManager] = createInjectionState(() => {
 | 
			
		||||
    const file = target.files?.[0];
 | 
			
		||||
    if (file) {
 | 
			
		||||
      (board as any)[fileKey] = file;
 | 
			
		||||
      console.log(`文件选择成功`, { boardIp: board.ipAddr, fileKey, fileName: file.name });
 | 
			
		||||
      console.log(`文件选择成功`, {
 | 
			
		||||
        boardIp: board.ipAddr,
 | 
			
		||||
        fileKey,
 | 
			
		||||
        fileName: file.name,
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -2,62 +2,74 @@
 | 
			
		||||
  <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>
 | 
			
		||||
      
 | 
			
		||||
      <form @submit.prevent="handleSubmit" class="space-y-4">
 | 
			
		||||
        <!-- 实验板名称 -->
 | 
			
		||||
        <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>
 | 
			
		||||
 | 
			
		||||
        <!-- IP 地址 -->
 | 
			
		||||
        <div class="form-control">
 | 
			
		||||
          <label class="label">
 | 
			
		||||
            <span class="label-text">IP 地址 <span class="text-error">*</span></span>
 | 
			
		||||
          </label>
 | 
			
		||||
          <input
 | 
			
		||||
            v-model="form.ipAddr"
 | 
			
		||||
            type="text"
 | 
			
		||||
            placeholder="例如:192.168.1.100"
 | 
			
		||||
            class="input input-bordered"
 | 
			
		||||
            :class="{ 'input-error': errors.ipAddr }"
 | 
			
		||||
            required
 | 
			
		||||
          />
 | 
			
		||||
          <label v-if="errors.ipAddr" class="label">
 | 
			
		||||
            <span class="label-text-alt text-error">{{ errors.ipAddr }}</span>
 | 
			
		||||
          </label>
 | 
			
		||||
        </div>
 | 
			
		||||
      <!-- 步骤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="form-control">
 | 
			
		||||
          <label class="label">
 | 
			
		||||
            <span class="label-text">端口号 <span class="text-error">*</span></span>
 | 
			
		||||
          </label>
 | 
			
		||||
          <input
 | 
			
		||||
            v-model.number="form.port"
 | 
			
		||||
            type="number"
 | 
			
		||||
            placeholder="例如:1234"
 | 
			
		||||
            min="1"
 | 
			
		||||
            max="65535"
 | 
			
		||||
            class="input input-bordered"
 | 
			
		||||
            :class="{ 'input-error': errors.port }"
 | 
			
		||||
            required
 | 
			
		||||
          />
 | 
			
		||||
          <label v-if="errors.port" class="label">
 | 
			
		||||
            <span class="label-text-alt text-error">{{ errors.port }}</span>
 | 
			
		||||
          </label>
 | 
			
		||||
          <!-- 操作按钮 -->
 | 
			
		||||
          <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>
 | 
			
		||||
 | 
			
		||||
        <!-- 操作按钮 -->
 | 
			
		||||
@@ -65,23 +77,84 @@
 | 
			
		||||
          <button
 | 
			
		||||
            type="button"
 | 
			
		||||
            class="btn btn-ghost"
 | 
			
		||||
            @click="handleCancel"
 | 
			
		||||
            :disabled="isSubmitting"
 | 
			
		||||
            @click="handleCancelPairing"
 | 
			
		||||
            :disabled="isConfiguring"
 | 
			
		||||
          >
 | 
			
		||||
            取消
 | 
			
		||||
          </button>
 | 
			
		||||
          <button
 | 
			
		||||
            type="submit"
 | 
			
		||||
            type="button"
 | 
			
		||||
            class="btn btn-primary"
 | 
			
		||||
            :class="{ 'loading': isSubmitting }"
 | 
			
		||||
            :disabled="isSubmitting"
 | 
			
		||||
            @click="handlePairingConfirm"
 | 
			
		||||
            :disabled="isConfiguring"
 | 
			
		||||
          >
 | 
			
		||||
            {{ isSubmitting ? '添加中...' : '确认添加' }}
 | 
			
		||||
            已开启
 | 
			
		||||
          </button>
 | 
			
		||||
        </div>
 | 
			
		||||
      </form>
 | 
			
		||||
      </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>
 | 
			
		||||
@@ -90,8 +163,10 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { ref, reactive, watch } from 'vue';
 | 
			
		||||
import { useBoardManager } from '../../utils/BoardManager';
 | 
			
		||||
import { ref, reactive, watch } from "vue";
 | 
			
		||||
import { AuthManager } from "../../utils/AuthManager";
 | 
			
		||||
import { useAlertStore } from "../../components/Alert";
 | 
			
		||||
import type { NetworkConfigDto } from "../../APIClient";
 | 
			
		||||
 | 
			
		||||
// Props 和 Emits
 | 
			
		||||
interface Props {
 | 
			
		||||
@@ -99,75 +174,53 @@ interface Props {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface Emits {
 | 
			
		||||
  (e: 'update:visible', value: boolean): void;
 | 
			
		||||
  (e: 'success'): void;
 | 
			
		||||
  (e: "update:visible", value: boolean): void;
 | 
			
		||||
  (e: "success"): void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const props = defineProps<Props>();
 | 
			
		||||
const emit = defineEmits<Emits>();
 | 
			
		||||
 | 
			
		||||
// 使用 BoardManager
 | 
			
		||||
const boardManager = useBoardManager()!;
 | 
			
		||||
// 使用 Alert
 | 
			
		||||
const alertStore = useAlertStore();
 | 
			
		||||
 | 
			
		||||
// 当前步骤
 | 
			
		||||
const currentStep = ref<'input' | 'pairing' | 'configuring' | 'result'>('input');
 | 
			
		||||
 | 
			
		||||
// 表单数据
 | 
			
		||||
const form = reactive({
 | 
			
		||||
  name: 'Board1',
 | 
			
		||||
  ipAddr: '169.254.103.0',
 | 
			
		||||
  port: 1234
 | 
			
		||||
  name: "Board1",
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// 表单错误
 | 
			
		||||
const errors = reactive({
 | 
			
		||||
  name: '',
 | 
			
		||||
  ipAddr: '',
 | 
			
		||||
  port: ''
 | 
			
		||||
  name: "",
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// 提交状态
 | 
			
		||||
// 状态
 | 
			
		||||
const isSubmitting = ref(false);
 | 
			
		||||
const isConfiguring = ref(false);
 | 
			
		||||
 | 
			
		||||
// IP地址验证正则
 | 
			
		||||
const IP_REGEX = /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
 | 
			
		||||
// 添加的板卡信息
 | 
			
		||||
const addedBoardId = ref<string>("");
 | 
			
		||||
const networkConfig = ref<NetworkConfigDto | null>(null);
 | 
			
		||||
 | 
			
		||||
// 验证表单
 | 
			
		||||
function validateForm(): boolean {
 | 
			
		||||
  // 清空之前的错误
 | 
			
		||||
  errors.name = '';
 | 
			
		||||
  errors.ipAddr = '';
 | 
			
		||||
  errors.port = '';
 | 
			
		||||
  errors.name = "";
 | 
			
		||||
 | 
			
		||||
  let isValid = true;
 | 
			
		||||
 | 
			
		||||
  // 验证名称
 | 
			
		||||
  if (!form.name.trim()) {
 | 
			
		||||
    errors.name = '请输入实验板名称';
 | 
			
		||||
    errors.name = "请输入实验板名称";
 | 
			
		||||
    isValid = false;
 | 
			
		||||
  } else if (form.name.trim().length < 2) {
 | 
			
		||||
    errors.name = '实验板名称至少需要2个字符';
 | 
			
		||||
    errors.name = "实验板名称至少需要2个字符";
 | 
			
		||||
    isValid = false;
 | 
			
		||||
  } else if (form.name.trim().length > 50) {
 | 
			
		||||
    errors.name = '实验板名称不能超过50个字符';
 | 
			
		||||
    isValid = false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 验证IP地址
 | 
			
		||||
  if (!form.ipAddr.trim()) {
 | 
			
		||||
    errors.ipAddr = '请输入IP地址';
 | 
			
		||||
    isValid = false;
 | 
			
		||||
  } else if (!IP_REGEX.test(form.ipAddr.trim())) {
 | 
			
		||||
    errors.ipAddr = '请输入有效的IP地址格式';
 | 
			
		||||
    isValid = false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 验证端口号
 | 
			
		||||
  if (!form.port) {
 | 
			
		||||
    errors.port = '请输入端口号';
 | 
			
		||||
    isValid = false;
 | 
			
		||||
  } else if (form.port < 1 || form.port > 65535) {
 | 
			
		||||
    errors.port = '端口号必须在1-65535之间';
 | 
			
		||||
    isValid = false;
 | 
			
		||||
  } else if (!Number.isInteger(form.port)) {
 | 
			
		||||
    errors.port = '端口号必须是整数';
 | 
			
		||||
    errors.name = "实验板名称不能超过50个字符";
 | 
			
		||||
    isValid = false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -176,18 +229,17 @@ function validateForm(): boolean {
 | 
			
		||||
 | 
			
		||||
// 重置表单
 | 
			
		||||
function resetForm() {
 | 
			
		||||
  form.name = 'Board1';
 | 
			
		||||
  form.ipAddr = '169.254.103.0';
 | 
			
		||||
  form.port = 1234;
 | 
			
		||||
  errors.name = '';
 | 
			
		||||
  errors.ipAddr = '';
 | 
			
		||||
  errors.port = '';
 | 
			
		||||
  form.name = "Board1";
 | 
			
		||||
  errors.name = "";
 | 
			
		||||
  currentStep.value = 'input';
 | 
			
		||||
  addedBoardId.value = "";
 | 
			
		||||
  networkConfig.value = null;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 处理取消
 | 
			
		||||
function handleCancel() {
 | 
			
		||||
  if (!isSubmitting.value) {
 | 
			
		||||
    emit('update:visible', false);
 | 
			
		||||
  if (!isSubmitting.value && !isConfiguring.value) {
 | 
			
		||||
    emit("update:visible", false);
 | 
			
		||||
    resetForm();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -201,29 +253,120 @@ async function handleSubmit() {
 | 
			
		||||
  isSubmitting.value = true;
 | 
			
		||||
 | 
			
		||||
  try {
 | 
			
		||||
    const success = await boardManager.addBoard(
 | 
			
		||||
      form.name.trim(),
 | 
			
		||||
      form.ipAddr.trim(),
 | 
			
		||||
      form.port
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    if (success) {
 | 
			
		||||
      emit('success');
 | 
			
		||||
      resetForm();
 | 
			
		||||
    // 通过 AuthManager 获取认证的 DataClient
 | 
			
		||||
    const dataClient = AuthManager.createAuthenticatedDataClient();
 | 
			
		||||
    
 | 
			
		||||
    // 添加板卡到数据库
 | 
			
		||||
    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);
 | 
			
		||||
    console.error("添加实验板失败:", error);
 | 
			
		||||
    alertStore?.error("添加实验板失败");
 | 
			
		||||
  } finally {
 | 
			
		||||
    isSubmitting.value = false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 监听对话框显示状态,重置表单
 | 
			
		||||
watch(() => props.visible, (newVisible) => {
 | 
			
		||||
  if (newVisible) {
 | 
			
		||||
// 处理取消配对
 | 
			
		||||
async function handleCancelPairing() {
 | 
			
		||||
  if (!addedBoardId.value) return;
 | 
			
		||||
 | 
			
		||||
  try {
 | 
			
		||||
    // 通过 AuthManager 获取认证的 DataClient
 | 
			
		||||
    const dataClient = AuthManager.createAuthenticatedDataClient();
 | 
			
		||||
    
 | 
			
		||||
    // 删除添加的板卡
 | 
			
		||||
    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.createAuthenticatedDataClient();
 | 
			
		||||
    const netConfigClient = AuthManager.createAuthenticatedNetConfigClient();
 | 
			
		||||
 | 
			
		||||
    // 获取数据库中对应分配的板卡信息
 | 
			
		||||
    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);
 | 
			
		||||
 | 
			
		||||
    // 获取实验板网络信息
 | 
			
		||||
    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.createAuthenticatedDataClient();
 | 
			
		||||
      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">
 | 
			
		||||
@@ -248,4 +391,20 @@ watch(() => props.visible, (newVisible) => {
 | 
			
		||||
.loading {
 | 
			
		||||
  @apply opacity-50 cursor-not-allowed;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
.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>
 | 
			
		||||
 
 | 
			
		||||
@@ -37,7 +37,9 @@ const [useProvideBoardTableManager, useBoardTableManager] =
 | 
			
		||||
      port: false,
 | 
			
		||||
      id: false,
 | 
			
		||||
      status: false,
 | 
			
		||||
      version: false,
 | 
			
		||||
      firmVersion: false,
 | 
			
		||||
      macAddr: false,
 | 
			
		||||
      occupiedUserName: false,
 | 
			
		||||
    });
 | 
			
		||||
    const rowSelection = ref({});
 | 
			
		||||
    const expanded = ref<ExpandedState>({});
 | 
			
		||||
@@ -88,20 +90,12 @@ const [useProvideBoardTableManager, useBoardTableManager] =
 | 
			
		||||
        enableHiding: true,
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        accessorKey: "devAddr",
 | 
			
		||||
        accessorKey: "ipAddr",
 | 
			
		||||
        header: "IP 地址",
 | 
			
		||||
        cell: ({ row }) => {
 | 
			
		||||
          const device = row.original;
 | 
			
		||||
          return isEditMode.value
 | 
			
		||||
            ? h("input", {
 | 
			
		||||
                type: "text",
 | 
			
		||||
                class: "input input-sm w-full",
 | 
			
		||||
                value: device.ipAddr,
 | 
			
		||||
                onInput: (e: Event) => {
 | 
			
		||||
                  device.ipAddr = (e.target as HTMLInputElement).value;
 | 
			
		||||
                },
 | 
			
		||||
              })
 | 
			
		||||
            : h("span", { class: "font-medium" }, device.ipAddr);
 | 
			
		||||
          // IP地址设置为不可更改
 | 
			
		||||
          return h("span", { class: "font-mono" }, device.ipAddr);
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
@@ -109,16 +103,28 @@ const [useProvideBoardTableManager, useBoardTableManager] =
 | 
			
		||||
        header: "端口",
 | 
			
		||||
        cell: ({ row }) => {
 | 
			
		||||
          const device = row.original;
 | 
			
		||||
          return isEditMode.value
 | 
			
		||||
            ? h("input", {
 | 
			
		||||
                type: "number",
 | 
			
		||||
                class: "input input-sm w-full",
 | 
			
		||||
                value: device.port,
 | 
			
		||||
                onInput: (e: Event) => {
 | 
			
		||||
                  device.port = parseInt((e.target as HTMLInputElement).value);
 | 
			
		||||
                },
 | 
			
		||||
              })
 | 
			
		||||
            : h("span", { class: "font-mono" }, device.port.toString());
 | 
			
		||||
          // 端口设置为不可更改
 | 
			
		||||
          return h("span", { class: "font-mono" }, device.port.toString());
 | 
			
		||||
        },
 | 
			
		||||
        enableHiding: true,
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        accessorKey: "macAddr",
 | 
			
		||||
        header: "MAC 地址",
 | 
			
		||||
        cell: ({ row }) => {
 | 
			
		||||
          const device = row.original;
 | 
			
		||||
          return h("span", { class: "font-mono text-sm" }, device.macAddr);
 | 
			
		||||
        },
 | 
			
		||||
        enableHiding: true,
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        accessorKey: "occupiedUserName",
 | 
			
		||||
        header: "占用用户",
 | 
			
		||||
        cell: ({ row }) => {
 | 
			
		||||
          const device = row.original;
 | 
			
		||||
          const userName = device.occupiedUserName || "未占用";
 | 
			
		||||
          const userClass = device.occupiedUserName ? "text-warning" : "text-success";
 | 
			
		||||
          return h("span", { class: `font-medium ${userClass}` }, userName);
 | 
			
		||||
        },
 | 
			
		||||
        enableHiding: true,
 | 
			
		||||
      },
 | 
			
		||||
@@ -134,9 +140,27 @@ const [useProvideBoardTableManager, useBoardTableManager] =
 | 
			
		||||
        header: "状态",
 | 
			
		||||
        cell: ({ row }) => {
 | 
			
		||||
          const device = row.original;
 | 
			
		||||
          const statusText = device.status === 0 ? "忙碌" : "可用";
 | 
			
		||||
          const statusClass =
 | 
			
		||||
            device.status === 0 ? "badge-warning" : "badge-success";
 | 
			
		||||
          let statusText = "";
 | 
			
		||||
          let statusClass = "";
 | 
			
		||||
          
 | 
			
		||||
          switch (device.status) {
 | 
			
		||||
            case 0: // Disabled
 | 
			
		||||
              statusText = "禁用";
 | 
			
		||||
              statusClass = "badge-error";
 | 
			
		||||
              break;
 | 
			
		||||
            case 1: // Busy
 | 
			
		||||
              statusText = "忙碌";
 | 
			
		||||
              statusClass = "badge-warning";
 | 
			
		||||
              break;
 | 
			
		||||
            case 2: // Available
 | 
			
		||||
              statusText = "可用";
 | 
			
		||||
              statusClass = "badge-success";
 | 
			
		||||
              break;
 | 
			
		||||
            default:
 | 
			
		||||
              statusText = "未知";
 | 
			
		||||
              statusClass = "badge-neutral";
 | 
			
		||||
          }
 | 
			
		||||
          
 | 
			
		||||
          return h(
 | 
			
		||||
            "span",
 | 
			
		||||
            {
 | 
			
		||||
@@ -148,10 +172,11 @@ const [useProvideBoardTableManager, useBoardTableManager] =
 | 
			
		||||
        enableHiding: true,
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        accessorKey: "version",
 | 
			
		||||
        header: "版本号",
 | 
			
		||||
        accessorKey: "firmVersion",
 | 
			
		||||
        header: "固件版本",
 | 
			
		||||
        cell: ({ row }) =>
 | 
			
		||||
          h("span", { class: "font-mono" }, row.original.firmVersion),
 | 
			
		||||
          h("span", { class: "font-mono text-sm" }, row.original.firmVersion),
 | 
			
		||||
        enableHiding: true,
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        accessorKey: "defaultBitstream",
 | 
			
		||||
@@ -248,7 +273,7 @@ const [useProvideBoardTableManager, useBoardTableManager] =
 | 
			
		||||
                    class: "btn btn-error btn-sm",
 | 
			
		||||
                    onClick: async () => {
 | 
			
		||||
                      const confirmed = confirm(
 | 
			
		||||
                        `确定要删除设备 ${device.ipAddr} 吗?`,
 | 
			
		||||
                        `确定要删除设备 ${device.boardName || device.ipAddr} 吗?`,
 | 
			
		||||
                      );
 | 
			
		||||
                      if (confirmed) {
 | 
			
		||||
                        await deleteBoard(device.id);
 | 
			
		||||
@@ -378,8 +403,8 @@ const [useProvideBoardTableManager, useBoardTableManager] =
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 新增板卡
 | 
			
		||||
    async function addBoard(name: string, ipAddr: string, port: number): Promise<boolean> {
 | 
			
		||||
      const result = await boardManager.addBoard(name, ipAddr, port);
 | 
			
		||||
    async function addBoard(name: string): Promise<boolean> {
 | 
			
		||||
      const result = await boardManager.addBoard(name);
 | 
			
		||||
      if (result.success) {
 | 
			
		||||
        dialog?.info("新增板卡成功");
 | 
			
		||||
      } else {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user