diff --git a/CHANGELOG.md b/CHANGELOG.md index c87d5e6..31a2239 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # 更新日志 +## 2026-08-15 + +### 日历实用性与筛选交互 + +- 日历排序统一使用当前 UTC 日期:未来与当天项目按日期升序展示,已过项目降权到当月未来项目之后,并在已过项目之间按日期倒序排列。 +- 当前年份的默认日历视图不再把今天之前的节日放入首屏月份统计;用户主动选择某个月时仍可查看该月完整历史记录。 +- “目标区域”在日历页直接隐藏,避免与下方市场区域、国家/地区、产品方向和销售平台筛选重复;离开日历页后自动恢复。 +- 删除主站校验时间行里的旧式箭头分享控件对应逻辑;独立站保留正常的顶部分享按钮。同步更新独立站静态资源版本号,避免部署命中旧缓存。 + +### 验证 + +- 独立站 Node 前端测试 14 项、Python 合同测试 11 项通过。 +- 独立站静态构建成功;390×844 移动端检查通过,无控制台错误,日历首条为 2026-08-15。 + 本文件按日期倒序记录独立版的代码、数据、交互和部署变化。每次提交必须先更新本文件,确保公司研发人员可以判断影响范围并使用 `git revert ` 回滚。 ## 2026-08-14 - 平台渠道入口与数据快照对齐 diff --git a/public/assets/css/intelligence.css b/public/assets/css/intelligence.css index 1e59700..ea4a7ff 100644 --- a/public/assets/css/intelligence.css +++ b/public/assets/css/intelligence.css @@ -67,21 +67,6 @@ .intelligence-overview-actions { display: flex; align-items: center; gap: 10px; flex: 0 0 auto; } #intelligenceUpdatedAt { color: var(--muted); font-size: 12px; } -.intelligence-share-btn { - display: inline-flex; - align-items: center; - min-height: 38px; - padding: 0 14px; - border: 1px solid var(--line); - border-radius: 6px; - background: transparent; - color: var(--muted); - font-weight: 800; - cursor: pointer; -} - -.intelligence-share-btn:hover { border-color: var(--intel-brand-blue); color: var(--intel-brand-blue); } - .intelligence-stats { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); @@ -608,9 +593,8 @@ .intelligence-overview p:last-child { color: var(--ink-secondary); font-size: 16px; line-height: 1.65; } .intelligence-overview-actions { align-items: center; } .intelligence-overview-actions > span { color: var(--ink-secondary); font-size: 13px; } -.intelligence-share-btn { display: inline-grid; width: 44px; min-width: 44px; height: 44px; padding: 0; place-items: center; border: 1px solid var(--line); background: transparent; color: var(--muted); font-size: 20px; } -.intelligence-share-btn:hover, .intelligence-share-btn:focus-visible { border-color: var(--ink-secondary); background: var(--trans-bg); color: var(--ink); } .intelligence-search-field { display: grid; gap: 6px; min-width: 0; color: var(--ink-secondary); font-size: 14px; font-weight: 700; } +.intelligence-search-field[hidden] { display: none !important; } .intelligence-toolbar input, .intelligence-toolbar select { min-height: 48px; font-size: 16px; } .intelligence-tab { min-height: 48px; font-size: 16px; } .intelligence-tab-count, .intelligence-badge { font-size: 13px; } diff --git a/public/assets/js/app.js b/public/assets/js/app.js index cf1126d..4804b97 100644 --- a/public/assets/js/app.js +++ b/public/assets/js/app.js @@ -1,4 +1,4 @@ -import { getIntelligenceSection, renderIntelligence, setIntelligenceSection, shareIntelligence } from "./render-intelligence.js?v=20260814o"; +import { getIntelligenceSection, renderIntelligence, setIntelligenceSection, shareIntelligence } from "./render-intelligence.js?v=20260815a"; const DATA_ROOT = "./data"; diff --git a/public/assets/js/render-intelligence-core.js b/public/assets/js/render-intelligence-core.js index 7f9cf56..26befb3 100644 --- a/public/assets/js/render-intelligence-core.js +++ b/public/assets/js/render-intelligence-core.js @@ -543,6 +543,28 @@ function formatMonthDay(value) { }; } +export function calendarTodayIso(now = new Date()) { + return now.toISOString().slice(0, 10); +} + +export function sortCalendarItems(items, now = new Date()) { + const today = calendarTodayIso(now); + return [...items].sort((a, b) => { + const dateA = String(a.start_date || "").slice(0, 10); + const dateB = String(b.start_date || "").slice(0, 10); + const pastOrder = Number(dateA < today) - Number(dateB < today); + if (pastOrder) return pastOrder; + const dateOrder = dateA < today && dateB < today ? dateB.localeCompare(dateA) : dateA.localeCompare(dateB); + return dateOrder || Number(b.priority_score || 0) - Number(a.priority_score || 0); + }); +} + +export function calendarDefaultItems(items, year, now = new Date()) { + const today = calendarTodayIso(now); + if (Number(year) !== Number(today.slice(0, 4))) return [...items]; + return items.filter((item) => String(item.start_date || "").slice(0, 10) >= today); +} + function compactList(values, limit = 5) { const unique = [...new Set((values || []).filter(Boolean))]; if (unique.length <= limit) return unique.join("、") || "暂无专项信息"; @@ -902,23 +924,25 @@ function renderCalendar(data, container) { decisionToolbar.appendChild(create("p", "calendar-platform-filter-hint", TEXT.calendar.platformFilterHint)); container.appendChild(decisionToolbar); + const today = calendarTodayIso(); const selectedMarketItems = all.filter((item) => { if (state.calendarMarketGroup && !(item.affected_regions || []).includes(state.calendarMarketGroup)) return false; if (state.calendarCountry && !(item.affected_countries || []).includes(state.calendarCountry)) return false; if (state.calendarProduct && !calendarProductCategories(item).includes(state.calendarProduct)) return false; if (state.calendarPlatform && !calendarPlatformFamilies(item).includes(state.calendarPlatform)) return false; - if (state.calendarFocus === "opportunity" && !isCalendarOpportunity(item, data.as_of_date)) return false; + if (state.calendarFocus === "opportunity" && !isCalendarOpportunity(item, today)) return false; return true; }); const yearItems = selectedMarketItems.filter((item) => Number(String(item.start_date || "").slice(0, 4)) === state.calendarYear); + const displayYearItems = state.calendarMonth ? yearItems : calendarDefaultItems(yearItems, state.calendarYear); const monthCounts = new Map(); - yearItems.forEach((item) => { + displayYearItems.forEach((item) => { const month = Number(String(item.start_date || "").slice(5, 7)); monthCounts.set(month, (monthCounts.get(month) || 0) + 1); }); const monthRail = create("div", "calendar-month-rail"); const allButton = create("button", `calendar-month-btn${state.calendarMonth === 0 ? " active" : ""}`); - allButton.append(create("strong", "", "全年"), create("span", "calendar-month-count", `${yearItems.length} 条`)); + allButton.append(create("strong", "", "全年"), create("span", "calendar-month-count", `${displayYearItems.length} 条`)); allButton.type = "button"; allButton.addEventListener("click", () => { state.calendarMonth = 0; @@ -942,12 +966,11 @@ function renderCalendar(data, container) { "back-to-school", "singles-day", "black-friday", "cyber-monday", "winter-holiday", "christmas", "lunar-new-year", "eid-fitr", "super-bowl", "la28", ]; - const highValueItems = yearItems - .filter((item) => item.business_relevance === "high" && isCalendarOpportunity(item, data.as_of_date)) - .sort((a, b) => String(a.start_date).localeCompare(String(b.start_date))); - const selected = yearItems + const highValueItems = displayYearItems + .filter((item) => item.business_relevance === "high" && isCalendarOpportunity(item, today)); + const selected = sortCalendarItems(displayYearItems .filter((item) => !state.calendarMonth || Number(String(item.start_date || "").slice(5, 7)) === state.calendarMonth) - .sort((a, b) => String(a.start_date).localeCompare(String(b.start_date)) || Number(b.priority_score || 0) - Number(a.priority_score || 0)); + , new Date()); if (state.calendarMonth) container.appendChild(renderCalendarMonthGrid(state.calendarYear, state.calendarMonth, selected)); const highlightPool = state.calendarMonth ? highValueItems.filter((item) => Number(String(item.start_date).slice(5, 7)) === state.calendarMonth) @@ -996,91 +1019,4 @@ function renderCalendar(data, container) { }); } -function makePlatformCard(item) { - const card = create("article", "platform-card"); - const header = create("div", "platform-card-header"); - const logoBox = create("div", "platform-logo-box"); - const logo = create("img", "platform-logo"); - logo.src = `./assets/platform-logos/${item.logo}.svg`; - logo.alt = `${item.name} 标志`; - logo.width = 112; - logo.height = 48; - logoBox.appendChild(logo); - const title = create("div", "platform-title"); - title.append(create("span", "", `${item.region} · ${item.type}`), create("h3", "", item.name)); - header.append(logoBox, title, makeBadge(`${item.access_level} ${item.access_label}`, `platform-access is-${String(item.access_level).toLowerCase()}`)); - const markets = create("div", "platform-markets"); - appendTags(markets, item.markets || []); - const fit = create("div", "platform-fit"); - fit.append(create("strong", "", "适合谁"), create("p", "", compactList(item.fit_sellers || []))); - const category = create("div", "platform-category"); - category.append(create("strong", "", "家纺经营重点"), create("p", "", item.textile_category || "打开平台类目核对。")); - const access = create("div", "platform-access-note"); - access.append(create("strong", "", "入驻判断"), create("p", "", item.cross_border || "以平台当前规则为准。")); - const actions = create("div", "platform-actions"); - if (item.homepage_url && item.homepage_url !== item.seller_url) actions.appendChild(makePlatformActionGroup("打开平台官网", item.homepage_url, item, "homepage")); - actions.appendChild(makePlatformActionGroup("去官方入驻", item.seller_url, item, "seller", "platform-action primary")); - if (item.language_url) actions.appendChild(makeExternalLink("官网学习中心(可选中文)", item.language_url, "platform-action language")); - const proof = create("p", "platform-proof", `${item.confidence} · ${item.link_status || "待人工复核"} · 核验 ${String(item.checked_at || "").slice(0, 10)}`); - card.append(header, markets, category, access, fit, actions, proof); - return card; -} - -function renderPlatforms(data, container) { - const all = (data.platforms || []).filter(matchesFilters); - const toolbar = create("section", "platform-filter-toolbar"); - const makeField = (label, control, extraClass = "") => { - const field = create("label", `platform-filter-field ${extraClass}`.trim()); - field.append(create("span", "", label), control); - return field; - }; - const makeSelect = (placeholder, values, current) => { - const select = create("select", "platform-filter-control"); - select.appendChild(Object.assign(create("option", "", placeholder), { value: "" })); - values.forEach((value) => select.appendChild(Object.assign(create("option", "", value), { value }))); - select.value = current; - return select; - }; - const markets = [...new Set(all.flatMap((item) => item.markets || []))].sort((a, b) => a.localeCompare(b, "zh-CN")); - const types = [...new Set(all.map((item) => item.type).filter(Boolean))].sort((a, b) => a.localeCompare(b, "zh-CN")); - const marketSelect = makeSelect("全部市场", markets, state.platformMarket); - const typeSelect = makeSelect("全部类型", types, state.platformType); - const accessSelect = makeSelect("全部开放等级", ["A", "B", "C"].filter((level) => all.some((item) => item.access_level === level)), state.platformAccess); - const search = create("input", "platform-filter-control"); - search.type = "search"; - search.name = "platform-channel-search"; - search.autocomplete = "off"; - search.placeholder = "搜索平台名称、国家或家纺类目…"; - search.value = state.platformSearch; - toolbar.append( - makeField("目标市场", marketSelect), - makeField("渠道类型", typeSelect), - makeField("开放等级", accessSelect), - makeField("平台检索", search, "platform-filter-search"), - ); - const header = create("section", "scene-summary platform-summary"); - const grid = create("div", "platform-grid"); - const apply = () => { - const keyword = normalized(state.platformSearch); - const selected = all.filter((item) => { - if (state.platformMarket && !(item.markets || []).includes(state.platformMarket)) return false; - if (state.platformType && item.type !== state.platformType) return false; - if (state.platformAccess && item.access_level !== state.platformAccess) return false; - if (!keyword) return true; - return normalized([item.name, item.region, item.type, item.textile_category, item.cross_border, ...(item.markets || []), ...(item.fit_sellers || [])].join(" ")).includes(keyword); - }).sort((a, b) => (Number(b.home_textile_sales_score || 0) + Number(b.emergence_score || 0)) - (Number(a.home_textile_sales_score || 0) + Number(a.emergence_score || 0)) || String(a.access_level).localeCompare(String(b.access_level)) || a.name.localeCompare(b.name, "zh-CN")); - header.innerHTML = ""; - header.append(create("strong", "", `${selected.length}/${all.length} 个家纺销售渠道`), create("span", "", "准入条件以平台卖家中心为准")); - grid.innerHTML = ""; - selected.forEach((item) => grid.appendChild(makePlatformCard(item))); - if (!selected.length) grid.appendChild(create("div", "intelligence-empty", "当前筛选条件下没有已核验平台,请调整市场或开放等级。")); - }; - marketSelect.addEventListener("change", () => { state.platformMarket = marketSelect.value; apply(); }); - typeSelect.addEventListener("change", () => { state.platformType = typeSelect.value; apply(); }); - accessSelect.addEventListener("change", () => { state.platformAccess = accessSelect.value; apply(); }); - search.addEventListener("input", () => { state.platformSearch = search.value; apply(); }); - container.append(toolbar, header, grid); - apply(); -} - -export { TEXT, create, openLightbox, sectionItems, trendItems, normalized, matchesFilters, formatDate, formatVerified, sectionOf, makeVisual, makeBadge, makeExternalLink, sourceLinkLabel, currentTranslationProvider, preferredTranslation, detectViewerCountry, cultureReferenceUrl, appendTranslationLinks, appendPrefixedTranslationLink, makePlatformActionGroup, itemShareUrl, makeShareButton, makeCard, appendTags, formatMonthDay, compactList, calendarProductCategories, escapeIcs, nextDateValue, makeCalendarDownloadButton, calendarScopeText, makeCalendarFact, makeLocalHolidayNames, makeCalendarDateVariants, countryFlag, isCalendarOpportunity, makeCalendarCard, makeCalendarSelect, renderCalendarMonthGrid, renderCalendar, makePlatformCard, renderPlatforms }; +export { TEXT, create, openLightbox, sectionItems, trendItems, normalized, matchesFilters, formatDate, formatVerified, sectionOf, makeVisual, makeBadge, makeExternalLink, sourceLinkLabel, currentTranslationProvider, preferredTranslation, detectViewerCountry, cultureReferenceUrl, appendTranslationLinks, appendPrefixedTranslationLink, makePlatformActionGroup, itemShareUrl, makeShareButton, makeCard, appendTags, formatMonthDay, compactList, calendarProductCategories, escapeIcs, nextDateValue, makeCalendarDownloadButton, calendarScopeText, makeCalendarFact, makeLocalHolidayNames, makeCalendarDateVariants, countryFlag, isCalendarOpportunity, makeCalendarCard, makeCalendarSelect, renderCalendarMonthGrid, renderCalendar }; diff --git a/public/assets/js/render-intelligence-platforms.js b/public/assets/js/render-intelligence-platforms.js new file mode 100644 index 0000000..6c8a374 --- /dev/null +++ b/public/assets/js/render-intelligence-platforms.js @@ -0,0 +1,84 @@ +import { state } from "./render-intelligence-state.js?v=20260814e"; +import { create, normalized, matchesFilters, makeBadge, makeExternalLink, makePlatformActionGroup, appendTags, compactList } from "./render-intelligence-core.js?v=20260815a"; + +function makePlatformCard(item) { + const card = create("article", "platform-card"); + const header = create("div", "platform-card-header"); + const logoBox = create("div", "platform-logo-box"); + const logo = create("img", "platform-logo"); + logo.src = `./assets/platform-logos/${item.logo}.svg`; + logo.alt = `${item.name} 标志`; + logo.width = 112; + logo.height = 48; + logoBox.appendChild(logo); + const title = create("div", "platform-title"); + title.append(create("span", "", `${item.region} · ${item.type}`), create("h3", "", item.name)); + header.append(logoBox, title, makeBadge(`${item.access_level} ${item.access_label}`, `platform-access is-${String(item.access_level).toLowerCase()}`)); + const markets = create("div", "platform-markets"); + appendTags(markets, item.markets || []); + const fit = create("div", "platform-fit"); + fit.append(create("strong", "", "适合谁"), create("p", "", compactList(item.fit_sellers || []))); + const category = create("div", "platform-category"); + category.append(create("strong", "", "家纺经营重点"), create("p", "", item.textile_category || "打开平台类目核对。")); + const access = create("div", "platform-access-note"); + access.append(create("strong", "", "入驻判断"), create("p", "", item.cross_border || "以平台当前规则为准。")); + const actions = create("div", "platform-actions"); + if (item.homepage_url && item.homepage_url !== item.seller_url) actions.appendChild(makePlatformActionGroup("打开平台官网", item.homepage_url, item, "homepage")); + actions.appendChild(makePlatformActionGroup("去官方入驻", item.seller_url, item, "seller", "platform-action primary")); + if (item.language_url) actions.appendChild(makeExternalLink("官网学习中心(可选中文)", item.language_url, "platform-action language")); + const proof = create("p", "platform-proof", `${item.confidence} · ${item.link_status || "待人工复核"} · 核验 ${String(item.checked_at || "").slice(0, 10)}`); + card.append(header, markets, category, access, fit, actions, proof); + return card; +} + +export function renderPlatforms(data, container) { + const all = (data.platforms || []).filter(matchesFilters); + const toolbar = create("section", "platform-filter-toolbar"); + const makeField = (label, control, extraClass = "") => { + const field = create("label", `platform-filter-field ${extraClass}`.trim()); + field.append(create("span", "", label), control); + return field; + }; + const makeSelect = (placeholder, values, current) => { + const select = create("select", "platform-filter-control"); + select.appendChild(Object.assign(create("option", "", placeholder), { value: "" })); + values.forEach((value) => select.appendChild(Object.assign(create("option", "", value), { value }))); + select.value = current; + return select; + }; + const markets = [...new Set(all.flatMap((item) => item.markets || []))].sort((a, b) => a.localeCompare(b, "zh-CN")); + const types = [...new Set(all.map((item) => item.type).filter(Boolean))].sort((a, b) => a.localeCompare(b, "zh-CN")); + const marketSelect = makeSelect("全部市场", markets, state.platformMarket); + const typeSelect = makeSelect("全部类型", types, state.platformType); + const accessSelect = makeSelect("全部开放等级", ["A", "B", "C"].filter((level) => all.some((item) => item.access_level === level)), state.platformAccess); + const search = create("input", "platform-filter-control"); + search.type = "search"; + search.name = "platform-channel-search"; + search.autocomplete = "off"; + search.placeholder = "搜索平台名称、国家或家纺类目…"; + search.value = state.platformSearch; + toolbar.append(makeField("目标市场", marketSelect), makeField("渠道类型", typeSelect), makeField("开放等级", accessSelect), makeField("平台检索", search, "platform-filter-search")); + const header = create("section", "scene-summary platform-summary"); + const grid = create("div", "platform-grid"); + const apply = () => { + const keyword = normalized(state.platformSearch); + const selected = all.filter((item) => { + if (state.platformMarket && !(item.markets || []).includes(state.platformMarket)) return false; + if (state.platformType && item.type !== state.platformType) return false; + if (state.platformAccess && item.access_level !== state.platformAccess) return false; + if (!keyword) return true; + return normalized([item.name, item.region, item.type, item.textile_category, item.cross_border, ...(item.markets || []), ...(item.fit_sellers || [])].join(" ")).includes(keyword); + }).sort((a, b) => (Number(b.home_textile_sales_score || 0) + Number(b.emergence_score || 0)) - (Number(a.home_textile_sales_score || 0) + Number(a.emergence_score || 0)) || String(a.access_level).localeCompare(String(b.access_level)) || a.name.localeCompare(b.name, "zh-CN")); + header.innerHTML = ""; + header.append(create("strong", "", `${selected.length}/${all.length} 个家纺销售渠道`), create("span", "", "准入条件以平台卖家中心为准")); + grid.innerHTML = ""; + selected.forEach((item) => grid.appendChild(makePlatformCard(item))); + if (!selected.length) grid.appendChild(create("div", "intelligence-empty", "当前筛选条件下没有已核验平台,请调整市场或开放等级。")); + }; + marketSelect.addEventListener("change", () => { state.platformMarket = marketSelect.value; apply(); }); + typeSelect.addEventListener("change", () => { state.platformType = typeSelect.value; apply(); }); + accessSelect.addEventListener("change", () => { state.platformAccess = accessSelect.value; apply(); }); + search.addEventListener("input", () => { state.platformSearch = search.value; apply(); }); + container.append(toolbar, header, grid); + apply(); +} diff --git a/public/assets/js/render-intelligence-scenes.js b/public/assets/js/render-intelligence-scenes.js index bf71284..e480e71 100644 --- a/public/assets/js/render-intelligence-scenes.js +++ b/public/assets/js/render-intelligence-scenes.js @@ -1,5 +1,5 @@ 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"; +import { TEXT, create, openLightbox, normalized, matchesFilters, makeVisual, makeBadge, makeExternalLink, sourceLinkLabel, currentTranslationProvider, appendTranslationLinks, appendTags, formatDate, formatVerified, makeShareButton } from "./render-intelligence-core.js?v=20260815a"; function makeExhibitionCard(item) { const article = create("article", "exhibition-card"); diff --git a/public/assets/js/render-intelligence.js b/public/assets/js/render-intelligence.js index 20396d4..38afa4b 100644 --- a/public/assets/js/render-intelligence.js +++ b/public/assets/js/render-intelligence.js @@ -1,7 +1,8 @@ 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=20260814l"; +import { TEXT, create, detectViewerCountry, currentTranslationProvider, formatVerified, sectionItems, matchesFilters, makeCard, renderCalendar } from "./render-intelligence-core.js?v=20260815a"; import { renderExhibitions, renderPolicies, renderStandards, renderTrendNews, renderTrendSearchHub, trendItems } from "./render-intelligence-scenes.js?v=20260814i"; import { renderActivities } from "./render-intelligence-activities.js?v=20260814h"; +import { renderPlatforms } from "./render-intelligence-platforms.js?v=20260815a"; function renderStats(data, container) { if (!container) return; @@ -48,6 +49,8 @@ function renderTabs(data, container, render) { function renderRegionOptions(data, select) { if (!select) return; + const regionField = typeof select.closest === "function" ? select.closest("label") : null; + if (regionField) regionField.hidden = state.activeSection === "calendar"; const previous = state.region; select.innerHTML = ""; if (state.activeSection === "calendar") { diff --git a/public/index.html b/public/index.html index a49fddd..088394d 100644 --- a/public/index.html +++ b/public/index.html @@ -25,7 +25,7 @@ - + @@ -78,6 +78,6 @@
- + diff --git a/tests/test_frontend.mjs b/tests/test_frontend.mjs index 70db42b..7f41881 100644 --- a/tests/test_frontend.mjs +++ b/tests/test_frontend.mjs @@ -10,11 +10,12 @@ test("独立首页只展示家纺情报并加载独立入口", async () => { const dongguanCss = await text("public/assets/fonts/dongguan-bold/result.css"); assert.match(html, /三笙AI情报局/); assert.match(html, /assets\/js\/app\.js/); - assert.match(html, /assets\/js\/app\.js\?v=20260814q/); + assert.match(html, /assets\/js\/app\.js\?v=20260815a/); assert.match(html, /assets\/css\/base\.css\?v=20260814h/); - assert.match(html, /assets\/css\/intelligence\.css\?v=20260814s/); + assert.match(html, /assets\/css\/intelligence\.css\?v=20260815a/); assert.match(html, /]+class="intelligence-metrics"/); assert.match(html, /]+class="intelligence-utility-row"/); + assert.doesNotMatch(html, /id="intelligenceShareBtn"/); assert.match(html, /class="tool-icon"/); assert.match(html, /assets\/fonts\/dongguan-bold\/result\.css/); assert.doesNotMatch(html, /cn-font\.claude-code-best\.win/); @@ -33,7 +34,7 @@ test("数据快照不复用浏览器旧缓存", async () => { test("七个业务入口与趋势四子类均保留", async () => { const renderer = await text("public/assets/js/render-intelligence.js"); - const modules = renderer + await text("public/assets/js/render-intelligence-core.js") + await text("public/assets/js/render-intelligence-scenes.js"); + const modules = renderer + await text("public/assets/js/render-intelligence-core.js") + await text("public/assets/js/render-intelligence-scenes.js") + await text("public/assets/js/render-intelligence-platforms.js"); for (const label of ["今日必看", "备货日历", "展会通", "政策预警", "趋势雷达", "平台渠道", "跨境活动"]) { assert.match(modules, new RegExp(label)); } @@ -59,6 +60,7 @@ test("所有外部业务链接使用新窗口和安全rel", async () => { await text("public/assets/js/render-intelligence.js"), await text("public/assets/js/render-intelligence-core.js"), await text("public/assets/js/render-intelligence-scenes.js"), + await text("public/assets/js/render-intelligence-platforms.js"), await text("public/assets/js/render-intelligence-activities.js"), ].join("\n"); assert.match(files, /target\s*=\s*["']_blank["']/); @@ -67,7 +69,7 @@ test("所有外部业务链接使用新窗口和安全rel", async () => { test("趋势分类读取真实 items 数组而不是分类对象", async () => { const renderer = await text("public/assets/js/render-intelligence.js"); - const modules = renderer + await text("public/assets/js/render-intelligence-core.js") + await text("public/assets/js/render-intelligence-scenes.js"); + const modules = renderer + await text("public/assets/js/render-intelligence-core.js") + await text("public/assets/js/render-intelligence-scenes.js") + await text("public/assets/js/render-intelligence-platforms.js"); assert.match(modules, /category\.items/); assert.match(modules, /item\.image\s*\|\|\s*item\.thumbnail/); assert.match(modules, /function renderCalendarMonthGrid/); @@ -153,9 +155,23 @@ test("跨境活动按访问当天重新计算相对日期并排除已结束活 assert.equal(activityDayOffset("2026-08-15", now), 1); }); +test("日历按当前 UTC 日期优先展示未过期节点并把已过节点压到后面", async () => { + const { calendarTodayIso, sortCalendarItems, calendarDefaultItems } = await import(new URL(`../public/assets/js/render-intelligence-core.js?calendar-date=${Date.now()}`, import.meta.url)); + const now = new Date("2026-08-15T00:30:00.000Z"); + const items = [ + { id: "july", start_date: "2026-07-15", priority_score: 90 }, + { id: "past-august", start_date: "2026-08-10", priority_score: 90 }, + { id: "today", start_date: "2026-08-15", priority_score: 1 }, + { id: "future", start_date: "2026-08-17", priority_score: 1 }, + ]; + assert.equal(calendarTodayIso(now), "2026-08-15"); + assert.deepEqual(sortCalendarItems(items, now).map((item) => item.id), ["today", "future", "past-august", "july"]); + assert.deepEqual(calendarDefaultItems(items, 2026, now).map((item) => item.id), ["today", "future"]); +}); + test("最新家纺交互与移动端契约已同步", async () => { const renderer = await text("public/assets/js/render-intelligence.js"); - const modules = renderer + await text("public/assets/js/render-intelligence-core.js") + await text("public/assets/js/render-intelligence-scenes.js"); + const modules = renderer + await text("public/assets/js/render-intelligence-core.js") + await text("public/assets/js/render-intelligence-scenes.js") + await text("public/assets/js/render-intelligence-platforms.js"); const baseCss = await text("public/assets/css/base.css"); const css = await text("public/assets/css/intelligence.css"); assert.doesNotMatch(renderer, /displayLimit/); @@ -171,6 +187,8 @@ test("最新家纺交互与移动端契约已同步", async () => { assert.doesNotMatch(modules, /家纺成熟度|新兴参考/); assert.doesNotMatch(modules, /scope_type === "state\.region"|\["state\.region", "地区政策"\]/); assert.match(modules, /scope_type === "region"/); + assert.match(renderer, /closest\("label"\)/); + assert.match(renderer, /regionField\.hidden\s*=\s*state\.activeSection === "calendar"/); assert.match(css, /@media \(max-width: 680px\)/); assert.match(css, /\.intelligence-tabs\s*\{[^}]*grid-template-columns: repeat\(3/); assert.match(css, /min-height: 48px/);