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=20260817a"; 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) => `
${escape(swatch.name_zh)}${escape(swatch.name_en)}

${escape(swatch.role)} · HEX ${escape(swatch.hex)} · RGB ${escape(swatch.rgb)}

${escape(swatch.source_type)}
`).join(""); const templates = (item.sample_templates || []).map((template) => `${escape(template.product)}${escape(template.material)}${escape(template.craft)}${escape(template.market)}${escape(template.platform)}`).join(""); printWindow.document.write(`${escape(item.title)} · 家纺配色打样单

家纺配色与打样任务单

${escape(item.title)}

官方颜色事实

中文标识${escape(official.name_zh)}英文名称${escape(official.name_en)}Pantone 编号${escape(official.pantone_code)} · ${escape(official.pantone_system)}屏幕参考值HEX ${escape(official.hex)} · RGB ${escape(official.rgb)}

${escape(official.value_notice)}

本站设计建议配色

${swatches}

家纺打样模板

${templates}
产品材质 / 规格工艺目标市场平台

执行与验收

样品数量:
负责人:
供应商:
交样日期:
洗后复色要求:
色牢度 / 尺寸稳定:
目标国家与平台:
主图 A/B 测试:

官方来源:${escape(item.url || item.source_url)}

`); 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 };