feat: 添加下载进度条
This commit is contained in:
@@ -1,276 +1,314 @@
|
||||
<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>
|
||||
下载中...
|
||||
</div>
|
||||
<div v-else>
|
||||
下载示例
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
@click="programExampleBitstream(bitstream)"
|
||||
class="btn btn-sm btn-primary"
|
||||
:disabled="isDownloading || isProgramming || !uploadEvent"
|
||||
>
|
||||
<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>
|
||||
{{ buttonText }}
|
||||
</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";
|
||||
|
||||
interface Props {
|
||||
uploadEvent?: (file: File, examId: string) => Promise<number | null>;
|
||||
downloadEvent?: (bitstreamId: number) => Promise<boolean>;
|
||||
maxMemory?: number;
|
||||
examId?: string; // 新增examId属性
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
maxMemory: 4,
|
||||
examId: '',
|
||||
});
|
||||
|
||||
const emits = defineEmits<{
|
||||
finishedUpload: [file: File];
|
||||
}>();
|
||||
|
||||
const dialog = useDialogStore();
|
||||
|
||||
const isUploading = ref(false);
|
||||
const isDownloading = ref(false);
|
||||
const isProgramming = ref(false);
|
||||
const availableBitstreams = ref<{id: number, name: string}[]>([]);
|
||||
|
||||
const buttonText = computed(() => {
|
||||
return isUndefined(props.downloadEvent) ? "上传" : "上传并下载";
|
||||
});
|
||||
|
||||
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 resourceClient = AuthManager.createAuthenticatedResourceClient();
|
||||
|
||||
if (props.downloadEvent) {
|
||||
const downloadSuccess = await props.downloadEvent(bitstream.id);
|
||||
if (downloadSuccess) {
|
||||
dialog.info("示例比特流烧录成功");
|
||||
} else {
|
||||
dialog.error("烧录失败");
|
||||
}
|
||||
} else {
|
||||
dialog.info("示例比特流props.downloadEvent未定义 无法烧录");
|
||||
}
|
||||
} 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;
|
||||
if (isUndefined(props.uploadEvent)) {
|
||||
dialog.error("无法上传");
|
||||
return;
|
||||
}
|
||||
|
||||
isUploading.value = true;
|
||||
let uploadedBitstreamId: number | null = null;
|
||||
try {
|
||||
console.log("开始上传比特流文件:", bitstream.value.name);
|
||||
const bitstreamId = await props.uploadEvent(bitstream.value, props.examId || '');
|
||||
console.log("上传结果,ID:", bitstreamId);
|
||||
if (isUndefined(props.downloadEvent)) {
|
||||
console.log("上传成功,下载未定义");
|
||||
isUploading.value = false;
|
||||
return;
|
||||
}
|
||||
if (bitstreamId === null || bitstreamId === undefined) {
|
||||
isUploading.value = false;
|
||||
return;
|
||||
}
|
||||
uploadedBitstreamId = bitstreamId;
|
||||
} catch (e) {
|
||||
dialog.error("上传失败");
|
||||
console.error(e);
|
||||
return;
|
||||
}
|
||||
|
||||
// Download
|
||||
try {
|
||||
console.log("开始下载比特流,ID:", uploadedBitstreamId);
|
||||
if (uploadedBitstreamId === null || uploadedBitstreamId === undefined) {
|
||||
dialog.error("uploadedBitstreamId is null or undefined");
|
||||
} else {
|
||||
const ret = await props.downloadEvent(uploadedBitstreamId);
|
||||
if (ret) dialog.info("下载成功");
|
||||
else dialog.error("下载失败");
|
||||
}
|
||||
} catch (e) {
|
||||
dialog.error("下载失败");
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
isUploading.value = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="postcss">
|
||||
@import "../assets/main.css";
|
||||
</style>
|
||||
<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>
|
||||
|
||||
Reference in New Issue
Block a user