Commit b8a11003 authored by xuchentao's avatar xuchentao

feat: 完善官网 SEO 与 GEO 能力

parent 3081fe57
Pipeline #453 passed with stage
in 16 seconds
......@@ -63,6 +63,6 @@ Content-Type: application/json
## SEO 与部署
项目包含 canonical、Open Graph、Organization/LocalBusiness/Service/FAQ/Article 结构化数据、sitemap、robots.txt、llms.txt 与 web manifest。正式域名配置在 `astro.config.mjs` 中;修改时需同时更新 `public/robots.txt` 中的 Sitemap。
项目包含网站、服务与资讯 TDK,canonical、智能 Open Graph/Twitter 摘要,Organization/LocalBusiness/WebSite/WebPage/Service/FAQ/Article 结构化数据,404 页面、ICO、sitemap、robots.txt、`llms.txt``llms-full.txt` 与 web manifest。服务详情页和资讯页会按内容自动生成独立标题、摘要与关键词。正式域名配置在 `astro.config.mjs` 中;修改时需同时更新 `public/robots.txt` 中的 Sitemap。
GitLab CI 将唯一的当前版本部署到 `/root/jiayun/current`,用户内容与生产环境配置保存在 `/root/jiayun/shared`。部署流程为替换当前代码、安装依赖、构建、重启与健康检查,不创建或清理历史发布目录,也不提供自动回退。`deploy/nginx/jiayun.conf` 是反向代理配置。
......@@ -7,7 +7,7 @@ const customOutDir = process.env.JIAYUN_BUILD_OUT_DIR;
export default defineConfig({
site: "http://jiayun.101-126-10-129.sslip.io",
integrations: [sitemap()],
integrations: [sitemap({ filter: (page) => !page.includes("/admin/") && !page.includes("/api/") })],
adapter: node({ mode: "standalone" }),
...(customOutDir ? { outDir: path.resolve(customOutDir) } : {}),
build: { format: "directory", inlineStylesheets: "auto" },
......
......@@ -6,5 +6,8 @@
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#ff7d41",
"lang": "zh-CN"
"lang": "zh-CN",
"icons": [
{ "src": "/favicon.ico", "sizes": "32x32", "type": "image/x-icon" }
]
}
---
import Base from "../layouts/Base.astro"; import Header from "./Header.astro"; import Footer from "./Footer.astro"; import ArticleListing from "./ArticleListing.astro"; import { site } from "../data/site"; import { getCategories, paginationWindow, type Article } from "../lib/articles";
import Base from "../layouts/Base.astro"; import Header from "./Header.astro"; import Footer from "./Footer.astro"; import ArticleListing from "./ArticleListing.astro"; import { site } from "../data/site"; import { getCategories, paginationWindow, type Article } from "../lib/articles"; import { normalizeKeywords } from "../lib/seo";
interface Props { items: Article[]; allItems: Article[]; categoryNames: string[]; currentPage: number; lastPage: number; activeName?: string; activeSlug?: string; }
const { items, allItems, categoryNames, currentPage, lastPage, activeName, activeSlug } = Astro.props;
const categories = getCategories(allItems, categoryNames); const heading = activeName || "资讯中心"; const title = `${heading}${currentPage > 1 ? ` 第${currentPage}页` : ""}|${site.brand.name}`; const description = activeName ? `${activeName}相关的嘉运业务、服务方案与网约车行业资讯。` : "了解嘉运汽车的业务服务、合作资质、司机支持方案与企业动态。";
const keywords = normalizeKeywords(activeName || "天津网约车资讯", "嘉运网约车", "司机服务", "租车买车");
const pageHref = (page: number) => activeSlug ? (page === 1 ? `/articles/topic/${activeSlug}/` : `/articles/topic/${activeSlug}/page/${page}/`) : (page === 1 ? "/articles/" : `/articles/page/${page}/`);
---
<Base title={title} description={description}><Header /><main id="main-content"><section class="page-hero journal-hero"><div class="container"><div class="breadcrumb"><a href="/">首页</a><span>/</span><a href="/articles/">资讯中心</a>{activeName && <><span>/</span>{activeName}</>}</div><p class="eyebrow"><span></span>JIAYUN NEWS</p><h1>{heading}</h1><p>了解嘉运的业务服务、合作动态与网约车行业相关信息。</p></div></section><section class="section journal-section"><div class="container"><nav class="category-nav" aria-label="资讯分类"><a href="/articles/" aria-current={!activeName ? "page" : undefined}>全部 <span>{allItems.length}</span></a>{categories.map((category) => <a href={`/articles/topic/${category.slug}/`} aria-current={activeName === category.name ? "page" : undefined}>{category.name} <span>{category.count}</span></a>)}</nav>{items.length ? <ArticleListing items={items} /> : <div class="empty-state"><h2>该分类还没有资讯</h2><p>后续内容会通过资讯中心后台持续更新。</p></div>}{lastPage > 1 && <nav class="pagination" aria-label="分页">{paginationWindow(currentPage, lastPage).map((page) => page === 0 ? <span>…</span> : <a href={pageHref(page)} aria-current={page === currentPage ? "page" : undefined}>{page}</a>)}</nav>}</div></section></main><Footer /></Base>
<Base title={title} description={description} keywords={keywords}><Header /><main id="main-content"><section class="page-hero journal-hero"><div class="container"><div class="breadcrumb"><a href="/">首页</a><span>/</span><a href="/articles/">资讯中心</a>{activeName && <><span>/</span>{activeName}</>}</div><p class="eyebrow"><span></span>JIAYUN NEWS</p><h1>{heading}</h1><p>了解嘉运的业务服务、合作动态与网约车行业相关信息。</p></div></section><section class="section journal-section"><div class="container"><nav class="category-nav" aria-label="资讯分类"><a href="/articles/" aria-current={!activeName ? "page" : undefined}>全部 <span>{allItems.length}</span></a>{categories.map((category) => <a href={`/articles/topic/${category.slug}/`} aria-current={activeName === category.name ? "page" : undefined}>{category.name} <span>{category.count}</span></a>)}</nav>{items.length ? <ArticleListing items={items} /> : <div class="empty-state"><h2>该分类还没有资讯</h2><p>后续内容会通过资讯中心后台持续更新。</p></div>}{lastPage > 1 && <nav class="pagination" aria-label="分页">{paginationWindow(currentPage, lastPage).map((page) => page === 0 ? <span>…</span> : <a href={pageHref(page)} aria-current={page === currentPage ? "page" : undefined}>{page}</a>)}</nav>}</div></section></main><Footer /></Base>
......@@ -2,6 +2,7 @@
import "../styles/global.css";
import logo from "../../白底黑字绿LOGO.svg";
import { site } from "../data/site";
import { buildOgSummary, normalizeKeywords } from "../lib/seo";
import ContactDialog from "../components/ContactDialog.astro";
interface Props {
......@@ -10,12 +11,18 @@ interface Props {
image?: string;
ogType?: "website" | "article";
noindex?: boolean;
keywords?: string | string[];
ogTitle?: string;
ogDescription?: string;
jsonLd?: Record<string, unknown> | Record<string, unknown>[];
}
const { title, description, image = "/og-cover.svg", ogType = "website", noindex = false, jsonLd = [] } = Astro.props;
const { title, description, image = "/og-cover.svg", ogType = "website", noindex = false, keywords, ogTitle, ogDescription, jsonLd = [] } = Astro.props;
const canonical = new URL(Astro.url.pathname, Astro.site).href;
const ogImage = new URL(image, Astro.site).href;
const metaKeywords = normalizeKeywords(keywords, title, site.brand.name, site.brand.industry);
const socialTitle = ogTitle || title;
const socialDescription = ogDescription || buildOgSummary(title, description);
const organizationLd = {
"@context": "https://schema.org",
"@type": ["AutoDealer", "LocalBusiness", "Organization"],
......@@ -38,7 +45,26 @@ const organizationLd = {
areaServed: { "@type": "City", name: "天津" },
};
const pageLd = Array.isArray(jsonLd) ? jsonLd : [jsonLd];
const allLd = [organizationLd, ...pageLd.filter((item) => Object.keys(item).length > 0)];
const websiteLd = {
"@context": "https://schema.org",
"@type": "WebSite",
"@id": new URL("/#website", Astro.site).href,
name: site.brand.name,
url: Astro.site?.href,
inLanguage: "zh-CN",
publisher: { "@id": new URL("/#organization", Astro.site).href },
};
const webpageLd = {
"@context": "https://schema.org",
"@type": "WebPage",
"@id": canonical,
url: canonical,
name: title,
description,
isPartOf: { "@id": new URL("/#website", Astro.site).href },
inLanguage: "zh-CN",
};
const allLd = [organizationLd, websiteLd, webpageLd, ...pageLd.filter((item) => Object.keys(item).length > 0)];
---
<!doctype html>
<html lang="zh-CN">
......@@ -51,18 +77,24 @@ const allLd = [organizationLd, ...pageLd.filter((item) => Object.keys(item).leng
<meta name="description" content={description} />
<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="/favicon.ico" sizes="32x32" />
<link rel="icon" href={logo.src} type="image/svg+xml" />
<link rel="manifest" href="/site.webmanifest" />
<link rel="alternate" hreflang="zh-CN" href={canonical} />
<link rel="alternate" hreflang="x-default" href={canonical} />
<meta property="og:type" content={ogType} />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta name="keywords" content={metaKeywords.join(",")} />
<meta property="og:title" content={socialTitle} />
<meta property="og:description" content={socialDescription} />
<meta property="og:url" content={canonical} />
<meta property="og:image" content={ogImage} />
<meta property="og:image:alt" content={socialTitle} />
<meta property="og:site_name" content={site.brand.name} />
<meta property="og:locale" content="zh_CN" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={socialTitle} />
<meta name="twitter:description" content={socialDescription} />
<meta name="twitter:image" content={ogImage} />
<script type="application/ld+json" set:html={JSON.stringify(allLd)} is:inline></script>
</head>
<body>
......
const SEPARATOR_RE = /[,,、||;;\s]+/;
/** Keep metadata compact and readable for search/social previews. */
export function truncateMeta(value: string, maxLength = 160): string {
const text = String(value || "").replace(/\s+/g, " ").trim();
if (text.length <= maxLength) return text;
return `${text.slice(0, Math.max(1, maxLength - 1)).trim()}…`;
}
/** Build a useful social preview summary when a page has no custom OG copy. */
export function buildOgSummary(title: string, description: string): string {
return truncateMeta(`${title}${description}`, 160);
}
/** Normalize keyword input and remove duplicates while preserving author order. */
export function normalizeKeywords(...values: Array<string | string[] | undefined>): string[] {
const keywords = values
.flatMap((value) => Array.isArray(value) ? value : [value])
.flatMap((value) => String(value || "").split(SEPARATOR_RE))
.map((value) => value.trim())
.filter((value) => /[\p{L}\p{N}]/u.test(value));
return [...new Set(keywords)].slice(0, 20);
}
---
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 { getPublishedArticle, getPublishedArticles } from "../../lib/article-store"; import { categoryToSlug } from "../../lib/articles"; import { site } from "../../data/site";
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 { getPublishedArticle, getPublishedArticles } from "../../lib/article-store"; import { categoryToSlug } from "../../lib/articles"; import { normalizeKeywords } from "../../lib/seo"; import { site } from "../../data/site";
export async function getStaticPaths() { const articles = await getPublishedArticles(); return articles.map((article) => ({ params: { slug: article.data.slug } })); }
const article = await getPublishedArticle(Astro.params.slug || ""); if (!article) return Astro.redirect("/404/");
const title = `${article.data.title}|资讯中心|${site.brand.name}`; const articleUrl = new URL(Astro.url.pathname, Astro.site).href;
const keywords = normalizeKeywords(article.data.title, article.data.category, "天津网约车", "嘉运网约车");
const jsonLd = { "@context": "https://schema.org", "@type": "Article", headline: article.data.title, description: article.data.excerpt, datePublished: article.data.date.toISOString(), dateModified: article.data.updated.toISOString(), author: { "@type": "Organization", name: article.data.author }, publisher: { "@id": new URL("/#organization", Astro.site).href }, mainEntityOfPage: articleUrl };
---
<Base title={title} description={article.data.excerpt} ogType="article" jsonLd={jsonLd}><Header /><main id="main-content"><article class="article-page"><header><div class="container article-header"><div class="breadcrumb"><a href="/">首页</a><span>/</span><a href="/articles/">资讯中心</a><span>/</span><a href={`/articles/topic/${categoryToSlug(article.data.category)}/`}>{article.data.category}</a></div><span class="article-category">{article.data.category}</span><h1>{article.data.title}</h1><div class="article-byline"><span>{article.data.author}</span><time datetime={article.data.date.toISOString()}>发布于 {article.data.date.toISOString().slice(0, 10)}</time>{article.data.updated.valueOf() !== article.data.date.valueOf() && <time datetime={article.data.updated.toISOString()}>更新于 {article.data.updated.toISOString().slice(0, 10)}</time>}</div></div></header><div class="container article-layout"><div class="article-content" set:html={article.html}></div><aside><strong>温馨提示</strong><p>平台规则、办证要求与车型政策可能调整,请以主管部门、平台与门店实时信息为准。</p><a href="/articles/">← 返回资讯中心</a></aside></div></article><CTA title="想进一步了解嘉运的服务" text="欢迎电话咨询或到店沟通我们会结合你的实际情况介绍适合的服务方案" /></main><Footer /></Base>
<Base title={title} description={article.data.excerpt} keywords={keywords} ogType="article" jsonLd={jsonLd}><Header /><main id="main-content"><article class="article-page"><header><div class="container article-header"><div class="breadcrumb"><a href="/">首页</a><span>/</span><a href="/articles/">资讯中心</a><span>/</span><a href={`/articles/topic/${categoryToSlug(article.data.category)}/`}>{article.data.category}</a></div><span class="article-category">{article.data.category}</span><h1>{article.data.title}</h1><div class="article-byline"><span>{article.data.author}</span><time datetime={article.data.date.toISOString()}>发布于 {article.data.date.toISOString().slice(0, 10)}</time>{article.data.updated.valueOf() !== article.data.date.valueOf() && <time datetime={article.data.updated.toISOString()}>更新于 {article.data.updated.toISOString().slice(0, 10)}</time>}</div></div></header><div class="container article-layout"><div class="article-content" set:html={article.html}></div><aside><strong>温馨提示</strong><p>平台规则、办证要求与车型政策可能调整,请以主管部门、平台与门店实时信息为准。</p><a href="/articles/">← 返回资讯中心</a></aside></div></article><CTA title="想进一步了解嘉运的服务" text="欢迎电话咨询或到店沟通我们会结合你的实际情况介绍适合的服务方案" /></main><Footer /></Base>
import type { APIRoute } from "astro";
function makeFavicon(): Uint8Array {
const size = 32;
const pixelBytes = size * size * 4;
const maskBytes = size * 4;
const imageBytes = 40 + pixelBytes + maskBytes;
const bytes = new Uint8Array(22 + imageBytes);
const view = new DataView(bytes.buffer);
view.setUint16(2, 1, true); // ICO type
view.setUint16(4, 1, true); // image count
bytes[6] = size;
bytes[7] = size;
view.setUint16(10, 1, true);
view.setUint16(12, 32, true);
view.setUint32(14, imageBytes, true);
view.setUint32(18, 22, true);
view.setUint32(22, 40, true); // BITMAPINFOHEADER
view.setInt32(26, size, true);
view.setInt32(30, size * 2, true); // XOR + AND mask heights
view.setUint16(34, 1, true);
view.setUint16(36, 32, true);
view.setUint32(42, pixelBytes, true);
const pixelOffset = 62;
for (let y = 0; y < size; y++) {
for (let x = 0; x < size; x++) {
const offset = pixelOffset + ((size - 1 - y) * size + x) * 4;
const roundedCorner = (x < 3 || x > 28) && (y < 3 || y > 28);
const jMark = (x >= 18 && x <= 22 && y >= 7 && y <= 22) ||
(x >= 10 && x <= 22 && y >= 7 && y <= 11) ||
(x >= 9 && x <= 18 && y >= 20 && y <= 24);
bytes[offset] = jMark ? 255 : 0;
bytes[offset + 1] = jMark ? 255 : 183;
bytes[offset + 2] = jMark ? 255 : 0;
bytes[offset + 3] = roundedCorner ? 0 : 255;
}
}
return bytes;
}
const favicon = makeFavicon();
export const prerender = true;
export const GET: APIRoute = () => new Response(favicon.buffer as ArrayBuffer, {
headers: {
"Content-Type": "image/x-icon",
"Cache-Control": "public, max-age=31536000, immutable",
},
});
......@@ -9,9 +9,10 @@ import { site } from "../data/site";
const title = `${site.brand.name}|天津网约车租售、办证与司机服务`;
const description = site.home.subhead;
const keywords = ["天津网约车", "网约车租车", "网约车买车", "网约车办证", "司机招募", ...site.services.map((service) => service.name)];
const serviceLd = site.services.map((service) => ({ "@type": "Service", name: service.name, description: service.intro, provider: { "@id": new URL("/#organization", Astro.site).href }, areaServed: "天津市" }));
---
<Base title={title} description={description} jsonLd={{ "@context": "https://schema.org", "@type": "ItemList", itemListElement: serviceLd.map((item, index) => ({ "@type": "ListItem", position: index + 1, item })) }}>
<Base title={title} description={description} keywords={keywords} jsonLd={{ "@context": "https://schema.org", "@type": "ItemList", itemListElement: serviceLd.map((item, index) => ({ "@type": "ListItem", position: index + 1, item })) }}>
<Header />
<main id="main-content">
<section class="hero">
......@@ -31,7 +32,7 @@ const serviceLd = site.services.map((service) => ({ "@type": "Service", name: se
<section class="section services-preview" id="services">
<div class="container">
<SectionHeading eyebrow="ONE-STOP SERVICE" title="从选车到上岗,一站解决跑车准备" intro="把原本需要多处奔波的租售、办证、平台注册与培训,放进同一套服务流程。" />
<div class="service-grid">{site.services.map((service) => <a class="service-card" href={`/services/#${service.slug}`} data-reveal><div class="service-card__icon"><Icon name={service.icon} size={28} /></div><span class="service-card__index">{service.index}</span><h3>{service.name}</h3><p>{service.intro}</p><span class="text-link">了解详情 <Icon name="arrow" size={17} /></span></a>)}</div>
<div class="service-grid">{site.services.map((service) => <a class="service-card" href={`/services/${service.slug}/`} data-reveal><div class="service-card__icon"><Icon name={service.icon} size={28} /></div><span class="service-card__index">{service.index}</span><h3>{service.name}</h3><p>{service.intro}</p><span class="text-link">了解详情 <Icon name="arrow" size={17} /></span></a>)}</div>
</div>
</section>
......
---
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 SectionHeading from "../../components/SectionHeading.astro"; import Icon from "../../components/Icon.astro"; import { site } from "../../data/site";
const title = `天津网约车司机招募|${site.brand.name}`; const description = "嘉运面向天津全职、兼职网约车司机提供车辆租售、合规办证、平台注册、免费岗前培训与专属运营顾问服务。";
const keywords = ["天津网约车司机招募", "网约车入行", "网约车租车", "网约车办证", "司机培训"];
---
<Base title={title} description={description}><Header /><main id="main-content"><section class="join-hero"><div class="container join-hero__inner"><div><p class="eyebrow"><span></span>DRIVER RECRUITMENT</p><h1>想在天津跑网约车?<br /><em>从这里稳稳起步</em></h1><p>全职、兼职都可以聊。车型怎么选、平台怎么配、证件怎么办,嘉运按你的实际情况逐项梳理。</p><div class="hero-actions"><a class="button" href={`tel:${site.contact.tel}`}><Icon name="phone" size={19} />咨询入行方案</a><a class="button button--secondary" href="#fit">看看是否适合</a></div></div><div class="join-card"><span>新人入行支持</span><ul><li><Icon name="check" size={18} />多种租售方案</li><li><Icon name="check" size={18} />人证车证协助</li><li><Icon name="check" size={18} />主流平台对接</li><li><Icon name="check" size={18} />免费岗前培训</li><li><Icon name="check" size={18} />专属顾问跟进</li></ul><small>优惠与扶持政策以门店当期方案为准</small></div></div></section>
<Base title={title} description={description} keywords={keywords}><Header /><main id="main-content"><section class="join-hero"><div class="container join-hero__inner"><div><p class="eyebrow"><span></span>DRIVER RECRUITMENT</p><h1>想在天津跑网约车?<br /><em>从这里稳稳起步</em></h1><p>全职、兼职都可以聊。车型怎么选、平台怎么配、证件怎么办,嘉运按你的实际情况逐项梳理。</p><div class="hero-actions"><a class="button" href={`tel:${site.contact.tel}`}><Icon name="phone" size={19} />咨询入行方案</a><a class="button button--secondary" href="#fit">看看是否适合</a></div></div><div class="join-card"><span>新人入行支持</span><ul><li><Icon name="check" size={18} />多种租售方案</li><li><Icon name="check" size={18} />人证车证协助</li><li><Icon name="check" size={18} />主流平台对接</li><li><Icon name="check" size={18} />免费岗前培训</li><li><Icon name="check" size={18} />专属顾问跟进</li></ul><small>优惠与扶持政策以门店当期方案为准</small></div></div></section>
<section class="section" id="fit"><div class="container"><SectionHeading eyebrow="IS IT FOR YOU" title="这几类人,都可以来嘉运聊聊" intro="先了解实际情况,再判断适合购车、租车,还是先从短周期方案起步。" /><div class="fit-grid"><div><strong>01</strong><h3>零经验新人</h3><p>对平台、车型和证照流程不熟,希望有人全程带着入行。</p></div><div><strong>02</strong><h3>兼职司机</h3><p>需要根据可投入时间,评估更灵活的车辆与平台组合。</p></div><div><strong>03</strong><h3>全职司机</h3><p>关注长期成本、车辆稳定性和后续运营支持。</p></div><div><strong>04</strong><h3>个人与小车队</h3><p>需要采购或租赁新能源营运车辆,并统一衔接平台服务。</p></div></div></div></section>
<section class="section join-support" id="support"><div class="container join-support__layout"><div><p class="eyebrow"><span></span>FULL SUPPORT</p><h2>上岗不是终点,<br />后续有人跟进才安心</h2><p>嘉运提供从岗前准备到日常运营的持续支持,遇到平台注册、账号或车辆问题,可以联系专属顾问与线下门店。</p><a class="button button--secondary" href="/services/">查看全部服务 <Icon name="arrow" size={18} /></a></div><div class="support-stack">{site.process.map((item) => <div><span>{item.step}</span><div><h3>{item.title}</h3><p>{item.desc}</p></div></div>)}</div></div></section>
<section class="section income-note" id="income"><div class="container"><Icon name="shield" size={28} /><div><h2>先算清成本,再谈跑车计划</h2><p>网约车收入会受到在线时长、接单效率、平台规则、市场供需和车辆成本等多种因素影响。嘉运不作绝对收益承诺,建议到店结合个人情况核算。</p></div></div></section><CTA title="告诉我们你的时间与预算,匹配一套入行方案" /></main><Footer /></Base>
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: siteUrl }) => {
const base = (siteUrl?.href || "http://jiayun.101-126-10-129.sslip.io/").replace(/\/$/, "");
const articles = await getPublishedArticles();
const pages = [
[`首页`, "/"], [`服务方案`, "/services/"], [`司机招募`, "/join/"],
[`资讯中心`, "/articles/"], [`关于嘉运`, "/about/"], [`常见问题`, "/faq/"], [`联系我们`, "/contact/"],
];
const body = [
`# ${site.brand.name}(完整站点资料)`,
`\n> ${site.brand.tagline}`,
`\n${site.home.subhead}`,
`\n## 品牌与联系方式`,
`- 主体:${site.brand.legalName}`,
`- 电话:${site.contact.phone}`,
`- 地址:${site.contact.address}`,
`- 服务时间:${site.contact.serviceHours}`,
`\n## 核心服务`,
site.services.map((item) => `### ${item.name}\n${item.intro}\n适合对象:${item.audience}\n核心价值:${item.value}\n${base}/services/${item.slug}/`).join("\n\n"),
`\n## 页面索引`,
pages.map(([label, path]) => `- ${label}${base}${path}`).join("\n"),
`\n## 已发布资讯`,
articles.length ? articles.map((article) => `### ${article.data.title}\n${article.data.excerpt}\n${base}/articles/${article.data.slug}/\n\n${article.body}`).join("\n\n---\n\n") : "暂无已发布资讯。",
`\n## 信息边界`,
`- 平台规则、准入条件、车型价格与优惠政策以主管部门、平台及门店实时信息为准。`,
`- 嘉运不对网约车营收作绝对承诺。`,
].join("\n");
return new Response(body, { headers: { "Content-Type": "text/plain; charset=utf-8" } });
};
......@@ -3,6 +3,6 @@ import { site } from "../data/site";
export const prerender = true;
export const GET: APIRoute = ({ site: siteUrl }) => {
const base = (siteUrl?.href || "http://jiayun.101-126-10-129.sslip.io/").replace(/\/$/, "");
const body = `# ${site.brand.name}\n\n> ${site.brand.tagline}\n\n${site.home.subhead}\n\n## 核心服务\n${site.services.map((item) => `- ${item.name}${item.intro}`).join("\n")}\n\n## 联系方式\n- 电话:${site.contact.phone}\n- 地址:${site.contact.address}\n- 服务时间:${site.contact.serviceHours}\n\n## 重要页面\n- 首页:${base}/\n- 服务方案:${base}/services/\n- 司机招募:${base}/join/\n- 资讯中心:${base}/articles/\n- 关于嘉运:${base}/about/\n- 常见问题:${base}/faq/\n- 联系我们:${base}/contact/\n\n## 信息边界\n- 平台规则、准入条件、车型价格与优惠政策以实时信息为准。\n- 嘉运不对网约车营收作绝对承诺。\n`;
const body = `# ${site.brand.name}\n\n> ${site.brand.tagline}\n\n${site.home.subhead}\n\n## 核心服务\n${site.services.map((item) => `- ${item.name}${item.intro}`).join("\n")}\n\n## 联系方式\n- 电话:${site.contact.phone}\n- 地址:${site.contact.address}\n- 服务时间:${site.contact.serviceHours}\n\n## 重要页面\n- 完整 AI 资料:${base}/llms-full.txt\n- 首页:${base}/\n- 服务方案:${base}/services/\n- 司机招募:${base}/join/\n- 资讯中心:${base}/articles/\n- 关于嘉运:${base}/about/\n- 常见问题:${base}/faq/\n- 联系我们:${base}/contact/\n\n## 信息边界\n- 平台规则、准入条件、车型价格与优惠政策以实时信息为准。\n- 嘉运不对网约车营收作绝对承诺。\n`;
return new Response(body, { 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 Icon from "../../components/Icon.astro";
import { site } from "../../data/site";
export function getStaticPaths() {
return site.services.map((service) => ({ params: { slug: service.slug }, props: { service } }));
}
const { service } = Astro.props;
const title = `${service.name}|天津网约车服务|${site.brand.name}`;
const description = `${service.intro}${service.value}。${service.note}`;
const keywords = [service.name, service.audience, service.value, "天津网约车", site.brand.name];
const serviceUrl = new URL(Astro.url.pathname, Astro.site).href;
const jsonLd = [
{
"@context": "https://schema.org",
"@type": "Service",
"@id": serviceUrl,
name: service.name,
description: service.intro,
provider: { "@id": new URL("/#organization", Astro.site).href },
areaServed: { "@type": "City", name: "天津" },
audience: { "@type": "Audience", audienceType: service.audience },
url: serviceUrl,
},
{
"@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: service.name, item: serviceUrl },
],
},
];
---
<Base title={title} description={description} keywords={keywords} jsonLd={jsonLd}>
<Header />
<main id="main-content">
<section class="page-hero">
<div class="container">
<div class="breadcrumb"><a href="/">首页</a><span>/</span><a href="/services/">服务方案</a><span>/</span>{service.name}</div>
<p class="eyebrow"><span></span>SERVICE {service.index}</p>
<h1>{service.name}</h1>
<p>{service.value}</p>
</div>
</section>
<section class="section">
<div class="container service-detail-list">
<article class="service-detail">
<div class="service-detail__summary">
<span>SERVICE {service.index}</span>
<h2>{service.name}</h2>
<p>{service.value}</p>
<dl><div><dt>服务对象</dt><dd>{service.audience}</dd></div><div><dt>服务区域</dt><dd>天津市</dd></div></dl>
</div>
<div class="service-detail__copy">
<p class="eyebrow"><span></span>JIAYUN SERVICE</p>
<h2>服务内容</h2>
<p class="large-copy">{service.intro}</p>
<dl><div><dt>适合谁</dt><dd>{service.audience}</dd></div><div><dt>核心价值</dt><dd>{service.value}</dd></div></dl>
<p class="service-note"><Icon name="check" size={17} />{service.note}</p>
<a class="button" href={`tel:${site.contact.tel}`}><Icon name="phone" size={18} />咨询这项服务</a>
</div>
</article>
</div>
</section>
<CTA title={`想了解${service.name}?`} text="告诉我们你的预算、时间安排和当前情况,我们会结合实际需求介绍对应方案。" />
</main>
<Footer />
</Base>
---
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 SectionHeading from "../../components/SectionHeading.astro"; import Icon from "../../components/Icon.astro"; import { site } from "../../data/site";
const title = `服务方案|${site.brand.name}`; const description = "嘉运网约车提供新能源车辆销售与租赁、合规证照代办、平台入驻、司机培训、运营指导、售后维保与道路救援。";
const keywords = site.services.flatMap((service) => [service.name, service.audience]);
---
<Base title={title} description={description}>
<Base title={title} description={description} keywords={keywords}>
<Header /><main id="main-content"><section class="page-hero"><div class="container"><div class="breadcrumb"><a href="/">首页</a><span>/</span>服务方案</div><p class="eyebrow"><span></span>OUR SERVICES</p><h1>跑网约车需要的,<br /><em>一站配齐</em></h1><p>不只给一台车,更从合规、平台与运营角度,帮你规划完整的跑车方案。</p></div></section>
<section class="section"><div class="container service-detail-list">{site.services.map((service, index) => <article class:list={["service-detail", index % 2 && "service-detail--reverse"]} id={service.slug} data-reveal><div class="service-detail__summary"><span>SERVICE {service.index}</span><h3>{service.name}</h3><p>{service.value}</p><dl><div><dt>服务对象</dt><dd>{service.audience}</dd></div><div><dt>服务方式</dt><dd>到店沟通后按实际情况匹配方案</dd></div></dl></div><div class="service-detail__copy"><p class="eyebrow"><span></span>SERVICE {service.index}</p><h2>{service.name}</h2><p class="large-copy">{service.intro}</p><dl><div><dt>适合谁</dt><dd>{service.audience}</dd></div><div><dt>核心价值</dt><dd>{service.value}</dd></div></dl><p class="service-note"><Icon name="check" size={17} />{service.note}</p><a class="button button--secondary" href={`tel:${site.contact.tel}`}>咨询这项服务 <Icon name="arrow" size={18} /></a></div></article>)}</div></section>
<section class="section process-section"><div class="container"><SectionHeading eyebrow="SERVICE FLOW" title="你的情况不同,方案也应该不同" intro="从沟通到上岗,每一步都有明确的下一步。" align="center" /><div class="process-grid">{site.process.map((item) => <div class="process-card"><span>{item.step}</span><h3>{item.title}</h3><p>{item.desc}</p></div>)}</div></div></section><CTA /></main><Footer />
......
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