支持批量导入模型
This commit is contained in:
@@ -64,6 +64,21 @@
|
|||||||
color: #405469;
|
color: #405469;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.selected-file-summary {
|
||||||
|
display: block;
|
||||||
|
color: #1f6f8b;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-file-list {
|
||||||
|
display: block;
|
||||||
|
max-height: 58px;
|
||||||
|
overflow: auto;
|
||||||
|
color: #405469;
|
||||||
|
line-height: 1.45;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
.upload-target-path {
|
.upload-target-path {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 64px minmax(0, 1fr);
|
grid-template-columns: 64px minmax(0, 1fr);
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { appState } from "../appState";
|
|||||||
|
|
||||||
type UploadFormState = {
|
type UploadFormState = {
|
||||||
file: File | null;
|
file: File | null;
|
||||||
|
files: File[];
|
||||||
};
|
};
|
||||||
|
|
||||||
type UploadModelPayload = {
|
type UploadModelPayload = {
|
||||||
@@ -481,7 +482,7 @@ async function openUploadModelDialog() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const state: UploadFormState = { file: null };
|
const state: UploadFormState = { file: null, files: [] };
|
||||||
await loadDictionaries();
|
await loadDictionaries();
|
||||||
const result = await formDialog<boolean>({
|
const result = await formDialog<boolean>({
|
||||||
title: "增加模型",
|
title: "增加模型",
|
||||||
@@ -493,9 +494,9 @@ async function openUploadModelDialog() {
|
|||||||
<label>
|
<label>
|
||||||
<span>模型文件</span>
|
<span>模型文件</span>
|
||||||
<div id="modelDropZone" class="drop-zone">
|
<div id="modelDropZone" class="drop-zone">
|
||||||
<input id="modelUploadFile" type="file" accept=".glb" />
|
<input id="modelUploadFile" type="file" accept=".glb" multiple />
|
||||||
<strong>选择模型</strong>
|
<strong>选择模型</strong>
|
||||||
<em>或拖拽 .glb 模型到这里</em>
|
<em>或拖拽一个或多个 .glb 模型到这里</em>
|
||||||
<small id="selectedFileName">未选择文件</small>
|
<small id="selectedFileName">未选择文件</small>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
@@ -513,25 +514,30 @@ async function openUploadModelDialog() {
|
|||||||
`,
|
`,
|
||||||
onOpen: () => bindUploadDialogEvents(state),
|
onOpen: () => bindUploadDialogEvents(state),
|
||||||
onSubmit: async () => {
|
onSubmit: async () => {
|
||||||
if (!state.file) {
|
if (state.files.length === 0) {
|
||||||
throw new Error("请选择 .glb 模型文件");
|
throw new Error("请选择 .glb 模型文件");
|
||||||
}
|
}
|
||||||
const name = document.querySelector<HTMLInputElement>("#modelUploadName")?.value.trim();
|
const name = document.querySelector<HTMLInputElement>("#modelUploadName")?.value.trim();
|
||||||
if (!name) {
|
if (state.files.length === 1 && !name) {
|
||||||
throw new Error("模型名称不能为空");
|
throw new Error("模型名称不能为空");
|
||||||
}
|
}
|
||||||
const form = new FormData(document.querySelector<HTMLFormElement>("#modelUploadPopupForm")!);
|
const form = new FormData(document.querySelector<HTMLFormElement>("#modelUploadPopupForm")!);
|
||||||
await uploadModelToBackend({
|
const brandName = String(form.get("brandName") ?? "");
|
||||||
file: state.file,
|
const typeName = String(form.get("typeName") ?? "");
|
||||||
name,
|
const properties = {
|
||||||
fileName: state.file.name,
|
model: String(form.get("model") ?? "")
|
||||||
brandName: String(form.get("brandName") ?? ""),
|
};
|
||||||
typeName: String(form.get("typeName") ?? ""),
|
for (const file of state.files) {
|
||||||
operationTree: defaultOperationTree,
|
await uploadModelToBackend({
|
||||||
properties: {
|
file,
|
||||||
model: String(form.get("model") ?? "")
|
name: state.files.length === 1 ? name ?? "" : modelNameFromFile(file.name),
|
||||||
}
|
fileName: file.name,
|
||||||
});
|
brandName,
|
||||||
|
typeName,
|
||||||
|
operationTree: defaultOperationTree,
|
||||||
|
properties
|
||||||
|
});
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -552,7 +558,7 @@ export async function openProcessModelUploadDialog(input?: {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const state: UploadFormState = { file: input?.file ?? null };
|
const state: UploadFormState = { file: input?.file ?? null, files: input?.file ? [input.file] : [] };
|
||||||
await loadDictionaries();
|
await loadDictionaries();
|
||||||
const initialName = input?.modelName || (input?.file ? modelNameFromFile(input.file.name) : "");
|
const initialName = input?.modelName || (input?.file ? modelNameFromFile(input.file.name) : "");
|
||||||
const result = await formDialog<boolean>({
|
const result = await formDialog<boolean>({
|
||||||
@@ -605,21 +611,48 @@ function bindUploadDialogEvents(state: UploadFormState) {
|
|||||||
const nameInput = document.querySelector<HTMLInputElement>("#modelUploadName")!;
|
const nameInput = document.querySelector<HTMLInputElement>("#modelUploadName")!;
|
||||||
const selectedFileName = document.querySelector<HTMLElement>("#selectedFileName")!;
|
const selectedFileName = document.querySelector<HTMLElement>("#selectedFileName")!;
|
||||||
|
|
||||||
const selectFile = (file: File) => {
|
const renderSelectedFiles = () => {
|
||||||
if (!file.name.toLowerCase().endsWith(".glb")) {
|
if (state.files.length === 0) {
|
||||||
|
selectedFileName.textContent = "未选择文件";
|
||||||
|
nameInput.disabled = false;
|
||||||
|
nameInput.value = "";
|
||||||
|
nameInput.placeholder = "选择文件后自动填入";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.files.length === 1) {
|
||||||
|
const [file] = state.files;
|
||||||
|
selectedFileName.textContent = file.name;
|
||||||
|
nameInput.disabled = false;
|
||||||
|
nameInput.value = modelNameFromFile(file.name);
|
||||||
|
nameInput.placeholder = "选择文件后自动填入";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedFileName.innerHTML = `
|
||||||
|
<span class="selected-file-summary">已选择 ${state.files.length} 个模型,名称按文件名自动生成</span>
|
||||||
|
<span class="selected-file-list">${state.files.map((file) => escapeHtml(file.name)).join("<br>")}</span>
|
||||||
|
`;
|
||||||
|
nameInput.disabled = true;
|
||||||
|
nameInput.value = `${state.files.length} 个模型按文件名自动命名`;
|
||||||
|
nameInput.placeholder = "";
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectFiles = (files: FileList | File[]) => {
|
||||||
|
const selectedFiles = Array.from(files);
|
||||||
|
if (selectedFiles.length === 0) return;
|
||||||
|
const invalidFiles = selectedFiles.filter((file) => !file.name.toLowerCase().endsWith(".glb"));
|
||||||
|
if (invalidFiles.length > 0) {
|
||||||
notify("当前阶段只允许上传 .glb 模型");
|
notify("当前阶段只允许上传 .glb 模型");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
state.file = file;
|
state.files = selectedFiles;
|
||||||
selectedFileName.textContent = file.name;
|
state.file = selectedFiles[0] ?? null;
|
||||||
if (!nameInput.value.trim()) {
|
renderSelectedFiles();
|
||||||
nameInput.value = modelNameFromFile(file.name);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
fileInput.addEventListener("change", () => {
|
fileInput.addEventListener("change", () => {
|
||||||
const file = fileInput.files?.[0];
|
if (fileInput.files) selectFiles(fileInput.files);
|
||||||
if (file) selectFile(file);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
dropZone.addEventListener("click", (event) => {
|
dropZone.addEventListener("click", (event) => {
|
||||||
@@ -636,8 +669,8 @@ function bindUploadDialogEvents(state: UploadFormState) {
|
|||||||
dropZone.addEventListener("drop", (event) => {
|
dropZone.addEventListener("drop", (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
dropZone.classList.remove("is-dragover");
|
dropZone.classList.remove("is-dragover");
|
||||||
const file = event.dataTransfer?.files?.[0];
|
const files = event.dataTransfer?.files;
|
||||||
if (file) selectFile(file);
|
if (files) selectFiles(files);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user