Commit c75fea00 authored by xuchentao's avatar xuchentao

feat: 完善官网 SEO 与 GEO 能力

parent 7e17675a
Pipeline #447 passed with stage
in 23 seconds
...@@ -7,7 +7,16 @@ const customOutDir = process.env.YINZHUANG_BUILD_OUT_DIR; ...@@ -7,7 +7,16 @@ const customOutDir = process.env.YINZHUANG_BUILD_OUT_DIR;
export default defineConfig({ export default defineConfig({
site: "https://www.inzung.cn", site: "https://www.inzung.cn",
integrations: [sitemap()], integrations: [sitemap({
filter: (page) => {
const pathname = new URL(page).pathname;
return pathname !== "/404/"
&& pathname !== "/admin/"
&& !pathname.startsWith("/api/")
&& !pathname.startsWith("/uploads/")
&& !pathname.endsWith(".txt");
},
})],
adapter: node({ mode: "standalone" }), adapter: node({ mode: "standalone" }),
...(customOutDir ? { outDir: path.resolve(customOutDir) } : {}), ...(customOutDir ? { outDir: path.resolve(customOutDir) } : {}),
build: { format: "directory" }, build: { format: "directory" },
......
...@@ -11,7 +11,8 @@ const heading = activeName ?? "银饰资讯"; ...@@ -11,7 +11,8 @@ const heading = activeName ?? "银饰资讯";
const pageSuffix = currentPage > 1 ? `|第${currentPage}页` : ""; const pageSuffix = currentPage > 1 ? `|第${currentPage}页` : "";
const title = `${heading}${pageSuffix}|${site.brand.name}`; const title = `${heading}${pageSuffix}|${site.brand.name}`;
const description = activeName ? `银妆${activeName}分类文章,分享银饰选购、佩戴养护、传统工艺与定制灵感。` : "银妆银饰资讯:关于银饰选购、日常养护、传统银作工艺与私人定制的实用分享。"; const description = activeName ? `银妆${activeName}分类文章,分享银饰选购、佩戴养护、传统工艺与定制灵感。` : "银妆银饰资讯:关于银饰选购、日常养护、传统银作工艺与私人定制的实用分享。";
const keywords = [heading, "银饰资讯", "银饰选购", "银饰养护", "银饰定制", site.brand.name];
const pageUrl = new URL(`${basePath}${currentPage > 1 ? `/page/${currentPage}` : ""}/`, Astro.site).href; const pageUrl = new URL(`${basePath}${currentPage > 1 ? `/page/${currentPage}` : ""}/`, Astro.site).href;
const jsonLd = [{ "@context": "https://schema.org", "@type": "CollectionPage", name: heading, description, url: pageUrl, inLanguage: "zh-CN", mainEntity: { "@type": "ItemList", itemListElement: items.map((item, index) => ({ "@type": "ListItem", position: (currentPage - 1) * PAGE_SIZE + index + 1, name: item.data.title, url: new URL(`/articles/${item.id}/`, Astro.site).href })) } }, { "@context": "https://schema.org", "@type": "BreadcrumbList", itemListElement: [{ "@type": "ListItem", position: 1, name: "首页", item: Astro.site?.href }, { "@type": "ListItem", position: 2, name: "银饰资讯", item: new URL("/articles/", Astro.site).href }, ...(activeName ? [{ "@type": "ListItem", position: 3, name: activeName, item: pageUrl }] : [])] }]; const jsonLd = [{ "@context": "https://schema.org", "@type": "CollectionPage", name: heading, description, url: pageUrl, inLanguage: "zh-CN", mainEntity: { "@type": "ItemList", itemListElement: items.map((item, index) => ({ "@type": "ListItem", position: (currentPage - 1) * PAGE_SIZE + index + 1, name: item.data.title, url: new URL(`/articles/${item.id}/`, Astro.site).href })) } }, { "@context": "https://schema.org", "@type": "BreadcrumbList", itemListElement: [{ "@type": "ListItem", position: 1, name: "首页", item: Astro.site?.href }, { "@type": "ListItem", position: 2, name: "银饰资讯", item: new URL("/articles/", Astro.site).href }, ...(activeName ? [{ "@type": "ListItem", position: 3, name: activeName, item: pageUrl }] : [])] }];
--- ---
<Base {title} {description} {jsonLd}><Header /><main id="main-content"><section class="page-hero article-hero" data-word="JOURNAL"><div class="container page-hero__inner" data-reveal><div class="breadcrumb"><a href="/">首页</a> / <a href="/articles/">银饰资讯</a>{activeName && ` / ${activeName}`}</div><div class="eyebrow"><span></span>INZUNG JOURNAL</div><h1>{heading}</h1><p>{description}</p></div></section><section class="section-pad section-cream"><div class="container"><ArticleListing {items} {categories} {totalCount} {activeSlug} {currentPage} {lastPage} {basePath} /></div></section></main><Footer /></Base> <Base {title} {description} {keywords} {jsonLd}><Header /><main id="main-content"><section class="page-hero article-hero" data-word="JOURNAL"><div class="container page-hero__inner" data-reveal><div class="breadcrumb"><a href="/">首页</a> / <a href="/articles/">银饰资讯</a>{activeName && ` / ${activeName}`}</div><div class="eyebrow"><span></span>INZUNG JOURNAL</div><h1>{heading}</h1><p>{description}</p></div></section><section class="section-pad section-cream"><div class="container"><ArticleListing {items} {categories} {totalCount} {activeSlug} {currentPage} {lastPage} {basePath} /></div></section></main><Footer /></Base>
export const site = { export const site = {
seo: {
title: "银妆银饰|足银首饰、手工定制与终身养护",
description: "银妆深耕银饰行业14年,提供足银手镯、银项链、银耳环、来料加工、专属雕刻与银饰DIY编织服务。",
keywords: ["银妆银饰", "足银首饰", "银手镯", "银项链", "银耳环", "银饰定制", "旧银加工", "银饰DIY"],
imageAlt: "银妆银饰品牌与足银首饰",
},
brand: { brand: {
name: "银妆银饰", name: "银妆银饰",
shortName: "银妆", shortName: "银妆",
...@@ -24,6 +30,9 @@ export const site = { ...@@ -24,6 +30,9 @@ export const site = {
slug: "silver-bracelet", slug: "silver-bracelet",
index: "01", index: "01",
name: "足银手镯", name: "足银手镯",
seoTitle: "足银手镯定制与选购|银妆银饰",
seoDescription: "银妆足银999/9999手镯支持圈口、克重与工艺选择,可刻字、錾刻、旧银来料改造,并提供终身清洗养护。",
seoKeywords: ["足银手镯", "银手镯定制", "足银999", "古法银手镯", "旧银加工"],
intro: "足银 999/9999 精工打造,多种圈口、克重与工艺可选,兼顾日常佩戴与纪念送礼,并支持刻字、錾刻、现场打制及旧银来料改造。", intro: "足银 999/9999 精工打造,多种圈口、克重与工艺可选,兼顾日常佩戴与纪念送礼,并支持刻字、錾刻、现场打制及旧银来料改造。",
features: [ features: [
"国标足银原料,每件附质检证书,支持复检,克重与计价透明", "国标足银原料,每件附质检证书,支持复检,克重与计价透明",
...@@ -42,6 +51,9 @@ export const site = { ...@@ -42,6 +51,9 @@ export const site = {
slug: "diy-weaving", slug: "diy-weaving",
index: "02", index: "02",
name: "银饰 DIY 编织", name: "银饰 DIY 编织",
seoTitle: "银饰DIY编织与定制手绳|银妆银饰",
seoDescription: "银妆银饰DIY编织提供多种线材、配色与足银配件,支持现场测量、亲手编织和长期免费换绳修护。",
seoKeywords: ["银饰DIY", "手绳编织", "银珠手绳", "定制手绳", "DIY银饰"],
intro: "自选银饰配件与线材,现场测量手围并亲手编织,适合宝宝满月、情侣纪念、生日送礼和日常自戴,让每一条手绳都成为专属手作。", intro: "自选银饰配件与线材,现场测量手围并亲手编织,适合宝宝满月、情侣纪念、生日送礼和日常自戴,让每一条手绳都成为专属手作。",
features: [ features: [
"数十种线材与配色可自由组合,支持双色、多色拼色", "数十种线材与配色可自由组合,支持双色、多色拼色",
...@@ -59,6 +71,9 @@ export const site = { ...@@ -59,6 +71,9 @@ export const site = {
slug: "silver-earrings", slug: "silver-earrings",
index: "03", index: "03",
name: "银耳环", name: "银耳环",
seoTitle: "银耳环、银耳钉与耳饰定制|银妆银饰",
seoDescription: "银妆S925与足银999耳饰覆盖耳钉、耳圈和长款耳坠,支持花纹、吊牌刻字与纪念款个性化加工。",
seoKeywords: ["银耳环", "银耳钉", "足银耳饰", "S925耳环", "耳饰定制"],
intro: "S925 与足银 999 系列覆盖耳钉、耳圈、长款耳坠,兼顾日常穿搭、纪念定制与节日送礼。", intro: "S925 与足银 999 系列覆盖耳钉、耳圈、长款耳坠,兼顾日常穿搭、纪念定制与节日送礼。",
features: [ features: [
"耳针无镍无铅,并做防过敏镀层处理,敏感肌可先试戴", "耳针无镍无铅,并做防过敏镀层处理,敏感肌可先试戴",
...@@ -75,6 +90,9 @@ export const site = { ...@@ -75,6 +90,9 @@ export const site = {
slug: "silver-necklace", slug: "silver-necklace",
index: "04", index: "04",
name: "银项链", name: "银项链",
seoTitle: "银项链、银吊坠与专属镌刻|银妆银饰",
seoDescription: "银妆S925与足银999项链提供多种链型、长度与银吊坠选择,支持名字、纪念日和专属图案镌刻。",
seoKeywords: ["银项链", "银吊坠", "足银项链", "S925项链", "项链刻字"],
intro: "O 字链、肖邦链、蛇骨链等多种链型,搭配丰富银吊坠,长短可选并支持专属镌刻。", intro: "O 字链、肖邦链、蛇骨链等多种链型,搭配丰富银吊坠,长短可选并支持专属镌刻。",
features: [ features: [
"S925、足银 999 双材质可选,每件附质检证书,无劣质镀层", "S925、足银 999 双材质可选,每件附质检证书,无劣质镀层",
......
...@@ -8,14 +8,40 @@ import ContactDialog from "../components/ContactDialog.astro"; ...@@ -8,14 +8,40 @@ import ContactDialog from "../components/ContactDialog.astro";
interface Props { interface Props {
title: string; title: string;
description: string; description?: string;
keywords?: string | readonly string[];
image?: string; image?: string;
imageAlt?: string;
ogType?: "website" | "article"; ogType?: "website" | "article";
noindex?: boolean; noindex?: boolean;
publishedTime?: string;
modifiedTime?: string;
articleSection?: string;
articleAuthor?: string;
jsonLd?: Record<string, unknown> | Record<string, unknown>[]; jsonLd?: Record<string, unknown> | Record<string, unknown>[];
} }
const { title, description, image = logo.src, ogType = "website", noindex = false, jsonLd = [] } = Astro.props; const {
title,
description = site.seo.description,
keywords = site.seo.keywords,
image = logo.src,
imageAlt = site.seo.imageAlt,
ogType = "website",
noindex = false,
publishedTime,
modifiedTime,
articleSection,
articleAuthor,
jsonLd = [],
} = Astro.props;
const cleanMeta = (value: string) => value.replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim();
const metaTitle = cleanMeta(title);
const metaDescription = cleanMeta(description);
const metaKeywords = (typeof keywords === "string" ? keywords.split(",") : keywords)
.map((keyword) => cleanMeta(keyword))
.filter(Boolean)
.join(",");
const optimizedLogo = await getImage({ src: logo, width: 320, format: "webp", quality: 90 }); const optimizedLogo = await getImage({ src: logo, width: 320, format: "webp", quality: 90 });
const optimizedFavicon = await getImage({ src: favicon, width: 128, height: 128, format: "png" }); const optimizedFavicon = await getImage({ src: favicon, width: 128, height: 128, format: "png" });
const canonical = new URL(Astro.url.pathname, Astro.site).href; const canonical = new URL(Astro.url.pathname, Astro.site).href;
...@@ -27,7 +53,7 @@ const organizationLd = { ...@@ -27,7 +53,7 @@ const organizationLd = {
name: site.brand.name, name: site.brand.name,
alternateName: [site.brand.shortName, site.brand.englishName], alternateName: [site.brand.shortName, site.brand.englishName],
legalName: site.brand.legalName, legalName: site.brand.legalName,
url: Astro.site?.href, url: new URL("/", Astro.site).href,
logo: new URL(optimizedLogo.src, Astro.site).href, logo: new URL(optimizedLogo.src, Astro.site).href,
slogan: site.brand.tagline, slogan: site.brand.tagline,
description: site.home.subhead, description: site.home.subhead,
...@@ -40,8 +66,25 @@ const organizationLd = { ...@@ -40,8 +66,25 @@ const organizationLd = {
addressCountry: "CN", addressCountry: "CN",
}, },
}; };
const webPageLd = {
"@context": "https://schema.org",
"@type": "WebPage",
"@id": `${canonical}#webpage`,
url: canonical,
name: metaTitle,
description: metaDescription,
inLanguage: "zh-CN",
isPartOf: { "@id": new URL("/#website", Astro.site).href },
about: { "@id": new URL("/#organization", Astro.site).href },
primaryImageOfPage: {
"@type": "ImageObject",
url: ogImage,
caption: imageAlt,
},
};
const pageLd = Array.isArray(jsonLd) ? jsonLd : [jsonLd]; const pageLd = Array.isArray(jsonLd) ? jsonLd : [jsonLd];
const allLd = [organizationLd, ...pageLd.filter((item) => Object.keys(item).length > 0)]; const allLd = [organizationLd, webPageLd, ...pageLd.filter((item) => Object.keys(item).length > 0)];
const serializedLd = JSON.stringify(allLd).replace(/</g, "\\u003c");
--- ---
<!doctype html> <!doctype html>
<html lang="zh-CN"> <html lang="zh-CN">
...@@ -50,23 +93,35 @@ const allLd = [organizationLd, ...pageLd.filter((item) => Object.keys(item).leng ...@@ -50,23 +93,35 @@ const allLd = [organizationLd, ...pageLd.filter((item) => Object.keys(item).leng
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#F7F3EA" /> <meta name="theme-color" content="#F7F3EA" />
<meta name="color-scheme" content="light" /> <meta name="color-scheme" content="light" />
<title>{title}</title> <title>{metaTitle}</title>
<meta name="description" content={description} /> <meta name="description" content={metaDescription} />
<meta name="keywords" content={metaKeywords} />
<meta name="robots" content={noindex ? "noindex,nofollow" : "index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1"} /> <meta name="robots" content={noindex ? "noindex,nofollow" : "index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1"} />
<link rel="canonical" href={canonical} /> <link rel="canonical" href={canonical} />
<link rel="icon" href={optimizedFavicon.src} type="image/png" /> <link rel="icon" href={optimizedFavicon.src} type="image/png" sizes="128x128" />
<link rel="shortcut icon" href={optimizedFavicon.src} type="image/png" />
<link rel="apple-touch-icon" href={optimizedFavicon.src} />
<link rel="manifest" href="/site.webmanifest" /> <link rel="manifest" href="/site.webmanifest" />
<link rel="alternate" hreflang="zh-CN" href={canonical} /> <link rel="alternate" hreflang="zh-CN" href={canonical} />
<link rel="alternate" hreflang="x-default" href={canonical} /> <link rel="alternate" hreflang="x-default" href={canonical} />
<meta property="og:type" content={ogType} /> <meta property="og:type" content={ogType} />
<meta property="og:title" content={title} /> <meta property="og:title" content={metaTitle} />
<meta property="og:description" content={description} /> <meta property="og:description" content={metaDescription} />
<meta property="og:url" content={canonical} /> <meta property="og:url" content={canonical} />
<meta property="og:image" content={ogImage} /> <meta property="og:image" content={ogImage} />
<meta property="og:image:alt" content={imageAlt} />
<meta property="og:site_name" content={site.brand.name} /> <meta property="og:site_name" content={site.brand.name} />
<meta property="og:locale" content="zh_CN" /> <meta property="og:locale" content="zh_CN" />
{publishedTime && <meta property="article:published_time" content={publishedTime} />}
{modifiedTime && <meta property="article:modified_time" content={modifiedTime} />}
{articleSection && <meta property="article:section" content={articleSection} />}
{articleAuthor && <meta property="article:author" content={articleAuthor} />}
<meta name="twitter:card" content="summary_large_image" /> <meta name="twitter:card" content="summary_large_image" />
<script type="application/ld+json" set:html={JSON.stringify(allLd)} is:inline></script> <meta name="twitter:title" content={metaTitle} />
<meta name="twitter:description" content={metaDescription} />
<meta name="twitter:image" content={ogImage} />
<meta name="twitter:image:alt" content={imageAlt} />
<script type="application/ld+json" set:html={serializedLd} is:inline></script>
</head> </head>
<body> <body>
<a class="skip-link" href="#main-content">跳到主要内容</a> <a class="skip-link" href="#main-content">跳到主要内容</a>
......
...@@ -3,6 +3,8 @@ import Base from "../../layouts/Base.astro"; import Header from "../../component ...@@ -3,6 +3,8 @@ import Base from "../../layouts/Base.astro"; import Header from "../../component
type ArticleDetail = NonNullable<Awaited<ReturnType<typeof getPublishedArticle>>>; type ArticleDetail = NonNullable<Awaited<ReturnType<typeof getPublishedArticle>>>;
export async function getStaticPaths() { const articles = await getPublishedArticles(); return Promise.all(articles.map(async (article) => ({ params: { slug: article.id }, props: { entry: await getPublishedArticle(article.id) } }))); } export async function getStaticPaths() { const articles = await getPublishedArticles(); return Promise.all(articles.map(async (article) => ({ params: { slug: article.id }, props: { entry: await getPublishedArticle(article.id) } }))); }
const { entry } = Astro.props as { entry: ArticleDetail }; const article = entry.data; const date = fmtDate(article.date); const title = `${article.title}|银饰资讯|${site.brand.name}`; const pageUrl = new URL(`/articles/${entry.id}/`, Astro.site).href; const { entry } = Astro.props as { entry: ArticleDetail }; const article = entry.data; const date = fmtDate(article.date); const title = `${article.title}|银饰资讯|${site.brand.name}`; const pageUrl = new URL(`/articles/${entry.id}/`, Astro.site).href;
const jsonLd = [{ "@context": "https://schema.org", "@type": "Article", headline: article.title, description: article.excerpt, datePublished: date, dateModified: fmtDate(article.updated || article.date), articleSection: article.category, inLanguage: "zh-CN", author: { "@type": "Organization", name: article.author }, publisher: { "@id": new URL("/#organization", Astro.site).href }, mainEntityOfPage: { "@type": "WebPage", "@id": pageUrl }, url: pageUrl }, { "@context": "https://schema.org", "@type": "BreadcrumbList", itemListElement: [{ "@type": "ListItem", position: 1, name: "首页", item: Astro.site?.href }, { "@type": "ListItem", position: 2, name: "银饰资讯", item: new URL("/articles/", Astro.site).href }, { "@type": "ListItem", position: 3, name: article.title, item: pageUrl }] }]; const modified = fmtDate(article.updated || article.date);
const keywords = [article.category, article.title, "银饰资讯", site.brand.name];
const jsonLd = [{ "@context": "https://schema.org", "@type": "Article", headline: article.title, description: article.excerpt, datePublished: date, dateModified: modified, articleSection: article.category, inLanguage: "zh-CN", author: { "@type": "Organization", name: article.author }, publisher: { "@id": new URL("/#organization", Astro.site).href }, mainEntityOfPage: { "@type": "WebPage", "@id": `${pageUrl}#webpage` }, url: pageUrl }, { "@context": "https://schema.org", "@type": "BreadcrumbList", itemListElement: [{ "@type": "ListItem", position: 1, name: "首页", item: Astro.site?.href }, { "@type": "ListItem", position: 2, name: "银饰资讯", item: new URL("/articles/", Astro.site).href }, { "@type": "ListItem", position: 3, name: article.title, item: pageUrl }] }];
--- ---
<Base {title} description={article.excerpt} ogType="article" {jsonLd}><Header /><main id="main-content" class="article-detail-page"><article><header class="article-detail-head"><div class="container article-detail-head__inner"><div class="breadcrumb"><a href="/">首页</a> / <a href="/articles/">银饰资讯</a> / {article.category}</div><span class="article-detail-category">{article.category}</span><h1>{article.title}</h1><p>{article.excerpt}</p><div class="article-detail-meta"><time datetime={date}>{date}</time><span>{article.author}</span></div></div></header><div class="container article-detail-layout"><aside><a href="/articles/">← 返回银饰资讯</a><p>银饰材质、价格与售后规则请以实际产品标识和购买门店说明为准。</p></aside><div class="article-prose" set:html={entry.html}></div></div></article></main><Footer /></Base> <Base {title} description={article.excerpt} {keywords} ogType="article" publishedTime={date} modifiedTime={modified} articleSection={article.category} articleAuthor={article.author} {jsonLd}><Header /><main id="main-content" class="article-detail-page"><article><header class="article-detail-head"><div class="container article-detail-head__inner"><div class="breadcrumb"><a href="/">首页</a> / <a href="/articles/">银饰资讯</a> / {article.category}</div><span class="article-detail-category">{article.category}</span><h1>{article.title}</h1><p>{article.excerpt}</p><div class="article-detail-meta"><time datetime={date}>{date}</time><span>{article.author}</span></div></div></header><div class="container article-detail-layout"><aside><a href="/articles/">← 返回银饰资讯</a><p>银饰材质、价格与售后规则请以实际产品标识和购买门店说明为准。</p></aside><div class="article-prose" set:html={entry.html}></div></div></article></main><Footer /></Base>
...@@ -11,11 +11,20 @@ import { fmtDate } from "../lib/articles"; ...@@ -11,11 +11,20 @@ import { fmtDate } from "../lib/articles";
import hero from "../../pic/product/微信图片_20260508131943_573_199.jpg"; import hero from "../../pic/product/微信图片_20260508131943_573_199.jpg";
import { Image } from "astro:assets"; import { Image } from "astro:assets";
const latest = (await getPublishedArticles()).slice(0, 3); const latest = (await getPublishedArticles()).slice(0, 3);
const title = `银妆银饰|足银首饰、手工定制与终身养护`; const title = site.seo.title;
const description = "银妆深耕银饰行业14年,提供足银手镯、银项链、银耳环、来料加工、专属雕刻与银饰DIY编织服务。"; const description = site.seo.description;
const jsonLd = { "@context": "https://schema.org", "@type": "WebSite", name: site.brand.name, url: Astro.site?.href, inLanguage: "zh-CN" }; const jsonLd = {
"@context": "https://schema.org",
"@type": "WebSite",
"@id": new URL("/#website", Astro.site).href,
name: site.brand.name,
alternateName: site.brand.englishName,
url: new URL("/", Astro.site).href,
inLanguage: "zh-CN",
publisher: { "@id": new URL("/#organization", Astro.site).href },
};
--- ---
<Base {title} {description} {jsonLd} image={hero.src}> <Base {title} {description} keywords={site.seo.keywords} {jsonLd} image={hero.src} imageAlt="银妆古法足银手镯与品牌形象">
<Header /> <Header />
<main id="main-content"> <main id="main-content">
<section class="hero"> <section class="hero">
...@@ -39,7 +48,7 @@ const jsonLd = { "@context": "https://schema.org", "@type": "WebSite", name: sit ...@@ -39,7 +48,7 @@ const jsonLd = { "@context": "https://schema.org", "@type": "WebSite", name: sit
<section id="products" class="section-pad section-cream"> <section id="products" class="section-pad section-cream">
<div class="container"> <div class="container">
<div class="section-intro-row"><SectionHeading eyebrow="SILVER COLLECTION" title="匠心银作,专属信物" description="从手镯、项链、耳饰到 DIY 编织,以足银为载体,融合古法手工与个性化定制。" /><a class="text-link" href="/services/">查看全部产品 <span>↗</span></a></div> <div class="section-intro-row"><SectionHeading eyebrow="SILVER COLLECTION" title="匠心银作,专属信物" description="从手镯、项链、耳饰到 DIY 编织,以足银为载体,融合古法手工与个性化定制。" /><a class="text-link" href="/services/">查看全部产品 <span>↗</span></a></div>
<div class="product-category-grid">{site.products.map((product) => <a class="product-category-card" href={`/services/#${product.slug}`} data-reveal><span>{product.index}</span><h3>{product.name}</h3><p>{product.intro}</p><strong>了解详情 ↗</strong></a>)}</div> <div class="product-category-grid">{site.products.map((product) => <a class="product-category-card" href={`/services/${product.slug}/`} data-reveal><span>{product.index}</span><h3>{product.name}</h3><p>{product.intro}</p><strong>了解详情 ↗</strong></a>)}</div>
</div> </div>
</section> </section>
......
import type { APIRoute } from "astro";
import { site } from "../data/site";
import { getPublishedArticles } from "../lib/articles";
export const prerender = true;
export const GET: APIRoute = async ({ site: configuredSite }) => {
const articles = await getPublishedArticles();
const origin = configuredSite ?? new URL("https://www.inzung.cn");
const url = (pathname: string) => new URL(pathname, origin).href;
const lines = [
`# ${site.brand.name}${site.brand.englishName})完整站点内容`,
"",
`> ${site.brand.tagline}`,
"",
`官方网站:${url("/")}`,
`企业主体:${site.brand.legalName}`,
`客服电话:${site.contact.phone}`,
`公司地址:${site.contact.address}`,
"",
"## 品牌与服务概览",
"",
site.home.subhead,
"",
...site.about.story,
"",
"## 核心数据",
"",
...site.home.stats.map((stat) => `- ${stat.label}${stat.value}`),
"",
"## 产品与定制",
"",
`产品总览:${url("/services/")}`,
"",
...site.products.flatMap((product) => [
`### ${product.name}`,
"",
`页面:${url(`/services/${product.slug}/`)}`,
"",
product.intro,
"",
"特点:",
...product.features.map((feature) => `- ${feature}`),
"",
"规格与工艺:",
...product.specifications.map((spec) => `- ${spec.label}${spec.value}`),
"",
]),
"## 服务承诺",
"",
...site.promises.map((promise) => `- ${promise.title}${promise.desc}`),
"",
"## 常见问题",
"",
`常见问题页面:${url("/faq/")}`,
"",
...site.faq.flatMap((item) => [`### ${item.q}`, "", item.a, ""]),
"## 银饰资讯全文",
"",
...articles.flatMap((article) => [
`### ${article.data.title}`,
"",
`页面:${url(`/articles/${article.id}/`)}`,
`分类:${article.data.category}`,
`作者:${article.data.author}`,
`发布日期:${article.data.date.toISOString().slice(0, 10)}`,
`更新时间:${article.data.updated.toISOString().slice(0, 10)}`,
"",
article.data.excerpt,
"",
article.body,
"",
]),
"## 联系方式",
"",
`联系页面:${url("/contact/")}`,
`客服电话:${site.contact.phone}`,
`公司地址:${site.contact.address}`,
`抖音账号:${site.contact.douyin.join("、")}`,
"",
];
return new Response(lines.join("\n"), {
headers: {
"Content-Type": "text/plain; charset=utf-8",
"Cache-Control": "public, max-age=3600",
},
});
};
import type { APIRoute } from "astro";
import { site } from "../data/site"; import { site } from "../data/site";
import { getPublishedArticles } from "../lib/articles"; import { getPublishedArticles } from "../lib/articles";
export async function GET() { export const prerender = true;
export const GET: APIRoute = async ({ site: configuredSite }) => {
const articles = await getPublishedArticles(); const articles = await getPublishedArticles();
const origin = configuredSite ?? new URL("https://www.inzung.cn");
const url = (pathname: string) => new URL(pathname, origin).href;
const lines = [ const lines = [
`# ${site.brand.name}`, `# ${site.brand.name}`,
"", "",
...@@ -11,10 +16,20 @@ export async function GET() { ...@@ -11,10 +16,20 @@ export async function GET() {
`银妆是拥有 14 年行业积淀的银饰连锁品牌,提供足银首饰零售、来料加工、专属雕刻与银饰 DIY 编织服务。`, `银妆是拥有 14 年行业积淀的银饰连锁品牌,提供足银首饰零售、来料加工、专属雕刻与银饰 DIY 编织服务。`,
"", "",
"## 核心页面", "## 核心页面",
"- [首页](/)", "- [产品与定制](/services/)", "- [关于银妆](/about/)", "- [门店与服务](/join/)", "- [银饰资讯](/articles/)", "- [常见问题](/faq/)", "- [联系我们](/contact/)", `- [首页](${url("/")})`,
"", "## 银饰资讯", `- [产品与定制](${url("/services/")})`,
...articles.map((article) => `- [${article.data.title}](/articles/${article.id}/): ${article.data.excerpt}`), ...site.products.map((product) => `- [${product.name}](${url(`/services/${product.slug}/`)}): ${product.seoDescription}`),
`- [关于银妆](${url("/about/")})`,
`- [门店与服务](${url("/join/")})`,
`- [银饰资讯](${url("/articles/")})`,
`- [常见问题](${url("/faq/")})`,
`- [联系我们](${url("/contact/")})`,
"",
"## 银饰资讯",
...articles.map((article) => `- [${article.data.title}](${url(`/articles/${article.id}/`)}): ${article.data.excerpt}`),
"",
`- [完整站点内容](${url("/llms-full.txt")})`,
"", `客服电话:${site.contact.phone}`, `公司地址:${site.contact.address}`, "", `客服电话:${site.contact.phone}`, `公司地址:${site.contact.address}`,
]; ];
return new Response(lines.join("\n"), { headers: { "Content-Type": "text/plain; charset=utf-8", "Cache-Control": "public, max-age=3600" } }); return new Response(lines.join("\n"), { headers: { "Content-Type": "text/plain; charset=utf-8", "Cache-Control": "public, max-age=3600" } });
} };
---
import { Image } from "astro:assets";
import Base from "../../layouts/Base.astro";
import Header from "../../components/Header.astro";
import Footer from "../../components/Footer.astro";
import CTA from "../../components/CTA.astro";
import { site } from "../../data/site";
import braceletImage from "../../../pic/product/微信图片_20260508131941_571_199.jpg";
import diyImage from "../../../pic/product/优先-画板 1(1).jpg";
import earringsImage from "../../../pic/product/800.jpg";
import necklaceImage from "../../../pic/product/微信图片_20260508131943_573_199.jpg";
export function getStaticPaths() {
return site.products.map((product) => ({
params: { slug: product.slug },
props: { product },
}));
}
type Product = (typeof site.products)[number];
const { product } = Astro.props as { product: Product };
const productImages = {
"silver-bracelet": braceletImage,
"diy-weaving": diyImage,
"silver-earrings": earringsImage,
"silver-necklace": necklaceImage,
} as const;
const image = productImages[product.slug];
const pageUrl = new URL(`/services/${product.slug}/`, Astro.site).href;
const imageUrl = new URL(image.src, Astro.site).href;
const jsonLd = [
{
"@context": "https://schema.org",
"@type": "Product",
"@id": `${pageUrl}#product`,
name: product.name,
description: product.seoDescription,
url: pageUrl,
image: imageUrl,
category: "银饰与定制",
brand: { "@type": "Brand", name: site.brand.name },
manufacturer: { "@id": new URL("/#organization", Astro.site).href },
additionalProperty: product.specifications.map((spec) => ({
"@type": "PropertyValue",
name: spec.label,
value: spec.value,
})),
},
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
itemListElement: [
{ "@type": "ListItem", position: 1, name: "首页", item: new URL("/", Astro.site).href },
{ "@type": "ListItem", position: 2, name: "产品与定制", item: new URL("/services/", Astro.site).href },
{ "@type": "ListItem", position: 3, name: product.name, item: pageUrl },
],
},
];
const related = site.products.filter((item) => item.slug !== product.slug);
---
<Base
title={product.seoTitle}
description={product.seoDescription}
keywords={product.seoKeywords}
image={image.src}
imageAlt={`银妆${product.name}产品图`}
{jsonLd}
>
<Header />
<main id="main-content">
<section class="page-hero page-hero--product" data-word="SILVER">
<div class="container page-hero__inner" data-reveal>
<div class="breadcrumb"><a href="/">首页</a> / <a href="/services/">产品与定制</a> / {product.name}</div>
<div class="eyebrow"><span></span>INZUNG {product.index}</div>
<h1>{product.name}<br /><em>手作与专属定制</em></h1>
<p>{product.intro}</p>
</div>
</section>
<section class="section-pad section-cream">
<div class="container product-detail-list">
<article class="product-detail">
<div class="product-detail__image" data-reveal>
<Image src={image} widths={[480, 720, 960]} sizes="(max-width: 760px) calc(100vw - 62px), 44vw" format="webp" quality={86} alt={`银妆${product.name}`} />
<span>{product.index}</span>
</div>
<div class="product-detail__content" data-reveal>
<div class="eyebrow"><span></span>PRODUCT DETAIL</div>
<h2>{product.name}</h2>
<p class="lead">{product.seoDescription}</p>
<ul>{product.features.map((feature) => <li><span>✓</span>{feature}</li>)}</ul>
<div class="spec-box">
<strong>规格与工艺</strong>
<dl>{product.specifications.map((spec) => <div><dt>{spec.label}</dt><dd>{spec.value}</dd></div>)}</dl>
</div>
<a class="button button--outline" href="tel:4008113922">咨询{product.name}</a>
</div>
</article>
</div>
</section>
<nav class="anchor-nav" aria-label="其他产品">
<div class="container">{related.map((item) => <a href={`/services/${item.slug}/`}><span>{item.index}</span>{item.name}</a>)}</div>
</nav>
<CTA title={`定制你的${product.name}`} text="告诉我们佩戴者、用途、尺寸、预算与喜欢的风格,首饰顾问将协助确认材质和工艺方案。" />
</main>
<Footer />
</Base>
...@@ -4,13 +4,44 @@ import b1 from "../../../pic/product/微信图片_20260508131941_571_199.jpg"; i ...@@ -4,13 +4,44 @@ import b1 from "../../../pic/product/微信图片_20260508131941_571_199.jpg"; i
import { Image } from "astro:assets"; import { Image } from "astro:assets";
const images = [b1, b2, b3, null]; const images = [b1, b2, b3, null];
const title = `产品与定制|${site.brand.name}`; const description = "探索银妆足银手镯、银饰DIY编织、银耳环、银项链与私人定制服务。"; const title = `产品与定制|${site.brand.name}`; const description = "探索银妆足银手镯、银饰DIY编织、银耳环、银项链与私人定制服务。";
const keywords = site.products.flatMap((product) => product.seoKeywords);
const pageUrl = new URL("/services/", Astro.site).href;
const jsonLd = [
{
"@context": "https://schema.org",
"@type": "ItemList",
name: "银妆产品与定制服务",
url: pageUrl,
numberOfItems: site.products.length,
itemListElement: site.products.map((product, index) => ({
"@type": "ListItem",
position: index + 1,
url: new URL(`/services/${product.slug}/`, Astro.site).href,
item: {
"@type": "Product",
name: product.name,
description: product.intro,
category: "银饰",
brand: { "@type": "Brand", name: site.brand.name },
},
})),
},
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
itemListElement: [
{ "@type": "ListItem", position: 1, name: "首页", item: new URL("/", Astro.site).href },
{ "@type": "ListItem", position: 2, name: "产品与定制", item: pageUrl },
],
},
];
--- ---
<Base {title} {description}><Header /><main id="main-content"> <Base {title} {description} {keywords} {jsonLd}><Header /><main id="main-content">
<section class="page-hero page-hero--product" data-word="SILVER"><div class="container page-hero__inner" data-reveal><div class="breadcrumb"><a href="/">首页</a> / 产品与定制</div><div class="eyebrow"><span></span>PRODUCT & CUSTOM</div><h1>匠心银作<br/><em>专属信物</em></h1><p>以足银为载体,融合古法手工与个性化定制,满足自戴、送礼、纪念与旧银新生的多重需要。</p></div></section> <section class="page-hero page-hero--product" data-word="SILVER"><div class="container page-hero__inner" data-reveal><div class="breadcrumb"><a href="/">首页</a> / 产品与定制</div><div class="eyebrow"><span></span>PRODUCT & CUSTOM</div><h1>匠心银作<br/><em>专属信物</em></h1><p>以足银为载体,融合古法手工与个性化定制,满足自戴、送礼、纪念与旧银新生的多重需要。</p></div></section>
<nav class="anchor-nav"><div class="container">{site.products.map((item) => <a href={`#${item.slug}`}><span>{item.index}</span>{item.name}</a>)}</div></nav> <nav class="anchor-nav"><div class="container">{site.products.map((item) => <a href={`#${item.slug}`}><span>{item.index}</span>{item.name}</a>)}</div></nav>
<section class="section-pad section-cream"><div class="container product-detail-list">{site.products.map((item, index) => <article id={item.slug} class:list={["product-detail", { "product-detail--reverse": index % 2, "product-detail--no-image": !images[index] }]}> <section class="section-pad section-cream"><div class="container product-detail-list">{site.products.map((item, index) => <article id={item.slug} class:list={["product-detail", { "product-detail--reverse": index % 2, "product-detail--no-image": !images[index] }]}>
{images[index] && <div class="product-detail__image" data-reveal><Image src={images[index]} widths={[480, 720, 960]} sizes="(max-width: 760px) calc(100vw - 62px), 44vw" format="webp" quality={86} alt={item.name} loading={index ? "lazy" : "eager"} /><span>{item.index}</span></div>} {images[index] && <div class="product-detail__image" data-reveal><Image src={images[index]} widths={[480, 720, 960]} sizes="(max-width: 760px) calc(100vw - 62px), 44vw" format="webp" quality={86} alt={item.name} loading={index ? "lazy" : "eager"} /><span>{item.index}</span></div>}
<div class="product-detail__content" data-reveal><div class="eyebrow"><span></span>INZUNG {item.index}</div><h2>{item.name}</h2><p class="lead">{item.intro}</p><ul>{item.features.map((feature) => <li><span>✓</span>{feature}</li>)}</ul><div class="spec-box"><strong>规格与工艺</strong><dl>{item.specifications.map((spec) => <div><dt>{spec.label}</dt><dd>{spec.value}</dd></div>)}</dl></div><a class="button button--outline" href="tel:4008113922">咨询该系列</a></div> <div class="product-detail__content" data-reveal><div class="eyebrow"><span></span>INZUNG {item.index}</div><h2>{item.name}</h2><p class="lead">{item.intro}</p><ul>{item.features.map((feature) => <li><span>✓</span>{feature}</li>)}</ul><div class="spec-box"><strong>规格与工艺</strong><dl>{item.specifications.map((spec) => <div><dt>{spec.label}</dt><dd>{spec.value}</dd></div>)}</dl></div><div class="hero__actions"><a class="button button--outline" href={`/services/${item.slug}/`}>查看产品详情</a><a class="button button--outline" href="tel:4008113922">咨询该系列</a></div></div>
</article>)}</div></section> </article>)}</div></section>
<section class="section-pad section-white"><div class="container"><SectionHeading eyebrow="MORE SILVER PIECES" title="更多银妆产品" description="不同款式与工艺持续上新,具体库存、圈口与克重欢迎咨询门店。" /><ProductCarousel /></div></section> <section class="section-pad section-white"><div class="container"><SectionHeading eyebrow="MORE SILVER PIECES" title="更多银妆产品" description="不同款式与工艺持续上新,具体库存、圈口与克重欢迎咨询门店。" /><ProductCarousel /></div></section>
<CTA title="有一个特别的银饰构想?" text="无论是刻字、錾刻、来料改造还是全新设计,我们都愿意从一张草图、一句话开始。" /> <CTA title="有一个特别的银饰构想?" text="无论是刻字、錾刻、来料改造还是全新设计,我们都愿意从一张草图、一句话开始。" />
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment