287 lines
8.6 KiB
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>
|