b1f8fbb6e4
Anchor queries boost retrieval for diplomacy, finance, and AI headlines; inline conversion now reads news002 wc/we/wt weather cards so drafts show city forecasts again. Co-authored-by: Cursor <cursoragent@cursor.com>
609 lines
39 KiB
JavaScript
609 lines
39 KiB
JavaScript
/**
|
||
* 每日新闻早报模板注册表。
|
||
*
|
||
* news001:2026-09-18 定稿版式(固定 23 栏目 + 定额卡片 + 无配图),已冻结,只做兼容保留。
|
||
* news002:订阅向改版(事件唯一化 + 每条配图 + 报头版式 + 深色模式)。
|
||
*
|
||
* 本文件不得 import 其它业务模块,避免与 wechat-news-morning-draft.mjs 形成循环依赖。
|
||
*/
|
||
|
||
export const NEWS_MORNING_TEMPLATE_REFERENCE_URL =
|
||
'https://m.tkmind.cn/MindSpace/a70ff537-8908-486e-9b6c-042e07cc25db/public/daily-news-0918.html';
|
||
|
||
/* ------------------------------------------------------------------ *
|
||
* news001(冻结)
|
||
* ------------------------------------------------------------------ */
|
||
|
||
export const NEWS001_VERSION = '2026-09-18-refined';
|
||
|
||
export const NEWS001_SEO_SPEC = [
|
||
'【SEO / GEO 必做】',
|
||
'1. `<title>`:`每日新闻早报 · {YYYY年M月D日} · TKMind`。',
|
||
'2. `<meta name="description">`:80~160 字,含日期 + 当日 3~5 条核心标题关键词(国内/国际/财经/科技至少各 1 个)。',
|
||
'3. `<meta name="mindspace-geo">` JSON:summary(120~180 字)、keywords 数组(8~12 个:新闻/日期/国内/国际/财经/科技/体育/城市名/英文主题词)、faq 至少 3 组 q/a(分别覆盖国内、国际财经、科技体育)。',
|
||
'4. 每个 `.section-title` 内标题文字必须用 `<h2>` 包裹(保留 icon span),便于搜索引擎理解栏目层级。',
|
||
'5. 每条 card 的 `<h3>` 标题须含具体事件关键词,禁止「暂无」「未能获取」等空词。',
|
||
'6. footer 注明整理时间与主要来源域名,便于 GEO 引用。',
|
||
].join('\n');
|
||
|
||
/** 生产 daily-news-0918 定稿版式:div.section + 全渠道栏目 + SEO。 */
|
||
export const NEWS001_TEMPLATE_SPEC = [
|
||
'生成「每日新闻早报」静态 HTML 页面(daily-news-MMDD),版式必须与生产参照页完全一致(仅 HTML/CSS 结构,禁止复制其正文):',
|
||
`参照页:${NEWS_MORNING_TEMPLATE_REFERENCE_URL}`,
|
||
'写页前可先 read_file 打开同目录最近 daily-news-*.html 或上述 URL 对应文件,对照 CSS 与 DOM 骨架;正文条目必须全部来自当日检索。',
|
||
'1. 外层:`.wrap` > `.hero`(`.hero-content` 内 h1「📰 每日新闻早报」、`.date-badge`、`.subtitle` 一句主编导语)> `.container`;禁止 `.stats` 或「X条资讯 · Y个栏目」统计行。',
|
||
'2. quick-links 固定顺序:📰 要闻|🌤️ 天气|🌍 国际|🇨🇳 国内|💰 财经|🤖 科技|🏆 体育|🔎 Google|𝕏 X|🌏 亚太|⛽ 能源|🧠 AI芯片|⚖️ 政策|🎓 高校|📈 全球股市|💬 社区|🔬 科学|🎬 文娱|🚀 创投|₿ 加密|🛡️ 地缘|🏙️ 城市|📡 权威快讯|📚 小知识|📅 历史(href 对应下方 id);禁止 id=hot / 🔥 热搜。',
|
||
'3. 区块容器必须用 `<div class="section" id="...">`(禁止 `<section id>`);`.section-title` 含 `<span class="icon">` emoji,标题文字用 `<h2>` 包裹。',
|
||
'3b. 视觉精细化:`.card` 左侧 3px 色条(tag 色系);`.card.highlight-box` 渐变底+白字;`.quick-links` 横向滚动+浅灰底圆角;hero 保留渐变与 date-badge;整体留白充足、字号层级清晰(h1 28px / h2 18px / h3 16px / p 14px)。',
|
||
'4. 区块 id 与内容:',
|
||
' - id=headlines:「今日要闻 · {完整日期}」;至少 3 条 `.card.highlight-box`(tag + h3 + p + source),标题 15~28 字、前 10 字有钩子。',
|
||
' - id=weather:「今日天气 · {月日}」;`.weather-today` + `.weather-grid` 8~12 张 `.weather-card`;热门城市(北京、上海、广州、深圳、成都、杭州、西安、重庆、武汉、南京、厦门、三亚等),禁止用区域预报顶替城市卡。',
|
||
' - id=world / domestic / finance / tech / sports:各 3~4 条 card。',
|
||
' - id=google:「🔎 Google 热点 · Global Headlines」;2 条 card,标题可英文,p 用中文摘要。',
|
||
' - id=x:「𝕏 X 平台热点 · Trending」;2 条 card,X/Twitter 热议或媒体转述。',
|
||
' - id=asia_pacific:「🌏 亚太观察 · Asia-Pacific」;2 条 card,日韩台港/东盟视角。',
|
||
' - id=commodities:「⛽ 能源大宗 · Commodities」;2 条 card,原油/黄金/铜/OPEC 等。',
|
||
' - id=ai_chips:「🧠 AI & 芯片 · Tech Deep」;2 条 card,大模型/半导体/NVIDIA/出口管制等。',
|
||
' - id=policy:「⚖️ 政策监管 · Policy Watch」;2 条 card,中外监管与重大政策。',
|
||
' - id=universities:「🎓 全球高校 · Research & Campus」;2 条 card,高校科研/校园新闻,标题可英文。',
|
||
' - id=global_markets:「📈 全球股市 · Markets」;2 条 card,美股/欧股/日股/港股等。',
|
||
' - id=community:「💬 社区热议 · Reddit/HN」;2 条 card,Reddit/Hacker News 热议经媒体转述。',
|
||
' - id=science:「🔬 科学前沿 · Science」;2 条 card,科研/太空/医学突破。',
|
||
' - id=culture:「🎬 文娱动态 · Culture」;2 条 card,影视/音乐/游戏行业新闻(禁八卦绯闻)。',
|
||
' - id=startup:「🚀 创投速递 · Startup」;2 条 card,融资/IPO/并购。',
|
||
' - id=crypto:「₿ 加密货币 · Crypto」;0~2 条 card,仅当 BTC/ETH/监管有重大波动时写,否则可省略本 section。',
|
||
' - id=geopolitics:「🛡️ 地缘军事 · Geopolitics」;2 条 card,冲突/制裁/外交/防务(与 id=world 去重)。',
|
||
' - id=local_life:「🏙️ 城市动态 · Cities」;2 条 card,北上广深成渝杭西等主要城市民生/交通/政策。',
|
||
' - id=official_feeds:「📡 权威快讯 · Wire Services」;2 条 card,Reuters/BBC/AP/NPR 等通讯社快讯,标题可英文。',
|
||
' - id=knowledge:「📚 知识小卡片」;1~2 条短知识 card。',
|
||
' - id=history:「📅 近7天新闻早报」;列出近 7 天 daily-news-MMDD.html 链接。',
|
||
'5. 每条 card:span.tag + h3 + p(2~4 句)+ div.source。',
|
||
'6. footer:新闻来源列表 + 整理时间 + TKMind 品牌;必须含 title、description、mindspace-cover、mindspace-geo;生成 daily-news-MMDD.thumbnail.png。',
|
||
'7. 推送微信草稿时转为内联 style 版式(微信不支持 <style> 标签),禁止整页长图;底部追加统一「阅读原文 + 二维码」layout;公众号侧 history/knowledge 可略短。',
|
||
'8. 英文栏目允许英文标题或中英并列,但每条必须有中文可读摘要,禁止整段纯英文正文。',
|
||
'9. 全页去重(必须):同一 URL、同一核心事件只允许出现一次;若已在 headlines/world/domestic/finance/tech 出现,扩展栏目不得重复同角度,可换角度或跳过;写页前合并预取+tkmind_search+web_search 后再去重。',
|
||
].join('\n');
|
||
|
||
/** 每日正文必须新搜,禁止把历史早报当新闻来源。 */
|
||
export const NEWS001_FRESH_CONTENT_SPEC = [
|
||
'内容硬约束(必须遵守,优先于版式):',
|
||
'1. 检索必须双路:先 load_skill → web,再同一轮调用 tkmind_search(type=news) 与 web_search,合并去重后写页;禁止只靠 web_search。',
|
||
'2. 覆盖面至少包括:国内、国际、财经、科技、体育、天气,以及 Google/X/亚太/能源/AI芯片/政策/高校/全球股市/社区/科学/文娱/创投/加密(有则写)/地缘/城市/权威快讯等扩展栏目;不得凭记忆编造;禁止单独「热搜/热门事件」栏目(原占位分给其它栏目)。',
|
||
'3. 若附有「专用联网搜索预取」,必须优先从中选题并写入对应栏目;预取有结果时禁止输出「未能获取」类空页。',
|
||
'4. 若 tkmind_search / web_search 暂时不可用,但预取 brief 已有条目:仍必须用预取结果写满各栏目,并在 source 标注预取来源 URL。',
|
||
'5. 仅当预取 brief 各路均为空且双路搜索均失败时,才允许写「检索暂不可用」说明页;禁止用历史 daily-news 正文充数。',
|
||
'6. 标题规则:中文标题 15~28 字;英文栏目标题可英文或中英并列;每条必须可核验并写来源 URL。',
|
||
'7. 扩展英文栏目:优先用预取 brief 对应分组,可保留英文标题,正文用 2~4 句中文摘要。',
|
||
'8. 全页去重:同一事件(如「美联储加息」「Fed rate hike」)只保留一条;主栏目优先,扩展栏目必须不同角度或跳过;禁止复制粘贴同段摘要。',
|
||
'9. 禁止复制、改写历史 daily-news-*.html 的正文;旧稿仅作 HTML 结构参考。',
|
||
].join('\n');
|
||
|
||
/* ------------------------------------------------------------------ *
|
||
* news002
|
||
* ------------------------------------------------------------------ */
|
||
|
||
export const NEWS002_VERSION = '2026-09-21-subscriber';
|
||
|
||
/**
|
||
* news002 栏目表。
|
||
* tier=core 的栏目每天必出;tier=flex 的栏目只有拿到「当天未被使用过的独立事件」时才输出,
|
||
* 拿不到就整节省略 —— 这是 news001 灌水重复的根因修复。
|
||
*/
|
||
export const NEWS002_SECTIONS = [
|
||
{ id: 'brief', label: '一分钟速读', icon: '⚡', tier: 'core', min: 6, max: 10 },
|
||
{ id: 'headlines', label: '今日头条', icon: '📰', tier: 'core', min: 3, max: 4 },
|
||
{ id: 'deepdive', label: '深读一条', icon: '🔍', tier: 'core', min: 1, max: 1 },
|
||
{ id: 'domestic', label: '国内', icon: '🇨🇳', tier: 'core', min: 2, max: 4 },
|
||
{ id: 'world', label: '国际', icon: '🌍', tier: 'core', min: 2, max: 4 },
|
||
{ id: 'business', label: '财经·市场', icon: '💰', tier: 'core', min: 2, max: 4 },
|
||
{ id: 'tech', label: '科技·AI', icon: '🤖', tier: 'core', min: 2, max: 4 },
|
||
{ id: 'weather', label: '天气', icon: '🌤️', tier: 'core', min: 8, max: 12 },
|
||
{ id: 'living', label: '生活·城市', icon: '🏙️', tier: 'flex', min: 1, max: 3 },
|
||
{ id: 'science', label: '科学·健康', icon: '🔬', tier: 'flex', min: 1, max: 3 },
|
||
{ id: 'culture', label: '文娱·体育', icon: '🎬', tier: 'flex', min: 1, max: 3 },
|
||
{ id: 'voices', label: '观点·热议', icon: '💬', tier: 'flex', min: 1, max: 3 },
|
||
{ id: 'knowledge', label: '知识卡片', icon: '📚', tier: 'core', min: 1, max: 2 },
|
||
{ id: 'history', label: '往期回顾', icon: '📅', tier: 'core', min: 7, max: 7 },
|
||
];
|
||
|
||
export const NEWS002_CORE_SECTION_IDS = NEWS002_SECTIONS
|
||
.filter((item) => item.tier === 'core')
|
||
.map((item) => item.id);
|
||
|
||
export const NEWS002_FLEX_SECTION_IDS = NEWS002_SECTIONS
|
||
.filter((item) => item.tier === 'flex')
|
||
.map((item) => item.id);
|
||
|
||
/** 全页独立事件卡上限;超过这个数就是在灌水,不是在做早报。 */
|
||
/** core(20) + 3×flex(9) 写满时最多 29 条故事卡,留 1 条余量给 SearXNG 补栏。 */
|
||
export const NEWS002_MAX_STORY_CARDS = 30;
|
||
|
||
/** news002 flex 栏目至少应出现的节数(写满策略下仍要求多栏目覆盖)。 */
|
||
export const NEWS002_MIN_FLEX_SECTIONS = 2;
|
||
|
||
const NEWS002_SECTION_ALIASES = {
|
||
business: ['business', 'finance'],
|
||
deepdive: ['deepdive', 'deep'],
|
||
};
|
||
|
||
function sliceNews002BalancedBlock(html, tagName, openStart, openEnd) {
|
||
const open = new RegExp(`<${tagName}\\b`, 'gi');
|
||
const close = new RegExp(`</${tagName}\\s*>`, 'gi');
|
||
let depth = 1;
|
||
let cursor = openEnd;
|
||
while (depth > 0 && cursor < html.length) {
|
||
open.lastIndex = cursor;
|
||
close.lastIndex = cursor;
|
||
const nextOpen = open.exec(html);
|
||
const nextClose = close.exec(html);
|
||
if (!nextClose) return { start: openStart, end: html.length };
|
||
if (nextOpen && nextOpen.index < nextClose.index) {
|
||
depth += 1;
|
||
cursor = nextOpen.index + nextOpen[0].length;
|
||
} else {
|
||
depth -= 1;
|
||
cursor = nextClose.index + nextClose[0].length;
|
||
}
|
||
}
|
||
return { start: openStart, end: cursor };
|
||
}
|
||
|
||
function extractNews002SectionBlock(html, sectionId) {
|
||
const source = String(html ?? '');
|
||
const aliases = NEWS002_SECTION_ALIASES[sectionId] ?? [sectionId];
|
||
for (const alias of aliases) {
|
||
if (alias === 'brief') {
|
||
const match = source.match(
|
||
/<div\b[^>]*\bsection\b[^>]*\bid=["']brief["'][^>]*>/i,
|
||
);
|
||
if (!match) continue;
|
||
const start = match.index;
|
||
const end = sliceNews002BalancedBlock(source, 'div', start, start + match[0].length).end;
|
||
return source.slice(start, end);
|
||
}
|
||
const match = source.match(
|
||
new RegExp(
|
||
`<div\\b[^>]*class=["'][^"']*\\bsection\\b[^"']*["'][^>]*id=["']${alias}["'][^>]*>`,
|
||
'i',
|
||
),
|
||
);
|
||
if (!match) continue;
|
||
const start = match.index;
|
||
const end = sliceNews002BalancedBlock(source, 'div', start, start + match[0].length).end;
|
||
return source.slice(start, end);
|
||
}
|
||
return '';
|
||
}
|
||
|
||
function countNews002StoryCardsInBlock(block) {
|
||
const source = String(block ?? '');
|
||
const re = /<(div|a)\b[^>]*\bclass="([^"]*)"[^>]*>/gi;
|
||
let count = 0;
|
||
let match;
|
||
while ((match = re.exec(source)) != null) {
|
||
const tokens = String(match[2]).split(/\s+/);
|
||
if (tokens.includes('card') || tokens.includes('lead-card')) count += 1;
|
||
re.lastIndex = match.index + match[0].length;
|
||
}
|
||
return count;
|
||
}
|
||
|
||
/** 统计 news002 各栏目当前条数。 */
|
||
export function findNews002SectionIdForOffset(html, offset) {
|
||
const before = String(html ?? '').slice(0, offset);
|
||
const matches = [...before.matchAll(
|
||
/<div\b[^>]*class=["'][^"']*\bsection\b[^"']*["'][^>]*id=["']([^"']+)["']/gi,
|
||
)];
|
||
return matches.length ? matches[matches.length - 1][1] : '';
|
||
}
|
||
|
||
export function countNews002HeadlineLeadCards(html) {
|
||
const block = extractNews002SectionBlock(html, 'headlines');
|
||
if (!block) return 0;
|
||
return (block.match(/\blead-card\b/gi) ?? []).length;
|
||
}
|
||
|
||
export function countNews002SectionItems(html, sectionId) {
|
||
const block = extractNews002SectionBlock(html, sectionId);
|
||
if (!block) return 0;
|
||
if (sectionId === 'brief') return (block.match(/<li\b/gi) ?? []).length;
|
||
if (sectionId === 'deepdive') {
|
||
return /<div\b[^>]*\bclass="[^"]*\bdeep\b/i.test(block) ? 1 : 0;
|
||
}
|
||
if (sectionId === 'weather') {
|
||
return (block.match(/class="[^"]*\bweather-card\b/gi) ?? []).length;
|
||
}
|
||
if (sectionId === 'knowledge') {
|
||
return (block.match(/class="[^"]*\bk-item\b/gi) ?? []).length;
|
||
}
|
||
if (sectionId === 'history') {
|
||
return (block.match(/class="[^"]*\bh-item\b/gi) ?? []).length;
|
||
}
|
||
return countNews002StoryCardsInBlock(block);
|
||
}
|
||
|
||
/**
|
||
* news002 栏目写满审计:core 栏目必须达到 max;flex 至少 minFlexSections 节且每节达到 max。
|
||
*/
|
||
export function auditNews002SectionFill(
|
||
html,
|
||
{
|
||
minFlexSections = NEWS002_MIN_FLEX_SECTIONS,
|
||
requireCoreMax = true,
|
||
requireFlexMax = true,
|
||
exemptSectionIds = [],
|
||
} = {},
|
||
) {
|
||
const counts = Object.fromEntries(
|
||
NEWS002_SECTIONS.map((section) => [section.id, countNews002SectionItems(html, section.id)]),
|
||
);
|
||
const violations = [];
|
||
// 内容安全闸门删过条目的栏目不再要求写满,否则「过滤」会变成「阻断发布」。
|
||
const exempt = new Set(exemptSectionIds);
|
||
|
||
for (const section of NEWS002_SECTIONS.filter((item) => item.tier === 'core')) {
|
||
if (exempt.has(section.id)) continue;
|
||
const count = counts[section.id] ?? 0;
|
||
if (section.id === 'deepdive') {
|
||
if (count !== section.max) {
|
||
violations.push(`underfilled:${section.id}:${count}/${section.max}`);
|
||
}
|
||
continue;
|
||
}
|
||
if (requireCoreMax && count < section.max) {
|
||
violations.push(`underfilled:${section.id}:${count}/${section.max}`);
|
||
}
|
||
}
|
||
|
||
const flexPresent = NEWS002_FLEX_SECTION_IDS.filter((id) => extractNews002SectionBlock(html, id));
|
||
const exemptMissingFlex = NEWS002_FLEX_SECTION_IDS
|
||
.filter((id) => exempt.has(id) && !flexPresent.includes(id)).length;
|
||
const requiredFlex = Math.max(0, minFlexSections - exemptMissingFlex);
|
||
if (flexPresent.length < requiredFlex) {
|
||
violations.push(`insufficient-flex-sections:${flexPresent.length}/${requiredFlex}`);
|
||
}
|
||
if (requireFlexMax) {
|
||
for (const id of flexPresent) {
|
||
if (exempt.has(id)) continue;
|
||
const section = NEWS002_SECTIONS.find((item) => item.id === id);
|
||
const count = counts[id] ?? 0;
|
||
if (section && count < section.max) {
|
||
violations.push(`underfilled:${id}:${count}/${section.max}`);
|
||
}
|
||
}
|
||
}
|
||
|
||
return {
|
||
ok: violations.length === 0,
|
||
violations,
|
||
counts,
|
||
flexPresent,
|
||
};
|
||
}
|
||
|
||
/** 交给 Agent 的「必须写满」附加约束(可写入 taskSpecAppend)。 */
|
||
export const NEWS002_FILL_REQUIREMENT_SPEC = [
|
||
'【news002 写满硬约束 · 未达标即作废重写】',
|
||
'1. core 栏目(brief/headlines/deepdive/domestic/world/business/tech/weather/knowledge/history)每一条都必须写到该栏目的上限条数,不能只写 min。',
|
||
`2. 各栏上限:${NEWS002_SECTIONS.map((s) => `${s.id}=${s.max}`).join(',')}。`,
|
||
`3. flex 栏目(${NEWS002_FLEX_SECTION_IDS.join('/')})至少出现 ${NEWS002_MIN_FLEX_SECTIONS} 个,且每个出现的 flex 栏目也必须写到上限。`,
|
||
'4. 写页前必须先合并预取 + tkmind_search(type=news) + web_search,事件表不足时继续搜索补事件,禁止用同一事件拆条凑数。',
|
||
`5. 全页独立事件仍遵守去重与 ${NEWS002_MAX_STORY_CARDS} 条上限;为写满各栏,必须覆盖更多不同事件,而不是重复。`,
|
||
'6. `.meta` 里的「今日 M 条」必须等于速读条数 + 各栏故事卡 + 知识卡 + 天气城数 + 历史条目的合计,且与实际上屏一致。',
|
||
].join('\n');
|
||
|
||
/** 定时任务默认附加约束:禁止回退 news001 + 必须写满各栏。 */
|
||
export const NEWS002_DEFAULT_TASK_SPEC_APPEND = [
|
||
'【强制 news002 版式 · 覆盖一切旧稿参考】',
|
||
'1. 禁止输出 news001 结构:不得出现 class="hero"、class="date-badge"、class="quick-links"、class="highlight-box"、背景 #f7fafc / 主色 #b0191e。',
|
||
'2. 必须使用 news002 结构:div.masthead + div.lede + a.lead-card + ol.brief + div.card[data-event-key],并内嵌 taskSpec 里的 NEWS002_STYLE_BLOCK(暖纸 #FAF7F2、主色 #C8362F)。',
|
||
'3. 写页前不要 read_file 任何旧 daily-news-*.html 学版式;旧稿正文禁止复用。只允许按本 taskSpec 生成。',
|
||
`4. 全页每张故事卡必须带 data-event-key;独立事件 ≤${NEWS002_MAX_STORY_CARDS};flex 栏目无独立事件则整节删除。`,
|
||
'5. 导航仅保留 news002 8 个主锚点(速读/头条/深读/国内/国际/财经/科技/天气/知识),禁止 25 项 quick-links。',
|
||
NEWS002_FILL_REQUIREMENT_SPEC,
|
||
].join('\n');
|
||
|
||
/** news002 标准 <style>,直接照抄,禁止自创色值。 */
|
||
export const NEWS002_STYLE_BLOCK = `<style>
|
||
*{margin:0;padding:0;box-sizing:border-box}
|
||
:root{
|
||
--ink:#1C1B19; --ink-2:#4A4742; --ink-3:#8A857D;
|
||
--paper:#FAF7F2; --surface:#FFFFFF; --line:#E8E2D9;
|
||
--brand:#C8362F; --brand-soft:#FBEDEB; --accent:#1B3A6B; --accent-soft:#EAF0F8;
|
||
--gold:#B7791F; --gold-soft:#FFF5D9; --green:#28705A; --green-soft:#E8F5EF;
|
||
--violet:#6653A3; --violet-soft:#F0ECFA;
|
||
--serif:"Noto Serif SC","Source Han Serif SC","Songti SC",Georgia,serif;
|
||
--sans:-apple-system,BlinkMacSystemFont,"PingFang SC","Hiragino Sans GB","Microsoft YaHei",sans-serif;
|
||
}
|
||
@media (prefers-color-scheme:dark){
|
||
:root{--ink:#F0EDE8;--ink-2:#B8B2A9;--ink-3:#807A72;--paper:#14130F;--surface:#1E1C18;
|
||
--line:#2E2B25;--brand:#E8635B;--brand-soft:#33201E;--accent:#7FA6DC;--accent-soft:#1B2433;
|
||
--gold:#E2AD51;--gold-soft:#302719;--green:#70B99E;--green-soft:#182B24;
|
||
--violet:#A89ADE;--violet-soft:#252037}
|
||
}
|
||
body{background:var(--paper);color:var(--ink);font-family:var(--sans);line-height:1.68;
|
||
-webkit-font-smoothing:antialiased;padding-bottom:40px}
|
||
.progress{position:fixed;top:0;left:0;height:3px;width:0;background:var(--brand);z-index:99}
|
||
/* 报头 */
|
||
.masthead{max-width:720px;margin:14px auto 0;padding:28px 20px 18px;text-align:center;
|
||
border:1px solid var(--line);border-radius:18px 18px 0 0;
|
||
background:radial-gradient(circle at 16% 0,var(--brand-soft),transparent 36%),
|
||
radial-gradient(circle at 88% 12%,var(--accent-soft),transparent 38%),var(--surface);
|
||
box-shadow:0 8px 30px rgba(52,42,31,.06)}
|
||
.masthead .kicker{font-size:11px;letter-spacing:.32em;color:var(--brand);font-weight:700}
|
||
.masthead h1{font-family:var(--serif);font-size:32px;font-weight:700;letter-spacing:2px;margin:6px 0 10px}
|
||
.masthead .meta{display:flex;justify-content:center;gap:10px;flex-wrap:wrap;
|
||
font-size:12px;color:var(--ink-3);padding-top:10px;border-top:1px solid var(--line)}
|
||
.masthead .meta b{color:var(--ink-2);font-weight:600}
|
||
.lede{max-width:720px;margin:0 auto;padding:13px 24px 15px;font-family:var(--serif);
|
||
font-size:15px;line-height:1.8;color:var(--ink-2);text-align:left;
|
||
background:var(--gold-soft);border-left:4px solid var(--gold)}
|
||
/* 吸顶导航:只放 8 个主锚点 */
|
||
.nav{position:sticky;top:0;z-index:50;background:color-mix(in srgb,var(--paper) 92%,transparent);
|
||
backdrop-filter:blur(12px);border-top:1px solid var(--line);border-bottom:1px solid var(--line)}
|
||
.nav-inner{max-width:720px;margin:0 auto;display:flex;gap:6px;overflow-x:auto;
|
||
padding:9px 16px;-webkit-overflow-scrolling:touch;scrollbar-width:none}
|
||
.nav-inner::-webkit-scrollbar{display:none}
|
||
.nav a{flex-shrink:0;padding:5px 12px;border-radius:999px;font-size:13px;font-weight:500;
|
||
color:var(--ink-2);text-decoration:none;white-space:nowrap}
|
||
.nav a:active{background:var(--brand-soft);color:var(--brand)}
|
||
.container{max-width:720px;margin:0 auto;padding:18px 16px}
|
||
.section{margin-bottom:32px;scroll-margin-top:56px}
|
||
.section-title{display:flex;align-items:baseline;gap:8px;margin-bottom:14px;
|
||
padding-bottom:9px;border-bottom:2px solid var(--ink)}
|
||
.section-title .icon{font-size:17px}
|
||
.section-title h2{font-family:var(--serif);font-size:19px;font-weight:700;letter-spacing:.5px}
|
||
.section-title .count{margin-left:auto;font-size:11px;color:var(--ink-3);letter-spacing:.1em}
|
||
#headlines .section-title{border-color:var(--brand)}
|
||
#domestic .section-title,#living .section-title{border-color:var(--green)}
|
||
#world .section-title,#tech .section-title{border-color:var(--accent)}
|
||
#business .section-title{border-color:var(--gold)}
|
||
#science .section-title,#culture .section-title{border-color:var(--violet)}
|
||
#domestic .tag,#living .tag{background:var(--green-soft);color:var(--green)}
|
||
#world .tag,#tech .tag{background:var(--accent-soft);color:var(--accent)}
|
||
#business .tag{background:var(--gold-soft);color:var(--gold)}
|
||
#science .tag,#culture .tag{background:var(--violet-soft);color:var(--violet)}
|
||
/* 一分钟速读 */
|
||
.brief{background:var(--surface);border:1px solid var(--line);border-radius:14px;padding:6px 16px}
|
||
.brief li{list-style:none;display:flex;gap:10px;padding:11px 0;
|
||
border-bottom:1px dashed var(--line);font-size:14.5px;line-height:1.6}
|
||
.brief li:last-child{border-bottom:0}
|
||
.brief .n{flex-shrink:0;width:19px;height:19px;border-radius:5px;background:var(--brand);
|
||
color:#fff;font-size:11px;font-weight:700;display:flex;align-items:center;
|
||
justify-content:center;margin-top:3px}
|
||
.brief a{color:inherit;text-decoration:none;border-bottom:1px solid var(--line)}
|
||
/* 头条卡:与普通卡一致,左侧小图 + 右侧正文 */
|
||
.lead-card{display:flex;gap:13px;background:var(--surface);border:1px solid var(--line);
|
||
border-radius:12px;padding:13px;margin-bottom:10px;text-decoration:none;color:inherit;
|
||
transition:transform .18s ease,box-shadow .18s ease}
|
||
.lead-card .media{flex-shrink:0;align-self:flex-start;width:92px;aspect-ratio:4/3;
|
||
border-radius:8px;overflow:hidden;background:var(--accent-soft)}
|
||
.lead-card .media img{width:100%;height:100%;object-fit:cover;display:block}
|
||
.lead-card .body{flex:1;min-width:0}
|
||
.lead-card h3{font-family:var(--serif);font-size:15.5px;font-weight:700;line-height:1.5;margin-bottom:5px}
|
||
.lead-card p{font-size:13.5px;color:var(--ink-2);line-height:1.65}
|
||
/* 图文混排卡 */
|
||
.card{display:flex;gap:13px;background:var(--surface);border:1px solid var(--line);
|
||
border-radius:12px;padding:13px;margin-bottom:10px;transition:transform .18s ease,box-shadow .18s ease}
|
||
@media (hover:hover){.card:hover,.lead-card:hover{transform:translateY(-2px);box-shadow:0 10px 26px rgba(52,42,31,.1)}}
|
||
/* align-self 不能省:flex 默认 stretch 会把缩略图拉成整卡高度,aspect-ratio 失效。 */
|
||
.card .media{flex-shrink:0;align-self:flex-start;width:92px;aspect-ratio:4/3;
|
||
border-radius:8px;overflow:hidden;background:var(--accent-soft)}
|
||
.card .media img{width:100%;height:100%;object-fit:cover;display:block}
|
||
.card .body{flex:1;min-width:0}
|
||
.card h3{font-family:var(--serif);font-size:15.5px;font-weight:700;line-height:1.5;margin-bottom:5px}
|
||
.card p{font-size:13.5px;color:var(--ink-2);line-height:1.65}
|
||
.card .why{margin-top:7px;padding-left:9px;border-left:2px solid var(--brand);
|
||
font-size:12.5px;color:var(--ink-2)}
|
||
.card .why b{color:var(--brand);font-weight:600}
|
||
/* ph 仅作浏览器端 onerror 降级;落盘页由 worker 剔除无图卡,禁止保留 ph 占位卡 */
|
||
.media.ph{display:flex;align-items:center;justify-content:center;font-size:26px;
|
||
background:linear-gradient(135deg,var(--brand-soft),var(--accent-soft))}
|
||
.tag{display:inline-block;font-size:10.5px;font-weight:600;letter-spacing:.06em;
|
||
padding:2px 7px;border-radius:4px;background:var(--brand-soft);color:var(--brand);margin-bottom:5px}
|
||
.tag.b{background:var(--accent-soft);color:var(--accent)}
|
||
.source{display:flex;align-items:center;gap:6px;margin-top:7px;font-size:11.5px;color:var(--ink-3)}
|
||
.source .dot{width:3px;height:3px;border-radius:50%;background:var(--ink-3)}
|
||
.source a{color:var(--ink-3);text-decoration:none;border-bottom:1px solid var(--line)}
|
||
/* 深读 */
|
||
.deep{background:var(--surface);border:1px solid var(--line);border-left:3px solid var(--brand);
|
||
border-radius:12px;padding:18px 17px}
|
||
.deep h3{font-family:var(--serif);font-size:18px;font-weight:700;margin-bottom:10px}
|
||
.deep p{font-size:14.5px;color:var(--ink-2);margin-bottom:9px}
|
||
.deep .pull{font-family:var(--serif);font-size:15px;color:var(--brand);
|
||
padding:10px 0;border-top:1px solid var(--line);border-bottom:1px solid var(--line);margin:11px 0}
|
||
/* 天气 */
|
||
.weather-today{background:linear-gradient(135deg,var(--accent-soft),var(--surface));
|
||
border:1px solid var(--line);border-radius:12px;padding:14px 16px;margin-bottom:10px;
|
||
display:flex;justify-content:space-between;align-items:center}
|
||
.weather-today .c{font-size:17px;font-weight:700;color:var(--accent)}
|
||
.weather-today .t{font-family:var(--serif);font-size:34px;font-weight:700;color:var(--accent)}
|
||
.weather-today .d{font-size:12.5px;color:var(--ink-2)}
|
||
.weather-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:7px}
|
||
.weather-card{background:var(--surface);border:1px solid var(--line);border-radius:10px;
|
||
padding:9px 6px;text-align:center}
|
||
.weather-card .wc{font-size:12px;font-weight:600;color:var(--ink-2)}
|
||
.weather-card .we{font-size:17px;margin:2px 0}
|
||
.weather-card .wt{font-size:13px;font-weight:700;color:var(--brand)}
|
||
/* 知识卡 / 往期 */
|
||
.k-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px}
|
||
.k-item{background:var(--surface);border:1px solid var(--line);border-radius:12px;padding:14px}
|
||
.k-item .k-icon{font-size:20px;line-height:1}
|
||
.k-item h4{font-family:var(--serif);font-size:14.5px;margin:5px 0}
|
||
.k-item p{font-size:12.5px;color:var(--ink-2)}
|
||
.h-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:7px}
|
||
.h-item{background:var(--surface);border:1px solid var(--line);border-radius:9px;
|
||
padding:9px 4px;text-align:center;text-decoration:none;color:var(--ink-2)}
|
||
.h-item .hd{font-size:12px;font-weight:600;color:var(--accent)}
|
||
.h-item .hw{font-size:10px;color:var(--ink-3)}
|
||
.h-item.today{border-color:var(--brand);background:var(--brand-soft)}
|
||
.h-item.today .hd{color:var(--brand)}
|
||
/* 订阅位 */
|
||
.subscribe{background:linear-gradient(135deg,var(--brand-soft),var(--gold-soft),var(--accent-soft));
|
||
border:1px solid var(--line);border-radius:14px;
|
||
padding:20px 18px;text-align:center;margin-bottom:28px}
|
||
.subscribe h3{font-family:var(--serif);font-size:17px;margin-bottom:6px}
|
||
.subscribe p{font-size:13px;color:var(--ink-2);margin-bottom:12px}
|
||
.subscribe a{display:inline-block;padding:9px 22px;border-radius:999px;background:var(--brand);
|
||
color:#fff;font-size:14px;font-weight:600;text-decoration:none}
|
||
.footer{max-width:720px;margin:0 auto;padding:22px 20px 34px;text-align:center;
|
||
font-size:12px;color:var(--ink-3);line-height:1.9;border-top:1px solid var(--line)}
|
||
@media (max-width:480px){
|
||
.masthead h1{font-size:26px}
|
||
.weather-grid{grid-template-columns:repeat(3,1fr)}
|
||
.k-grid{grid-template-columns:1fr}
|
||
.card .media{width:80px}
|
||
}
|
||
</style>`;
|
||
|
||
export const NEWS002_SEO_SPEC = [
|
||
'【SEO / GEO 必做】',
|
||
'1. `<title>`:`每日新闻早报 · {YYYY年M月D日} · TKMind`。',
|
||
'2. `<meta name="description">`:80~160 字,含日期 + 当日 3~5 条核心标题关键词(国内/国际/财经/科技至少各 1 个)。',
|
||
'3. `<meta name="mindspace-geo">` JSON:summary(120~180 字)、keywords 数组(8~12 个)、faq 至少 3 组 q/a(分别覆盖国内、国际财经、科技)。',
|
||
'4. `.section-title` 内标题文字必须用 `<h2>` 包裹(保留 icon span)。',
|
||
'5. 每条卡片 `<h3>` 须含具体事件关键词,禁止「暂无」「未能获取」等空词。',
|
||
'6. 头条大图卡必须输出 `ImageObject` 到 JSON-LD `image` 数组,便于 Google Discover / 图片搜索收录。',
|
||
'7. footer 注明整理时间与主要来源域名,便于 GEO 引用。',
|
||
].join('\n');
|
||
|
||
export const NEWS002_TEMPLATE_SPEC = [
|
||
'生成「每日新闻早报」静态 HTML 页面(daily-news-MMDD.html),版式为 news002(订阅向改版)。',
|
||
'news002 与旧版 news001 的区别:事件唯一化、每条配图、报头版式、支持深色模式。禁止套用 news001 的 `.hero` 渐变大色块、25 个 quick-link、`.highlight-box` 系列。',
|
||
'',
|
||
'一、事件唯一化(最高优先级,违反即作废重写)',
|
||
`1. 全页独立事件卡上限 ${NEWS002_MAX_STORY_CARDS} 条;各栏目必须写到上限条数(见写满约束),用更多独立事件填满,禁止重复灌水。`,
|
||
'2. 写页前先把预取 + tkmind_search + web_search 的结果归并成一张「今日事件表」:每个事件一行,字段为 事件指纹 / 标题 / 摘要 / 来源URL / 配图URL / 栏目归属。',
|
||
'3. 事件指纹 = 核心主体 + 核心动作 的小写拼音或英文短横线串(例:`wangyi-rubio-call`、`asean-expo-nanning`、`openai-hodge-conjecture`)。中英文报道同一事件必须共用同一指纹。',
|
||
'4. 每张卡片必须写 `data-event-key="{事件指纹}"`;全页指纹不得重复,一个事件只能出现在一个栏目里。',
|
||
'5. 同一事件的「不同角度」不算新事件,不得为了填满栏目而拆成两条。',
|
||
'',
|
||
'二、栏目结构(core 必出且写满,flex 至少开两节且写满)',
|
||
`1. core 栏目(必须全部出现并写到上限):${NEWS002_CORE_SECTION_IDS.join(' / ')}。`,
|
||
`2. flex 栏目(至少 ${NEWS002_MIN_FLEX_SECTIONS} 个必须出现,且每个出现的 flex 栏目写到上限):${NEWS002_FLEX_SECTION_IDS.join(' / ')}。`,
|
||
'3. 各栏目条数(必须达到 max,不是 min):' + NEWS002_SECTIONS.map((s) => `${s.id}=${s.max}`).join('|') + '。',
|
||
'4. 区块容器用 `<div class="section" id="...">`,`.section-title` 内含 `<span class="icon">` + `<h2>` + 可选 `<span class="count">`。',
|
||
'',
|
||
'三、DOM 骨架(顺序固定)',
|
||
'1. `<div class="progress" id="progress"></div>`(阅读进度条,配套一小段 scroll 脚本)。',
|
||
'2. `<div class="masthead">`:`.kicker` 写 `TKMIND DAILY`、`<h1>每日新闻早报</h1>`、`.meta` 一行写 `<b>{YYYY年M月D日}</b> · 星期X · 第 {N} 期 · 今日 {M} 条`。',
|
||
'3. `<div class="lede">`:一句主编导语,25~45 字,点出当天最值得关心的一件事,不要罗列。',
|
||
'4. `<nav class="nav"><div class="nav-inner">`:只放 8 个锚点(速读/头条/深读/国内/国际/财经/科技/天气),flex 栏目不进导航。',
|
||
'5. `<div class="container">` 内按栏目顺序输出 section。',
|
||
'6. 在 history 之前插入 `.subscribe` 订阅引导块(标题 + 一句话价值 + 按钮)。',
|
||
'7. `<div class="footer">`:来源域名列表 + 整理时间 + TKMind 品牌。',
|
||
'',
|
||
'四、卡片写法',
|
||
'1. 速读区:`<ol class="brief">` + `<li><span class="n">1</span><span>一句话 20~32 字,必须含具体主体和数字</span></li>`,6~10 条,每条对应下方某个事件指纹。',
|
||
'2. 头条:`<a class="lead-card" href="{来源URL}" data-event-key="..."><div class="media">{图}</div><div class="body"><span class="tag">…</span><h3>…</h3><p>…</p><div class="source">…</div></div></a>`,3~4 条。',
|
||
'3. 普通条目:`<div class="card" data-event-key="..."><div class="media">{图}</div><div class="body"><span class="tag">…</span><h3>…</h3><p>…</p><div class="why"><b>为什么重要</b>:…</div><div class="source">…</div></div></div>`。',
|
||
'4. `<h3>` 中文 15~26 字,前 10 字必须有信息钩子(主体+动作),禁止「相关消息」「持续关注」这类空转。',
|
||
'5. `<p>` 写 2~3 句:第一句说发生了什么(含时间地点数字),第二句说进展或背景。',
|
||
'6. `.why` 一句话说清「跟读者有什么关系」,20~40 字。头条卡可省略 `.why`。',
|
||
'7. `.source` 格式:`<a href="{URL}" target="_blank" rel="noopener">{媒体名}</a><span class="dot"></span>{HH:MM}`。',
|
||
'8. 深读区用 `.deep`:一条当天最重要的事,300~450 字,中间插一句 `.pull` 金句。',
|
||
'',
|
||
'五、配图(news002 新增,必须做)',
|
||
'1. 预取 brief 里每条结果若带 `图:{URL}`,就把该 URL 用作这条新闻的配图。',
|
||
'1b. 正式 news002 能拿到来源文章真实 https 配图的卡片必须显示该图;没有真实图就不要放 `<img>`、不要用默认小图或 `.media.ph` 占位。没有图的事件也必须保留,禁止因缺图删卡。',
|
||
'2. 有真实图才写 `<div class="media"><img src="{图URL}" alt="{标题}" loading="lazy" referrerpolicy="no-referrer"></div>`。',
|
||
'3. 禁止为了过闸门编造图片地址;禁止用默认小图凑数。拿不到图就把 `.media` 整块拿掉,只留文字卡。',
|
||
'3b. 知识卡片的 emoji 用 `<div class="k-icon">`,否则字号会掉到正文大小。',
|
||
'4. 配图优先用预取 `图:{URL}` 和来源文章的 `og:image` / `twitter:image`;能取到就必须显示,禁止拿甲事件的图配乙事件,禁止因缺图拿掉卡片。',
|
||
'5. 图片一律 https,禁止 data URI 长图,禁止整页长图截图。',
|
||
'',
|
||
'六、视觉(照抄,禁止自创色值)',
|
||
'1. `<style>` 块直接使用下方给定内容,可追加但不得修改已有选择器的色值与字号。',
|
||
'2. 色调:暖纸底 #FAF7F2 + 墨红 #C8362F + 靛蓝 #1B3A6B;标题衬线体,正文黑体;必须保留 `prefers-color-scheme: dark` 分支。',
|
||
NEWS002_STYLE_BLOCK,
|
||
'',
|
||
'七、交付',
|
||
'1. 必须含 title、description、mindspace-cover、mindspace-geo、og/twitter、canonical、JSON-LD。',
|
||
'2. `mindspace-cover` 的 accent 用 `#C8362F`、accent2 用 `#1B3A6B`。',
|
||
'3. 生成 daily-news-MMDD.thumbnail.png。',
|
||
'4. 推送微信草稿时转为内联 style(微信不支持 `<style>`),底部追加统一「阅读原文 + 二维码」layout。',
|
||
].join('\n');
|
||
|
||
export const NEWS002_FRESH_CONTENT_SPEC = [
|
||
'内容硬约束(必须遵守,优先于版式):',
|
||
'1. 检索必须双路:先 load_skill → web,再同一轮调用 tkmind_search(type=news) 与 web_search,合并去重后写页;禁止只靠 web_search。',
|
||
'2. 合并后先产出「今日事件表」并去重,再按事件数决定写几个栏目 —— 是事件决定栏目,不是栏目决定事件。',
|
||
`3. 全页独立事件不超过 ${NEWS002_MAX_STORY_CARDS} 条;为写满各 core/flex 栏目,必须继续 tkmind_search / web_search 补独立事件,禁止把已写过的事件换个标题再写一遍。`,
|
||
'4. 判重标准:同一 URL、同一主体+同一动作、中英文互译标题、同一事件的不同媒体转述,都算同一事件。',
|
||
'5. 若附有「专用联网搜索预取」,必须优先从中选题;预取含重大新闻锚点补搜(元首外交、央行定调、AI/芯片发布、市场异动等),当日硬头条必须在 headlines/world/business/tech 体现;预取有结果时禁止输出「未能获取」类空页。',
|
||
'6. 若 tkmind_search / web_search 暂时不可用但预取 brief 有条目:用预取结果写页,并在 source 标注预取来源 URL。',
|
||
'7. 仅当预取各路均为空且双路搜索均失败时,才允许写「检索暂不可用」说明页;禁止用历史 daily-news 正文充数。',
|
||
'8. 标题规则:中文 15~26 字;英文来源须转写为中文标题,可在括号内保留英文原题;每条必须可核验并写来源 URL。',
|
||
'9. 禁止复制、改写历史 daily-news-*.html 的正文;旧稿仅作 HTML 结构参考。',
|
||
'10. 禁止八卦绯闻、未经证实的传闻、标题党;每条都要能说清「为什么重要」,说不清就不要写。',
|
||
].join('\n');
|
||
|
||
/* ------------------------------------------------------------------ *
|
||
* 注册表
|
||
* ------------------------------------------------------------------ */
|
||
|
||
export const NEWS_MORNING_TEMPLATES = {
|
||
news001: {
|
||
id: 'news001',
|
||
version: NEWS001_VERSION,
|
||
label: '每日新闻早报 · 定稿版(2026-09-18)',
|
||
status: 'frozen',
|
||
referenceUrl: NEWS_MORNING_TEMPLATE_REFERENCE_URL,
|
||
supportsCardImages: false,
|
||
maxStoryCards: null,
|
||
freshContentSpec: NEWS001_FRESH_CONTENT_SPEC,
|
||
seoSpec: NEWS001_SEO_SPEC,
|
||
templateSpec: NEWS001_TEMPLATE_SPEC,
|
||
},
|
||
news002: {
|
||
id: 'news002',
|
||
version: NEWS002_VERSION,
|
||
label: '每日新闻早报 · 订阅版(news002)',
|
||
status: 'active',
|
||
referenceUrl: null,
|
||
supportsCardImages: true,
|
||
maxStoryCards: NEWS002_MAX_STORY_CARDS,
|
||
freshContentSpec: NEWS002_FRESH_CONTENT_SPEC,
|
||
seoSpec: NEWS002_SEO_SPEC,
|
||
templateSpec: NEWS002_TEMPLATE_SPEC,
|
||
},
|
||
};
|
||
|
||
export const NEWS_MORNING_TEMPLATE_IDS = Object.keys(NEWS_MORNING_TEMPLATES);
|
||
|
||
export const DEFAULT_NEWS_MORNING_TEMPLATE_ID = 'news002';
|
||
|
||
export function resolveNewsMorningTemplateId(value, env = process.env) {
|
||
const candidates = [value, env?.H5_WECHAT_NEWS_MORNING_TEMPLATE_ID];
|
||
for (const candidate of candidates) {
|
||
const normalized = String(candidate ?? '').trim().toLowerCase();
|
||
if (normalized && NEWS_MORNING_TEMPLATES[normalized]) return normalized;
|
||
}
|
||
return DEFAULT_NEWS_MORNING_TEMPLATE_ID;
|
||
}
|
||
|
||
export function getNewsMorningTemplate(value, env = process.env) {
|
||
return NEWS_MORNING_TEMPLATES[resolveNewsMorningTemplateId(value, env)];
|
||
}
|
||
|
||
/** 组装交给 Agent 的完整 taskSpec 前缀(内容约束 + SEO + 版式)。 */
|
||
function envFlag(value, fallback = true) {
|
||
const raw = String(value ?? '').trim().toLowerCase();
|
||
if (!raw) return fallback;
|
||
return ['1', 'true', 'yes', 'on'].includes(raw);
|
||
}
|
||
|
||
export function buildNewsMorningTemplateSpec(templateId, env = process.env) {
|
||
const template = getNewsMorningTemplate(templateId, env);
|
||
const requireFill = template.id === 'news002'
|
||
&& envFlag(env.MEMIND_NEWS_MORNING_NEWS002_REQUIRE_FILL, true);
|
||
return [
|
||
`版式模板:${template.id}(${template.version})`,
|
||
template.freshContentSpec,
|
||
template.seoSpec,
|
||
template.templateSpec,
|
||
requireFill ? NEWS002_FILL_REQUIREMENT_SPEC : '',
|
||
template.referenceUrl ? `版式参照(只读结构):${template.referenceUrl}` : '',
|
||
].filter(Boolean).join('\n');
|
||
}
|