feat: 添加站点设置中的 favicon URL 支持,更新相关接口和页面
All checks were successful
ui-regression / playwright-regression (push) Successful in 6m20s
docker-images / resolve-build-targets (push) Successful in 6s
docker-images / build-and-push (admin) (push) Successful in 25s
docker-images / build-and-push (backend) (push) Successful in 35s
docker-images / build-and-push (frontend) (push) Successful in 1m46s
docker-images / submit-indexnow (push) Successful in 15s

This commit is contained in:
2026-04-03 02:13:27 +08:00
parent 27d0827f3e
commit 36d505ece6
19 changed files with 143 additions and 5 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 655 B

View File

@@ -148,7 +148,7 @@ const i18nPayload = JSON.stringify({ locale, messages });
title={`${siteSettings.siteName} llms-full.txt`}
href={`${siteUrl}/llms-full.txt`}
/>
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="icon" href="/favicon.ico" />
<title>{title}</title>
{jsonLd && <script type="application/ld+json" set:html={jsonLd}></script>}
<slot name="head" />

View File

@@ -293,6 +293,7 @@ export interface ApiSiteSettings {
subscription_popup_title: string | null;
subscription_popup_description: string | null;
subscription_popup_delay_seconds: number | null;
seo_favicon_url: string | null;
seo_default_og_image: string | null;
seo_default_twitter_handle: string | null;
seo_wechat_share_qr_enabled: boolean;
@@ -492,6 +493,7 @@ export const DEFAULT_SITE_SETTINGS: SiteSettings = {
webPushVapidPublicKey: undefined,
},
seo: {
faviconUrl: undefined,
defaultOgImage: undefined,
defaultTwitterHandle: undefined,
wechatShareQrEnabled: false,
@@ -669,6 +671,7 @@ const normalizeSiteSettings = (settings: ApiSiteSettings): SiteSettings => {
undefined,
},
seo: {
faviconUrl: settings.seo_favicon_url ?? undefined,
defaultOgImage: settings.seo_default_og_image ?? undefined,
defaultTwitterHandle: settings.seo_default_twitter_handle ?? undefined,
wechatShareQrEnabled: Boolean(settings.seo_wechat_share_qr_enabled),

View File

@@ -33,7 +33,9 @@ export function shouldBypassMaintenance(pathname: string): boolean {
pathname === '/maintenance' ||
pathname.startsWith('/api/maintenance') ||
pathname === '/healthz' ||
pathname === '/favicon.svg' ||
pathname === '/favicon.ico' ||
pathname.startsWith('/favicon.') ||
pathname.startsWith('/apple-touch-icon') ||
pathname.startsWith('/_astro/') ||
pathname.startsWith('/_image') ||
pathname.startsWith('/_img')

View File

@@ -106,6 +106,7 @@ export interface SiteSettings {
webPushVapidPublicKey?: string;
};
seo: {
faviconUrl?: string;
defaultOgImage?: string;
defaultTwitterHandle?: string;
wechatShareQrEnabled: boolean;

View File

@@ -0,0 +1,35 @@
import type { APIRoute } from 'astro'
import { createApiClient, DEFAULT_SITE_SETTINGS } from '../lib/api/client'
function resolveFaviconTarget(requestUrl: URL, configured: string | undefined) {
const fallbackTarget = '/favicon.svg'
const candidate = configured?.trim()
if (!candidate) {
return fallbackTarget
}
try {
const resolved = new URL(candidate, requestUrl)
if (resolved.pathname === '/favicon.ico' && resolved.origin === requestUrl.origin) {
return fallbackTarget
}
return resolved.toString()
} catch {
return fallbackTarget
}
}
export const GET: APIRoute = async ({ url, redirect }) => {
let siteSettings = DEFAULT_SITE_SETTINGS
try {
siteSettings = await createApiClient({ requestUrl: url }).getSiteSettings()
} catch (error) {
console.error('Failed to load site settings for favicon:', error)
}
return redirect(resolveFaviconTarget(url, siteSettings.seo.faviconUrl), 302)
}

View File

@@ -20,9 +20,9 @@ const errorMessage =
? '请先输入访问口令。'
: errorCode === 'invalid'
? '口令不正确,请重新输入。'
: errorCode === 'unavailable'
? '当前无法校验访问口令,请稍后再试。'
: ''
: errorCode === 'unavailable'
? '当前无法校验访问口令,请稍后再试。'
: ''
---
<!DOCTYPE html>
@@ -31,6 +31,7 @@ const errorMessage =
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="robots" content="noindex, nofollow" />
<link rel="icon" href="/favicon.ico" />
<title>{siteSettings.siteName} · 维护模式</title>
</head>
<body class="min-h-screen bg-[var(--bg)] text-[var(--text)]">