增加模型库模型下载入口
This commit is contained in:
@@ -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%;
|
||||
}
|
||||
|
||||
@@ -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>` : ""}
|
||||
|
||||
@@ -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,
|
||||
|
||||
28
web/src/utils/fileDownload.ts
Normal file
28
web/src/utils/fileDownload.ts
Normal 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`;
|
||||
}
|
||||
Reference in New Issue
Block a user