/** 跨境活动独立渲染器,避免继续膨胀情报主渲染文件。 */ let activityFocus = "priority"; let activityRegion = ""; let activityPlatform = ""; let activityFormat = ""; export function activityDayOffset(startDate, now = new Date()) { const [year, month, day] = String(startDate || "").slice(0, 10).split("-").map(Number); const eventDay = Date.UTC(year, month - 1, day); const currentDay = Date.UTC(now.getFullYear(), now.getMonth(), now.getDate()); return Math.round((eventDay - currentDay) / 86400000); } function create(tag, className = "", text = "") { const node = document.createElement(tag); if (className) node.className = className; if (text) node.textContent = text; return node; } function link(label, href, className = "activity-action") { const node = create("a", className, label); node.href = href; node.target = "_blank"; node.rel = "noopener noreferrer nofollow"; return node; } function selectControl(label, placeholder, values, value) { const field = create("label", "activity-filter-field"); field.appendChild(create("span", "", label)); const select = create("select", "activity-filter-control"); const all = create("option", "", placeholder); all.value = ""; select.appendChild(all); values.forEach((item) => { const option = create("option", "", item); option.value = item; select.appendChild(option); }); select.value = value; field.appendChild(select); return { field, select }; } function activityCard(item, translationProvider) { const card = create("article", "activity-card"); const visual = create("div", "activity-card-visual"); const poster = create("div", "activity-generated-poster"); poster.append( create("span", "activity-poster-source", item.source || item.authority || "官方活动"), create("strong", "", item.title), create("small", "", `${item.region || "全球"} · ${item.activity_type || "跨境活动"}`), ); visual.appendChild(poster); if (item.image) { const image = create("img", "activity-card-image"); image.src = item.image; image.alt = `${item.title} 活动海报`; image.width = 640; image.height = 360; image.loading = "lazy"; image.referrerPolicy = "no-referrer"; image.addEventListener("error", () => image.remove()); visual.appendChild(image); } const dateBlock = create("div", "activity-date-block"); const daysUntil = activityDayOffset(item.start_date); const eventDate = new Date(`${item.start_date}T00:00:00`); const monthLabel = new Intl.DateTimeFormat("zh-CN", { month: "long" }).format(eventDate); const dayLabel = new Intl.DateTimeFormat("zh-CN", { day: "numeric" }).format(eventDate).replace(/日$/, ""); const countdownLabel = daysUntil === 0 ? "今天" : `${daysUntil} 天后`; dateBlock.setAttribute("aria-label", `${item.start_date},${countdownLabel}`); dateBlock.append( create("span", "activity-date-month", monthLabel), create("strong", "activity-date-day", dayLabel), create("span", "activity-date-countdown", countdownLabel), ); visual.appendChild(dateBlock); const body = create("div", "activity-card-body"); const meta = create("div", "activity-card-meta"); meta.append( create("span", `activity-source-level${item.official_activity ? " is-official" : ""}`, item.source_level), create("span", "", item.activity_type), create("span", "", item.format), ); body.append(meta, create("h3", "", item.title)); if (item.title_original) body.appendChild(create("p", "activity-title-original", item.title_original)); body.appendChild(create("p", "activity-location", `${item.start_date} · ${item.location || item.region}`)); const tags = create("div", "activity-platforms"); (item.platforms || []).forEach((platform) => tags.appendChild(create("span", "", platform))); if (tags.children.length) body.appendChild(tags); body.append( create("p", "activity-summary", item.summary), create("p", "activity-next-action", item.next_action), ); const actions = create("div", "activity-actions"); const registrationUrl = item.registration_url || item.url; if (registrationUrl) actions.appendChild(link(item.format === "线上" ? "查看并报名" : "查看地点并报名", registrationUrl, "activity-action primary")); const sourceUrl = item.source_url || ""; if (sourceUrl && sourceUrl !== registrationUrl && sourceUrl !== item.url) actions.appendChild(link("查看来源页", sourceUrl)); const googleUrl = item.translation_url; const yandexUrl = item.translation_fallback_url; const translationUrl = translationProvider === "yandex" ? (yandexUrl || googleUrl) : translationProvider === "google" ? (googleUrl || yandexUrl) : ""; if (translationUrl) actions.appendChild(link("查看译文版", translationUrl, `activity-action translate${translationUrl === yandexUrl ? " is-fallback" : ""}`)); body.append(actions, create("p", "activity-proof", `${item.source} · ${item.authority} · 核验 ${String(item.verified_at || "").slice(0, 10)}`)); card.append(visual, body); return card; } export function renderActivities(items, container, translationProvider = "") { const sourceItems = Array.isArray(items) ? items : []; const currentItems = sourceItems.filter((item) => activityDayOffset(item.start_date) >= 0); const toolbar = create("section", "activity-filter-toolbar"); const focus = create("div", "activity-focus-switch"); [ ["priority", "优先参加"], ["direct", "家纺直接相关"], ["must", "跨境必看"], ["all", "全部活动"], ].forEach(([value, label]) => { const button = create("button", `activity-focus-btn${activityFocus === value ? " active" : ""}`, label); button.type = "button"; button.addEventListener("click", () => { activityFocus = value; render(); }); focus.appendChild(button); }); const regions = [...new Set(currentItems.map((item) => item.region).filter(Boolean))].sort((a, b) => a.localeCompare(b, "zh-CN")); const platforms = [...new Set(currentItems.flatMap((item) => item.platforms || []))].sort((a, b) => a.localeCompare(b, "zh-CN")); const regionField = selectControl("目标地区", "全部地区", regions, activityRegion); const platformField = selectControl("相关平台", "全部平台", platforms, activityPlatform); const formatField = selectControl("参加方式", "线上和线下", ["线上", "线下"], activityFormat); const fields = create("div", "activity-filter-fields"); fields.append(regionField.field, platformField.field, formatField.field); toolbar.append(focus, fields); const summary = create("section", "scene-summary activity-summary-bar"); const grid = create("div", "activity-grid"); const render = () => { focus.querySelectorAll?.("button").forEach?.((button, index) => { button.classList.toggle("active", ["priority", "direct", "must", "all"][index] === activityFocus); }); const selected = currentItems.filter((item) => { if (activityFocus === "priority" && !["direct", "must"].includes(item.relevance)) return false; if (["direct", "must"].includes(activityFocus) && item.relevance !== activityFocus) return false; if (activityRegion && item.region !== activityRegion) return false; if (activityPlatform && !(item.platforms || []).includes(activityPlatform)) return false; if (activityFormat && item.format !== activityFormat) return false; return true; }).sort((a, b) => String(a.start_date).localeCompare(String(b.start_date)) || Number(b.official_activity) - Number(a.official_activity)); summary.innerHTML = ""; summary.append( create("strong", "", `${selected.length}/${currentItems.length} 场可参加活动`), create("span", "", "官方活动优先;行业媒体用于发现,报名与议程以主办方页面为准"), ); grid.innerHTML = ""; selected.forEach((item) => grid.appendChild(activityCard(item, translationProvider))); if (!selected.length) grid.appendChild(create("div", "intelligence-empty", "当前筛选下没有可参加活动,请调整地区、平台或活动类型。")); }; regionField.select.addEventListener("change", () => { activityRegion = regionField.select.value; render(); }); platformField.select.addEventListener("change", () => { activityPlatform = platformField.select.value; render(); }); formatField.select.addEventListener("change", () => { activityFormat = formatField.select.value; render(); }); container.append(toolbar, summary, grid); render(); }