diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e97fcc..440cc63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,12 @@ - PC 选将 overlay 不再在顶栏头像下显示定位图标;仍保留网格「克/搭/补」与阵容分析条。 +## [0.6.15] - 2026-07-31 + +### Added + +- 物品详情下方展示近一年该物品的版本改动时间线(与英雄「改动」同源 `patches` 数据;无改动则不显示)。 + ## [0.6.14] - 2026-07-31 ### Changed diff --git a/web/export_relations_site.py b/web/export_relations_site.py index c7d6499..80d956a 100644 --- a/web/export_relations_site.py +++ b/web/export_relations_site.py @@ -55,7 +55,7 @@ from shared.paths import ( from seo_prerender import DEFAULT_SITE_ORIGIN, write_seo_bundle from serve_relations import WEB_DIR, build_payload -SITE_VERSION = "0.6.14" +SITE_VERSION = "0.6.15" DEFAULT_OSS_BASE = "https://climperor.oss-cn-shanghai.aliyuncs.com" diff --git a/web/frontend/app.js b/web/frontend/app.js index 4b95d18..45a8585 100644 --- a/web/frontend/app.js +++ b/web/frontend/app.js @@ -3590,6 +3590,9 @@ function renderItemDetail() { render(); }, }); + + const patchPanel = buildItemPatchChangesPanel(meta.key); + if (patchPanel) root.appendChild(patchPanel); } // Stat-icon labels for patch hero_notes (icon field). Attr values map to the @@ -3772,6 +3775,62 @@ function buildPatchChangesPanel(heroKey) { return wrap; } +/** Match shop/item key against a patch items or neutral_items entry via lookup. */ +function patchItemEntryForKey(entries, itemKey, lookup) { + if (!itemKey || !entries?.length) return null; + const itemsLookup = (lookup && lookup.items) || {}; + for (const entry of entries) { + if (!entry || entry.is_general_note || entry.ability_id === -1) continue; + const meta = itemsLookup[String(entry.ability_id)]; + if (meta && meta.key === itemKey) return entry; + } + return null; +} + +/** Recent patch notes for one item (shop detail). Null when none in window. */ +function buildItemPatchChangesPanel(itemKey) { + const patches = state.data.patches || []; + const details = state.data.patch_details || {}; + const lookup = state.data.patch_lookup || { items: {}, abilities: {}, heroes: {} }; + if (!itemKey || !patches.length) return null; + + const blocks = []; + for (const p of patches) { + const det = details[p.version]; + if (!det) continue; + const entry = + patchItemEntryForKey(det.items, itemKey, lookup) || + patchItemEntryForKey(det.neutral_items, itemKey, lookup); + if (!entry) continue; + const notesHtml = renderNotesList(entry.ability_notes); + if (!notesHtml) continue; + const patchDate = + formatAbsoluteTime(p.date || p.timestamp, { dateOnly: true }) || + p.date || + ""; + blocks.push( + `