feat: 前端完成适配后端api

This commit is contained in:
SikongJueluo 2025-07-11 21:44:23 +08:00
parent 8789d6f9ee
commit da6386c6f0
No known key found for this signature in database
3 changed files with 339 additions and 156 deletions

View File

@ -50,10 +50,31 @@ const [useAlertProvider, useAlertStore] = createInjectionState(() => {
}
}
// Convenience methods for different alert types
function error(message: string, duration = 2000) {
show(message, "error", duration);
}
function info(message: string, duration = 2000) {
show(message, "info", duration);
}
function warn(message: string, duration = 2000) {
show(message, "warning", duration);
}
function success(message: string, duration = 2000) {
show(message, "success", duration);
}
return {
alertState,
show,
hide,
error,
info,
warn,
success,
};
});

View File

@ -3,11 +3,7 @@
<h1 class="text-3xl font-bold mb-6">FPGA 设备管理</h1>
<button
class="btn btn-ghost text-error hover:underline"
@click="
() => {
isEditMode = !isEditMode;
}
"
@click="toggleEditMode"
>
编辑
</button>
@ -17,7 +13,9 @@
<div class="card-body">
<div class="flex flex-row justify-between items-center mb-4">
<h2 class="card-title">IP 地址列表</h2>
<button class="btn btn-ghost" @click="refreshData">刷新</button>
<button class="btn btn-ghost" @click="boardManager.refreshData">
刷新
</button>
</div>
<!-- 搜索和列控制 -->
@ -185,45 +183,20 @@ import {
getSortedRowModel,
useVueTable,
} from "@tanstack/vue-table";
import { isNull, isUndefined } from "lodash";
import { h, ref } from "vue";
import { RemoteUpdateClient } from "@/APIClient";
import { useDialogStore } from "@/stores/dialog";
import { Common } from "@/utils/Common";
import { h, onMounted, ref } from "vue";
import { useProvideBoardManager, type BoardData } from "./BoardManager";
const dialog = useDialogStore();
// 使 BoardManager
const boardManager = useProvideBoardManager()!;
//
const isEditMode = ref(false);
//
const devPort = 1234;
const remoteUpdater = new RemoteUpdateClient();
//
export interface DeviceData {
id: string;
devAddr: string;
version: string;
defaultBitstream: string;
goldBitstreamFile?: File;
appBitstream1File?: File;
appBitstream2File?: File;
appBitstream3File?: File;
function toggleEditMode() {
isEditMode.value = !isEditMode.value;
}
// API
const data = ref<DeviceData[]>([
{
id: "1",
devAddr: "192.168.1.100",
version: "v1.2.3",
defaultBitstream: "黄金位流",
},
]);
//
const columns: ColumnDef<DeviceData>[] = [
const columns: ColumnDef<BoardData>[] = [
{
id: "select",
header: ({ table }) =>
@ -258,18 +231,19 @@ const columns: ColumnDef<DeviceData>[] = [
? h("input", {
type: "text",
class: "input input-sm w-full",
value: device.devAddr,
value: device.ipAddr,
onInput: (e: Event) => {
device.devAddr = (e.target as HTMLInputElement).value;
device.ipAddr = (e.target as HTMLInputElement).value;
},
})
: h("span", { class: "font-medium" }, device.devAddr);
: h("span", { class: "font-medium" }, device.ipAddr);
},
},
{
accessorKey: "version",
header: "版本号",
cell: ({ row }) => row.getValue("version"),
cell: ({ row }) =>
h("span", { class: "font-mono" }, row.original.firmVersion),
},
{
accessorKey: "defaultBitstream",
@ -303,7 +277,7 @@ const columns: ColumnDef<DeviceData>[] = [
type: "file",
class: "file-input file-input-primary file-input-sm",
onChange: (e: Event) =>
handleFileChange(e, device, "goldBitstreamFile"),
boardManager.handleFileChange(e, device, "goldBitstreamFile"),
});
},
},
@ -316,7 +290,7 @@ const columns: ColumnDef<DeviceData>[] = [
type: "file",
class: "file-input file-input-secondary file-input-sm",
onChange: (e: Event) =>
handleFileChange(e, device, "appBitstream1File"),
boardManager.handleFileChange(e, device, "appBitstream1File"),
});
},
},
@ -329,7 +303,7 @@ const columns: ColumnDef<DeviceData>[] = [
type: "file",
class: "file-input file-input-accent file-input-sm",
onChange: (e: Event) =>
handleFileChange(e, device, "appBitstream2File"),
boardManager.handleFileChange(e, device, "appBitstream2File"),
});
},
},
@ -342,7 +316,7 @@ const columns: ColumnDef<DeviceData>[] = [
type: "file",
class: "file-input file-input-info file-input-sm",
onChange: (e: Event) =>
handleFileChange(e, device, "appBitstream3File"),
boardManager.handleFileChange(e, device, "appBitstream3File"),
});
},
},
@ -357,8 +331,8 @@ const columns: ColumnDef<DeviceData>[] = [
{
class: "btn btn-warning btn-sm",
onClick: () =>
uploadAndDownloadBitstreams(
device.devAddr,
boardManager.uploadAndDownloadBitstreams(
device,
device.goldBitstreamFile,
device.appBitstream1File,
device.appBitstream2File,
@ -372,9 +346,9 @@ const columns: ColumnDef<DeviceData>[] = [
{
class: "btn btn-success btn-sm",
onClick: () =>
hotresetBitstream(
device.devAddr,
getSelectedBitstreamNum(device.defaultBitstream),
boardManager.hotresetBitstream(
device,
boardManager.getSelectedBitstreamNum(device.defaultBitstream),
),
},
"热启动",
@ -395,7 +369,7 @@ const expanded = ref<ExpandedState>({});
//
const table = useVueTable({
get data() {
return data.value;
return boardManager.boards.value;
},
columns,
getCoreRowModel: getCoreRowModel(),
@ -456,110 +430,10 @@ const table = useVueTable({
},
},
});
//
function handleFileChange(
event: Event,
device: DeviceData,
fileKey: keyof DeviceData,
) {
const target = event.target as HTMLInputElement;
const file = target.files?.[0];
if (file) {
(device as any)[fileKey] = file;
}
}
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 uploadAndDownloadBitstreams(
devAddr: string,
goldBitstream?: File,
appBitstream1?: File,
appBitstream2?: File,
appBitstream3?: File,
) {
let cnt = 0;
if (!isUndefined(goldBitstream)) cnt++;
if (!isUndefined(appBitstream1)) cnt++;
if (!isUndefined(appBitstream2)) cnt++;
if (!isUndefined(appBitstream3)) cnt++;
if (cnt === 0) {
dialog.error("未选择比特流");
return;
}
try {
{
const ret = await remoteUpdater.uploadBitstreams(
devAddr,
Common.toFileParameterOrNull(goldBitstream),
Common.toFileParameterOrNull(appBitstream1),
Common.toFileParameterOrNull(appBitstream2),
Common.toFileParameterOrNull(appBitstream3),
);
if (!ret) {
dialog.warn("上传比特流出错");
return;
}
}
{
const ret = await remoteUpdater.downloadMultiBitstreams(
devAddr,
devPort,
getSelectedBitstreamNum(data.value[0].defaultBitstream),
);
if (ret != cnt) {
dialog.warn("固化比特流出错");
} else {
dialog.info("固化比特流成功");
}
}
} catch (e) {
dialog.error("比特流上传错误");
console.error(e);
}
}
async function hotresetBitstream(devAddr: string, bitstreamNum: number) {
try {
const ret = await remoteUpdater.hotResetBitstream(
devAddr,
devPort,
bitstreamNum,
);
if (ret) {
dialog.info("切换比特流成功");
} else {
dialog.error("切换比特流失败");
}
} catch (e) {
dialog.error("切换比特流失败");
console.error(e);
}
}
async function refreshData() {
try {
const ret = await remoteUpdater.getFirmwareVersion(
data.value[0].devAddr,
devPort,
);
//
if (ret) {
data.value[0].version = String(ret);
}
} catch (e) {
dialog.error("获取数据失败");
console.error(e);
}
}
onMounted(() => {
//
boardManager.fetchBoardsData();
});
</script>
<style scoped lang="postcss">

View File

@ -0,0 +1,288 @@
import { ref } from "vue";
import { createInjectionState } from "@vueuse/core";
import { RemoteUpdateClient, DataClient, Board } from "@/APIClient";
import { useDialogStore } from "@/stores/dialog";
import { useAlertStore } from "@/components/Alert";
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 dialog = useDialogStore();
const alert = useAlertStore();
// 远程升级相关参数
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 fetchBoardsData(): Promise<boolean> {
try {
// 验证管理员权限
const hasAdminAuth = await AuthManager.verifyAdminAuth();
if (!hasAdminAuth) {
return false;
}
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),
};
});
return true;
} else {
return false;
}
} catch (e) {
console.error(e);
return false;
}
}
// 获取所有板卡信息(管理员权限)- 显示提示信息,供外部调用
async function getAllBoards(): Promise<boolean> {
const result = await fetchBoardsData();
if (result) {
dialog.info("获取板卡信息成功");
} else {
dialog.warn("获取板卡信息失败");
}
return result;
}
// 新增板卡(管理员权限)
async function addBoard(
name: string,
ipAddr: string,
port: number,
): Promise<boolean> {
try {
// 验证管理员权限
const hasAdminAuth = await AuthManager.verifyAdminAuth();
if (!hasAdminAuth) {
dialog.error("需要管理员权限");
return false;
}
// 验证输入参数
if (!name || !ipAddr || !port) {
dialog.error("请填写完整的板卡信息");
return false;
}
const client = AuthManager.createAuthenticatedClient();
const boardId = await client.addBoard(name, ipAddr, port);
if (boardId) {
// 静默刷新板卡列表,不显示重复提示
await fetchBoardsData();
dialog.info("新增板卡成功");
return true;
} else {
dialog.warn("新增板卡失败");
return false;
}
} catch (e: any) {
if (e.status === 401) {
dialog.error("权限不足,需要管理员权限");
} else if (e.status === 400) {
dialog.error("输入参数错误");
} else {
dialog.error("新增板卡失败");
}
console.error(e);
return false;
}
}
// 删除板卡(管理员权限)
async function deleteBoard(boardId: string): Promise<boolean> {
try {
// 验证管理员权限
const hasAdminAuth = await AuthManager.verifyAdminAuth();
if (!hasAdminAuth) {
dialog.error("需要管理员权限");
return false;
}
if (!boardId) {
dialog.error("板卡ID不能为空");
return false;
}
const client = AuthManager.createAuthenticatedClient();
const result = await client.deleteBoard(boardId);
if (result > 0) {
// 静默刷新板卡列表,不显示重复提示
await fetchBoardsData();
dialog.info("删除板卡成功");
return true;
} else {
dialog.warn("删除板卡失败");
return false;
}
} catch (e: any) {
if (e.status === 401) {
dialog.error("权限不足,需要管理员权限");
} else if (e.status === 400) {
dialog.error("输入参数错误");
} else {
dialog.error("删除板卡失败");
}
console.error(e);
return false;
}
}
// 上传并固化位流
async function uploadAndDownloadBitstreams(
board: BoardData,
goldBitstream?: File,
appBitstream1?: File,
appBitstream2?: File,
appBitstream3?: File,
) {
let cnt = 0;
if (!isUndefined(goldBitstream)) cnt++;
if (!isUndefined(appBitstream1)) cnt++;
if (!isUndefined(appBitstream2)) cnt++;
if (!isUndefined(appBitstream3)) cnt++;
if (cnt === 0) {
dialog.error("未选择比特流");
return;
}
try {
const uploadResult = await remoteUpdater.uploadBitstreams(
board.ipAddr, // 使用板卡的IP地址
Common.toFileParameterOrNull(goldBitstream),
Common.toFileParameterOrNull(appBitstream1),
Common.toFileParameterOrNull(appBitstream2),
Common.toFileParameterOrNull(appBitstream3),
);
if (!uploadResult) {
dialog.warn("上传比特流出错");
return;
}
const downloadResult = await remoteUpdater.downloadMultiBitstreams(
board.ipAddr,
board.port,
getSelectedBitstreamNum(board.defaultBitstream),
);
if (downloadResult != cnt) {
dialog.warn("固化比特流出错");
} else {
dialog.info("固化比特流成功");
}
} catch (e) {
dialog.error("比特流上传错误");
console.error(e);
}
}
// 热启动位流
async function hotresetBitstream(board: BoardData, bitstreamNum: number) {
try {
const ret = await remoteUpdater.hotResetBitstream(
board.ipAddr,
board.port,
bitstreamNum,
);
if (ret) {
dialog.info("切换比特流成功");
} else {
dialog.error("切换比特流失败");
}
} catch (e) {
dialog.error("切换比特流失败");
console.error(e);
}
}
// 刷新板卡数据 - 简化逻辑,避免重复提示
async function refreshData() {
try {
const result = await fetchBoardsData();
if (result) {
alert?.info("刷新数据成功");
} else {
alert?.error("获取板卡信息失败");
}
} catch (e) {
alert?.error("获取数据失败");
console.error(e);
}
}
// 处理文件上传
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;
}
}
return {
boards,
uploadAndDownloadBitstreams,
hotresetBitstream,
refreshData,
handleFileChange,
getSelectedBitstreamNum,
getAllBoards,
fetchBoardsData,
addBoard,
deleteBoard,
};
});
export { useProvideBoardManager, useBoardManager };