优化友情链接页面

This commit is contained in:
2026-09-17 01:53:36 +08:00
parent 251fd434ea
commit 4bdddc15fa
4 changed files with 87 additions and 17 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 MiB

After

Width:  |  Height:  |  Size: 1.5 MiB

+3 -2
View File
@@ -55,7 +55,7 @@ a { color: inherit; text-decoration: none; }
.home-collage { position: relative; height: 648px; margin-top: 50px; }.home-collage-tile { position: absolute; border-radius: 13px; background: #d9d9d9; }.home-collage-wide { top: 0; left: 0; width: 41.4%; height: 359px; }.home-collage-medium { top: 0; left: 42.57%; width: 28.87%; height: 359px; }.home-collage-small { top: 376px; left: 27.28%; width: 44.3%; height: 272px; }.home-collage-bottom { top: 376px; left: 0; width: 25.83%; height: 272px; }.home-collage-tall { top: 0; right: 0; width: 27.1%; height: 648px; }
.news-page { padding: 30px 0 60px; }
.friendly-links-hero { background: var(--policy-hero-image) center 26.71% / 100% auto no-repeat; }
.friendly-links-hero { background: var(--policy-hero-image) center 28.2% / 100% auto no-repeat; }
.friendly-links-page-inner { gap: 50px; }
.friendly-links-content { width: min(1214px, 100%); margin: 0 auto; }
.friendly-links-section + .friendly-links-section { margin-top: 50px; }
@@ -65,7 +65,8 @@ a { color: inherit; text-decoration: none; }
a.friendly-link-card:hover, a.friendly-link-card:focus-visible { border-color: #8f56a2; box-shadow: 0 8px 20px rgba(40, 29, 47, .1); transform: translateY(-2px); }
.friendly-link-card-content { display: flex; flex: 1; flex-direction: column; align-items: center; justify-content: center; gap: 16.42px; padding: 32.83px 10.94px; }
.friendly-link-card-heading { display: flex; align-items: center; justify-content: center; gap: 16px; min-width: 0; font-size: 27.36px; font-weight: 400; line-height: normal; white-space: nowrap; }
.friendly-link-card-heading img { width: 65.67px; height: 65.67px; overflow: hidden; border-radius: 8px; object-fit: cover; }
.friendly-link-card-logo { display: flex; width: 65.67px; height: 65.67px; flex: 0 0 65.67px; align-items: center; justify-content: center; overflow: hidden; border-radius: 10.944px; background: #fff; }
.friendly-link-card-logo img { display: block; width: 65.67px; height: 65.67px; object-fit: cover; }
.friendly-link-card-description { color: #8d949a; font-size: 16.42px; line-height: 21.89px; text-align: center; }
.friendly-link-card-archive { position: absolute; top: 13px; right: 5px; padding: 4px 6px; border-radius: 4px; background: #e72329; color: #fff; font-size: 10px; line-height: 12px; }
.friendly-links-page .page-bottom-nav { margin-top: 176px; }
+58 -7
View File
@@ -3,18 +3,69 @@ import type { CSSProperties } from "react";
import { PageBreadcrumb } from "@/components/layout/page-breadcrumb";
import { getLocaleMessages, type Locale } from "@/lib/i18n";
type LinkEntry = { name: string; description: string; logo: string; url?: string; archived?: boolean };
type LocalizedText = Record<Locale, string>;
type LinkSection = { id: string; title: LocalizedText; links: LinkEntry[] };
type LocalizedText = Partial<Record<Locale, string>>;
type LinkEntry = {
name: string;
description: string;
logo: string;
url?: string;
archived?: boolean;
i18n?: { name?: LocalizedText; description?: LocalizedText };
};
type LinkSection = { id: string; title: Record<Locale, string>; links: LinkEntry[] };
const linkSections = links.sections as LinkSection[];
function FriendlyLinkCard({ link }: { link: LinkEntry }) {
const content = <><span className="friendly-link-card-content"><span className="friendly-link-card-heading"><img src={link.logo} alt="" width={48} height={48} /><span>{link.name}</span></span><span className="friendly-link-card-description">{link.description}</span></span>{link.archived && <span className="friendly-link-card-archive">已归档</span>}</>;
return link.url ? <a className="friendly-link-card" href={link.url} target="_blank" rel="noreferrer" aria-label={`访问 ${link.name}`}>{content}</a> : <article className="friendly-link-card">{content}</article>;
function localized(value: string, translations: LocalizedText | undefined, locale: Locale) {
return translations?.[locale] ?? value;
}
function FriendlyLinkCard({ link, locale, visitLabel }: { link: LinkEntry; locale: Locale; visitLabel: string }) {
const name = localized(link.name, link.i18n?.name, locale);
const description = localized(link.description, link.i18n?.description, locale);
const content = (
<>
<span className="friendly-link-card-content">
<span className="friendly-link-card-heading">
<span className="friendly-link-card-logo"><img src={link.logo} alt="" width={66} height={66} /></span>
<span>{name}</span>
</span>
<span className="friendly-link-card-description">{description}</span>
</span>
{link.archived && <span className="friendly-link-card-archive">已归档</span>}
</>
);
return link.url ? (
<a className="friendly-link-card" href={link.url} target="_blank" rel="noreferrer" aria-label={`${visitLabel} ${name}`}>
{content}
</a>
) : <article className="friendly-link-card">{content}</article>;
}
export function FriendlyLinksPage({ locale }: { locale: Locale }) {
const messages = getLocaleMessages(locale);
const heroStyle = { "--policy-hero-image": 'url("/links/friendly-links-hero.png")' } as CSSProperties;
return <main className="policy-page friendly-links-page"><div className="policy-page-inner friendly-links-page-inner"><section className="policy-hero friendly-links-hero" aria-labelledby="friendly-links-title" style={heroStyle}><div className="policy-hero-inner"><h1 id="friendly-links-title">{messages.footer.legal[2]}</h1></div></section><div className="friendly-links-content">{linkSections.map((section) => <section className="friendly-links-section" key={section.id} aria-labelledby={`friendly-links-${section.id}`}><h2 id={`friendly-links-${section.id}`}>{section.title[locale]}</h2><div className="friendly-links-grid">{section.links.map((link, index) => <FriendlyLinkCard key={`${section.id}-${link.name}-${index}`} link={link} />)}</div></section>)}</div></div><div className="page-bottom-nav"><div className="page-bottom-nav-inner"><PageBreadcrumb items={[{ label: messages.common.home, href: "/" }, { label: messages.footer.legal[2] }]} /></div></div></main>;
const visitLabels: Record<Locale, string> = { "zh-Hans": "访问", "zh-Hant": "造訪", en: "Visit", jp: "訪問" };
return (
<main className="policy-page friendly-links-page">
<div className="policy-page-inner friendly-links-page-inner">
<section className="policy-hero friendly-links-hero" aria-labelledby="friendly-links-title" style={heroStyle}>
<div className="policy-hero-inner"><h1 id="friendly-links-title">{messages.footer.legal[2]}</h1></div>
</section>
<div className="friendly-links-content">
{linkSections.map((section) => (
<section className="friendly-links-section" key={section.id} aria-labelledby={`friendly-links-${section.id}`}>
<h2 id={`friendly-links-${section.id}`}>{section.title[locale]}</h2>
<div className="friendly-links-grid">
{section.links.map((link, index) => <FriendlyLinkCard key={`${section.id}-${link.name}-${index}`} link={link} locale={locale} visitLabel={visitLabels[locale]} />)}
</div>
</section>
))}
</div>
</div>
<div className="page-bottom-nav"><div className="page-bottom-nav-inner"><PageBreadcrumb items={[{ label: messages.common.home, href: "/" }, { label: messages.footer.legal[2] }]} /></div></div>
</main>
);
}
+26 -8
View File
@@ -4,9 +4,27 @@
"id": "services",
"title": { "zh-Hans": "服务提供", "zh-Hant": "服務提供", "en": "Service providers", "jp": "サービス提供" },
"links": [
{ "name": "德威云", "description": "Minecraft 服务器提供方", "logo": "/links/fetarute.png", "url": "https://www.deviy.cn/" },
{ "name": "腾讯云", "description": "CDN、对象存储 提供方", "logo": "/links/fetarute.png", "url": "https://cloud.tencent.com/" },
{ "name": "蓝色秋生", "description": "网页服务提供方,对象存储、CDN 维护方", "logo": "/links/blueakio.svg", "url": "https://www.blueakio.com/" }
{
"name": "德威云",
"description": "Minecraft 服务器提供方",
"logo": "/links/fetarute.png",
"url": "https://www.deviy.cn/",
"i18n": { "description": { "en": "Minecraft server provider", "jp": "Minecraft サーバー提供元" } }
},
{
"name": "腾讯云",
"description": "CDN、对象存储提供方",
"logo": "/links/fetarute.png",
"url": "https://cloud.tencent.com/",
"i18n": { "description": { "en": "CDN and object storage provider", "jp": "CDN・オブジェクトストレージ提供元" } }
},
{
"name": "蓝色秋生",
"description": "网页服务提供方,对象存储、CDN 维护方",
"logo": "/links/blueakio.svg",
"url": "https://www.blueakio.com/",
"i18n": { "description": { "en": "Web service, object storage, and CDN provider", "jp": "ウェブサービス、オブジェクトストレージ、CDN の提供元" } }
}
]
},
{
@@ -14,11 +32,11 @@
"title": { "zh-Hans": "朋友们", "zh-Hant": "朋友們", "en": "Friends", "jp": "友だち" },
"links": [
{ "name": "德威云", "description": "Minecraft 服务器提供方", "logo": "/links/fetarute.png", "url": "https://deviy.cn/" },
{ "name": "腾讯云", "description": "CDN、对象存储 提供方", "logo": "/links/fetarute.png", "url": "https://cloud.tencent.com/" },
{ "name": "腾讯云", "description": "CDN、对象存储 提供方", "logo": "/links/fetarute.png", "url": "https://cloud.tencent.com/" },
{ "name": "蓝色秋生", "description": "网页服务提供方,对象存储、CDN 维护方", "logo": "/links/fetarute.png", "url": "https://liqiusheng.cn/" },
{ "name": "腾讯云", "description": "CDN、对象存储 提供方", "logo": "/links/fetarute.png", "url": "https://cloud.tencent.com/" },
{ "name": "腾讯云", "description": "CDN、对象存储 提供方", "logo": "/links/fetarute.png", "url": "https://cloud.tencent.com/" }
{ "name": "腾讯云", "description": "CDN、对象存储提供方", "logo": "/links/fetarute.png", "url": "https://cloud.tencent.com/" },
{ "name": "腾讯云", "description": "CDN、对象存储提供方", "logo": "/links/fetarute.png", "url": "https://cloud.tencent.com/" },
{ "name": "蓝色秋生", "description": "网页服务提供方,对象存储、CDN 维护方", "logo": "/links/blueakio.svg", "url": "https://liqiusheng.cn/" },
{ "name": "腾讯云", "description": "CDN、对象存储提供方", "logo": "/links/fetarute.png", "url": "https://cloud.tencent.com/" },
{ "name": "腾讯云", "description": "CDN、对象存储提供方", "logo": "/links/fetarute.png", "url": "https://cloud.tencent.com/" }
]
}
]