增加模型库模型下载入口
This commit is contained in:
@@ -236,6 +236,16 @@
|
|||||||
padding: 8px;
|
padding: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.preview-scene-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-scene-actions > button {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.preview-scene-body > button {
|
.preview-scene-body > button {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { renderPagination } from "../../../components/pagination";
|
|||||||
import { getCurrentUser } from "../../../services/authState";
|
import { getCurrentUser } from "../../../services/authState";
|
||||||
import type { DictionaryResponse, ModelItem, ModelListResponse } from "../../../types";
|
import type { DictionaryResponse, ModelItem, ModelListResponse } from "../../../types";
|
||||||
import { confirmDialog, formDialog, notify, notifyError } from "../../../ui/dialogs";
|
import { confirmDialog, formDialog, notify, notifyError } from "../../../ui/dialogs";
|
||||||
|
import { downloadFileFromUrl } from "../../../utils/fileDownload";
|
||||||
import { escapeHtml, modelNameFromFile } from "../../../utils/format";
|
import { escapeHtml, modelNameFromFile } from "../../../utils/format";
|
||||||
import { appState } from "../appState";
|
import { appState } from "../appState";
|
||||||
|
|
||||||
@@ -192,6 +193,10 @@ function bindModelGridActions(grid: HTMLDivElement) {
|
|||||||
notify("导入场景数据已输出");
|
notify("导入场景数据已输出");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case "download":
|
||||||
|
if (!model) return;
|
||||||
|
await downloadFileFromUrl(model.file_url, model.original_filename || model.name);
|
||||||
|
break;
|
||||||
case "delete":
|
case "delete":
|
||||||
await deleteModel(id);
|
await deleteModel(id);
|
||||||
break;
|
break;
|
||||||
@@ -293,6 +298,7 @@ function renderModelCard(item: ModelItem) {
|
|||||||
${thumb}
|
${thumb}
|
||||||
<div class="thumb-actions">
|
<div class="thumb-actions">
|
||||||
<button data-action="preview" data-id="${item.id}">预览</button>
|
<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>` : ""}
|
${canWrite ? `<button data-action="edit" data-id="${item.id}">编辑</button>` : ""}
|
||||||
<button data-action="import" data-id="${item.id}">导入</button>
|
<button data-action="import" data-id="${item.id}">导入</button>
|
||||||
${canWrite ? `<button data-action="delete" 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 { ModelItem } from "../../../types";
|
||||||
import type { OperationTree, OperationTreeDB } from "../../../types/OperationTree";
|
import type { OperationTree, OperationTreeDB } from "../../../types/OperationTree";
|
||||||
import { P_OPERATION } from "../../../types/OPERATION_BaseClass";
|
import { P_OPERATION } from "../../../types/OPERATION_BaseClass";
|
||||||
|
import { downloadFileFromUrl } from "../../../utils/fileDownload";
|
||||||
import { escapeHtml, formatBytes } from "../../../utils/format";
|
import { escapeHtml, formatBytes } from "../../../utils/format";
|
||||||
import { notify, notifyError } from "../../../ui/dialogs";
|
import { notify, notifyError } from "../../../ui/dialogs";
|
||||||
import { api } from "../../../services/api";
|
import { api } from "../../../services/api";
|
||||||
@@ -153,13 +154,14 @@ export function openModelPreview(
|
|||||||
</div>
|
</div>
|
||||||
<div class="preview-scene-section">
|
<div class="preview-scene-section">
|
||||||
<div class="preview-scene-body">
|
<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="previewImportSceneBtn" class="primary-btn" type="button">导入</button>
|
||||||
|
<button id="previewDownloadBtn" type="button">下载</button>
|
||||||
|
</div>
|
||||||
<label class="preview-switch">
|
<label class="preview-switch">
|
||||||
<input id="previewUseBasePoint" type="checkbox" />
|
<input id="previewUseBasePoint" type="checkbox" />
|
||||||
<span>启用基点</span>
|
<span>启用基点</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="preview-basepoint-grid">
|
<div class="preview-basepoint-grid">
|
||||||
<label><span>X</span><input name="baseX" type="number" value="0" step="0.001" /></label>
|
<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)
|
console.log(popup)
|
||||||
bindProcessPlaceholder(operations);
|
bindProcessPlaceholder(operations);
|
||||||
bindImportScene(model, operations, options.onImportScene);
|
bindImportScene(model, operations, options.onImportScene);
|
||||||
|
bindDownloadModel(model);
|
||||||
if (canWrite) bindThumbnailCapture(model.id, onThumbnailSaved);
|
if (canWrite) bindThumbnailCapture(model.id, onThumbnailSaved);
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
initPreview(url, context).catch((error) => {
|
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 {
|
function resolveBasePoint(basePoint?: PreviewBasePoint): PreviewResolvedBasePoint {
|
||||||
return {
|
return {
|
||||||
x: basePoint?.x ?? zeroBasePoint.x,
|
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