feat: 后端添加获取空闲实验板,继续修改前端界面使其更加合理

This commit is contained in:
2025-07-12 14:59:28 +08:00
parent 44e357b887
commit 0fb0c4e395
11 changed files with 222 additions and 117 deletions

View File

@@ -34,55 +34,9 @@
to="/project"
class="btn btn-primary text-base-100 shadow-lg transform transition-all duration-300 hover:scale-105 hover:shadow-xl hover:-translate-y-1"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5 mr-2"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"></path>
<path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"></path>
</svg>
<BookOpen class="h-5 w-5 mr-2" />
进入工程界面
</router-link>
<router-link
to="/login"
class="btn btn-secondary text-base-100 shadow-lg transform transition-all duration-300 hover:scale-105 hover:shadow-xl hover:-translate-y-1"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5 mr-2"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect>
<path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
</svg>
登录
</router-link>
<router-link
to="/user"
class="btn btn-accent text-base-100 shadow-lg transform transition-all duration-300 hover:scale-105 hover:shadow-xl hover:-translate-y-1"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5 mr-2"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
<circle cx="12" cy="7" r="4"></circle>
</svg>
用户中心
</router-link>
</div>
<div
class="mt-8 p-4 bg-base-300 rounded-lg shadow-inner opacity-80 transition-all duration-300 hover:opacity-100 hover:shadow-md"
@@ -101,6 +55,7 @@
<script lang="ts" setup>
import "@/router";
import TutorialCarousel from "@/components/TutorialCarousel.vue";
import { BookOpen } from "lucide-vue-next";
</script>
<style scoped lang="postcss">

View File

@@ -72,7 +72,7 @@
:min-size="15"
class="w-full overflow-hidden"
>
<FunctionBar class="mx-4 mt-1" />
<BottomBar class="mx-4 mt-1" />
</SplitterPanel>
</SplitterGroup>
</div>
@@ -89,18 +89,21 @@
<script setup lang="ts">
import { ref, onMounted, watch } from "vue";
import { useRouter } from "vue-router";
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 FunctionBar from "@/views/Project/BottomBar.vue";
import BottomBar from "@/views/Project/BottomBar.vue";
import { useProvideComponentManager } from "@/components/LabCanvas";
import type { DiagramData } from "@/components/LabCanvas";
import { useAlertStore } from "@/components/Alert";
import { AuthManager } from "@/utils/AuthManager";
import { useRoute } from "vue-router";
const route = useRoute();
const router = useRouter();
// 提供组件管理服务
const componentManager = useProvideComponentManager();
@@ -207,6 +210,20 @@ function updateComponentDirectProp(
// --- 生命周期钩子 ---
onMounted(async () => {
// 验证用户身份
try {
const isAuthenticated = await AuthManager.isAuthenticated();
if (!isAuthenticated) {
// 验证失败,跳转到登录页面
router.push('/login');
return;
}
} catch (error) {
console.error('身份验证失败:', error);
router.push('/login');
return;
}
// 检查是否有例程参数,如果有则自动打开文档面板
if (route.query.tutorial) {
showDocPanel.value = true;

View File

@@ -1,6 +1,6 @@
<template>
<div
class="min-h-screen bg-base-100 flex flex-col mx-auto p-6 space-y-6 container"
class="min-h-screen bg-base-100 flex flex-col p-6 space-y-6 "
>
<!-- 设置 -->
<div class="card bg-base-200 shadow-xl">

View File

@@ -1,6 +1,6 @@
<template>
<div class="min-h-screen bg-base-100">
<div class="container mx-auto p-6 space-y-6">
<div class="p-6">
<!-- 控制面板 -->
<div class="card bg-base-200 shadow-xl">
<div class="card-body">

View File

@@ -91,7 +91,7 @@
<script lang="ts" setup>
import { ref, reactive, watch } from 'vue';
import { useBoardManager } from './BoardManager';
import { useBoardManager } from '../../utils/BoardManager';
// Props 和 Emits
interface Props {

View File

@@ -1,272 +0,0 @@
import { ref } from "vue";
import { createInjectionState } from "@vueuse/core";
import { RemoteUpdateClient, DataClient, Board } from "@/APIClient";
import { Common } from "@/utils/Common";
import { isUndefined } from "lodash";
import { AuthManager } from "@/utils/AuthManager";
// 统一的板卡数据接口扩展原有的Board类型
export interface BoardData extends Board {
defaultBitstream: string;
goldBitstreamFile?: File;
appBitstream1File?: File;
appBitstream2File?: File;
appBitstream3File?: File;
}
const [useProvideBoardManager, useBoardManager] = createInjectionState(() => {
// 远程升级相关参数
const devPort = 1234;
const remoteUpdater = new RemoteUpdateClient();
// 统一的板卡数据
const boards = ref<BoardData[]>([]);
// 获取位流编号
function getSelectedBitstreamNum(bitstreamName: string): number {
if (bitstreamName === "黄金位流") return 0;
if (bitstreamName === "应用位流1") return 1;
if (bitstreamName === "应用位流2") return 2;
if (bitstreamName === "应用位流3") return 3;
return 0;
}
// 获取所有板卡信息(管理员权限)
async function getAllBoards(): Promise<{ success: boolean; error?: string }> {
try {
// 验证管理员权限
const hasAdminAuth = await AuthManager.verifyAdminAuth();
if (!hasAdminAuth) {
console.error("权限验证失败");
return { success: false, error: "权限不足" };
}
const client = AuthManager.createAuthenticatedClient();
const result = await client.getAllBoards();
if (result) {
// 将Board类型转换为BoardData类型添加默认值
boards.value = result.map((board) => {
return {
...board,
defaultBitstream: "黄金位流", // 设置默认位流
goldBitstreamFile: undefined,
appBitstream1File: undefined,
appBitstream2File: undefined,
appBitstream3File: undefined,
// Ensure methods from Board are present
init: board.init?.bind(board),
toJSON: board.toJSON?.bind(board),
};
});
console.log("获取板卡信息成功", result.length);
return { success: true };
} else {
console.error("获取板卡信息失败:返回结果为空");
return { success: false, error: "获取板卡信息失败" };
}
} catch (e: any) {
console.error("获取板卡信息异常:", e);
return { success: false, error: e.message || "获取板卡信息异常" };
}
}
// 新增板卡(管理员权限)
async function addBoard(
name: string,
ipAddr: string,
port: number,
): Promise<{ success: boolean; error?: string; boardId?: string }> {
try {
// 验证管理员权限
const hasAdminAuth = await AuthManager.verifyAdminAuth();
if (!hasAdminAuth) {
console.error("权限验证失败");
return { success: false, error: "权限不足" };
}
// 验证输入参数
if (!name || !ipAddr || !port) {
console.error("参数验证失败", { name, ipAddr, port });
return { success: false, error: "参数不完整" };
}
const client = AuthManager.createAuthenticatedClient();
const boardId = await client.addBoard(name, ipAddr, port);
if (boardId) {
console.log("新增板卡成功", { boardId, name, ipAddr, port });
// 刷新板卡列表
await getAllBoards();
return { success: true};
} else {
console.error("新增板卡失败返回ID为空");
return { success: false, error: "新增板卡失败" };
}
} catch (e: any) {
console.error("新增板卡异常:", e);
if (e.status === 401) {
return { success: false, error: "权限不足" };
} else if (e.status === 400) {
return { success: false, error: "输入参数错误" };
} else {
return { success: false, error: e.message || "新增板卡异常" };
}
}
}
// 删除板卡(管理员权限)
async function deleteBoard(boardId: string): Promise<{ success: boolean; error?: string }> {
try {
// 验证管理员权限
const hasAdminAuth = await AuthManager.verifyAdminAuth();
if (!hasAdminAuth) {
console.error("权限验证失败");
return { success: false, error: "权限不足" };
}
if (!boardId) {
console.error("板卡ID为空");
return { success: false, error: "板卡ID不能为空" };
}
const client = AuthManager.createAuthenticatedClient();
const result = await client.deleteBoard(boardId);
if (result > 0) {
console.log("删除板卡成功", { boardId, deletedCount: result });
// 刷新板卡列表
await getAllBoards();
return { success: true };
} else {
console.error("删除板卡失败影响行数为0");
return { success: false, error: "删除板卡失败" };
}
} catch (e: any) {
console.error("删除板卡异常:", e);
if (e.status === 401) {
return { success: false, error: "权限不足" };
} else if (e.status === 400) {
return { success: false, error: "输入参数错误" };
} else {
return { success: false, error: e.message || "删除板卡异常" };
}
}
}
// 上传并固化位流
async function uploadAndDownloadBitstreams(
board: BoardData,
goldBitstream?: File,
appBitstream1?: File,
appBitstream2?: File,
appBitstream3?: File,
): Promise<{ success: boolean; error?: string }> {
let cnt = 0;
if (!isUndefined(goldBitstream)) cnt++;
if (!isUndefined(appBitstream1)) cnt++;
if (!isUndefined(appBitstream2)) cnt++;
if (!isUndefined(appBitstream3)) cnt++;
if (cnt === 0) {
console.error("未选择比特流文件");
return { success: false, error: "未选择比特流文件" };
}
try {
console.log("开始上传比特流", { boardIp: board.ipAddr, fileCount: cnt });
const uploadResult = await remoteUpdater.uploadBitstreams(
board.ipAddr,
Common.toFileParameterOrNull(goldBitstream),
Common.toFileParameterOrNull(appBitstream1),
Common.toFileParameterOrNull(appBitstream2),
Common.toFileParameterOrNull(appBitstream3),
);
if (!uploadResult) {
console.error("上传比特流失败");
return { success: false, error: "上传比特流失败" };
}
console.log("比特流上传成功,开始固化");
const downloadResult = await remoteUpdater.downloadMultiBitstreams(
board.ipAddr,
board.port,
getSelectedBitstreamNum(board.defaultBitstream),
);
if (downloadResult != cnt) {
console.error("固化比特流失败", { expected: cnt, actual: downloadResult });
return { success: false, error: "固化比特流失败" };
} else {
console.log("固化比特流成功", { count: downloadResult });
return { success: true };
}
} catch (e: any) {
console.error("比特流操作异常:", e);
return { success: false, error: e.message || "比特流操作异常" };
}
}
// 热启动位流
async function hotresetBitstream(
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 };
} else {
console.error("热启动比特流失败");
return { success: false, error: "热启动比特流失败" };
}
} catch (e: any) {
console.error("热启动比特流异常:", e);
return { success: false, error: e.message || "热启动比特流异常" };
}
}
// 处理文件上传
function handleFileChange(
event: Event,
board: BoardData,
fileKey: keyof Pick<
BoardData,
| "goldBitstreamFile"
| "appBitstream1File"
| "appBitstream2File"
| "appBitstream3File"
>,
) {
const target = event.target as HTMLInputElement;
const file = target.files?.[0];
if (file) {
(board as any)[fileKey] = file;
console.log(`文件选择成功`, { boardIp: board.ipAddr, fileKey, fileName: file.name });
}
}
return {
boards,
uploadAndDownloadBitstreams,
hotresetBitstream,
handleFileChange,
getSelectedBitstreamNum,
getAllBoards,
addBoard,
deleteBoard,
};
});
export { useProvideBoardManager, useBoardManager };

View File

@@ -202,7 +202,7 @@
import { FlexRender } from "@tanstack/vue-table";
import { onMounted, ref } from "vue";
import { RefreshCw, Edit, Plus, Trash2 } from "lucide-vue-next";
import { useProvideBoardManager } from "./BoardManager";
import { useProvideBoardManager } from "../../utils/BoardManager";
import { useProvideBoardTableManager } from "./BoardTableManager";
import AddBoardDialog from "./AddBoardDialog.vue";

View File

@@ -15,8 +15,8 @@ import {
} from "@tanstack/vue-table";
import { h, ref, computed, version } from "vue";
import { createInjectionState } from "@vueuse/core";
import type { BoardData } from "./BoardManager";
import { useBoardManager } from "./BoardManager";
import type { BoardData } from "../../utils/BoardManager";
import { useBoardManager } from "../../utils/BoardManager";
import { useDialogStore } from "@/stores/dialog";
const [useProvideBoardTableManager, useBoardTableManager] =