diff --git a/server/Program.cs b/server/Program.cs index 3d519c5..a226845 100644 --- a/server/Program.cs +++ b/server/Program.cs @@ -89,6 +89,7 @@ try { options.AddPolicy("Users", policy => policy .AllowAnyOrigin() + .AllowAnyMethod() .AllowAnyHeader() ); }); diff --git a/src/views/User/AddBoardDialog.vue b/src/views/User/AddBoardDialog.vue new file mode 100644 index 0000000..c43e685 --- /dev/null +++ b/src/views/User/AddBoardDialog.vue @@ -0,0 +1,251 @@ + + + + + \ No newline at end of file diff --git a/src/views/User/BoardControl.vue b/src/views/User/BoardControl.vue deleted file mode 100644 index eb76249..0000000 --- a/src/views/User/BoardControl.vue +++ /dev/null @@ -1,485 +0,0 @@ - - - - - diff --git a/src/views/User/BoardManager.ts b/src/views/User/BoardManager.ts index 88c54e3..1b55213 100644 --- a/src/views/User/BoardManager.ts +++ b/src/views/User/BoardManager.ts @@ -1,8 +1,6 @@ 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"; @@ -17,9 +15,6 @@ export interface BoardData extends Board { } const [useProvideBoardManager, useBoardManager] = createInjectionState(() => { - const dialog = useDialogStore(); - const alert = useAlertStore(); - // 远程升级相关参数 const devPort = 1234; const remoteUpdater = new RemoteUpdateClient(); @@ -36,13 +31,14 @@ const [useProvideBoardManager, useBoardManager] = createInjectionState(() => { return 0; } - // 获取所有板卡信息(管理员权限)- 不显示提示信息,供内部调用 - async function fetchBoardsData(): Promise { + // 获取所有板卡信息(管理员权限) + async function getAllBoards(): Promise<{ success: boolean; error?: string }> { try { // 验证管理员权限 const hasAdminAuth = await AuthManager.verifyAdminAuth(); if (!hasAdminAuth) { - return false; + console.error("权限验证失败"); + return { success: false, error: "权限不足" }; } const client = AuthManager.createAuthenticatedClient(); @@ -63,109 +59,98 @@ const [useProvideBoardManager, useBoardManager] = createInjectionState(() => { toJSON: board.toJSON?.bind(board), }; }); - return true; + console.log("获取板卡信息成功", result.length); + return { success: true }; } else { - return false; + console.error("获取板卡信息失败:返回结果为空"); + return { success: false, error: "获取板卡信息失败" }; } - } catch (e) { - console.error(e); - return false; + } catch (e: any) { + console.error("获取板卡信息异常:", e); + return { success: false, error: e.message || "获取板卡信息异常" }; } } - // 获取所有板卡信息(管理员权限)- 显示提示信息,供外部调用 - async function getAllBoards(): Promise { - const result = await fetchBoardsData(); - if (result) { - dialog.info("获取板卡信息成功"); - } else { - dialog.warn("获取板卡信息失败"); - } - return result; - } - // 新增板卡(管理员权限) async function addBoard( name: string, ipAddr: string, port: number, - ): Promise { + ): Promise<{ success: boolean; error?: string; boardId?: string }> { try { // 验证管理员权限 const hasAdminAuth = await AuthManager.verifyAdminAuth(); if (!hasAdminAuth) { - dialog.error("需要管理员权限"); - return false; + console.error("权限验证失败"); + return { success: false, error: "权限不足" }; } // 验证输入参数 if (!name || !ipAddr || !port) { - dialog.error("请填写完整的板卡信息"); - return false; + console.error("参数验证失败", { name, ipAddr, port }); + return { success: false, error: "参数不完整" }; } const client = AuthManager.createAuthenticatedClient(); const boardId = await client.addBoard(name, ipAddr, port); if (boardId) { - // 静默刷新板卡列表,不显示重复提示 - await fetchBoardsData(); - dialog.info("新增板卡成功"); - return true; + console.log("新增板卡成功", { boardId, name, ipAddr, port }); + // 刷新板卡列表 + await getAllBoards(); + return { success: true}; } else { - dialog.warn("新增板卡失败"); - return false; + console.error("新增板卡失败:返回ID为空"); + return { success: false, error: "新增板卡失败" }; } } catch (e: any) { + console.error("新增板卡异常:", e); if (e.status === 401) { - dialog.error("权限不足,需要管理员权限"); + return { success: false, error: "权限不足" }; } else if (e.status === 400) { - dialog.error("输入参数错误"); + return { success: false, error: "输入参数错误" }; } else { - dialog.error("新增板卡失败"); + return { success: false, error: e.message || "新增板卡异常" }; } - console.error(e); - return false; } } // 删除板卡(管理员权限) - async function deleteBoard(boardId: string): Promise { + async function deleteBoard(boardId: string): Promise<{ success: boolean; error?: string }> { try { // 验证管理员权限 const hasAdminAuth = await AuthManager.verifyAdminAuth(); if (!hasAdminAuth) { - dialog.error("需要管理员权限"); - return false; + console.error("权限验证失败"); + return { success: false, error: "权限不足" }; } if (!boardId) { - dialog.error("板卡ID不能为空"); - return false; + console.error("板卡ID为空"); + return { success: false, error: "板卡ID不能为空" }; } const client = AuthManager.createAuthenticatedClient(); const result = await client.deleteBoard(boardId); if (result > 0) { - // 静默刷新板卡列表,不显示重复提示 - await fetchBoardsData(); - dialog.info("删除板卡成功"); - return true; + console.log("删除板卡成功", { boardId, deletedCount: result }); + // 刷新板卡列表 + await getAllBoards(); + return { success: true }; } else { - dialog.warn("删除板卡失败"); - return false; + console.error("删除板卡失败:影响行数为0"); + return { success: false, error: "删除板卡失败" }; } } catch (e: any) { + console.error("删除板卡异常:", e); if (e.status === 401) { - dialog.error("权限不足,需要管理员权限"); + return { success: false, error: "权限不足" }; } else if (e.status === 400) { - dialog.error("输入参数错误"); + return { success: false, error: "输入参数错误" }; } else { - dialog.error("删除板卡失败"); + return { success: false, error: e.message || "删除板卡异常" }; } - console.error(e); - return false; } } @@ -176,20 +161,23 @@ const [useProvideBoardManager, useBoardManager] = createInjectionState(() => { 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) { - dialog.error("未选择比特流"); - return; + console.error("未选择比特流文件"); + return { success: false, error: "未选择比特流文件" }; } try { + console.log("开始上传比特流", { boardIp: board.ipAddr, fileCount: cnt }); + const uploadResult = await remoteUpdater.uploadBitstreams( - board.ipAddr, // 使用板卡的IP地址 + board.ipAddr, Common.toFileParameterOrNull(goldBitstream), Common.toFileParameterOrNull(appBitstream1), Common.toFileParameterOrNull(appBitstream2), @@ -197,10 +185,12 @@ const [useProvideBoardManager, useBoardManager] = createInjectionState(() => { ); if (!uploadResult) { - dialog.warn("上传比特流出错"); - return; + console.error("上传比特流失败"); + return { success: false, error: "上传比特流失败" }; } + console.log("比特流上传成功,开始固化"); + const downloadResult = await remoteUpdater.downloadMultiBitstreams( board.ipAddr, board.port, @@ -208,47 +198,42 @@ const [useProvideBoardManager, useBoardManager] = createInjectionState(() => { ); if (downloadResult != cnt) { - dialog.warn("固化比特流出错"); + console.error("固化比特流失败", { expected: cnt, actual: downloadResult }); + return { success: false, error: "固化比特流失败" }; } else { - dialog.info("固化比特流成功"); + console.log("固化比特流成功", { count: downloadResult }); + return { success: true }; } - } catch (e) { - dialog.error("比特流上传错误"); - console.error(e); + } catch (e: any) { + console.error("比特流操作异常:", e); + return { success: false, error: e.message || "比特流操作异常" }; } } // 热启动位流 - async function hotresetBitstream(board: BoardData, bitstreamNum: number) { + 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) { - dialog.info("切换比特流成功"); + console.log("热启动比特流成功"); + return { success: true }; } else { - dialog.error("切换比特流失败"); + console.error("热启动比特流失败"); + return { success: false, 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); + } catch (e: any) { + console.error("热启动比特流异常:", e); + return { success: false, error: e.message || "热启动比特流异常" }; } } @@ -268,6 +253,7 @@ 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 }); } } @@ -275,11 +261,9 @@ const [useProvideBoardManager, useBoardManager] = createInjectionState(() => { boards, uploadAndDownloadBitstreams, hotresetBitstream, - refreshData, handleFileChange, getSelectedBitstreamNum, getAllBoards, - fetchBoardsData, addBoard, deleteBoard, }; diff --git a/src/views/User/BoardTable.vue b/src/views/User/BoardTable.vue new file mode 100644 index 0000000..aa924c7 --- /dev/null +++ b/src/views/User/BoardTable.vue @@ -0,0 +1,220 @@ + + + + + diff --git a/src/views/User/BoardTableManager.ts b/src/views/User/BoardTableManager.ts new file mode 100644 index 0000000..ef9b51f --- /dev/null +++ b/src/views/User/BoardTableManager.ts @@ -0,0 +1,544 @@ +import type { + ColumnDef, + ColumnFiltersState, + ExpandedState, + SortingState, + VisibilityState, +} from "@tanstack/vue-table"; +import { + getCoreRowModel, + getExpandedRowModel, + getFilteredRowModel, + getPaginationRowModel, + getSortedRowModel, + useVueTable, +} 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 { useDialogStore } from "@/stores/dialog"; + +const [useProvideBoardTableManager, useBoardTableManager] = + createInjectionState(() => { + // 从BoardManager获取数据和方法 + const boardManager = useBoardManager()!; + + const dialog = useDialogStore(); + + // 编辑状态 + const isEditMode = ref(false); + + // 表格状态管理 + const sorting = ref([]); + const columnFilters = ref([]); + const columnVisibility = ref({ + // 默认隐藏端口、ID、状态列和板卡名称列 + port: false, + id: false, + status: false, + version: false, + }); + const rowSelection = ref({}); + const expanded = ref({}); + + // 表格列定义 + const columns: ColumnDef[] = [ + { + id: "select", + header: ({ table }) => + h("input", { + type: "checkbox", + class: "checkbox", + checked: + table.getIsAllPageRowsSelected() || + (table.getIsSomePageRowsSelected() ? "indeterminate" : false), + onChange: (event: Event) => + table.toggleAllPageRowsSelected( + !!(event.target as HTMLInputElement).checked, + ), + }), + cell: ({ row }) => + h("input", { + type: "checkbox", + class: "checkbox", + checked: row.getIsSelected(), + onChange: (event: Event) => + row.toggleSelected(!!(event.target as HTMLInputElement).checked), + }), + enableSorting: false, + enableHiding: false, + }, + { + accessorKey: "boardName", + header: "板卡名称", + cell: ({ row }) => { + const device = row.original; + return isEditMode.value + ? h("input", { + type: "text", + class: "input input-sm w-full", + value: device.boardName, + onInput: (e: Event) => { + device.boardName = (e.target as HTMLInputElement).value; + }, + }) + : h("span", { class: "font-medium" }, device.boardName); + }, + enableHiding: true, + }, + { + accessorKey: "devAddr", + 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); + }, + }, + { + accessorKey: "port", + 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()); + }, + enableHiding: true, + }, + { + accessorKey: "id", + header: "设备ID", + cell: ({ row }) => + h("span", { class: "font-mono text-xs" }, row.original.id), + enableHiding: true, + }, + { + accessorKey: "status", + header: "状态", + cell: ({ row }) => { + const device = row.original; + const statusText = device.status === 0 ? "忙碌" : "可用"; + const statusClass = + device.status === 0 ? "badge-warning" : "badge-success"; + return h( + "span", + { + class: `badge ${statusClass} min-w-15`, + }, + statusText, + ); + }, + enableHiding: true, + }, + { + accessorKey: "version", + header: "版本号", + cell: ({ row }) => + h("span", { class: "font-mono" }, row.original.firmVersion), + }, + { + accessorKey: "defaultBitstream", + header: "默认启动位流", + cell: ({ row }) => { + const device = row.original; + return h( + "select", + { + class: "select select-bordered select-sm w-full", + value: device.defaultBitstream, + onChange: (e: Event) => { + device.defaultBitstream = (e.target as HTMLSelectElement).value; + }, + }, + [ + h("option", { value: "黄金位流" }, "黄金位流"), + h("option", { value: "应用位流1" }, "应用位流1"), + h("option", { value: "应用位流2" }, "应用位流2"), + h("option", { value: "应用位流3" }, "应用位流3"), + ], + ); + }, + }, + { + id: "goldBitstream", + header: "黄金位流", + cell: ({ row }) => { + const device = row.original; + return h("input", { + type: "file", + class: "file-input file-input-primary file-input-sm", + onChange: (e: Event) => + boardManager.handleFileChange(e, device, "goldBitstreamFile"), + }); + }, + }, + { + id: "appBitstream1", + header: "应用位流1", + cell: ({ row }) => { + const device = row.original; + return h("input", { + type: "file", + class: "file-input file-input-secondary file-input-sm", + onChange: (e: Event) => + boardManager.handleFileChange(e, device, "appBitstream1File"), + }); + }, + }, + { + id: "appBitstream2", + header: "应用位流2", + cell: ({ row }) => { + const device = row.original; + return h("input", { + type: "file", + class: "file-input file-input-accent file-input-sm", + onChange: (e: Event) => + boardManager.handleFileChange(e, device, "appBitstream2File"), + }); + }, + }, + { + id: "appBitstream3", + header: "应用位流3", + cell: ({ row }) => { + const device = row.original; + return h("input", { + type: "file", + class: "file-input file-input-info file-input-sm", + onChange: (e: Event) => + boardManager.handleFileChange(e, device, "appBitstream3File"), + }); + }, + }, + { + id: "actions", + header: "操作", + cell: ({ row }) => { + const device = row.original; + + // 根据编辑模式显示不同的按钮 + if (isEditMode.value) { + return h( + "div", + { + class: ["flex gap-2", { "min-w-30": !isEditMode.value }], + }, + [ + h( + "button", + { + class: "btn btn-error btn-sm", + onClick: async () => { + const confirmed = confirm( + `确定要删除设备 ${device.ipAddr} 吗?`, + ); + if (confirmed) { + await deleteBoard(device.id); + } + }, + }, + "删除", + ), + ], + ); + } else { + return h("div", { class: "flex gap-2 min-w-30" }, [ + h( + "button", + { + class: "btn btn-warning btn-sm", + onClick: () => + uploadAndDownloadBitstreams( + device, + device.goldBitstreamFile, + device.appBitstream1File, + device.appBitstream2File, + device.appBitstream3File, + ), + }, + "固化", + ), + h( + "button", + { + class: "btn btn-success btn-sm", + onClick: () => + hotresetBitstream( + device, + boardManager.getSelectedBitstreamNum( + device.defaultBitstream, + ), + ), + }, + "热启动", + ), + ]); + } + }, + enableHiding: false, + }, + ]; + + // 创建表格实例 + const table = useVueTable({ + get data() { + return boardManager.boards.value; + }, + get columns() { + return columns; + }, + getCoreRowModel: getCoreRowModel(), + getPaginationRowModel: getPaginationRowModel(), + getSortedRowModel: getSortedRowModel(), + getFilteredRowModel: getFilteredRowModel(), + getExpandedRowModel: getExpandedRowModel(), + onSortingChange: (updaterOrValue) => { + if (typeof updaterOrValue === "function") { + sorting.value = updaterOrValue(sorting.value); + } else { + sorting.value = updaterOrValue; + } + }, + onColumnFiltersChange: (updaterOrValue) => { + if (typeof updaterOrValue === "function") { + columnFilters.value = updaterOrValue(columnFilters.value); + } else { + columnFilters.value = updaterOrValue; + } + }, + onColumnVisibilityChange: (updaterOrValue) => { + if (typeof updaterOrValue === "function") { + columnVisibility.value = updaterOrValue(columnVisibility.value); + } else { + columnVisibility.value = updaterOrValue; + } + }, + onRowSelectionChange: (updaterOrValue) => { + if (typeof updaterOrValue === "function") { + rowSelection.value = updaterOrValue(rowSelection.value); + } else { + rowSelection.value = updaterOrValue; + } + }, + onExpandedChange: (updaterOrValue) => { + if (typeof updaterOrValue === "function") { + expanded.value = updaterOrValue(expanded.value); + } else { + expanded.value = updaterOrValue; + } + }, + state: { + get sorting() { + return sorting.value; + }, + get columnFilters() { + return columnFilters.value; + }, + get columnVisibility() { + return columnVisibility.value; + }, + get rowSelection() { + return rowSelection.value; + }, + get expanded() { + return expanded.value; + }, + }, + }); + + // UI层的API封装方法 - 添加dialog提示 + + // 获取所有板卡信息 + async function getAllBoards(): Promise { + const result = await boardManager.getAllBoards(); + if (result.success) { + dialog?.info("获取板卡信息成功"); + } else { + dialog?.error(result.error || "获取板卡信息失败"); + } + return result.success; + } + + // 新增板卡 + async function addBoard(name: string, ipAddr: string, port: number): Promise { + const result = await boardManager.addBoard(name, ipAddr, port); + if (result.success) { + dialog?.info("新增板卡成功"); + } else { + dialog?.error(result.error || "新增板卡失败"); + } + return result.success; + } + + // 删除板卡 + async function deleteBoard(boardId: string): Promise { + const result = await boardManager.deleteBoard(boardId); + if (result.success) { + dialog?.info("删除板卡成功"); + } else { + dialog?.error(result.error || "删除板卡失败"); + } + return result.success; + } + + // 上传并固化位流 + async function uploadAndDownloadBitstreams( + board: BoardData, + goldBitstream?: File, + appBitstream1?: File, + appBitstream2?: File, + appBitstream3?: File, + ): Promise { + const result = await boardManager.uploadAndDownloadBitstreams( + board, + goldBitstream, + appBitstream1, + appBitstream2, + appBitstream3, + ); + if (result.success) { + dialog?.info("固化比特流成功"); + } else { + dialog?.error(result.error || "固化比特流失败"); + } + return result.success; + } + + // 热启动位流 + async function hotresetBitstream(board: BoardData, bitstreamNum: number): Promise { + const result = await boardManager.hotresetBitstream(board, bitstreamNum); + if (result.success) { + dialog?.info("切换比特流成功"); + } else { + dialog?.error(result.error || "切换比特流失败"); + } + return result.success; + } + + // 表格操作方法 + const getSelectedRows = () => table.getFilteredSelectedRowModel().rows; + const getAllRows = () => table.getFilteredRowModel().rows; + const getColumnByKey = (key: string) => table.getColumn(key); + const getAllHideableColumns = () => + table.getAllColumns().filter((column) => column.getCanHide()); + const getHeaderGroups = () => table.getHeaderGroups(); + const getRowModel = () => table.getRowModel(); + const canPreviousPage = () => table.getCanPreviousPage(); + const canNextPage = () => table.getCanNextPage(); + const previousPage = () => table.previousPage(); + const nextPage = () => table.nextPage(); + + // 编辑模式控制 + const toggleEditMode = () => { + isEditMode.value = !isEditMode.value; + }; + + // 删除选中的实验板 + const deleteSelectedBoards = async () => { + const selectedRows = getSelectedRows(); + + if (selectedRows.length === 0) { + dialog?.warn("请先选择要删除的实验板"); + return false; + } + + const boardNames = selectedRows + .map((row) => row.original.boardName || row.original.ipAddr) + .join("、"); + const confirmed = confirm( + `确定要删除以下 ${selectedRows.length} 个实验板吗?\n${boardNames}`, + ); + + if (!confirmed) { + return false; + } + + let successCount = 0; + let failCount = 0; + + // 批量删除 + for (const row of selectedRows) { + const board = row.original; + const result = await boardManager.deleteBoard(board.id); + if (result.success) { + successCount++; + } else { + failCount++; + } + } + + // 清空选择状态 + rowSelection.value = {}; + + // 显示结果提示 + if (failCount === 0) { + dialog?.info(`成功删除 ${successCount} 个实验板`); + } else if (successCount === 0) { + dialog?.error( + `删除失败,共 ${failCount} 个实验板删除失败`, + ); + } else { + dialog?.warn( + `部分删除成功:成功 ${successCount} 个,失败 ${failCount} 个`, + ); + } + + return successCount > 0; + }; + + return { + // 表格实例 + table, + // 列定义 + columns, + // 表格操作方法 + getSelectedRows, + getAllRows, + getColumnByKey, + getAllHideableColumns, + getHeaderGroups, + getRowModel, + canPreviousPage, + canNextPage, + previousPage, + nextPage, + deleteSelectedBoards, + // 状态 + sorting, + columnFilters, + columnVisibility, + rowSelection, + expanded, + // 编辑模式 + isEditMode, + toggleEditMode, + // UI层封装的API方法 + getAllBoards, + addBoard, + deleteBoard, + uploadAndDownloadBitstreams, + hotresetBitstream, + // BoardManager 的引用 + boardManager, + }; + }); + +export { useProvideBoardTableManager, useBoardTableManager }; diff --git a/src/views/User/Index.vue b/src/views/User/Index.vue index 6001ed2..2d3368b 100644 --- a/src/views/User/Index.vue +++ b/src/views/User/Index.vue @@ -20,14 +20,14 @@

这里是用户信息页面的内容。

- +