mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-10-04 17:16:37 +08:00
d2645589da
* feat(frontend): add customizable favicon and corner logo with live preview - Add app_logo and app_favicon config fields in config page - Implement BrandLogo component with URL fallback to SVG logo - Integrate BrandLogo into all layouts (admin, h5, h5-simple) and navbar - Add real-time preview for favicon and logo in config page with error state - Support both relative paths and full image URLs for branding assets - Sync branding changes (app_name/app_logo/app_favicon) to site config on save * fix(frontend): preserve tunnel node selection order * fix(select): use useMemo for option label map to improve performance
80 lines
1.7 KiB
TypeScript
80 lines
1.7 KiB
TypeScript
import { useEffect, useState } from "react";
|
|
|
|
import { siteConfig, getCachedConfigs } from "@/config/site";
|
|
import { Logo } from "@/components/icons";
|
|
|
|
type BrandLogoProps = {
|
|
size?: number;
|
|
className?: string;
|
|
alt?: string;
|
|
};
|
|
|
|
export const BrandLogo = ({
|
|
size = 24,
|
|
className,
|
|
alt = "brand logo",
|
|
}: BrandLogoProps) => {
|
|
const [logoUrl, setLogoUrl] = useState<string>(siteConfig.app_logo || "");
|
|
const [imageFailed, setImageFailed] = useState(false);
|
|
|
|
useEffect(() => {
|
|
let mounted = true;
|
|
|
|
const syncLogo = async () => {
|
|
try {
|
|
const configMap = await getCachedConfigs();
|
|
const nextLogo = (
|
|
configMap.app_logo ||
|
|
siteConfig.app_logo ||
|
|
""
|
|
).trim();
|
|
|
|
if (!mounted) {
|
|
return;
|
|
}
|
|
|
|
siteConfig.app_logo = nextLogo;
|
|
setLogoUrl(nextLogo);
|
|
setImageFailed(false);
|
|
} catch {
|
|
if (!mounted) {
|
|
return;
|
|
}
|
|
setLogoUrl((siteConfig.app_logo || "").trim());
|
|
}
|
|
};
|
|
|
|
void syncLogo();
|
|
|
|
const handleConfigUpdate = () => {
|
|
const nextLogo = (siteConfig.app_logo || "").trim();
|
|
|
|
setLogoUrl(nextLogo);
|
|
setImageFailed(false);
|
|
void syncLogo();
|
|
};
|
|
|
|
window.addEventListener("configUpdated", handleConfigUpdate);
|
|
|
|
return () => {
|
|
mounted = false;
|
|
window.removeEventListener("configUpdated", handleConfigUpdate);
|
|
};
|
|
}, []);
|
|
|
|
if (!logoUrl || imageFailed) {
|
|
return <Logo className={className} size={size} />;
|
|
}
|
|
|
|
return (
|
|
<img
|
|
alt={alt}
|
|
className={className}
|
|
height={size}
|
|
src={logoUrl}
|
|
width={size}
|
|
onError={() => setImageFailed(true)}
|
|
/>
|
|
);
|
|
};
|