fix: 修复进度条的问题

This commit is contained in:
2025-08-17 13:33:11 +08:00
parent 97b86acfa8
commit 55edfd771e
13 changed files with 512 additions and 584 deletions

View File

@@ -16,22 +16,32 @@
<span class="text-sm">{{ bitstream.name }}</span>
<div class="flex gap-2">
<button
@click="downloadExampleBitstream(bitstream)"
@click="handleExampleBitstream('download', bitstream)"
class="btn btn-sm btn-secondary"
:disabled="isDownloading || isProgramming"
:disabled="currentTask !== 'none'"
>
<div v-if="isDownloading">
<div
v-if="
currentTask === 'downloading' &&
currentBitstreamId === bitstream.id
"
>
<span class="loading loading-spinner loading-xs"></span>
{{ downloadProgress }}%
下载中...
</div>
<div v-else>下载示例</div>
</button>
<button
@click="programExampleBitstream(bitstream)"
@click="handleExampleBitstream('program', bitstream)"
class="btn btn-sm btn-primary"
:disabled="isDownloading || isProgramming"
:disabled="currentTask !== 'none'"
>
<div v-if="isProgramming">
<div
v-if="
currentTask === 'programming' &&
currentBitstreamId === bitstream.id
"
>
<span class="loading loading-spinner loading-xs"></span>
烧录中...
</div>
@@ -63,14 +73,18 @@
<!-- Upload Button -->
<div class="card-actions w-full">
<button
@click="handleClick"
@click="handleUploadAndDownload"
class="btn btn-primary grow"
:disabled="isUploading || isProgramming"
:disabled="currentTask !== 'none'"
>
<div v-if="isUploading">
<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>
@@ -78,28 +92,19 @@
</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 { ref, useTemplateRef, onMounted } from "vue";
import { AuthManager } from "@/utils/AuthManager";
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 "@/utils/signalR/TypedSignalR.Client/server.Hubs";
import {
getHubProxyFactory,
getReceiverRegister,
} from "@/utils/signalR/TypedSignalR.Client";
import { ProgressStatus } from "@/utils/signalR/server.Hubs";
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; // 新增examId属性
examId?: string;
}
const props = withDefaults(defineProps<Props>(), {
@@ -112,71 +117,82 @@ const emits = defineEmits<{
}>();
const alert = useRequiredInjection(useAlertStore);
const progressTracker = useProgressStore();
const dialog = useDialogStore();
const eqps = useEquipments();
const isUploading = ref(false);
const isDownloading = ref(false);
const isProgramming = ref(false);
const availableBitstreams = ref<{ id: string; 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.createHubConnection("ProgressHub");
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,
});
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 (!isUndefined(bitstream.value) && !isNull(fileInput.value)) {
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() {
console.log("加载可用比特流文件examId:", props.examId);
if (!props.examId) {
availableBitstreams.value = [];
return;
}
try {
const resourceClient = AuthManager.createClient(ResourceClient);
// 使用新的ResourceClient API获取比特流模板资源列表
const resources = await resourceClient.getResourceList(
props.examId,
"bitstream",
@@ -185,129 +201,82 @@ async function loadAvailableBitstreams() {
availableBitstreams.value =
resources.map((r) => ({ id: r.id, name: r.name })) || [];
} catch (error) {
console.error("加载比特流列表失败:", error);
availableBitstreams.value = [];
}
}
// 下载示例比特流
async function downloadExampleBitstream(bitstream: {
id: string;
name: string;
}) {
if (isDownloading.value) return;
isDownloading.value = true;
try {
const resourceClient = AuthManager.createClient(ResourceClient);
// 使用新的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("下载失败:响应数据为空");
// 统一处理示例比特流的下载/烧录
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 = "";
}
} catch (error) {
console.error("下载示例比特流失败:", error);
dialog.error("下载示例比特流失败");
} finally {
isDownloading.value = false;
} else if (action === "program") {
currentBitstreamId.value = bitstreamObj.id;
await downloadBitstream();
}
}
// 直接烧录示例比特流
async function programExampleBitstream(bitstream: {
id: string;
name: string;
}) {
if (isProgramming.value) return;
// 上传并下载
async function handleUploadAndDownload() {
if (currentTask.value !== "none") return;
if (!checkFileInput()) 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;
currentTask.value = "uploading";
let uploadedBitstreamId: string | null = null;
try {
console.log("开始上传比特流文件:", bitstream.value.name);
const bitstreamId = await eqps.jtagUploadBitstream(
bitstream.value,
uploadedBitstreamId = await eqps.jtagUploadBitstream(
bitstream.value!,
props.examId || "",
);
console.log("上传结果ID:", bitstreamId);
if (bitstreamId === null || bitstreamId === undefined) {
isUploading.value = false;
return;
}
uploadedBitstreamId = bitstreamId;
} catch (e) {
if (!uploadedBitstreamId) throw new Error("上传失败");
emits("finishedUpload", bitstream.value!);
} catch {
dialog.error("上传失败");
console.error(e);
currentTask.value = "none";
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);
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>

83
src/stores/progress.ts Normal file
View File

@@ -0,0 +1,83 @@
import type { HubConnection } from "@microsoft/signalr";
import type {
IProgressHub,
IProgressReceiver,
} from "@/utils/signalR/TypedSignalR.Client/server.Hubs";
import {
getHubProxyFactory,
getReceiverRegister,
} from "@/utils/signalR/TypedSignalR.Client";
import { ProgressStatus, type ProgressInfo } from "@/utils/signalR/server.Hubs";
import { onMounted, onUnmounted, ref, shallowRef } from "vue";
import { defineStore } from "pinia";
import { AuthManager } from "@/utils/AuthManager";
import { forEach, isUndefined } from "lodash";
export type ProgressCallback = (msg: ProgressInfo) => void;
export const useProgressStore = defineStore("progress", () => {
// taskId -> name -> callback
const progressCallbackFuncs = shallowRef<
Map<string, Map<string, ProgressCallback>>
>(new Map());
const progressHubConnection = shallowRef<HubConnection>();
const progressHubProxy = shallowRef<IProgressHub>();
const progressHubReceiver: IProgressReceiver = {
onReceiveProgress: async (msg) => {
const taskMap = progressCallbackFuncs.value.get(msg.taskId);
if (taskMap) {
for (const func of taskMap.values()) {
func(msg);
}
}
},
};
onMounted(async () => {
progressHubConnection.value =
AuthManager.createHubConnection("ProgressHub");
progressHubProxy.value = getHubProxyFactory("IProgressHub").createHubProxy(
progressHubConnection.value,
);
getReceiverRegister("IProgressReceiver").register(
progressHubConnection.value,
progressHubReceiver,
);
progressHubConnection.value.start();
});
onUnmounted(() => {
if (progressHubConnection.value) {
progressHubConnection.value.stop();
progressHubConnection.value = undefined;
progressHubProxy.value = undefined;
}
});
function register(progressId: string, name: string, func: ProgressCallback) {
progressHubProxy.value?.join(progressId);
let taskMap = progressCallbackFuncs.value.get(progressId);
if (!taskMap) {
taskMap = new Map();
progressCallbackFuncs.value?.set(progressId, taskMap);
}
taskMap.set(name, func);
}
function unregister(taskId: string, name: string) {
progressHubProxy.value?.leave(taskId);
const taskMap = progressCallbackFuncs.value.get(taskId);
if (taskMap) {
taskMap.delete(name);
if (taskMap.size === 0) {
progressCallbackFuncs.value?.delete(taskId);
}
}
}
return {
register,
unregister,
};
});

View File

@@ -161,6 +161,14 @@ class IProgressHub_HubProxy implements IProgressHub {
public readonly join = async (taskId: string): Promise<boolean> => {
return await this.connection.invoke("Join", taskId);
}
public readonly leave = async (taskId: string): Promise<boolean> => {
return await this.connection.invoke("Leave", taskId);
}
public readonly getProgress = async (taskId: string): Promise<ProgressInfo> => {
return await this.connection.invoke("GetProgress", taskId);
}
}

View File

@@ -48,6 +48,16 @@ export type IProgressHub = {
* @returns Transpiled from System.Threading.Tasks.Task<bool>
*/
join(taskId: string): Promise<boolean>;
/**
* @param taskId Transpiled from string
* @returns Transpiled from System.Threading.Tasks.Task<bool>
*/
leave(taskId: string): Promise<boolean>;
/**
* @param taskId Transpiled from string
* @returns Transpiled from System.Threading.Tasks.Task<server.Hubs.ProgressInfo?>
*/
getProgress(taskId: string): Promise<ProgressInfo>;
}
export type IDigitalTubesReceiver = {

View File

@@ -12,11 +12,10 @@ export type DigitalTubeTaskStatus = {
/** Transpiled from server.Hubs.ProgressStatus */
export enum ProgressStatus {
Pending = 0,
InProgress = 1,
Completed = 2,
Canceled = 3,
Failed = 4,
Running = 0,
Completed = 1,
Canceled = 2,
Failed = 3,
}
/** Transpiled from server.Hubs.ProgressInfo */