315 lines
9.3 KiB
Vue
315 lines
9.3 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="downloadExampleBitstream(bitstream)"
|
||
class="btn btn-sm btn-secondary"
|
||
:disabled="isDownloading || isProgramming"
|
||
>
|
||
<div v-if="isDownloading">
|
||
<span class="loading loading-spinner loading-xs"></span>
|
||
{{ downloadProgress }}%
|
||
</div>
|
||
<div v-else>下载示例</div>
|
||
</button>
|
||
<button
|
||
@click="programExampleBitstream(bitstream)"
|
||
class="btn btn-sm btn-primary"
|
||
:disabled="isDownloading || isProgramming"
|
||
>
|
||
<div v-if="isProgramming">
|
||
<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="handleClick"
|
||
class="btn btn-primary grow"
|
||
:disabled="isUploading || isProgramming"
|
||
>
|
||
<div v-if="isUploading">
|
||
<span class="loading loading-spinner"></span>
|
||
上传中...
|
||
</div>
|
||
<div v-else>上传并下载</div>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script lang="ts" setup>
|
||
import { computed, ref, useTemplateRef, onMounted } from "vue";
|
||
import { AuthManager } from "@/utils/AuthManager"; // Adjust the path based on your project structure
|
||
import { useDialogStore } from "@/stores/dialog";
|
||
import { isNull, isUndefined } from "lodash";
|
||
import { useEquipments } from "@/stores/equipments";
|
||
import type { HubConnection } from "@microsoft/signalr";
|
||
import type {
|
||
IProgressHub,
|
||
IProgressReceiver,
|
||
} from "@/TypedSignalR.Client/server.Hubs";
|
||
import { getHubProxyFactory, getReceiverRegister } from "@/TypedSignalR.Client";
|
||
import { ProgressStatus } from "@/server.Hubs";
|
||
import { useRequiredInjection } from "@/utils/Common";
|
||
import { useAlertStore } from "./Alert";
|
||
|
||
interface Props {
|
||
maxMemory?: number;
|
||
examId?: string; // 新增examId属性
|
||
}
|
||
|
||
const props = withDefaults(defineProps<Props>(), {
|
||
maxMemory: 4,
|
||
examId: "",
|
||
});
|
||
|
||
const emits = defineEmits<{
|
||
finishedUpload: [file: File];
|
||
}>();
|
||
|
||
const alert = useRequiredInjection(useAlertStore);
|
||
const dialog = useDialogStore();
|
||
const eqps = useEquipments();
|
||
|
||
const isUploading = ref(false);
|
||
const isDownloading = ref(false);
|
||
const isProgramming = ref(false);
|
||
const availableBitstreams = ref<{ id: number; name: string }[]>([]);
|
||
|
||
// Progress
|
||
const downloadTaskId = ref("");
|
||
const downloadProgress = ref(0);
|
||
const progressHubConnection = ref<HubConnection>();
|
||
const progressHubProxy = ref<IProgressHub>();
|
||
const progressHubReceiver: IProgressReceiver = {
|
||
onReceiveProgress: async (msg) => {
|
||
if (msg.taskId == downloadTaskId.value) {
|
||
if (msg.status == ProgressStatus.InProgress) {
|
||
downloadProgress.value = msg.progressPercent;
|
||
} else if (msg.status == ProgressStatus.Failed) {
|
||
dialog.error(msg.errorMessage);
|
||
} else if (msg.status == ProgressStatus.Completed) {
|
||
alert.info("比特流下载成功");
|
||
}
|
||
}
|
||
},
|
||
};
|
||
onMounted(async () => {
|
||
progressHubConnection.value =
|
||
AuthManager.createAuthenticatedProgressHubConnection();
|
||
progressHubProxy.value = getHubProxyFactory("IProgressHub").createHubProxy(
|
||
progressHubConnection.value,
|
||
);
|
||
getReceiverRegister("IProgressReceiver").register(
|
||
progressHubConnection.value,
|
||
progressHubReceiver,
|
||
);
|
||
});
|
||
|
||
const fileInput = useTemplateRef("fileInput");
|
||
const bitstream = defineModel("bitstreamFile", {
|
||
type: File,
|
||
default: undefined,
|
||
});
|
||
|
||
// 初始化时加载示例比特流
|
||
onMounted(async () => {
|
||
if (!isUndefined(bitstream.value) && !isNull(fileInput.value)) {
|
||
let fileList = new DataTransfer();
|
||
fileList.items.add(bitstream.value);
|
||
fileInput.value.files = fileList.files;
|
||
}
|
||
|
||
await loadAvailableBitstreams();
|
||
});
|
||
|
||
// 加载可用的比特流文件列表
|
||
async function loadAvailableBitstreams() {
|
||
console.log("加载可用比特流文件,examId:", props.examId);
|
||
if (!props.examId) {
|
||
availableBitstreams.value = [];
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const resourceClient = AuthManager.createAuthenticatedResourceClient();
|
||
// 使用新的ResourceClient API获取比特流模板资源列表
|
||
const resources = await resourceClient.getResourceList(
|
||
props.examId,
|
||
"bitstream",
|
||
"template",
|
||
);
|
||
availableBitstreams.value =
|
||
resources.map((r) => ({ id: r.id, name: r.name })) || [];
|
||
} catch (error) {
|
||
console.error("加载比特流列表失败:", error);
|
||
availableBitstreams.value = [];
|
||
}
|
||
}
|
||
|
||
// 下载示例比特流
|
||
async function downloadExampleBitstream(bitstream: {
|
||
id: number;
|
||
name: string;
|
||
}) {
|
||
if (isDownloading.value) return;
|
||
|
||
isDownloading.value = true;
|
||
try {
|
||
const resourceClient = AuthManager.createAuthenticatedResourceClient();
|
||
|
||
// 使用新的ResourceClient API获取资源文件
|
||
const response = await resourceClient.getResourceById(bitstream.id);
|
||
|
||
if (response && response.data) {
|
||
// 创建下载链接
|
||
const url = URL.createObjectURL(response.data);
|
||
const link = document.createElement("a");
|
||
link.href = url;
|
||
link.download = response.fileName || bitstream.name;
|
||
document.body.appendChild(link);
|
||
link.click();
|
||
document.body.removeChild(link);
|
||
URL.revokeObjectURL(url);
|
||
|
||
dialog.info("示例比特流下载成功");
|
||
} else {
|
||
dialog.error("下载失败:响应数据为空");
|
||
}
|
||
} catch (error) {
|
||
console.error("下载示例比特流失败:", error);
|
||
dialog.error("下载示例比特流失败");
|
||
} finally {
|
||
isDownloading.value = false;
|
||
}
|
||
}
|
||
|
||
// 直接烧录示例比特流
|
||
async function programExampleBitstream(bitstream: {
|
||
id: number;
|
||
name: string;
|
||
}) {
|
||
if (isProgramming.value) return;
|
||
|
||
isProgramming.value = true;
|
||
try {
|
||
const downloadTaskId = await eqps.jtagDownloadBitstream(bitstream.id);
|
||
} catch (error) {
|
||
console.error("烧录示例比特流失败:", error);
|
||
dialog.error("烧录示例比特流失败");
|
||
} finally {
|
||
isProgramming.value = false;
|
||
}
|
||
}
|
||
|
||
function handleFileChange(event: Event): void {
|
||
const target = event.target as HTMLInputElement;
|
||
const file = target.files?.[0]; // 获取选中的第一个文件
|
||
|
||
if (!file) {
|
||
return;
|
||
}
|
||
|
||
bitstream.value = file;
|
||
}
|
||
|
||
function checkFile(file: File): boolean {
|
||
const maxBytes = props.maxMemory! * 1024 * 1024; // 将最大容量从 MB 转换为字节
|
||
if (file.size > maxBytes) {
|
||
dialog.error(`文件大小超过最大限制: ${props.maxMemory}MB`);
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
async function handleClick(event: Event): Promise<void> {
|
||
console.log("上传按钮被点击");
|
||
if (isNull(bitstream.value) || isUndefined(bitstream.value)) {
|
||
dialog.error(`未选择文件`);
|
||
return;
|
||
}
|
||
|
||
if (!checkFile(bitstream.value)) return;
|
||
|
||
isUploading.value = true;
|
||
let uploadedBitstreamId: number | null = null;
|
||
try {
|
||
console.log("开始上传比特流文件:", bitstream.value.name);
|
||
const bitstreamId = await eqps.jtagUploadBitstream(
|
||
bitstream.value,
|
||
props.examId || "",
|
||
);
|
||
console.log("上传结果,ID:", bitstreamId);
|
||
if (bitstreamId === null || bitstreamId === undefined) {
|
||
isUploading.value = false;
|
||
return;
|
||
}
|
||
uploadedBitstreamId = bitstreamId;
|
||
} catch (e) {
|
||
dialog.error("上传失败");
|
||
console.error(e);
|
||
return;
|
||
}
|
||
isUploading.value = false;
|
||
|
||
// Download
|
||
try {
|
||
console.log("开始下载比特流,ID:", uploadedBitstreamId);
|
||
if (uploadedBitstreamId === null || uploadedBitstreamId === undefined) {
|
||
dialog.error("uploadedBitstreamId is null or undefined");
|
||
} else {
|
||
isDownloading.value = true;
|
||
downloadTaskId.value =
|
||
await eqps.jtagDownloadBitstream(uploadedBitstreamId);
|
||
}
|
||
} catch (e) {
|
||
dialog.error("下载失败");
|
||
console.error(e);
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped lang="postcss">
|
||
@import "../assets/main.css";
|
||
</style>
|