feat: 发布三笙AI情报局独立版公司研发基线

同步家纺情报 v2 数据、59 个严格权威源、趋势图库、移动端交互、Google/Yandex 翻译门控及静态 SEO/GEO 构建。

补充面向 C# 研发的 README,明确当前无 LLM、多模态与生图模型,记录翻译请求参数、缓存与失败策略、数据处理边界、前端职责和无 GitHub Pages 部署流程。

新增按日期倒序维护的 CHANGELOG,记录影响、验证与代码/数据/dist 回滚方法。

验证:11 个 Python 契约测试、13 个 Node 前端测试通过;静态构建生成 1096 个 Sitemap URL。
This commit is contained in:
Jack
2026-08-14 19:26:39 +08:00
commit f1ce583fd1
231 changed files with 245559 additions and 0 deletions
@@ -0,0 +1,385 @@
import { state } from "./render-intelligence-state.js?v=20260814e";
import { TEXT, create, openLightbox, normalized, matchesFilters, makeVisual, makeBadge, makeExternalLink, sourceLinkLabel, currentTranslationProvider, appendTranslationLinks, appendTags, formatDate, formatVerified, makeShareButton } from "./render-intelligence-core.js?v=20260814i";
function makeExhibitionCard(item) {
const article = create("article", "exhibition-card");
const visual = makeVisual(item);
visual.classList.add("exhibition-card-visual");
const body = create("div", "exhibition-card-body");
const meta = create("div", "exhibition-card-meta");
const countdown = item.days_until <= 30 ? `距开展 ${item.days_until} 天` : `${item.start_date} 开展`;
meta.append(
makeBadge(item.region || "全球", "badge-exhibitions"),
makeBadge(item.industry_relevance || "纺织行业展会", "exhibition-relevance"),
makeBadge(countdown, item.days_until <= 30 ? "risk-高" : ""),
);
body.append(meta, create("h3", "exhibition-card-title", item.title));
if (item.title_original && item.title_original !== item.title) {
body.appendChild(create("p", "exhibition-card-original", item.title_original));
}
body.appendChild(create("p", "exhibition-card-venue", `${formatDate(item)} · ${item.venue || item.region}`));
const focus = create("div", "exhibition-focus-tags");
appendTags(focus, item.event_focus || []);
body.appendChild(focus);
const recommendation = create("section", "exhibition-recommendation");
recommendation.append(
create("strong", "", item.attendance_recommendation || "先评估是否值得参加"),
create("p", "", item.attendance_reason || item.opportunity || ""),
);
body.appendChild(recommendation);
const checklist = create("ol", "exhibition-checklist");
(item.trip_checklist || []).forEach((value) => checklist.appendChild(create("li", "", value)));
body.appendChild(checklist);
const actions = create("div", "exhibition-actions");
const usedUrls = new Set();
const appendUniqueLink = (label, url, className) => {
if (!url || usedUrls.has(url)) return;
usedUrls.add(url);
actions.appendChild(makeExternalLink(label, url, className));
};
appendUniqueLink(item.registration_mode === "官方报名" ? "去官网报名(原文)" : (item.registration_mode || TEXT.exhibitorInfo), item.registration_url, "exhibition-action primary");
appendUniqueLink(item.visitor_mode || TEXT.visitorRegistration, item.visitor_url, "exhibition-action");
appendUniqueLink(sourceLinkLabel(item), item.url || item.source_url, "exhibition-action");
const provider = currentTranslationProvider();
const googleUrl = item.registration_translation_url || item.visitor_translation_url || item.translation_url;
const yandexUrl = item.registration_translation_fallback_url || item.visitor_translation_fallback_url || item.translation_fallback_url;
const translatedUrl = provider === "yandex" ? (yandexUrl || googleUrl) : provider === "google" ? (googleUrl || yandexUrl) : "";
appendUniqueLink(TEXT.translatedVersion, translatedUrl, `exhibition-action translate${translatedUrl === yandexUrl ? " is-fallback" : ""}`);
const share = makeShareButton(item);
share.classList.add("is-tertiary");
actions.appendChild(share);
const proof = create("div", "exhibition-proof");
proof.append(create("strong", "", item.source), create("span", "", `${item.authority} · 核验 ${formatVerified(item.verified_at)}`));
body.append(actions, proof);
article.append(visual, body);
return article;
}
function renderExhibitions(data, container) {
const filtered = (data.exhibitions || []).filter(matchesFilters);
const header = create("section", "scene-summary exhibition-summary");
const directCount = filtered.filter((item) => item.registration_mode === "官方报名").length;
header.append(
create("strong", "", `${filtered.length} 场官网核验展会`),
create("span", "", `${directCount} 场识别到直接报名入口 · 中文辅助阅读不替代官方报名提交`),
);
container.appendChild(header);
const grid = create("div", "exhibition-grid");
filtered.forEach((item) => grid.appendChild(makeExhibitionCard(item)));
container.appendChild(grid);
}
function policyMatchesScope(item) {
if (state.policyScope === "all") return true;
if (state.policyScope === "priority") return item.policy_type !== "产品安全与召回";
if (state.policyScope === "platform") return item.scope_type === "platform";
if (state.policyScope === "ip") return item.scope_type === "ip";
if (state.policyScope === "safety") return item.policy_type === "产品安全与召回";
return item.scope_type === "region" && item.policy_type !== "产品安全与召回";
}
function makePolicyCard(item) {
const article = create("article", "policy-card");
const header = create("div", "policy-card-header");
const meta = create("div", "policy-card-meta");
meta.append(
makeBadge(`${item.risk_level || "关注"}风险`, `risk-${item.risk_level || "关注"}`),
makeBadge(item.policy_type || "综合合规", "badge-policies"),
makeBadge(item.change_status || "持续监测", /更新|新发布/.test(item.change_status || "") ? "policy-changed" : ""),
);
header.append(meta, create("span", "policy-card-date", formatDate(item)));
const title = create("h3", "policy-card-title", item.title);
const original = item.title_original && item.title_original !== item.title ? create("p", "policy-card-original", item.title_original) : null;
const summary = create("p", "policy-card-summary", item.summary || "");
const affected = create("div", "policy-affected-tags");
appendTags(affected, item.affected_markets || []);
appendTags(affected, item.platforms || [], "intelligence-tag is-platform");
const change = create("div", "policy-change-summary");
change.append(create("span", "", "发生了什么"), create("p", "", item.change_summary || item.summary || "请打开来源页核对规则变化。"));
const consequence = create("div", "policy-consequence");
consequence.append(create("span", "", "不处理的后果"), create("p", "", item.consequence || "可能影响商品准入、税费、平台经营或履约。"));
const action = create("div", "policy-action");
action.append(create("span", "", "今天要做什么"), create("p", "", item.action || "打开官方来源复核后执行。"));
const footer = create("div", "policy-card-footer");
const proof = create("div", "policy-proof");
proof.append(create("strong", "", item.source), create("span", "", `${item.authority || "官方"} · ${formatVerified(item.verified_at)}`));
const actions = create("div", "policy-card-actions");
actions.appendChild(makeExternalLink(sourceLinkLabel(item), item.url || item.source_url));
appendTranslationLinks(actions, item);
const share = makeShareButton(item);
share.classList.add("is-tertiary");
actions.appendChild(share);
footer.append(proof, actions);
article.append(header, title);
if (original) article.appendChild(original);
article.append(summary, affected, change, consequence, action, footer);
return article;
}
function renderPolicies(data, container) {
const all = (data.policies || []).filter(matchesFilters);
const scopes = [
["priority", "经营政策"], ["region", "地区政策"], ["platform", "平台规则"], ["ip", "知识产权"], ["safety", "产品召回"], ["all", "全部"],
];
const controls = create("section", "policy-scope-switch");
scopes.forEach(([key, label]) => {
const count = all.filter((item) => {
const previous = state.policyScope;
state.policyScope = key;
const matched = policyMatchesScope(item);
state.policyScope = previous;
return matched;
}).length;
const button = create("button", `policy-scope-btn${state.policyScope === key ? " active" : ""}`, `${label} ${count}`);
button.type = "button";
button.addEventListener("click", () => {
state.policyScope = key;
state.renderList?.(data, container);
});
controls.appendChild(button);
});
container.appendChild(controls);
const selected = all.filter(policyMatchesScope).sort((a, b) => {
const priorityA = Number(a.action_priority || 0);
const priorityB = Number(b.action_priority || 0);
const changedA = /新发布|近期更新/.test(a.change_status || "") ? 1 : 0;
const changedB = /新发布|近期更新/.test(b.change_status || "") ? 1 : 0;
const dateA = Date.parse(a.published_at || "") || 0;
const dateB = Date.parse(b.published_at || "") || 0;
return priorityB - priorityA || changedB - changedA || dateB - dateA || String(a.policy_type || "").localeCompare(String(b.policy_type || ""), "zh-CN");
});
const coverage = create("section", "scene-summary policy-summary");
const platforms = [...new Set(selected.flatMap((item) => item.platforms || []))];
const markets = [...new Set(selected.flatMap((item) => item.affected_markets || []))];
coverage.append(
create("strong", "", `${markets.length} 个国家/区域 · ${platforms.length} 个平台`),
create("span", "", "默认先看关税、平台、知识产权和市场规则;产品召回独立查看"),
);
container.appendChild(coverage);
const grid = create("div", "policy-grid");
selected.forEach((item) => grid.appendChild(makePolicyCard(item)));
container.appendChild(grid);
}
const TREND_SEARCH_PRESETS = [
["现代床品配色", "modern bedding color palette textile design"],
["窗帘与软装纹样", "curtain upholstery pattern interior textile"],
["凉感与功能面料", "cooling fabric q-max functional textile"],
["刺绣与提花工艺", "embroidery jacquard textile craftsmanship"],
["酒店布草设计", "hotel linen bedding design hospitality textile"],
["童装与婴童图案", "kidswear baby textile pattern design"],
];
const SEARCH_PROVIDERS = [
["Pinterest", "Pinterest", "pinterest"],
["Bing 图片", "Bing", "bing"],
["Google 图片", "Google", "google"],
];
function trendItems(data) {
return Object.values(data.trends?.categories || {}).flatMap((category) => category.items || []);
}
function makeTrendSearchHref(provider, keyword) {
const q = encodeURIComponent(keyword);
if (provider === "Pinterest") return `https://www.pinterest.com/search/pins/?q=${q}`;
if (provider === "Google 图片") return `https://www.google.com/search?tbm=isch&q=${q}`;
return `https://cn.bing.com/images/search?q=${q}`;
}
function makeTrendSearchLink(provider, label, logoKey, keyword) {
const link = makeExternalLink("", makeTrendSearchHref(provider, keyword), `trend-search-button is-${logoKey}`);
link.title = `${label} 图案灵感检索`;
const logo = create("img", "trend-search-logo");
logo.src = `./assets/search-logos/${logoKey}.svg`;
logo.alt = "";
logo.width = 20;
logo.height = 20;
logo.setAttribute("aria-hidden", "true");
link.append(logo, create("span", "", label));
return link;
}
function renderTrendSearchHub(container) {
const section = create("section", "trend-search-hub");
const heading = create("div", "trend-search-heading");
heading.append(create("strong", "", "图案灵感检索"), create("span", "", "直接打开成熟搜索引擎;图片仅作灵感参考,商用前核对版权"));
const grid = create("div", "trend-search-grid");
TREND_SEARCH_PRESETS.forEach(([title, keyword]) => {
const card = create("article", "trend-search-card");
card.append(
create("span", "trend-search-kicker", "图案灵感检索"),
create("h3", "", title),
create("p", "trend-search-purpose", {
"现代床品配色": "找床品配色、材质和卧室场景参考",
"窗帘与软装纹样": "找窗帘、靠垫和软装纹样参考",
"凉感与功能面料": "找凉感、Q-MAX 和功能面料应用",
"刺绣与提花工艺": "找刺绣、提花和织造工艺参考",
"酒店布草设计": "找酒店床品、浴巾和布草方案",
"童装与婴童图案": "找童装、婴童用品和安全图案参考",
}[title] || "找家纺、纺织与服装设计参考"),
create("code", "trend-search-keyword", `英文检索词:${keyword}`),
);
const actions = create("div", "trend-search-actions");
SEARCH_PROVIDERS.forEach(([provider, label, logoKey]) => {
actions.appendChild(makeTrendSearchLink(provider, label, logoKey, keyword));
});
card.appendChild(actions);
grid.appendChild(card);
});
section.append(heading, grid);
container.appendChild(section);
}
function renderTrendNews(items, container, headingText, gallery = false) {
const filteredItems = items.filter(matchesFilters);
if (!filteredItems.length) return;
const heading = create("section", "scene-summary trend-news-summary");
const note = gallery
? "馆藏按作品档案长期保留;页首核验时间只表示系统检查,不代表作品更新"
: "外文资讯按访客地区提供简体中文辅助阅读;图片商用前请核对版权";
heading.append(create("strong", "", `${filteredItems.length}${headingText}`), create("span", "", note));
const grid = create("div", "trend-news-grid");
if (gallery) grid.classList.add("is-gallery");
filteredItems.forEach((item) => {
const card = create("article", `trend-news-card${gallery ? " is-gallery" : ""}`);
const visual = create("div", "trend-news-visual");
const imageUrl = item.image || item.thumbnail;
if (imageUrl) {
const image = create("img");
image.src = imageUrl;
image.alt = item.title || "家纺趋势配图";
image.loading = "lazy";
if (!gallery) {
image.width = 480;
image.height = 300;
}
image.referrerPolicy = "no-referrer";
if (gallery) {
const originalSrc = item.image_original || item.thumbnail;
const zoom = create("button", "trend-gallery-zoom");
zoom.type = "button";
zoom.setAttribute("aria-label", `查看《${item.title || "馆藏作品"}》原图`);
zoom.addEventListener("click", () => openLightbox(originalSrc, item.title || "馆藏作品原图", { previewSrc: item.thumbnail }));
zoom.appendChild(image);
visual.appendChild(zoom);
} else {
visual.appendChild(image);
}
} else {
visual.append(create("span", "", item.category || "趋势新闻"), create("strong", "", item.title || "家纺趋势"));
}
const body = create("div", "trend-news-body");
body.append(makeBadge(item.category || "趋势新闻", "badge-standards"), create("span", "trend-news-source", item.source || "趋势来源"), create("h3", "", item.title || "家纺趋势"));
if (item.title_original && item.title_original !== item.title) body.appendChild(create("p", "trend-news-original", item.title_original));
if (item.summary) body.appendChild(create("p", "trend-news-copy", item.summary));
const actions = create("div", "trend-news-actions");
actions.appendChild(makeExternalLink("查看原文", item.url, "trend-news-button primary"));
appendTranslationLinks(actions, item, "trend-news-button translate");
body.appendChild(actions);
card.append(visual, body);
grid.appendChild(card);
});
container.append(heading, grid);
}
function openSampleSheet(item) {
const official = item.official_color || {};
const printWindow = window.open("", "_blank");
if (!printWindow) return;
printWindow.opener = null;
const escape = (value) => String(value || "").replace(/[&<>"']/g, (char) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", "\"": "&quot;", "'": "&#39;" }[char]));
const swatches = (item.swatches || []).map((swatch) => `<article class="swatch"><span style="background:${escape(swatch.hex)}"></span><div><b>${escape(swatch.name_zh)}</b><small>${escape(swatch.name_en)}</small><p>${escape(swatch.role)} · HEX ${escape(swatch.hex)} · RGB ${escape(swatch.rgb)}</p><em>${escape(swatch.source_type)}</em></div></article>`).join("");
const templates = (item.sample_templates || []).map((template) => `<tr><th>${escape(template.product)}</th><td>${escape(template.material)}</td><td>${escape(template.craft)}</td><td>${escape(template.market)}</td><td>${escape(template.platform)}</td></tr>`).join("");
printWindow.document.write(`<!doctype html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${escape(item.title)} · 家纺配色打样单</title><style>@page{size:A4;margin:12mm}*{box-sizing:border-box}body{margin:0;color:#17202a;font:15px/1.6 Arial,"Microsoft YaHei",sans-serif}header{border-bottom:3px solid #17202a;padding-bottom:12px}h1{margin:0;font-size:26px}h2{margin:22px 0 8px;font-size:18px}.facts{display:grid;grid-template-columns:120px 1fr;gap:8px;margin-top:14px}.hero{width:100%;height:115px;background:${escape(official.hex)};border:1px solid #bbb}.swatches{display:grid;grid-template-columns:1fr 1fr;gap:10px}.swatch{display:grid;grid-template-columns:84px 1fr;gap:10px;border:1px solid #ccd2d8;padding:10px}.swatch>span{width:84px;height:84px;border:1px solid #aaa}.swatch b,.swatch small,.swatch em{display:block}.swatch small,.swatch em{color:#59636e}.swatch p{margin:4px 0}table{width:100%;border-collapse:collapse;font-size:12px}th,td{border:1px solid #bfc7cf;padding:7px;text-align:left;vertical-align:top}.fields{display:grid;grid-template-columns:repeat(2,1fr);gap:10px}.field{min-height:42px;border-bottom:1px solid #5d6670;padding:8px}.notice{padding:10px;background:#fff4d8;border-left:4px solid #c5861a}.toolbar{position:sticky;top:0;display:flex;gap:8px;justify-content:flex-end;padding:8px;background:#fff;border-bottom:1px solid #ddd}.toolbar button{min-height:44px;padding:0 18px;border:0;background:#17202a;color:#fff;font-weight:700;cursor:pointer}@media print{.toolbar{display:none}}@media(max-width:680px){.swatches,.fields{grid-template-columns:1fr}table{font-size:11px}}</style></head><body><div class="toolbar"><button onclick="window.print()">打印 / 保存为 PDF</button></div><header><h1>家纺配色与打样任务单</h1><p>${escape(item.title)}</p></header><h2>官方颜色事实</h2><div class="hero"></div><div class="facts"><b>中文标识</b><span>${escape(official.name_zh)}</span><b>英文名称</b><span>${escape(official.name_en)}</span><b>Pantone 编号</b><span>${escape(official.pantone_code)} · ${escape(official.pantone_system)}</span><b>屏幕参考值</b><span>HEX ${escape(official.hex)} · RGB ${escape(official.rgb)}</span></div><p class="notice">${escape(official.value_notice)}</p><h2>本站设计建议配色</h2><div class="swatches">${swatches}</div><h2>家纺打样模板</h2><table><thead><tr><th>产品</th><th>材质 / 规格</th><th>工艺</th><th>目标市场</th><th>平台</th></tr></thead><tbody>${templates}</tbody></table><h2>执行与验收</h2><div class="fields"><div class="field">样品数量:</div><div class="field">负责人:</div><div class="field">供应商:</div><div class="field">交样日期:</div><div class="field">洗后复色要求:</div><div class="field">色牢度 / 尺寸稳定:</div><div class="field">目标国家与平台:</div><div class="field">主图 A/B 测试:</div></div><p>官方来源:${escape(item.url || item.source_url)}</p></body></html>`);
printWindow.document.close();
}
function makeBriefButton(item) {
const label = item.official_color ? "预览并打印打样单" : "打印当前执行清单";
const button = create("button", "standard-download", label);
button.type = "button";
button.addEventListener("click", () => item.official_color ? openSampleSheet(item) : window.print());
return button;
}
function renderStandards(data, container) {
const allTrends = trendItems(data);
const galleryItems = allTrends.filter((item) => item.category_key === "textile_gallery");
const blogItems = allTrends.filter((item) => item.category_key === "trend_blogs");
const fashionItems = allTrends.filter((item) => item.category_key === "fashion_trends");
const tabs = create("nav", "trend-subtabs");
[
["gallery", "灵感图库", galleryItems.length],
["blogs", "趋势博客", blogItems.length + (data.standards?.length || 0)],
["fashion", "时装趋势", fashionItems.length],
["search", "图案灵感检索", TREND_SEARCH_PRESETS.length],
].forEach(([key, label, count]) => {
const button = create("button", `trend-subtab${state.trendSubsection === key ? " active" : ""}`);
button.type = "button";
button.append(create("strong", "", label), create("span", "", `${count} 条`));
button.addEventListener("click", () => {
state.trendSubsection = key;
state.renderList?.(data, container);
});
tabs.appendChild(button);
});
container.appendChild(tabs);
if (state.trendSubsection === "gallery") {
renderTrendNews(galleryItems, container, "组家纺灵感图库", true);
return;
}
if (state.trendSubsection === "search") {
renderTrendSearchHub(container);
return;
}
if (state.trendSubsection === "fashion") {
renderTrendNews(fashionItems, container, "条时装与面料趋势");
return;
}
renderTrendNews(blogItems, container, "条家纺趋势博客");
const filtered = (data.standards || []).filter(matchesFilters);
const header = create("section", "scene-summary standards-summary");
header.append(create("strong", "", "权威色彩、材料与检测任务"), create("span", "", "标准证据与消费趋势分层展示,不把检测入口冒充市场热度"));
container.appendChild(header);
const grid = create("div", "standards-grid");
filtered.forEach((item) => {
const card = create("article", "standard-card");
card.appendChild(makeVisual(item));
const body = create("div", "standard-card-body");
const badges = create("div", "standard-card-badges");
badges.append(makeBadge(item.trend_dimension || "趋势结论", "badge-standards"), makeBadge(item.signal || "权威源已验证"));
body.append(badges, create("h3", "", item.title), create("p", "", item.summary || ""));
const targets = create("div", "calendar-product-tags");
appendTags(targets, item.product_targets || []);
body.appendChild(targets);
if ((item.swatches || []).length) {
const swatches = create("div", "standard-swatches");
item.swatches.forEach((swatch) => {
const swatchNode = create("div", "standard-swatch");
const color = create("span", "standard-swatch-color");
color.style.setProperty("--swatch-color", swatch.hex || "#cccccc");
swatchNode.append(color, create("strong", "", swatch.name_zh || swatch.name || "配色"), create("small", "", `${swatch.name_en || ""} · ${swatch.hex || ""} · RGB ${swatch.rgb || ""} · ${swatch.role || ""}`));
swatches.appendChild(swatchNode);
});
body.appendChild(swatches);
}
const application = create("div", "standard-application");
application.append(create("span", "", "产品应用"), create("p", "", item.application || "用于选色、选材、功能和合规决策。"), create("strong", "standard-decision-note", item.decision_note || "先核对来源和适用范围。"));
const checklist = create("ul", "standard-task-list");
(item.brief_items || []).forEach((value) => checklist.appendChild(create("li", "", value)));
const actions = create("div", "standard-card-actions");
actions.append(makeBriefButton(item), makeExternalLink(sourceLinkLabel(item), item.url || item.source_url));
appendTranslationLinks(actions, item);
body.append(application, checklist, actions);
card.appendChild(body);
grid.appendChild(card);
});
container.appendChild(grid);
}
export { trendItems, renderExhibitions, renderPolicies, renderStandards, renderTrendNews, renderTrendSearchHub };