545 lines
16 KiB
TypeScript
545 lines
16 KiB
TypeScript
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 "../../utils/BoardManager";
|
|
import { useBoardManager } from "../../utils/BoardManager";
|
|
import { useDialogStore } from "@/stores/dialog";
|
|
|
|
const [useProvideBoardTableManager, useBoardTableManager] =
|
|
createInjectionState(() => {
|
|
// 从BoardManager获取数据和方法
|
|
const boardManager = useBoardManager()!;
|
|
|
|
const dialog = useDialogStore();
|
|
|
|
// 编辑状态
|
|
const isEditMode = ref(false);
|
|
|
|
// 表格状态管理
|
|
const sorting = ref<SortingState>([]);
|
|
const columnFilters = ref<ColumnFiltersState>([]);
|
|
const columnVisibility = ref<VisibilityState>({
|
|
// 默认隐藏端口、ID、状态列和板卡名称列
|
|
port: false,
|
|
id: false,
|
|
status: false,
|
|
version: false,
|
|
});
|
|
const rowSelection = ref({});
|
|
const expanded = ref<ExpandedState>({});
|
|
|
|
// 表格列定义
|
|
const columns: ColumnDef<BoardData>[] = [
|
|
{
|
|
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<boolean> {
|
|
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<boolean> {
|
|
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<boolean> {
|
|
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<boolean> {
|
|
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<boolean> {
|
|
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 };
|