优化后台

This commit is contained in:
Azmat@qq.com
2026-09-22 11:46:22 +08:00
parent 2745760d33
commit 03ceb82329
13 changed files with 76 additions and 237 deletions
+3 -12
View File
@@ -1,10 +1,9 @@
import { useEffect, useState } from "react";
import { ArrowLeft, ArrowRight, LogOut } from "lucide-react";
import { ArrowRight, LogOut } from "lucide-react";
import { IconKitSvg } from "../../components/IconKitSvg";
import { CornerMarks, Decorations, ToastLike } from "../../components/app-shell";
import { ConfirmModal } from "../../components/overlays";
import type { Team, User } from "../../types";
import type { NavigateFn } from "../route-config";
import type { User } from "../../types";
import { AdminLedgersPage, AdminQuotaPage } from "./admin-billing";
import { AdminGovernancePage } from "./admin-governance";
import { AdminInvitesPage } from "./admin-invites";
@@ -70,13 +69,11 @@ const GROUP_HINT: Record<string, string> = {
type AdminAppProps = {
section: string;
user: User;
team: Team | null;
navigateAdmin: (section: string, options?: { replace?: boolean }) => void;
navigate: NavigateFn;
logout: () => void;
};
export function AdminApp({ section, user, team, navigateAdmin, navigate, logout }: AdminAppProps) {
export function AdminApp({ section, user, navigateAdmin, logout }: AdminAppProps) {
const active = ADMIN_SECTIONS.find((s) => s.slug === section) || ADMIN_SECTIONS[0];
const [toast, setToast] = useState<{ type: "success" | "error" | "info"; text: string } | null>(null);
const [logoutOpen, setLogoutOpen] = useState(false);
@@ -143,12 +140,6 @@ export function AdminApp({ section, user, team, navigateAdmin, navigate, logout
})}
</div>
<div className="admin-foot">
{team && (
<button type="button" className="admin-back" onClick={() => navigate("dashboard")}>
<ArrowLeft size={16} strokeWidth={1.8} />
返回工作台
</button>
)}
<div className="admin-user">
<div className="av">{(user.username || "A").slice(0, 1).toUpperCase()}</div>
<div className="admin-user-meta">
+38 -24
View File
@@ -71,6 +71,37 @@ const IMAGE_PRESETS: PresetItem[] = [
const MENTION_REF_LIMIT = 20;
const ROLE_REF_LIMIT = 3;
function playPresetPreview(card: HTMLElement) {
const video = card.querySelector<HTMLVideoElement>("video");
if (!video || video.dataset.broken === "1") return;
const reveal = () => {
if (video.dataset.broken === "1" || video.videoWidth === 0) return;
video.classList.add("is-playing");
};
const fail = () => {
video.dataset.broken = "1";
video.classList.remove("is-playing");
video.pause();
};
video.addEventListener("error", fail, { once: true });
video.addEventListener("playing", reveal, { once: true });
void video.play().catch(fail);
}
function stopPresetPreview(card: HTMLElement) {
const video = card.querySelector<HTMLVideoElement>("video");
if (!video) return;
video.pause();
if (video.dataset.broken !== "1" && Number.isFinite(video.duration)) {
try {
video.currentTime = 0;
} catch {
/* Safari 在还没解码时不允许回绕 */
}
}
video.classList.remove("is-playing");
}
const MENTION_TABS: Array<{ type: CreationRef["type"]; label: string; Icon: typeof ImageIcon }> = [
{ type: "asset", label: "素材", Icon: ImageIcon },
{ type: "character", label: "角色", Icon: UserRound },
@@ -671,39 +702,22 @@ export function OmniCreatePage({
className={`omni-case-card${selectedCase?.name === card.name ? " active" : ""}`}
data-mode={card.mode}
key={card.name}
onMouseEnter={(event) => {
const video = event.currentTarget.querySelector<HTMLVideoElement>("video");
void video?.play().catch(() => undefined);
}}
onMouseLeave={(event) => {
const video = event.currentTarget.querySelector<HTMLVideoElement>("video");
if (!video) return;
video.pause();
video.currentTime = 0;
}}
onFocus={(event) => {
const video = event.currentTarget.querySelector<HTMLVideoElement>("video");
void video?.play().catch(() => undefined);
}}
onBlur={(event) => {
const video = event.currentTarget.querySelector<HTMLVideoElement>("video");
if (!video) return;
video.pause();
video.currentTime = 0;
}}
onMouseEnter={(event) => playPresetPreview(event.currentTarget)}
onMouseLeave={(event) => stopPresetPreview(event.currentTarget)}
onFocus={(event) => playPresetPreview(event.currentTarget)}
onBlur={(event) => stopPresetPreview(event.currentTarget)}
onClick={() => {
if (card.mode === "image") applyPreset(card);
else setPreviewCase(card);
}}
>
<span className="omni-case-visual">
<img src={card.cover} alt="" />
{card.previewVideo ? (
<video muted loop playsInline preload="metadata" poster={card.cover} aria-label={`${card.title} 样片预览`}>
<video muted loop playsInline preload="none" poster={card.cover} aria-hidden="true">
<source src={card.previewVideo} type="video/mp4" />
</video>
) : (
<img src={card.cover} alt="" />
)}
) : null}
</span>
<span className="omni-case-copy">
<strong>{card.title}</strong>