From ebc79f36e77ff683b9be8e82f7cb39d3c4cd02d1 Mon Sep 17 00:00:00 2001 From: Tim Date: Fri, 6 Feb 2026 17:35:59 +0800 Subject: [PATCH 1/2] fix: keep browser history for home category and tag filters --- frontend_nuxt/components/ArticleCategory.vue | 2 +- frontend_nuxt/components/ArticleTags.vue | 2 +- frontend_nuxt/components/MenuComponent.vue | 4 ++-- frontend_nuxt/components/SearchDropdown.vue | 4 ++-- frontend_nuxt/pages/users/[id].vue | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/frontend_nuxt/components/ArticleCategory.vue b/frontend_nuxt/components/ArticleCategory.vue index 4e229c80b..6844d84c8 100644 --- a/frontend_nuxt/components/ArticleCategory.vue +++ b/frontend_nuxt/components/ArticleCategory.vue @@ -21,7 +21,7 @@ const props = defineProps({ const gotoCategory = async () => { if (!props.category) return const value = encodeURIComponent(props.category.id ?? props.category.name) - await navigateTo({ path: '/', query: { category: value } }, { replace: true }) + await navigateTo({ path: '/', query: { category: value } }) } const isImageIcon = (icon) => { diff --git a/frontend_nuxt/components/ArticleTags.vue b/frontend_nuxt/components/ArticleTags.vue index 09ef3aad2..7adab1c90 100644 --- a/frontend_nuxt/components/ArticleTags.vue +++ b/frontend_nuxt/components/ArticleTags.vue @@ -30,7 +30,7 @@ defineProps({ const gotoTag = async (tag) => { const value = encodeURIComponent(tag.id ?? tag.name) - await navigateTo({ path: '/', query: { tags: value } }, { replace: true }) + await navigateTo({ path: '/', query: { tags: value } }) } const isImageIcon = (icon) => { diff --git a/frontend_nuxt/components/MenuComponent.vue b/frontend_nuxt/components/MenuComponent.vue index 871eb1675..d173e40b3 100644 --- a/frontend_nuxt/components/MenuComponent.vue +++ b/frontend_nuxt/components/MenuComponent.vue @@ -357,13 +357,13 @@ const isImageIcon = (icon) => { const gotoCategory = (c) => { const value = encodeURIComponent(c.id ?? c.name) - navigateTo({ path: '/', query: { category: value } }, { replace: true }) + navigateTo({ path: '/', query: { category: value } }) handleItemClick() } const gotoTag = (t) => { const value = encodeURIComponent(t.id ?? t.name) - navigateTo({ path: '/', query: { tags: value } }, { replace: true }) + navigateTo({ path: '/', query: { tags: value } }) handleItemClick() } diff --git a/frontend_nuxt/components/SearchDropdown.vue b/frontend_nuxt/components/SearchDropdown.vue index fedbee882..4f87b2dc5 100644 --- a/frontend_nuxt/components/SearchDropdown.vue +++ b/frontend_nuxt/components/SearchDropdown.vue @@ -170,9 +170,9 @@ watch(selected, (val) => { navigateTo(`/posts/${opt.postId}#comment-${opt.id}`, { replace: true }) } } else if (opt.type === 'category') { - navigateTo({ path: '/', query: { category: opt.id } }, { replace: true }) + navigateTo({ path: '/', query: { category: opt.id } }) } else if (opt.type === 'tag') { - navigateTo({ path: '/', query: { tags: opt.id } }, { replace: true }) + navigateTo({ path: '/', query: { tags: opt.id } }) } selected.value = null keyword.value = '' diff --git a/frontend_nuxt/pages/users/[id].vue b/frontend_nuxt/pages/users/[id].vue index a2d99b307..5a482191e 100644 --- a/frontend_nuxt/pages/users/[id].vue +++ b/frontend_nuxt/pages/users/[id].vue @@ -643,7 +643,7 @@ const sendMessage = async () => { const gotoTag = (tag) => { const value = encodeURIComponent(tag.id ?? tag.name) - navigateTo({ path: '/', query: { tags: value } }, { replace: true }) + navigateTo({ path: '/', query: { tags: value } }) } const init = async () => { From 18edde64c343ebb32cea8c2dbbf9a884900ad37b Mon Sep 17 00:00:00 2001 From: Tim Date: Fri, 6 Feb 2026 17:42:35 +0800 Subject: [PATCH 2/2] fix: sync home filter dropdown state to URL history --- frontend_nuxt/pages/index.vue | 48 +++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/frontend_nuxt/pages/index.vue b/frontend_nuxt/pages/index.vue index b0f06fc56..e8e0ea650 100644 --- a/frontend_nuxt/pages/index.vue +++ b/frontend_nuxt/pages/index.vue @@ -202,6 +202,28 @@ const selectedTagsSet = (tags) => { .map((v) => (isNaN(v) ? v : Number(v))) } +const normalizeCategoryFromQuery = (category) => { + if (category == null || category === '') return '' + const raw = Array.isArray(category) ? category[0] : category + const decoded = decodeURIComponent(raw) + return isNaN(decoded) ? decoded : Number(decoded) +} + +const normalizeTagsFromQuery = (tags) => { + if (tags == null || tags === '') return [] + const raw = Array.isArray(tags) ? tags.join(',') : tags + return raw + .split(',') + .filter((v) => v) + .map((v) => decodeURIComponent(v)) + .map((v) => (isNaN(v) ? v : Number(v))) +} + +const arraysShallowEqual = (a = [], b = []) => { + if (a.length !== b.length) return false + return a.every((v, idx) => String(v) === String(b[idx])) +} + /** 初始化:仅在客户端首渲染时根据路由同步一次 **/ onMounted(() => { const { category, tags } = route.query @@ -239,6 +261,32 @@ watch( }, ) +// 从筛选器变更回写到 URL,确保浏览器历史可回退到上一个筛选状态。 +watch([selectedCategory, selectedTags], async ([category, tags]) => { + const routeCategory = normalizeCategoryFromQuery(route.query.category) + const routeTags = normalizeTagsFromQuery(route.query.tags) + + const categoryChanged = String(category ?? '') !== String(routeCategory ?? '') + const tagsChanged = !arraysShallowEqual(tags || [], routeTags) + if (!categoryChanged && !tagsChanged) return + + const nextQuery = { ...route.query } + + if (category == null || category === '') { + delete nextQuery.category + } else { + nextQuery.category = encodeURIComponent(String(category)) + } + + if (!Array.isArray(tags) || tags.length === 0) { + delete nextQuery.tags + } else { + nextQuery.tags = tags.map((v) => encodeURIComponent(String(v))).join(',') + } + + await navigateTo({ path: '/', query: nextQuery }) +}) + /** 选项加载(分类/标签名称回填) **/ const loadOptions = async () => { if (selectedCategory.value && !isNaN(selectedCategory.value)) {