增加前端运行时后端地址配置
This commit is contained in:
@@ -7,6 +7,7 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
<script src="/model-library-config.js"></script>
|
||||||
<script type="module" src="/src/main.ts"></script>
|
<script type="module" src="/src/main.ts"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
9
web/public/model-library-config.js
Normal file
9
web/public/model-library-config.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
window.__DMT_MODEL_LIBRARY_CONFIG__ = {
|
||||||
|
// 后端服务根地址。为空时使用当前页面域名,例如 /api/models。
|
||||||
|
// 示例:"https://model-api.example.com"
|
||||||
|
API_BASE_URL: "",
|
||||||
|
|
||||||
|
// 模型文件和缩略图服务根地址。为空时优先复用 API_BASE_URL。
|
||||||
|
// 示例:"https://model-api.example.com"
|
||||||
|
STORAGE_BASE_URL: ""
|
||||||
|
};
|
||||||
52
web/src/config/runtime.ts
Normal file
52
web/src/config/runtime.ts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
type RuntimeConfig = {
|
||||||
|
API_BASE_URL?: string;
|
||||||
|
STORAGE_BASE_URL?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
__DMT_MODEL_LIBRARY_CONFIG__?: RuntimeConfig;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function runtimeConfig() {
|
||||||
|
return window.__DMT_MODEL_LIBRARY_CONFIG__ ?? {};
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanBaseUrl(value?: string) {
|
||||||
|
return value?.trim().replace(/\/+$/g, "") ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function isAbsoluteUrl(url: string) {
|
||||||
|
return /^https?:\/\//i.test(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
function joinUrl(baseUrl: string, path: string) {
|
||||||
|
if (!baseUrl || isAbsoluteUrl(path)) return path;
|
||||||
|
return `${baseUrl}${path.startsWith("/") ? "" : "/"}${path}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function joinApiUrl(baseUrl: string, path: string) {
|
||||||
|
if (!baseUrl || isAbsoluteUrl(path)) return path;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const base = new URL(baseUrl);
|
||||||
|
if (base.pathname.replace(/\/+$/g, "") === "/api" && path.startsWith("/api/")) {
|
||||||
|
return joinUrl(baseUrl, path.slice(4));
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// 非完整 URL 时按普通路径拼接。
|
||||||
|
}
|
||||||
|
|
||||||
|
return joinUrl(baseUrl, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveApiUrl(path: string) {
|
||||||
|
return joinApiUrl(cleanBaseUrl(runtimeConfig().API_BASE_URL), path);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveStorageUrl(path: string) {
|
||||||
|
const storageBaseUrl = cleanBaseUrl(runtimeConfig().STORAGE_BASE_URL);
|
||||||
|
const apiBaseUrl = cleanBaseUrl(runtimeConfig().API_BASE_URL);
|
||||||
|
return joinUrl(storageBaseUrl || apiBaseUrl, path);
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import type { DictionaryResponse, ModelItem, ModelListResponse } from "../../../
|
|||||||
import { confirmDialog, formDialog, notify, notifyError } from "../../../ui/dialogs";
|
import { confirmDialog, formDialog, notify, notifyError } from "../../../ui/dialogs";
|
||||||
import { downloadFileFromUrl } from "../../../utils/fileDownload";
|
import { downloadFileFromUrl } from "../../../utils/fileDownload";
|
||||||
import { escapeHtml, modelNameFromFile } from "../../../utils/format";
|
import { escapeHtml, modelNameFromFile } from "../../../utils/format";
|
||||||
|
import { resolveStorageUrl } from "../../../config/runtime";
|
||||||
import { appState } from "../appState";
|
import { appState } from "../appState";
|
||||||
|
|
||||||
type UploadFormState = {
|
type UploadFormState = {
|
||||||
@@ -287,7 +288,7 @@ function renderModelCard(item: ModelItem) {
|
|||||||
const canWrite = item.permissions.write;
|
const canWrite = item.permissions.write;
|
||||||
const prop = item.properties ?? {};
|
const prop = item.properties ?? {};
|
||||||
const thumb = item.thumbnail_url
|
const thumb = item.thumbnail_url
|
||||||
? `<img src="${item.thumbnail_url}" alt="" />`
|
? `<img src="${resolveStorageUrl(item.thumbnail_url)}" alt="" />`
|
||||||
: `<div class="thumb-placeholder">暂无预览图</div>`;
|
: `<div class="thumb-placeholder">暂无预览图</div>`;
|
||||||
return `
|
return `
|
||||||
<article class="model-card" data-model-id="${item.id}">
|
<article class="model-card" data-model-id="${item.id}">
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ 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";
|
||||||
|
import { resolveStorageUrl } from "../../../config/runtime";
|
||||||
|
|
||||||
type PreviewRuntime = {
|
type PreviewRuntime = {
|
||||||
renderer: WebGLRenderer;
|
renderer: WebGLRenderer;
|
||||||
@@ -146,7 +147,7 @@ export function openModelPreview(
|
|||||||
) {
|
) {
|
||||||
const context = beginPreviewContext();
|
const context = beginPreviewContext();
|
||||||
const canWrite = model.permissions.write;
|
const canWrite = model.permissions.write;
|
||||||
const url = model.file_url;
|
const url = resolveStorageUrl(model.file_url);
|
||||||
const operations = parseModelOperations(model.operation_tree);
|
const operations = parseModelOperations(model.operation_tree);
|
||||||
const popup = w2popup.open({
|
const popup = w2popup.open({
|
||||||
title: `模型预览 - ${escapeHtml(model.name)}【${formatBytes(model.file_size)}】`,
|
title: `模型预览 - ${escapeHtml(model.name)}【${formatBytes(model.file_size)}】`,
|
||||||
@@ -300,7 +301,7 @@ function buildParentImportScenePayload(payload: PreviewImportScenePayload): Pare
|
|||||||
id: payload.model.id,
|
id: payload.model.id,
|
||||||
name: payload.model.name,
|
name: payload.model.name,
|
||||||
original_filename: payload.model.original_filename,
|
original_filename: payload.model.original_filename,
|
||||||
file_url: payload.model.file_url
|
file_url: resolveStorageUrl(payload.model.file_url)
|
||||||
},
|
},
|
||||||
modelUrl: payload.modelUrl,
|
modelUrl: payload.modelUrl,
|
||||||
basePointEnabled: payload.basePointEnabled,
|
basePointEnabled: payload.basePointEnabled,
|
||||||
@@ -739,7 +740,7 @@ function buildImportScenePayload(options: {
|
|||||||
}): PreviewImportScenePayload {
|
}): PreviewImportScenePayload {
|
||||||
return {
|
return {
|
||||||
model: options.model,
|
model: options.model,
|
||||||
modelUrl: options.model.file_url,
|
modelUrl: resolveStorageUrl(options.model.file_url),
|
||||||
basePointEnabled: options.basePointEnabled,
|
basePointEnabled: options.basePointEnabled,
|
||||||
basePoint: options.basePoint,
|
basePoint: options.basePoint,
|
||||||
rawOperationTree: options.model.operation_tree,
|
rawOperationTree: options.model.operation_tree,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { getToken } from "./authState";
|
import { getToken } from "./authState";
|
||||||
|
import { resolveApiUrl } from "../config/runtime";
|
||||||
|
|
||||||
function authHeaders(): Record<string, string> {
|
function authHeaders(): Record<string, string> {
|
||||||
const token = getToken();
|
const token = getToken();
|
||||||
@@ -7,7 +8,7 @@ function authHeaders(): Record<string, string> {
|
|||||||
|
|
||||||
export async function api<T>(url: string, options: RequestInit = {}): Promise<T> {
|
export async function api<T>(url: string, options: RequestInit = {}): Promise<T> {
|
||||||
const hasBody = options.body !== undefined && options.body !== null;
|
const hasBody = options.body !== undefined && options.body !== null;
|
||||||
const response = await fetch(url, {
|
const response = await fetch(resolveApiUrl(url), {
|
||||||
...options,
|
...options,
|
||||||
headers: {
|
headers: {
|
||||||
...(hasBody && !(options.body instanceof FormData) ? { "Content-Type": "application/json" } : {}),
|
...(hasBody && !(options.body instanceof FormData) ? { "Content-Type": "application/json" } : {}),
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
import { resolveStorageUrl } from "../config/runtime";
|
||||||
|
|
||||||
export async function downloadFileFromUrl(fileUrl: string, fileName: string) {
|
export async function downloadFileFromUrl(fileUrl: string, fileName: string) {
|
||||||
const response = await fetch(fileUrl, {
|
const response = await fetch(resolveStorageUrl(fileUrl), {
|
||||||
credentials: "include"
|
credentials: "include"
|
||||||
});
|
});
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|||||||
Reference in New Issue
Block a user