This repository has been archived on 2025-10-29. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
FPGA_WebLab/src/components/UploadCard.vue

287 lines
8.6 KiB
Vue

<template>
<div class="flex flex-col bg-base-100 justify-center items-center gap-4">
<!-- Title -->
<h1 class="font-bold text-2xl">比特流文件</h1>
<!-- 示例比特流下载区域 (仅在有examId时显示) -->
<div v-if="examId && availableBitstreams.length > 0" class="w-full">
<fieldset class="fieldset w-full">
<legend class="fieldset-legend text-sm">示例比特流文件</legend>
<div class="space-y-2">
<div
v-for="bitstream in availableBitstreams"
:key="bitstream.id"
class="flex items-center justify-between p-2 border-2 border-base-300 rounded-lg"
>
<span class="text-sm">{{ bitstream.name }}</span>
<div class="flex gap-2">
<button
@click="handleExampleBitstream('download', bitstream)"
class="btn btn-sm btn-secondary"
:disabled="currentTask !== 'none'"
>
<div
v-if="
currentTask === 'downloading' &&
currentBitstreamId === bitstream.id
"
>
<span class="loading loading-spinner loading-xs"></span>
下载中...
</div>
<div v-else>下载示例</div>
</button>
<button
@click="handleExampleBitstream('program', bitstream)"
class="btn btn-sm btn-primary"
:disabled="currentTask !== 'none'"
>
<div
v-if="
currentTask === 'programming' &&
currentBitstreamId === bitstream.id
"
>
<span class="loading loading-spinner loading-xs"></span>
烧录中...
</div>
<div v-else>直接烧录</div>
</button>
</div>
</div>
</div>
</fieldset>
</div>
<!-- 分割线 -->
<div v-if="examId && availableBitstreams.length > 0" class="divider">
</div>
<!-- Input File -->
<fieldset class="fieldset w-full">
<legend class="fieldset-legend text-sm">上传自定义比特流文件</legend>
<input
type="file"
ref="fileInput"
class="file-input w-full"
@change="handleFileChange"
/>
<label class="fieldset-label">文件最大容量: {{ maxMemory }}MB</label>
</fieldset>
<!-- Upload Button -->
<div class="card-actions w-full">
<button
@click="handleUploadAndDownload"
class="btn btn-primary grow"
:disabled="currentTask !== 'none'"
>
<div v-if="currentTask === 'uploading'">
<span class="loading loading-spinner"></span>
上传中...
</div>
<div v-else-if="currentTask === 'programming'">
<span class="loading loading-spinner"></span>
{{ currentProgressPercent }}% ...
</div>
<div v-else>上传并下载</div>
</button>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, useTemplateRef, onMounted } from "vue";
import { AuthManager } from "@/utils/AuthManager";
import { useDialogStore } from "@/stores/dialog";
import { useEquipments } from "@/stores/equipments";
import { useRequiredInjection } from "@/utils/Common";
import { useAlertStore } from "./Alert";
import { ResourceClient, ResourcePurpose } from "@/APIClient";
import { useProgressStore } from "@/stores/progress";
import { ProgressStatus, type ProgressInfo } from "@/utils/signalR/server.Hubs";
interface Props {
maxMemory?: number;
examId?: string;
}
const props = withDefaults(defineProps<Props>(), {
maxMemory: 4,
examId: "",
});
const emits = defineEmits<{
finishedUpload: [file: File];
}>();
const alert = useRequiredInjection(useAlertStore);
const progressTracker = useProgressStore();
const dialog = useDialogStore();
const eqps = useEquipments();
const availableBitstreams = ref<{ id: string; name: string }[]>([]);
const fileInput = useTemplateRef("fileInput");
const bitstream = ref<File | undefined>(undefined);
// 用一个状态变量替代多个
const currentTask = ref<"none" | "uploading" | "downloading" | "programming">(
"none",
);
const currentBitstreamId = ref<string>("");
const currentProgressId = ref<string>("");
const currentProgressPercent = ref<number>(0);
onMounted(async () => {
if (bitstream.value && fileInput.value) {
let fileList = new DataTransfer();
fileList.items.add(bitstream.value);
fileInput.value.files = fileList.files;
}
await loadAvailableBitstreams();
});
function handleFileChange(event: Event): void {
const target = event.target as HTMLInputElement;
const file = target.files?.[0];
bitstream.value = file || undefined;
}
function checkFileInput(): boolean {
if (!bitstream.value) {
dialog.error(`未选择文件`);
return false;
}
const maxBytes = props.maxMemory! * 1024 * 1024;
if (bitstream.value.size > maxBytes) {
dialog.error(`文件大小超过最大限制: ${props.maxMemory}MB`);
return false;
}
return true;
}
async function downloadBitstream() {
currentTask.value = "programming";
try {
currentProgressId.value = await eqps.jtagDownloadBitstream(
currentBitstreamId.value,
);
progressTracker.register(
currentProgressId.value,
"programBitstream",
handleProgressUpdate,
);
} catch {
dialog.error("比特流烧录失败");
cleanProgressTracker();
}
}
function cleanProgressTracker() {
currentTask.value = "none";
currentProgressId.value = "";
currentBitstreamId.value = "";
currentProgressPercent.value = 0;
progressTracker.unregister(currentProgressId.value, "programBitstream");
}
async function loadAvailableBitstreams() {
if (!props.examId) {
availableBitstreams.value = [];
return;
}
try {
const resourceClient = AuthManager.createClient(ResourceClient);
const resources = await resourceClient.getResourceList(
props.examId,
"bitstream",
ResourcePurpose.Template,
);
availableBitstreams.value =
resources.map((r) => ({ id: r.id, name: r.name })) || [];
} catch (error) {
availableBitstreams.value = [];
}
}
// 统一处理示例比特流的下载/烧录
async function handleExampleBitstream(
action: "download" | "program",
bitstreamObj: { id: string; name: string },
) {
if (currentTask.value !== "none") return;
currentBitstreamId.value = bitstreamObj.id;
if (action === "download") {
currentTask.value = "downloading";
try {
const resourceClient = AuthManager.createClient(ResourceClient);
const response = await resourceClient.getResourceById(bitstreamObj.id);
if (response && response.data) {
const url = URL.createObjectURL(response.data);
const link = document.createElement("a");
link.href = url;
link.download = response.fileName || bitstreamObj.name;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
alert.info("示例比特流下载成功");
} else {
alert.error("下载失败:响应数据为空");
}
} catch {
alert.error("下载示例比特流失败");
} finally {
currentTask.value = "none";
currentBitstreamId.value = "";
}
} else if (action === "program") {
currentBitstreamId.value = bitstreamObj.id;
await downloadBitstream();
}
}
// 上传并下载
async function handleUploadAndDownload() {
if (currentTask.value !== "none") return;
if (!checkFileInput()) return;
currentTask.value = "uploading";
let uploadedBitstreamId: string | null = null;
try {
uploadedBitstreamId = await eqps.jtagUploadBitstream(
bitstream.value!,
props.examId || "",
);
if (!uploadedBitstreamId) throw new Error("上传失败");
emits("finishedUpload", bitstream.value!);
} catch {
dialog.error("上传失败");
currentTask.value = "none";
return;
}
currentBitstreamId.value = uploadedBitstreamId;
await downloadBitstream();
}
function handleProgressUpdate(msg: ProgressInfo) {
// console.log(msg);
if (msg.status === ProgressStatus.Running)
currentProgressPercent.value = msg.progressPercent;
else if (msg.status === ProgressStatus.Failed) {
dialog.error(`比特流烧录失败: ${msg.errorMessage}`);
cleanProgressTracker();
} else if (msg.status === ProgressStatus.Completed) {
dialog.info("比特流烧录成功");
cleanProgressTracker();
}
}
</script>
<style scoped lang="postcss">
@import "../assets/main.css";
</style>