同步家纺情报 v2 数据、59 个严格权威源、趋势图库、移动端交互、Google/Yandex 翻译门控及静态 SEO/GEO 构建。 补充面向 C# 研发的 README,明确当前无 LLM、多模态与生图模型,记录翻译请求参数、缓存与失败策略、数据处理边界、前端职责和无 GitHub Pages 部署流程。 新增按日期倒序维护的 CHANGELOG,记录影响、验证与代码/数据/dist 回滚方法。 验证:11 个 Python 契约测试、13 个 Node 前端测试通过;静态构建生成 1096 个 Sitemap URL。
201 lines
7.5 KiB
JavaScript
201 lines
7.5 KiB
JavaScript
import { state } from "./render-intelligence-state.js?v=20260814e";
|
|
import { TEXT, create, detectViewerCountry, currentTranslationProvider, formatVerified, sectionItems, matchesFilters, makeCard, renderCalendar, renderPlatforms } from "./render-intelligence-core.js?v=20260814k";
|
|
import { renderExhibitions, renderPolicies, renderStandards, renderTrendNews, renderTrendSearchHub, trendItems } from "./render-intelligence-scenes.js?v=20260814i";
|
|
import { renderActivities } from "./render-intelligence-activities.js?v=20260814h";
|
|
|
|
function renderStats(data, container) {
|
|
if (!container) return;
|
|
container.innerHTML = "";
|
|
const values = [
|
|
["新增权威源", `${data.source_success || 0}/${data.source_target || 0}`],
|
|
["总体源基线", data.total_source_baseline || 0],
|
|
["重点备货商机", data.summary?.calendar_featured || data.summary?.calendar_insights || 0],
|
|
["已核实展会", data.summary?.exhibitions || 0],
|
|
["政策与召回", data.summary?.policies || 0],
|
|
["重点销售平台", data.summary?.platforms || data.platforms?.length || 0],
|
|
["可参加活动", data.summary?.activities || data.activities?.length || 0],
|
|
];
|
|
values.forEach(([label, value]) => {
|
|
const node = create("div", "intelligence-stat");
|
|
node.append(create("span", "", label), create("strong", "", String(value)));
|
|
container.appendChild(node);
|
|
});
|
|
}
|
|
|
|
function renderTabs(data, container, render) {
|
|
if (!container) return;
|
|
container.innerHTML = "";
|
|
Object.entries(TEXT.tabs).forEach(([key, label]) => {
|
|
const button = create("button", `intelligence-tab${state.activeSection === key ? " active" : ""}`, label);
|
|
button.type = "button";
|
|
button.setAttribute("role", "tab");
|
|
button.setAttribute("aria-selected", String(state.activeSection === key));
|
|
button.setAttribute("aria-controls", "intelligenceList");
|
|
const count = key === "today"
|
|
? data.today?.length
|
|
: (key === "calendar"
|
|
? (data.calendar_insights?.length || data.calendar?.length)
|
|
: (key === "standards" ? ((data.standards?.length || 0) + trendItems(data).length) : data[key]?.length));
|
|
button.appendChild(create("span", "intelligence-tab-count", String(count || 0)));
|
|
button.addEventListener("click", () => {
|
|
state.activeSection = key;
|
|
render();
|
|
state.onStateChange?.();
|
|
});
|
|
container.appendChild(button);
|
|
});
|
|
}
|
|
|
|
function renderRegionOptions(data, select) {
|
|
if (!select) return;
|
|
const previous = state.region;
|
|
select.innerHTML = "";
|
|
if (state.activeSection === "calendar") {
|
|
state.region = "";
|
|
select.disabled = true;
|
|
const calendarOption = create("option", "", "使用下方日历市场筛选");
|
|
calendarOption.value = "";
|
|
select.appendChild(calendarOption);
|
|
return;
|
|
}
|
|
select.disabled = false;
|
|
const sourceItems = state.activeSection === "today" ? (data.today || []) : (data[state.activeSection] || []);
|
|
const regions = new Set(sourceItems.map((item) => item.region).filter(Boolean));
|
|
const all = create("option", "", TEXT.allRegions);
|
|
all.value = "";
|
|
select.appendChild(all);
|
|
[...regions].sort((a, b) => a.localeCompare(b, "zh-CN")).forEach((value) => {
|
|
const option = create("option", "", value);
|
|
option.value = value;
|
|
select.appendChild(option);
|
|
});
|
|
select.value = regions.has(previous) ? previous : "";
|
|
state.region = select.value;
|
|
}
|
|
|
|
function renderList(data, container) {
|
|
if (!container) return;
|
|
const filtered = sectionItems(data).filter(matchesFilters);
|
|
container.innerHTML = "";
|
|
["calendar", "exhibitions", "policies", "standards", "platforms", "activities"].forEach((section) => {
|
|
container.classList.toggle(`is-${section}`, state.activeSection === section);
|
|
});
|
|
container.classList.toggle("is-today", state.activeSection === "today");
|
|
if (!filtered.length) {
|
|
const empty = create("div", "intelligence-empty", TEXT.empty);
|
|
container.appendChild(empty);
|
|
return;
|
|
}
|
|
if (state.activeSection === "calendar") {
|
|
renderCalendar(data, container);
|
|
return;
|
|
}
|
|
if (state.activeSection === "today") {
|
|
const header = create("section", "today-briefing");
|
|
header.append(
|
|
create("strong", "", `今天有 ${filtered.length} 件值得处理的事`),
|
|
create("span", "", "按截止时间、经营相关性和风险动态排序;继续下滑可查看全部事项"),
|
|
);
|
|
container.appendChild(header);
|
|
}
|
|
if (state.activeSection === "exhibitions") {
|
|
renderExhibitions(data, container);
|
|
return;
|
|
}
|
|
if (state.activeSection === "policies") {
|
|
renderPolicies(data, container);
|
|
return;
|
|
}
|
|
if (state.activeSection === "standards") {
|
|
renderStandards(data, container);
|
|
return;
|
|
}
|
|
if (state.activeSection === "platforms") {
|
|
renderPlatforms(data, container);
|
|
return;
|
|
}
|
|
if (state.activeSection === "activities") {
|
|
renderActivities(data.activities || [], container, currentTranslationProvider());
|
|
return;
|
|
}
|
|
const visible = filtered;
|
|
visible.forEach((item) => container.appendChild(makeCard(item)));
|
|
}
|
|
|
|
export function setIntelligenceSection(value) {
|
|
if (value && Object.prototype.hasOwnProperty.call(TEXT.tabs, value)) state.activeSection = value;
|
|
}
|
|
|
|
export function getIntelligenceSection() {
|
|
return state.activeSection;
|
|
}
|
|
|
|
export function renderIntelligence(data, options = {}) {
|
|
state.currentData = data;
|
|
if (!state.calendarYear) {
|
|
state.calendarYear = Number(String(data.as_of_date || "").slice(0, 4));
|
|
state.calendarMonth = 0;
|
|
}
|
|
state.onStateChange = options.onStateChange || null;
|
|
const root = options.root || document.getElementById("intelligencePanel");
|
|
if (!root || !data) return;
|
|
|
|
const stats = root.querySelector("#intelligenceStats");
|
|
const tabs = root.querySelector("#intelligenceTabs");
|
|
const list = root.querySelector("#intelligenceList");
|
|
const search = root.querySelector("#intelligenceSearch");
|
|
const regionSelect = root.querySelector("#intelligenceRegion");
|
|
const updated = root.querySelector("#intelligenceUpdatedAt");
|
|
|
|
const rerender = () => {
|
|
renderTabs(state.currentData, tabs, rerender);
|
|
renderList(state.currentData, list);
|
|
};
|
|
state.activeIntelligenceRerender = rerender;
|
|
|
|
renderStats(data, stats);
|
|
renderRegionOptions(data, regionSelect);
|
|
renderTabs(data, tabs, rerender);
|
|
renderList(data, list);
|
|
if (updated) updated.textContent = `核验时间:${formatVerified(data.generated_at)}`;
|
|
|
|
if (search && !search.dataset.bound) {
|
|
search.dataset.bound = "1";
|
|
search.placeholder = TEXT.searchPlaceholder;
|
|
search.addEventListener("input", (event) => {
|
|
state.query = event.target.value;
|
|
renderList(state.currentData, list);
|
|
});
|
|
}
|
|
if (regionSelect && !regionSelect.dataset.bound) {
|
|
regionSelect.dataset.bound = "1";
|
|
regionSelect.addEventListener("change", (event) => {
|
|
state.region = event.target.value;
|
|
renderList(state.currentData, list);
|
|
});
|
|
}
|
|
detectViewerCountry();
|
|
}
|
|
|
|
export async function shareIntelligence() {
|
|
const shareData = {
|
|
title: "家纺跨境情报 · 每日机会与风险提醒",
|
|
text: "全球备货日历、家纺展会、跨境政策与趋势雷达",
|
|
url: window.location.href,
|
|
};
|
|
try {
|
|
if (navigator.share) {
|
|
await navigator.share(shareData);
|
|
return "已打开系统分享面板";
|
|
}
|
|
await navigator.clipboard.writeText(shareData.url);
|
|
return "分享链接已复制,可粘贴到微信或企业微信";
|
|
} catch (error) {
|
|
if (error?.name === "AbortError") return "已取消分享";
|
|
console.error("[家纺跨境情报] 分享失败:", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
state.renderList = renderList;
|