增加模型库模型下载入口

This commit is contained in:
zhangshun
2026-06-01 14:34:44 +08:00
parent 5b6d29f0f6
commit b1ef3deb03
4 changed files with 59 additions and 2 deletions

View File

@@ -236,6 +236,16 @@
padding: 8px;
}
.preview-scene-actions {
display: flex;
align-items: center;
gap: 8px;
}
.preview-scene-actions > button {
flex: 1;
}
.preview-scene-body > button {
width: 100%;
}

View File

@@ -3,6 +3,7 @@ import { renderPagination } from "../../../components/pagination";
import { getCurrentUser } from "../../../services/authState";
import type { DictionaryResponse, ModelItem, ModelListResponse } from "../../../types";
import { confirmDialog, formDialog, notify, notifyError } from "../../../ui/dialogs";
import { downloadFileFromUrl } from "../../../utils/fileDownload";
import { escapeHtml, modelNameFromFile } from "../../../utils/format";
import { appState } from "../appState";
@@ -192,6 +193,10 @@ function bindModelGridActions(grid: HTMLDivElement) {
notify("导入场景数据已输出");
break;
}
case "download":
if (!model) return;
await downloadFileFromUrl(model.file_url, model.original_filename || model.name);
break;
case "delete":
await deleteModel(id);
break;
@@ -293,6 +298,7 @@ function renderModelCard(item: ModelItem) {
${thumb}
<div class="thumb-actions">
<button data-action="preview" data-id="${item.id}">预览</button>
<button data-action="download" data-id="${item.id}">下载</button>
${canWrite ? `<button data-action="edit" data-id="${item.id}">编辑</button>` : ""}
<button data-action="import" data-id="${item.id}">导入</button>
${canWrite ? `<button data-action="delete" data-id="${item.id}">删除</button>` : ""}

View File

@@ -5,6 +5,7 @@ import type { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
import type { ModelItem } from "../../../types";
import type { OperationTree, OperationTreeDB } from "../../../types/OperationTree";
import { P_OPERATION } from "../../../types/OPERATION_BaseClass";
import { downloadFileFromUrl } from "../../../utils/fileDownload";
import { escapeHtml, formatBytes } from "../../../utils/format";
import { notify, notifyError } from "../../../ui/dialogs";
import { api } from "../../../services/api";
@@ -153,13 +154,14 @@ export function openModelPreview(
</div>
<div class="preview-scene-section">
<div class="preview-scene-body">
<div style="display: flex; align-items: center; ">
<div class="preview-scene-actions">
<button id="previewImportSceneBtn" class="primary-btn" type="button">导入</button>
<button id="previewDownloadBtn" type="button">下载</button>
</div>
<label class="preview-switch">
<input id="previewUseBasePoint" type="checkbox" />
<span>启用基点</span>
</label>
</div>
<div class="preview-basepoint-grid">
<label><span>X</span><input name="baseX" type="number" value="0" step="0.001" /></label>
@@ -199,6 +201,7 @@ export function openModelPreview(
console.log(popup)
bindProcessPlaceholder(operations);
bindImportScene(model, operations, options.onImportScene);
bindDownloadModel(model);
if (canWrite) bindThumbnailCapture(model.id, onThumbnailSaved);
requestAnimationFrame(() => {
initPreview(url, context).catch((error) => {
@@ -580,6 +583,16 @@ function bindImportScene(
});
}
function bindDownloadModel(model: ModelItem) {
document.querySelector<HTMLButtonElement>("#previewDownloadBtn")?.addEventListener("click", async () => {
try {
await downloadFileFromUrl(model.file_url, model.original_filename || model.name);
} catch (error) {
notifyError(error);
}
});
}
function resolveBasePoint(basePoint?: PreviewBasePoint): PreviewResolvedBasePoint {
return {
x: basePoint?.x ?? zeroBasePoint.x,

View File

@@ -0,0 +1,28 @@
export async function downloadFileFromUrl(fileUrl: string, fileName: string) {
const response = await fetch(fileUrl, {
credentials: "include"
});
if (!response.ok) {
throw new Error(`文件下载失败:${response.status} ${response.statusText}`);
}
const blob = await response.blob();
const objectUrl = URL.createObjectURL(blob);
try {
const anchor = document.createElement("a");
anchor.href = objectUrl;
anchor.download = ensureGlbFileName(fileName);
anchor.rel = "noopener";
anchor.style.display = "none";
document.body.appendChild(anchor);
anchor.click();
anchor.remove();
} finally {
window.setTimeout(() => URL.revokeObjectURL(objectUrl), 1000);
}
}
function ensureGlbFileName(fileName: string) {
const name = fileName.trim() || "model.glb";
return name.toLowerCase().endsWith(".glb") ? name : `${name}.glb`;
}