2026-08-14 19:26:39 +08:00
|
|
|
import { state } from "./render-intelligence-state.js?v=20260814e";
|
2026-08-15 19:17:16 +08:00
|
|
|
import { TEXT, create, openLightbox, normalized, matchesFilters, makeVisual, makeBadge, makeExternalLink, sourceLinkLabel, currentTranslationProvider, appendTranslationLinks, appendTags, formatDate, formatVerified, makeShareButton } from "./render-intelligence-core.js?v=20260815a";
|
2026-08-14 19:26:39 +08:00
|
|
|
|
|
|
|
|
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) => ({ "&": "&", "<": "<", ">": ">", "\"": """, "'": "'" }[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 };
|