312 lines
15 KiB
JavaScript
312 lines
15 KiB
JavaScript
/**
|
||
* SEO模板生成器
|
||
* 根据 useSEO.js 的页面配置自动生成静态 HTML 模板,供爬虫访问时返回
|
||
* 域名、站点名、默认 SEO 等与 src/composables/useSEO.js 一致,来自环境变量
|
||
*
|
||
* 必需(与前端构建一致):VITE_SITE_ORIGIN、VITE_SEO_SITE_NAME、VITE_SEO_TITLE、
|
||
* VITE_SEO_DESCRIPTION、VITE_SEO_KEYWORDS、VITE_SEO_OG_IMAGE
|
||
* 可选:SEO_BASE_URL 覆盖站点根 URL;VITE_SEO_ORG_DESCRIPTION 结构化数据中 Organization 描述;
|
||
* VITE_SHARE_DESC 作为 Organization 描述的后备
|
||
*
|
||
* pnpm build 时由 vite.config 中 loadEnv 注入
|
||
*/
|
||
|
||
const fs = require("fs");
|
||
const path = require("path");
|
||
|
||
function env(name, fallback = "") {
|
||
const v = process.env[name];
|
||
return v != null && String(v).trim() !== "" ? String(v).trim() : fallback;
|
||
}
|
||
|
||
const SITE_ORIGIN = env("SEO_BASE_URL") || env("VITE_SITE_ORIGIN");
|
||
const SEO_SITE_NAME = env("VITE_SEO_SITE_NAME");
|
||
const SEO_TITLE = env("VITE_SEO_TITLE");
|
||
const SEO_DESCRIPTION = env("VITE_SEO_DESCRIPTION");
|
||
const SEO_KEYWORDS = env("VITE_SEO_KEYWORDS");
|
||
const SEO_OG_IMAGE = env("VITE_SEO_OG_IMAGE");
|
||
const SEO_ORG_DESCRIPTION =
|
||
env("VITE_SEO_ORG_DESCRIPTION") ||
|
||
env("VITE_SHARE_DESC") ||
|
||
"专业大数据风险报告查询与代理平台,支持个人和企业多场景风控应用";
|
||
|
||
const missing = [];
|
||
if (!SITE_ORIGIN) missing.push("VITE_SITE_ORIGIN(或 SEO_BASE_URL)");
|
||
if (!SEO_SITE_NAME) missing.push("VITE_SEO_SITE_NAME");
|
||
if (!SEO_TITLE) missing.push("VITE_SEO_TITLE");
|
||
if (!SEO_DESCRIPTION) missing.push("VITE_SEO_DESCRIPTION");
|
||
if (!SEO_KEYWORDS) missing.push("VITE_SEO_KEYWORDS");
|
||
if (!SEO_OG_IMAGE) missing.push("VITE_SEO_OG_IMAGE");
|
||
if (missing.length) {
|
||
console.error(
|
||
"❌ 缺少环境变量: " + missing.join("、") + "\n请配置 .env 或通过 SEO_BASE_URL 等注入。",
|
||
);
|
||
process.exit(1);
|
||
}
|
||
|
||
/** 与 useSEO.js 中 routeConfigs 一致,文案中的站点名均用 SEO_SITE_NAME */
|
||
function buildPageSEOConfigs() {
|
||
const O = SITE_ORIGIN;
|
||
const N = SEO_SITE_NAME;
|
||
return {
|
||
"index.html": {
|
||
title: SEO_TITLE,
|
||
description: SEO_DESCRIPTION,
|
||
keywords: SEO_KEYWORDS,
|
||
url: O,
|
||
},
|
||
"historyQuery.html": {
|
||
title: `我的报告_历史查询记录_${N}`,
|
||
description: `${N}用户个人中心,查看及下载历史查询报告。`,
|
||
keywords: `我的报告,历史记录,${N}`,
|
||
url: `${O}/historyQuery`,
|
||
},
|
||
"inquire-personalData.html": {
|
||
title: `个人综合风险报告_个人履约评分_多维数据检测_${N}`,
|
||
description:
|
||
`${N}提供个人全维信用画像扫描。一键检测司法涉诉记录、历史履约情况、号码状态异常检测及个人消费等级。数据同步权威行业系统,采用银行级加密技术,保障信息安全,助您全面了解自身信用状况。`,
|
||
keywords:
|
||
"个人风险评估,信用状况评估,综合履约能力,司法风险自查,风险标签检测",
|
||
url: `${O}/inquire/personalData`,
|
||
},
|
||
"inquire-companyinfo.html": {
|
||
title: `企业信用风险评估_工商经营异常与司法诉讼核查_${N}`,
|
||
description:
|
||
`${N}提供全维度的企业商业画像。基于官方公示数据,核验目标企业的工商变更、经营异常名录、行政处罚、投融资背景及关联风险。深度评估商业履约能力,透视合作方真实状况,规避合同陷阱。`,
|
||
keywords:
|
||
"企业信用画像,公司经营风险,工商异常名录,企业履约评估,商业背景核验",
|
||
url: `${O}/inquire/companyinfo`,
|
||
},
|
||
"inquire-preloanbackgroundcheck.html": {
|
||
title: `综合履约评分检测_多平台履约记录分析_个人财务履约报告_${N}`,
|
||
description:
|
||
`${N}提供专业的个人履约健康度体检服务。基于多维大数据分析,检测您的综合评分波动、历史履约记录及潜在的风险标签。本服务旨在帮助用户优化个人数据画像,提升信用管理意识,不提供任何信贷金融服务。`,
|
||
keywords:
|
||
"综合评分检测,多重履约压力分析,履约能力评估,综合评分优化,个人数据画像",
|
||
url: `${O}/inquire/preloanbackgroundcheck`,
|
||
},
|
||
"inquire-marriage.html": {
|
||
title: `婚恋对象背景了解_司法诉讼与不良记录筛查_婚前风险评估_${N}`,
|
||
description:
|
||
`${N}提供专业的婚恋风险评估工具。基于合法的公开司法数据,深度排查法律诉讼记录、失信被执行信息及潜在的履约风险。数据来源合规,仅供个人防范参考,拒绝隐私泄露,为幸福保驾护航。`,
|
||
keywords:
|
||
"婚恋背景核验,婚前风险了解,司法记录核验,个人履约风险,背景核实",
|
||
url: `${O}/inquire/marriage`,
|
||
},
|
||
"inquire-backgroundcheck.html": {
|
||
title: `入职背景核验_候选人履历核验_职场信用与竞业排查_${N}`,
|
||
description:
|
||
`${N}职场风险报告助力企业构建防御体系。基于司法级大数据,一键筛查候选人司法涉诉记录、学历信息、商业利益冲突、历史违约记录及不良社会风险。官方数据,客观预警入职风险,辅助HR高效决策。`,
|
||
keywords:
|
||
"入职风险评估,候选人背景核验,简历真伪辨别,竞业限制核验,职场信用报告,员工风险预警",
|
||
url: `${O}/inquire/backgroundcheck`,
|
||
},
|
||
"inquire-homeservice.html": {
|
||
title: `家政人员背景核实_保姆月嫂司法风险筛查_用工安全评估_${N}`,
|
||
description:
|
||
`${N}家政风险报告,为家庭用人安全把关。通过合法大数据,快速筛查家政人员的司法诉讼、失信被执行记录及社会不良风险标签。有效识别潜在隐患,预防雇佣纠纷,让您找保姆、请月嫂更放心。`,
|
||
keywords:
|
||
"保姆背景核验,月嫂风险筛查,家政人员核验,育儿嫂背景核实,雇佣风险防范",
|
||
url: `${O}/inquire/homeservice`,
|
||
},
|
||
"agent.html": {
|
||
title: `${N}代理 - 免费开通代理权限 | 大数据风险报告代理`,
|
||
description:
|
||
`${N}代理平台,免费开通代理权限,享受大数据风险报告查询服务代理收益。专业的大数据风险报告、婚姻查询、个人信用评估等服务的代理合作。`,
|
||
keywords: `${N}代理, 免费代理, 大数据风险报告代理, 代理权限, 代理收益`,
|
||
url: `${O}/agent`,
|
||
},
|
||
"agent-promote.html": {
|
||
title: `生成推广码_自定义价格_0成本代理查价系统_${N}`,
|
||
description:
|
||
`${N}推广中心支持代理自主设置报告价格。在此选择报告类型,自定义客户查询价,0成本生成专属推广链接或二维码。差价即为利润,实现个人风险报告的私域流量变现。`,
|
||
keywords:
|
||
`推广码生成,自定义查价系统,代理收益工具,代理推广链接,流量变现平台,${N}推广`,
|
||
url: `${O}/agent/promote`,
|
||
},
|
||
"agent-invitation.html": {
|
||
title: `邀请合作伙伴_发展代理享收益_推广返利计划_${N}`,
|
||
description:
|
||
`${N}推出合伙人邀请奖励机制。邀请好友注册成为合作伙伴,每单不仅可获得推广收益,还可叠加合作伙伴活跃奖励及定价差额收益。打造专属推广团队,实现收益持续增长。`,
|
||
keywords:
|
||
"渠道合作伙伴,商业收益管理,流量合规变现,业务推广系统,代理后台",
|
||
url: `${O}/agent/invitation`,
|
||
},
|
||
"help.html": {
|
||
title: `${N}帮助中心_代理操作指南_推广收益计算常见问题`,
|
||
description:
|
||
`${N}帮助中心提供全方位的代理操作指引。包含如何成为代理、推广报告生成教程、收益与成本计算规则及推广效率提升方案。`,
|
||
keywords: `${N}教程,代理新手指南,推广收益计算,报告推广`,
|
||
url: `${O}/help`,
|
||
},
|
||
"help-guide.html": {
|
||
title: `使用指南 - ${N}操作教程 | 功能说明`,
|
||
description:
|
||
`${N}详细使用指南,包含各功能模块的操作教程、功能说明、注意事项等,让用户快速上手使用。`,
|
||
keywords: `使用指南, 操作教程, 功能说明, 快速上手, ${N}教程`,
|
||
url: `${O}/help/guide`,
|
||
},
|
||
"example.html": {
|
||
title: `示例报告 - ${N}报告展示 | 大数据风险报告样例`,
|
||
description:
|
||
`${N}示例报告展示,包含大数据风险报告、婚姻状况查询、个人信用评估等服务的报告样例,让用户了解报告内容和格式。`,
|
||
keywords: `示例报告, 报告展示, 报告样例, 大数据风险报告, 婚姻查询报告`,
|
||
url: `${O}/example`,
|
||
},
|
||
"service.html": {
|
||
title: `客服中心 - ${N}在线客服 | 技术支持`,
|
||
description:
|
||
`${N}客服中心,提供在线客服支持、技术咨询、问题反馈等服务,确保用户获得及时有效的帮助。`,
|
||
keywords: `客服中心, 在线客服, 技术支持, 问题反馈, ${N}客服`,
|
||
url: `${O}/service`,
|
||
},
|
||
};
|
||
}
|
||
|
||
const pageSEOConfigs = buildPageSEOConfigs();
|
||
|
||
/**
|
||
* 规范化文案:统一为中文标点,避免乱码
|
||
*/
|
||
function normalizeText(str) {
|
||
if (typeof str !== "string") return str;
|
||
return str
|
||
.replace(/\uFFFD/g, "")
|
||
.replace(/。/g, "。")
|
||
.replace(/、/g, "、");
|
||
}
|
||
|
||
/**
|
||
* 转义 HTML 属性值
|
||
*/
|
||
function escapeAttr(str) {
|
||
if (typeof str !== "string") return "";
|
||
return str
|
||
.replace(/&/g, "&")
|
||
.replace(/"/g, """)
|
||
.replace(/</g, "<")
|
||
.replace(/>/g, ">");
|
||
}
|
||
|
||
/**
|
||
* 生成单页 HTML 模板
|
||
*/
|
||
function generateHTMLTemplate(config) {
|
||
const title = normalizeText(config.title);
|
||
const description = normalizeText(config.description);
|
||
const keywords = normalizeText(config.keywords);
|
||
const orgUrl = `${SITE_ORIGIN.replace(/\/?$/, "/")}`;
|
||
const structuredData = {
|
||
"@context": "https://schema.org",
|
||
"@type": "WebPage",
|
||
name: title,
|
||
description: description,
|
||
url: config.url,
|
||
mainEntity: {
|
||
"@type": "Organization",
|
||
name: SEO_SITE_NAME,
|
||
url: orgUrl,
|
||
description: SEO_ORG_DESCRIPTION,
|
||
},
|
||
};
|
||
|
||
const ogImageMeta = SEO_OG_IMAGE
|
||
? `
|
||
<meta property="og:image" content="${escapeAttr(SEO_OG_IMAGE)}">
|
||
<meta name="twitter:image" content="${escapeAttr(SEO_OG_IMAGE)}">`
|
||
: "";
|
||
|
||
return `<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||
|
||
<title>${escapeAttr(title)}</title>
|
||
|
||
<meta name="description" content="${escapeAttr(description)}">
|
||
<meta name="keywords" content="${escapeAttr(keywords)}">
|
||
|
||
<meta property="og:title" content="${escapeAttr(title)}">
|
||
<meta property="og:description" content="${escapeAttr(description)}">
|
||
<meta property="og:url" content="${escapeAttr(config.url)}">
|
||
<meta property="og:type" content="website">
|
||
<meta property="og:site_name" content="${escapeAttr(SEO_SITE_NAME)}">
|
||
<meta property="og:locale" content="zh_CN">${ogImageMeta}
|
||
|
||
<meta name="twitter:card" content="summary">
|
||
<meta name="twitter:title" content="${escapeAttr(title)}">
|
||
<meta name="twitter:description" content="${escapeAttr(description)}">
|
||
<meta name="twitter:url" content="${escapeAttr(config.url)}">
|
||
|
||
<link rel="canonical" href="${escapeAttr(config.url)}">
|
||
|
||
<script type="application/ld+json">
|
||
${JSON.stringify(structuredData, null, 8)}
|
||
</script>
|
||
|
||
<meta name="robots" content="index, follow">
|
||
<meta name="googlebot" content="index, follow">
|
||
<meta name="baiduspider" content="index, follow">
|
||
|
||
<style>
|
||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; margin: 0; padding: 0; line-height: 1.6; }
|
||
.seo-content { max-width: 1200px; margin: 0 auto; padding: 20px; }
|
||
h1 { color: #333; }
|
||
p { color: #666; }
|
||
.redirect-notice { background: #fff3cd; border: 1px solid #ffc107; color: #856404; padding: 10px; margin: 20px 0; border-radius: 4px; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="seo-content">
|
||
<h1>${escapeAttr(title)}</h1>
|
||
<div class="redirect-notice">
|
||
<p>正在跳转到完整版网站...</p>
|
||
<p>如果浏览器没有自动跳转,请 <a href="${escapeAttr(config.url)}">点击这里</a></p>
|
||
</div>
|
||
<p>${escapeAttr(description)}</p>
|
||
<section>
|
||
<h2>关于${escapeAttr(SEO_SITE_NAME)}</h2>
|
||
<p>${escapeAttr(SEO_SITE_NAME)}提供背调报告代理加盟与个人风控系统搭建,合规数据服务平台。支持职场背调、家政背景核验、企业风控服务与数据报告分销。</p>
|
||
</section>
|
||
<section>
|
||
<h2>核心服务</h2>
|
||
<ul>
|
||
<li>个人综合风险报告与履约评分</li>
|
||
<li>企业信用风险评估与工商核查</li>
|
||
<li>综合履约评分与贷前风险检测</li>
|
||
<li>婚恋对象背景了解与婚前风险评估</li>
|
||
<li>入职背景核验与职场信用排查</li>
|
||
<li>家政人员背景核实与用工安全评估</li>
|
||
</ul>
|
||
</section>
|
||
</div>
|
||
</body>
|
||
</html>`;
|
||
}
|
||
|
||
function main() {
|
||
const outputDir = path.join(__dirname, "../public/seo-templates");
|
||
|
||
if (!fs.existsSync(outputDir)) {
|
||
fs.mkdirSync(outputDir, { recursive: true });
|
||
console.log(`✓ 创建模板目录: ${outputDir}`);
|
||
}
|
||
|
||
let successCount = 0;
|
||
Object.entries(pageSEOConfigs).forEach(([filename, config]) => {
|
||
const htmlContent = generateHTMLTemplate(config);
|
||
const filePath = path.join(outputDir, filename);
|
||
fs.writeFileSync(filePath, htmlContent, "utf-8");
|
||
console.log(`✓ 生成模板: ${filename}`);
|
||
successCount++;
|
||
});
|
||
|
||
console.log(`\n✓ 成功生成 ${successCount} 个 SEO 模板文件`);
|
||
console.log(`📁 模板目录: ${outputDir}`);
|
||
console.log(`💡 站点: ${SEO_SITE_NAME} | 域名: ${SITE_ORIGIN}`);
|
||
}
|
||
|
||
main();
|