feat(*): 画布助手 - 新增 Live2D 配置导入与模型渲染支持
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
import { http } from "@/services/api/request";
|
||||
import type { SkinDefinition } from "@/lib/skin-themes";
|
||||
import type { CanvasAppearance } from "@/lib/canvas/agent-appearance";
|
||||
import { apiBaseURL } from "@/services/api/request";
|
||||
|
||||
export type PublicAppearance = {
|
||||
canvas?: CanvasAppearance;
|
||||
schemaVersion: number;
|
||||
brandName: string;
|
||||
brandSlug: string;
|
||||
@@ -31,6 +34,7 @@ export type PublicAppearance = {
|
||||
};
|
||||
|
||||
export type AdminAppearance = {
|
||||
canvas?: CanvasAppearance;
|
||||
schemaVersion: number;
|
||||
brandName: string;
|
||||
brandSlug: string;
|
||||
@@ -81,6 +85,7 @@ export async function updateAdminAppearance(
|
||||
input: Pick<
|
||||
AdminAppearance,
|
||||
| "brandName"
|
||||
| "canvas"
|
||||
| "brandSlug"
|
||||
| "authHeroTitle"
|
||||
| "authHeroDescription"
|
||||
@@ -115,3 +120,13 @@ export async function uploadAppearanceAsset(slot: AppearanceAssetSlot, file: Fil
|
||||
const result = await http.post<{ resource: AppearanceResource }>(`/admin/settings/appearance/assets/${slot}`, body);
|
||||
return result.resource;
|
||||
}
|
||||
|
||||
export async function uploadLive2D(file: File) {
|
||||
const body = new FormData(); body.append("file", file);
|
||||
const result = await http.post<{ model: { resourceId: string; entry: string } }>("/admin/settings/appearance/live2d", body);
|
||||
return result.model;
|
||||
}
|
||||
|
||||
export function live2DModelURL(resourceId: string, entry: string, preview = false) {
|
||||
return `${apiBaseURL.replace(/\/$/, "")}/${preview ? "admin/settings" : "public"}/appearance/live2d/${encodeURIComponent(resourceId)}/${entry.split("/").map(encodeURIComponent).join("/")}`;
|
||||
}
|
||||
|
||||
29
web/src/services/live2d-loader.ts
Normal file
29
web/src/services/live2d-loader.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import type { Live2DLoader as Loader } from "pixi-live2d-display/cubism4";
|
||||
import { apiBaseURL } from "@/services/api/request";
|
||||
|
||||
const installed = new WeakSet<typeof Loader>();
|
||||
|
||||
// The SDK's default XHR loader omits cross-origin cookies and has no deadline.
|
||||
// Restrict credentialed requests to our own model API, never model-provided hosts.
|
||||
export function installLive2DLoader(loader: typeof Loader) {
|
||||
if (installed.has(loader)) return;
|
||||
installed.add(loader);
|
||||
loader.middlewares.unshift(async (context) => {
|
||||
const base = new URL(`${apiBaseURL.replace(/\/$/, "")}/`, window.location.href);
|
||||
const url = new URL(context.settings ? context.settings.resolveURL(context.url) : context.url, window.location.href);
|
||||
const allowed = ["public", "admin/settings"].some((prefix) => url.pathname.startsWith(`${base.pathname}${prefix}/appearance/live2d/`));
|
||||
if (url.origin !== base.origin || !allowed) throw new Error("Live2D 资源必须来自本站模型接口");
|
||||
const controller = new AbortController();
|
||||
const abort = () => controller.abort();
|
||||
const deadline = setTimeout(abort, 20000);
|
||||
context.target?.once("destroy", abort);
|
||||
try {
|
||||
const response = await fetch(url, { credentials: "include", signal: controller.signal });
|
||||
if (!response.ok) throw new Error(`Live2D 资源加载失败(${response.status})`);
|
||||
context.result = context.type === "json" ? await response.json() : await response.arrayBuffer();
|
||||
} finally {
|
||||
clearTimeout(deadline);
|
||||
context.target?.off("destroy", abort);
|
||||
}
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user