Commit bbf00c36 authored by xuchentao's avatar xuchentao

feat: complete website SEO and GEO support

parent aceb0832
Pipeline #454 passed with stage
in 15 seconds
......@@ -7,7 +7,7 @@ const customOutDir = process.env.SUMEIQIAO_BUILD_OUT_DIR;
export default defineConfig({
site: "https://www.sumeiqiao.com",
integrations: [sitemap()],
integrations: [sitemap({ filter: (page) => !new URL(page).pathname.startsWith("/admin/") })],
adapter: node({ mode: "standalone" }),
...(customOutDir ? { outDir: path.resolve(customOutDir) } : {}),
build: { format: "directory" },
......
......@@ -441,12 +441,18 @@ async function openEditor(slug) {
const article = await api(`/articles/${slug}`);
$("#edit-heading").textContent = "编辑文章";
$("#title").value = article.title;
$("#seo-title").value = article.seoTitle || "";
$("#seo-description").value = article.seoDescription || "";
$("#seo-keywords").value = article.seoKeywords || "";
fillCategories(article.category);
$("#body").value = article.body;
setEditorStatus(article.publishStatus);
} else {
$("#edit-heading").textContent = "新建文章";
$("#title").value = "";
$("#seo-title").value = "";
$("#seo-description").value = "";
$("#seo-keywords").value = "";
$("#body").value = "";
setEditorStatus("new-unsaved");
}
......@@ -466,6 +472,9 @@ function payload() {
return {
title: $("#title").value.trim(),
category: $("#category").value,
seoTitle: $("#seo-title").value.trim(),
seoDescription: $("#seo-description").value.trim(),
seoKeywords: $("#seo-keywords").value.trim(),
body: $("#body").value,
};
}
......@@ -558,6 +567,9 @@ function schedulePreview(delay = 350) {
}
$("#title").addEventListener("input", () => { dirty = true; });
$("#seo-title").addEventListener("input", () => { dirty = true; });
$("#seo-description").addEventListener("input", () => { dirty = true; });
$("#seo-keywords").addEventListener("input", () => { dirty = true; });
$("#body").addEventListener("input", () => { dirty = true; schedulePreview(); });
$("#upload-button").addEventListener("click", () => $("#file").click());
......
......@@ -64,6 +64,9 @@ input:focus, textarea:focus, select:focus { outline: 2px solid rgba(22,174,156,.
.field-panel, .editor-panel { padding: 24px; background: white; border: 1px solid var(--border); border-radius: 16px; }
.field-panel { display: grid; gap: 15px; }
.field-panel small { font-weight: 400; opacity: .8; }
.panel-heading { display: flex; align-items: baseline; justify-content: space-between; gap: 14px; }
.panel-heading small { color: var(--muted); }
.field-panel textarea { resize: vertical; line-height: 1.6; }
.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; }
.custom-select { position: relative; }
.select-trigger { width: 100%; padding: 0 13px; justify-content: space-between; color: var(--text); background: white; border: 1px solid var(--border); border-radius: 9px; text-align: left; }
......
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<rect width="64" height="64" rx="16" fill="#16ae9c"/>
<path d="M32 10c-2.8 7.4-7.8 12.2-12.1 17.7C16.7 31.8 15 36.1 15 41c0 9.4 7.6 17 17 17s17-7.6 17-17c0-4.9-1.7-9.2-4.9-13.3C39.8 22.2 34.8 17.4 32 10Z" fill="#fff"/>
<path d="M23 42c4.7 0 8.1-2 10.5-6.2 1.8 4.2 4.3 6.2 7.5 6.2-1.6 6.3-5.1 9.4-10.4 9.4-4.5 0-7-3.1-7.6-9.4Z" fill="#0e727c" opacity=".72"/>
</svg>
......@@ -5,5 +5,13 @@
"display": "standalone",
"background_color": "#FAF8F4",
"theme_color": "#17AE9C",
"lang": "zh-CN"
"lang": "zh-CN",
"icons": [
{
"src": "/favicon.svg",
"sizes": "any",
"type": "image/svg+xml",
"purpose": "any"
}
]
}
......@@ -23,5 +23,5 @@ const { service, link = true } = Astro.props;
<div><dt>适合人群</dt><dd>{service.audience}</dd></div>
<div><dt>核心方向</dt><dd>{service.value}</dd></div>
</dl>
{link && <a class="text-link" href={`/services/#${service.slug}`}>了解项目 <span>↗</span></a>}
{link && <a class="text-link" href={`/services/${service.slug}/`}>了解项目 <span>↗</span></a>}
</article>
......@@ -7,6 +7,14 @@ export const site = {
tagline: "让世界体验中式经络调理,改善全民健康",
industry: "美业 · 大健康 · 体龄管理 · 减肥塑形 · 中式调理",
},
seo: {
title: "塑美俏体龄管理|中式经络调理与科学减重塑形",
description:
"塑美俏以中式经络调理为核心,提供体龄管理减重、排湿排酸、体型塑形与亚健康调理服务,并为合作门店提供持续运营支持。",
keywords: ["塑美俏", "体龄管理", "中式经络调理", "科学减重", "体型塑形", "亚健康调理"],
image: "/og-cover.svg",
favicon: "/favicon.svg",
},
home: {
eyebrow: "中式经络体龄管理专家",
headline: "让身体回到轻盈、舒展的自然状态",
......@@ -50,6 +58,11 @@ export const site = {
audience: "有减重与体质管理需求的人群",
value: "饮食管理更轻松,兼顾体质与体重",
note: "除孕期外,具体适用情况需由门店专业人员评估。",
seo: {
title: "体龄管理减重|中式经络调理减重|塑美俏",
description: "了解塑美俏体龄管理减重服务:从个人体质与日常状态出发,通过中式经络调理制定兼顾体质、体重与长期习惯的周期方案。",
keywords: ["体龄管理减重", "中式经络调理减重", "科学减重", "塑美俏"],
},
},
{
slug: "dampness-conditioning",
......@@ -59,6 +72,11 @@ export const site = {
audience: "关注湿气与亚健康状态的人群",
value: "改善身体内环境,为后续管理打基础",
note: "属于日常健康管理服务,不替代医学检查与治疗。",
seo: {
title: "排湿排酸调理|中式经络日常健康管理|塑美俏",
description: "了解塑美俏排湿排酸调理服务:面向关注湿气、疲惫与日常亚健康状态的人群,通过中式经络调理改善身体状态。",
keywords: ["排湿排酸调理", "祛湿调理", "中式经络调理", "塑美俏"],
},
},
{
slug: "body-shaping",
......@@ -68,6 +86,11 @@ export const site = {
audience: "有局部塑形或体态管理需求的人群",
value: "减重与塑形协同规划",
note: "实际方案与体验因个人基础、周期和配合度而异。",
seo: {
title: "体型塑形管理|减重与局部塑形方案|塑美俏",
description: "了解塑美俏体型塑形管理服务:在整体体重管理基础上关注局部线条、体态与身形协调,按个人状态制定周期方案。",
keywords: ["体型塑形", "局部塑形", "体态管理", "减重塑形", "塑美俏"],
},
},
{
slug: "wellness-conditioning",
......@@ -77,6 +100,11 @@ export const site = {
audience: "有慢性亚健康管理需求的人群",
value: "在体重管理同时关注多维健康指标",
note: "不用于诊断、治疗疾病;异常指标请及时就医。",
seo: {
title: "亚健康调理|日常健康与经络调理|塑美俏",
description: "了解塑美俏亚健康调理服务:面向关注循环及多维健康指标的人群,提供以中式经络调理为核心的日常健康管理支持。",
keywords: ["亚健康调理", "日常健康管理", "中式经络调理", "体龄管理", "塑美俏"],
},
},
],
about: {
......@@ -158,10 +186,10 @@ export const site = {
label: "体龄管理",
children: [
{ href: "/services/", label: "服务总览" },
{ href: "/services/#weight-management", label: "体龄管理减重" },
{ href: "/services/#dampness-conditioning", label: "排湿排酸调理" },
{ href: "/services/#body-shaping", label: "体型塑形管理" },
{ href: "/services/#wellness-conditioning", label: "亚健康调理" },
{ href: "/services/weight-management/", label: "体龄管理减重" },
{ href: "/services/dampness-conditioning/", label: "排湿排酸调理" },
{ href: "/services/body-shaping/", label: "体型塑形管理" },
{ href: "/services/wellness-conditioning/", label: "亚健康调理" },
],
},
{
......
......@@ -7,8 +7,11 @@ import { site } from "../data/site";
interface Props {
title: string;
description: string;
keywords?: string | readonly string[];
image?: string;
ogType?: "website" | "article";
publishedTime?: string;
modifiedTime?: string;
noindex?: boolean;
jsonLd?: Record<string, unknown> | Record<string, unknown>[];
}
......@@ -16,14 +19,18 @@ interface Props {
const {
title,
description,
image = "/og-cover.svg",
keywords = site.seo.keywords,
image = site.seo.image,
ogType = "website",
publishedTime,
modifiedTime,
noindex = false,
jsonLd = [],
} = Astro.props;
const canonical = new URL(Astro.url.pathname, Astro.site).href;
const ogImage = new URL(image, Astro.site).href;
const keywordContent = typeof keywords === "string" ? keywords : [...keywords].join(",");
const organizationLd = {
"@context": "https://schema.org",
"@type": ["HealthAndBeautyBusiness", "Organization"],
......@@ -56,10 +63,14 @@ const allLd = [organizationLd, ...pageLd.filter((item) => Object.keys(item).leng
<meta name="color-scheme" content="light" />
<title>{title}</title>
<meta name="description" content={description} />
<meta name="keywords" content={keywordContent} />
<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="icon" href={logo.src} type="image/svg+xml" />
<link rel="icon" href={site.seo.favicon} type="image/svg+xml" />
<link rel="shortcut icon" href={site.seo.favicon} />
<link rel="apple-touch-icon" href={site.seo.favicon} />
<link rel="manifest" href="/site.webmanifest" />
<link rel="alternate" type="text/plain" href="/llms.txt" title={`${site.brand.name} AI 内容索引`} />
<link rel="alternate" hreflang="zh-CN" href={canonical} />
<link rel="alternate" hreflang="x-default" href={canonical} />
......@@ -68,9 +79,15 @@ const allLd = [organizationLd, ...pageLd.filter((item) => Object.keys(item).leng
<meta property="og:description" content={description} />
<meta property="og:url" content={canonical} />
<meta property="og:image" content={ogImage} />
<meta property="og:image:alt" content={`${title} - ${site.brand.name}`} />
<meta property="og:site_name" content={site.brand.name} />
<meta property="og:locale" content="zh_CN" />
{ogType === "article" && publishedTime && <meta property="article:published_time" content={publishedTime} />}
{ogType === "article" && modifiedTime && <meta property="article:modified_time" content={modifiedTime} />}
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={title} />
<meta name="twitter:description" content={description} />
<meta name="twitter:image" content={ogImage} />
<script type="application/ld+json" set:html={JSON.stringify(allLd)} is:inline></script>
</head>
......
......@@ -31,6 +31,9 @@ export interface StoredArticle {
category: string;
author: string;
excerpt: string;
seoTitle: string;
seoDescription: string;
seoKeywords: string;
status: "draft" | "published";
body: string;
}
......@@ -45,6 +48,9 @@ export interface Article {
category: string;
author: string;
excerpt: string;
seoTitle: string;
seoDescription: string;
seoKeywords: string;
status: "published";
};
body: string;
......@@ -89,6 +95,9 @@ const autoExcerpt = (body: string, fallback: string): string => {
return text.length > 120 ? `${text.slice(0, 120).trim()}…` : text;
};
const autoSeoKeywords = (category: string): string =>
[category, "体龄管理", "中式经络调理", "塑美俏"].filter((item, index, items) => item && items.indexOf(item) === index).join(",");
function serialize(data: StoredArticle, body: string): string {
const quote = (value: unknown) => JSON.stringify(value == null ? "" : String(value));
return [
......@@ -100,6 +109,9 @@ function serialize(data: StoredArticle, body: string): string {
`category: ${quote(data.category)}`,
`author: ${quote(data.author || DEFAULT_AUTHOR)}`,
`excerpt: ${quote(data.excerpt)}`,
`seoTitle: ${quote(data.seoTitle)}`,
`seoDescription: ${quote(data.seoDescription)}`,
`seoKeywords: ${quote(data.seoKeywords)}`,
`status: ${quote(data.status || "draft")}`,
"---",
"",
......@@ -171,14 +183,20 @@ export async function addCategory(value: unknown): Promise<string[]> {
export async function readArticleFrom(directory: string, slug: string): Promise<StoredArticle> {
const raw = await fs.readFile(path.join(directory, `${slug}.md`), "utf8");
const { data, content } = matter(raw);
const title = String(data.title || "");
const category = String(data.category || "");
const excerpt = String(data.excerpt || autoExcerpt(content, title));
return {
slug,
title: String(data.title || ""),
title,
date: fmtDate(data.date),
updated: fmtDate(data.updated || data.date),
category: String(data.category || ""),
category,
author: String(data.author || DEFAULT_AUTHOR),
excerpt: String(data.excerpt || ""),
excerpt,
seoTitle: String(data.seoTitle || title),
seoDescription: String(data.seoDescription || excerpt),
seoKeywords: String(data.seoKeywords || autoSeoKeywords(category)),
status: data.status === "draft" ? "draft" : "published",
body: content.trim(),
};
......@@ -221,7 +239,7 @@ export async function createContentSnapshot(): Promise<ContentSnapshot> {
}
const sameArticleContent = (left: StoredArticle, right: StoredArticle): boolean =>
["slug", "title", "date", "category", "author", "excerpt", "body"]
["slug", "title", "date", "category", "author", "excerpt", "seoTitle", "seoDescription", "seoKeywords", "body"]
.every((key) => String(left[key as keyof StoredArticle] ?? "") === String(right[key as keyof StoredArticle] ?? ""));
export async function getPublishStatus(article: StoredArticle): Promise<PublishStatus> {
......@@ -247,14 +265,20 @@ export async function writeArticle(slug: string, input: Record<string, unknown>,
const existing = !isNew && await fileExists(file) ? await readWorkingArticle(slug) : null;
const categories = await getCategoryNames();
const body = String(input.body || "").trim();
const title = String(input.title || "").trim();
const category = categories.includes(String(input.category)) ? String(input.category) : categories[0];
const excerpt = autoExcerpt(body, title);
const normalized: StoredArticle = {
title: String(input.title || "").trim(),
title,
slug,
date: existing?.date || fmtDate(),
updated: fmtDate(),
category: categories.includes(String(input.category)) ? String(input.category) : categories[0],
category,
author: DEFAULT_AUTHOR,
excerpt: autoExcerpt(body, String(input.title || "")),
excerpt,
seoTitle: String(input.seoTitle ?? existing?.seoTitle ?? "").trim() || title,
seoDescription: String(input.seoDescription ?? existing?.seoDescription ?? "").trim() || excerpt,
seoKeywords: String(input.seoKeywords ?? existing?.seoKeywords ?? "").trim() || autoSeoKeywords(category),
status: "draft",
body,
};
......@@ -431,6 +455,9 @@ export async function getPublishedArticles(): Promise<Article[]> {
category: article.category,
author: article.author,
excerpt: article.excerpt,
seoTitle: article.seoTitle,
seoDescription: article.seoDescription,
seoKeywords: article.seoKeywords,
status: "published" as const,
},
body: article.body,
......@@ -453,6 +480,9 @@ export async function getPublishedArticle(slug: string): Promise<(Article & { ht
category: article.category,
author: article.author,
excerpt: article.excerpt,
seoTitle: article.seoTitle,
seoDescription: article.seoDescription,
seoKeywords: article.seoKeywords,
status: "published",
},
body: article.body,
......
......@@ -65,7 +65,13 @@
</div>
</div>
</div>
<p class="auto-meta">文章网址、摘要、作者和 SEO 信息将由系统自动生成,无需填写。</p>
<p class="auto-meta">文章网址、摘要与作者由系统自动生成;SEO 留空时会自动使用文章标题、摘要和分类关键词。</p>
</section>
<section class="field-panel">
<div class="panel-heading"><strong>文章 SEO(TDK)</strong><small>可选;用于搜索结果和社交分享摘要</small></div>
<label>SEO 标题(Title)<input id="seo-title" maxlength="70" placeholder="留空则使用文章标题" /></label>
<label>SEO 描述(Description)<textarea id="seo-description" maxlength="180" rows="3" placeholder="留空则自动提取文章摘要"></textarea></label>
<label>SEO 关键词(Keywords)<input id="seo-keywords" maxlength="200" placeholder="多个关键词用英文逗号分隔" /></label>
</section>
<section class="editor-panel">
<div class="editor-head"><div><strong>文章正文</strong><small>右侧会自动显示预览</small></div><div><span id="upload-status"></span><button id="upload-button" class="ghost small" type="button">插入图片</button></div></div>
......
......@@ -19,14 +19,15 @@ export async function getStaticPaths() {
const { entry } = Astro.props as { entry: ArticleDetail };
const article = entry.data;
const date = fmtDate(article.date);
const title = `${article.title}|健康资讯|${site.brand.name}`;
const title = article.seoTitle === article.title ? `${article.title}|健康资讯|${site.brand.name}` : article.seoTitle;
const description = article.seoDescription || article.excerpt;
const pageUrl = new URL(`/articles/${entry.id}/`, Astro.site).href;
const jsonLd = [
{
"@context": "https://schema.org",
"@type": "Article",
headline: article.title,
description: article.excerpt,
description,
datePublished: date,
dateModified: fmtDate(article.updated || article.date),
articleSection: article.category,
......@@ -47,7 +48,15 @@ const jsonLd = [
},
];
---
<Base title={title} description={article.excerpt} ogType="article" jsonLd={jsonLd}>
<Base
title={title}
description={description}
keywords={article.seoKeywords}
ogType="article"
publishedTime={article.date.toISOString()}
modifiedTime={article.updated.toISOString()}
jsonLd={jsonLd}
>
<Header />
<main id="main-content" class="article-detail-page">
<article>
......
......@@ -7,8 +7,7 @@ import ServiceCard from "../components/ServiceCard.astro";
import CTA from "../components/CTA.astro";
import { site } from "../data/site";
const title = `${site.brand.name}|中式经络调理与体龄管理`;
const description = site.home.subhead;
const { title, description, keywords } = site.seo;
const jsonLd = [
{
"@context": "https://schema.org",
......@@ -32,7 +31,7 @@ const jsonLd = [
},
];
---
<Base title={title} description={description} jsonLd={jsonLd}>
<Base title={title} description={description} keywords={keywords} jsonLd={jsonLd}>
<Header />
<main id="main-content">
<section class="hero">
......
import type { APIRoute } from "astro";
import { site } from "../data/site";
import { getPublishedArticles } from "../lib/article-store";
export const GET: APIRoute = async ({ site: siteUrl }) => {
const base = (siteUrl?.href || "https://www.sumeiqiao.com/").replace(/\/$/, "");
const articles = await getPublishedArticles();
const serviceSections = site.services.map((item) => `## ${item.name}
- 官方页面:${base}/services/${item.slug}/
- 服务简介:${item.intro}
- 适合人群:${item.audience}
- 核心方向:${item.value}
- 服务边界:${item.note}`).join("\n\n");
const articleSections = articles.map((article) => `## ${article.data.title}
- 官方页面:${base}/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.seoDescription}
${article.body}`).join("\n\n---\n\n");
const text = `# ${site.brand.name}完整内容
> 本文件供搜索引擎与 AI 系统读取塑美俏官网的公开、可引用内容。最后生成时间:${new Date().toISOString()}
## 官方主体
- 品牌:${site.brand.name}${site.brand.englishName}
- 运营主体:${site.brand.legalName}
- 品牌主张:${site.brand.tagline}
- 成立时间:${site.about.founded}
- 行业:${site.brand.industry}
- 地址:${site.contact.address}
- 邮箱:${site.contact.email}
- 官网:${base}/
# 服务内容
${serviceSections}
# 品牌介绍
${site.about.story.join("\n\n")}
${site.about.advantages.map((item) => `## ${item.title}\n\n${item.desc}`).join("\n\n")}
# 常见问题
${site.faq.map((item) => `## ${item.q}\n\n${item.a}`).join("\n\n")}
# 健康资讯全文
${articleSections || "暂无已发布文章。"}
# 内容边界
- 官网健康内容用于日常健康信息参考,不替代医疗诊断与治疗。
- 个人体验与经营结果会因实际情况而异,官网不作绝对效果或收益承诺。
- 引用时请保留原始官方页面链接,并以页面最新内容为准。
`;
return new Response(text, { headers: { "Content-Type": "text/plain; charset=utf-8" } });
};
import type { APIRoute } from "astro";
import { site } from "../data/site";
import { getPublishedArticles } from "../lib/article-store";
export const GET: APIRoute = ({ site: siteUrl }) => {
export const GET: APIRoute = async ({ site: siteUrl }) => {
const base = (siteUrl?.href || "https://www.sumeiqiao.com/").replace(/\/$/, "");
const articles = await getPublishedArticles();
const services = site.services.map((item) => `- ${item.name}${item.intro}`).join("\n");
const advantages = site.about.advantages.map((item) => `- ${item.title}${item.desc}`).join("\n");
const faq = site.faq.map((item) => `- 问:${item.q}\n 答:${item.a}`).join("\n");
......@@ -37,10 +39,17 @@ ${faq}
## 主要页面
- 体龄管理:${base}/services/
${site.services.map((item) => `- ${item.name}${base}/services/${item.slug}/`).join("\n")}
- 关于塑美俏:${base}/about/
- 加盟支持:${base}/join/
- 常见问题:${base}/faq/
- 联系合作:${base}/contact/
## 健康资讯
${articles.map((article) => `- ${article.data.title}${base}/articles/${article.id}/ — ${article.data.seoDescription}`).join("\n") || "- 暂无已发布文章"}
## 完整内容
- 完整站点与文章正文:${base}/llms-full.txt
`;
return new Response(text, { headers: { "Content-Type": "text/plain; charset=utf-8" } });
};
---
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";
export function getStaticPaths() {
return site.services.map((service) => ({ params: { slug: service.slug }, props: { service } }));
}
const { service } = Astro.props as { service: (typeof site.services)[number] };
const pageUrl = new URL(`/services/${service.slug}/`, Astro.site).href;
const jsonLd = [
{
"@context": "https://schema.org",
"@type": "Service",
"@id": `${pageUrl}#service`,
name: service.name,
description: service.seo.description,
serviceType: service.name,
audience: { "@type": "Audience", audienceType: service.audience },
provider: { "@id": new URL("/#organization", Astro.site).href },
areaServed: { "@type": "Country", name: "中国" },
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("/services/", Astro.site).href },
{ "@type": "ListItem", position: 3, name: service.name, item: pageUrl },
],
},
];
---
<Base title={service.seo.title} description={service.seo.description} keywords={service.seo.keywords} jsonLd={jsonLd}>
<Header />
<main id="main-content">
<section class="page-hero" data-word="SERVICE">
<div class="container page-hero__inner" data-reveal>
<div class="breadcrumb"><a href="/">首页</a> / <a href="/services/">体龄管理</a> / {service.name}</div>
<div class="eyebrow"><span></span>BODY AGE SERVICE · {service.index}</div>
<h1>{service.name}</h1>
<p>{service.seo.description}</p>
</div>
</section>
<section class="section-pad">
<div class="container detail-grid">
<article class="detail-card" data-reveal>
<div class="detail-card__number">{service.index}</div>
<div>
<h2>服务方向</h2>
<p>{service.intro}</p>
<div class="detail-facts">
<div class="detail-fact"><span>适合人群</span><b>{service.audience}</b></div>
<div class="detail-fact"><span>核心价值</span><b>{service.value}</b></div>
</div>
<p class="detail-note">{service.note}</p>
</div>
</article>
</div>
</section>
<CTA title={`想进一步了解${service.name}?`} text="联系附近门店,说明你的目标与身体情况,由专业人员结合实际状态进行评估。" />
</main>
<Footer />
</Base>
......@@ -22,7 +22,7 @@ const jsonLd = [
name: service.name,
description: service.intro,
provider: { "@id": new URL("/#organization", Astro.site).href },
url: `${pageUrl}#${service.slug}`,
url: new URL(`/services/${service.slug}/`, Astro.site).href,
areaServed: "CN",
},
})),
......@@ -62,6 +62,7 @@ const jsonLd = [
<div class="detail-fact"><span>核心方向</span><b>{service.value}</b></div>
</div>
<p class="detail-note">{service.note}</p>
<a class="text-link" href={`/services/${service.slug}/`}>查看服务详情 <span>↗</span></a>
</div>
</article>
))}
......
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