diff --git a/web/src/pages/app/dialogs.css b/web/src/pages/app/dialogs.css index 291d814..43dd822 100644 --- a/web/src/pages/app/dialogs.css +++ b/web/src/pages/app/dialogs.css @@ -79,6 +79,22 @@ word-break: break-all; } +.upload-status { + padding: 8px 10px; + border: 1px solid #b9d7c1; + border-radius: 6px; + background: #eef8f1; + color: #27613a; + font-size: 13px; + line-height: 1.45; + word-break: break-all; +} + +.upload-popup-form.is-uploading .drop-zone { + cursor: progress; + opacity: 0.72; +} + .upload-target-path { display: grid; grid-template-columns: 64px minmax(0, 1fr); diff --git a/web/src/pages/app/modules/models.ts b/web/src/pages/app/modules/models.ts index 1c94db5..a008e73 100644 --- a/web/src/pages/app/modules/models.ts +++ b/web/src/pages/app/modules/models.ts @@ -509,6 +509,7 @@ async function openUploadModelDialog() { + ${dictionaryDatalistHtml()} `, @@ -527,22 +528,38 @@ async function openUploadModelDialog() { const properties = { model: String(form.get("model") ?? "") }; - for (const file of state.files) { - await uploadModelToBackend({ - file, - name: state.files.length === 1 ? name ?? "" : modelNameFromFile(file.name), - fileName: file.name, - brandName, - typeName, - operationTree: defaultOperationTree, - properties - }); + let completedCount = 0; + setUploadDialogBusy(state, true); + try { + for (const [index, file] of state.files.entries()) { + updateUploadStatus(`正在上传 ${index + 1}/${state.files.length}:${file.name}`); + await waitForUploadStatusPaint(); + await uploadModelToBackend({ + file, + name: state.files.length === 1 ? name ?? "" : modelNameFromFile(file.name), + fileName: file.name, + brandName, + typeName, + operationTree: defaultOperationTree, + properties + }); + completedCount += 1; + updateUploadStatus(`已完成 ${completedCount}/${state.files.length}`); + } + } catch (error) { + updateUploadStatus(`上传失败,已完成 ${completedCount}/${state.files.length},请处理后重试。`); + throw error; + } finally { + if (completedCount < state.files.length) { + setUploadDialogBusy(state, false); + } } return true; } }); if (result) { + notify("模型上传完成"); invalidateDictionaries(); await reloadFoldersAndModels(); } @@ -674,6 +691,28 @@ function bindUploadDialogEvents(state: UploadFormState) { }); } +function updateUploadStatus(message: string) { + const status = document.querySelector("#modelUploadStatus"); + if (!status) return; + status.hidden = false; + status.textContent = message; +} + +function waitForUploadStatusPaint() { + return new Promise((resolve) => { + window.requestAnimationFrame(() => window.setTimeout(resolve, 0)); + }); +} + +function setUploadDialogBusy(state: UploadFormState, busy: boolean) { + const form = document.querySelector("#modelUploadPopupForm"); + if (!form) return; + form.classList.toggle("is-uploading", busy); + form.querySelectorAll("input").forEach((input) => { + input.disabled = busy || (input.id === "modelUploadName" && state.files.length > 1); + }); +} + function bindProcessUploadDialogEvents(state: UploadFormState) { // const dropZone = document.querySelector("#processModelDropZone")!; // const fileInput = document.querySelector("#processModelUploadFile")!;