fix: 第31轮搜索审计修复 — 否定组日期NULL安全/多列NOT假阳性/year_counts一致性/缓存键空格等8项
CI / backend (push) Canceled after 0s
CI / frontend (push) Canceled after 0s

This commit is contained in:
34047007@qq.com
2026-07-29 08:24:28 +08:00
parent fc53a5b255
commit 0197867153
5 changed files with 169 additions and 18 deletions
+13 -2
View File
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted, watch } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { useRouter, useRoute, onBeforeRouteUpdate } from 'vue-router'
import { useAuthStore } from '../../stores/auth'
import { NInput, NButton, NEmpty, NSelect, NCheckboxGroup, NCheckbox, NPagination, NIcon, NRadioGroup, NRadio, NSlider, NModal, NPopover } from 'naive-ui'
import { SearchOutline, FilterOutline, EyeOffOutline, SettingsOutline } from '@vicons/ionicons5'
@@ -63,6 +63,7 @@ const expandedGroups = ref<Record<string, boolean>>({})
const showFilters = ref(localStorage.getItem('search:showFilters') !== 'false')
const yearCounts = ref<{ year: number; count: number }[]>([])
const showCustomYear = ref(false) // 自定义年份范围展开
const _skipRouteSync = ref(0) // R31: guard against re-entry from syncSearchToUrl
const pageSize = ref(Number(localStorage.getItem('search:pageSize')) || 20)
// 从 URL 恢复的页码(syncSearchToUrl 写入)
const restoredPage = ref(1)
@@ -93,6 +94,7 @@ function onYearSliderChange(val: any) {
yearFromStr.value = String(val[0])
yearToStr.value = String(val[1])
datePreset.value = null
showCustomYear.value = false // R31: 滑动时收起自定义年份
urlDateFrom.value = ''; urlDateTo.value = '' // P6: clear stale URL dates
if (_sliderTimer) clearTimeout(_sliderTimer)
_sliderTimer = setTimeout(() => goToPage(1), 250)
@@ -503,6 +505,13 @@ onMounted(async () => {
await goToPage(restoredPage.value)
})
// R31: 浏览器后退/前进时恢复搜索状态
onBeforeRouteUpdate(() => {
if (_skipRouteSync.value > 0) return // syncSearchToUrl 触发的路由变化,跳过
restoreFromQuery()
goToPage(restoredPage.value)
})
/** 同步当前搜索参数到 URL query */
function syncSearchToUrl() {
const q: Record<string, string> = {}
@@ -549,7 +558,8 @@ function syncSearchToUrl() {
const currentPage = KEYSET_SORTS.has(sort.value) ? keysetPage.value : page.value
if (currentPage > 1) q.p = String(currentPage)
// R24: use push instead of replace to preserve browser back-button history
router.push({ query: q }).catch(() => {})
_skipRouteSync.value++
router.push({ query: q }).catch(() => {}).finally(() => _skipRouteSync.value--)
}
function resetAllFilters() {
@@ -578,6 +588,7 @@ function resetAllFilters() {
booleanOp.value = 'and'
exactPhrase.value = false
showCustomYear.value = false
pageSize.value = Number(localStorage.getItem('search:pageSize')) || 20 // R31: 重置每页条数
goToPage(1)
}
@@ -85,9 +85,9 @@ const translated = computed(() => {
items.push({ field: `[${tag}] ${label}`, value: `"${val}"` })
}
}
// Date range: YYYY:YYYY[DP] or YYYY/MM/DD:YYYY/MM/DD[DP]
// 对 displayText(已展开 #N)扫描,确保历史引用中的 DP 也能被翻译
const yrRe = /(\d{4}(?:[-\/]\d{2}[-\/]\d{2})?)\s*:\s*(\d{4}(?:[-\/]\d{2}[-\/]\d{2})?)\s*\[DP\]/g
// Date range: supports YYYY:YYYY[DP], YYYY-MM-DD:YYYY-MM-DD[DP], YYYY/MM/DD:YYYY/MM/DD[DP],
// YYYY-M-D:YYYY-M-D[DP] (single digit), YYYY-MM:YYYY-MM[DP] (month-only)
const yrRe = /(\d{4}(?:[-\/]\d{1,2}(?:[-\/]\d{1,2})?)?)\s*:\s*(\d{4}(?:[-\/]\d{1,2}(?:[-\/]\d{1,2})?)?)\s*\[DP\]/g
while ((m = yrRe.exec(displayText)) !== null) {
const key = `dp_range_${m.index}`
if (!seen.has(key)) {
@@ -155,6 +155,8 @@ function addToQuery() {
let val = builderValue.value.trim()
// 去除用户输入的多余引号
val = val.replace(/^['""“]+|['""”]+$/g, '')
// R31: 去除内部双引号防止构建非法 PubMed 语法(保留单引号如 don't)
val = val.replace(/["""“”]/g, '')
if (!val) {
message.warning('请输入搜索词')
return