chore: batch commit remaining changes
CI / backend (push) Canceled after 0s
CI / frontend (push) Canceled after 0s

Includes search engine improvements, Alembic migrations,
new services (pubmed_daily_update, query_expansion),
frontend updates, and documentation sync.
This commit is contained in:
34047007@qq.com
2026-07-27 08:35:12 +08:00
parent 35b0a5c565
commit 62ca8fa6b8
82 changed files with 314248 additions and 1519 deletions
@@ -0,0 +1,46 @@
import { reactive, watch } from 'vue'
import type { DisplaySettings } from '../types'
const STORAGE_KEY = 'search:displaySettings'
const defaults: DisplaySettings = {
showAuthors: true,
showAffiliation: true,
showJournal: true,
showPmid: true,
showDoi: true,
showAbstract: true,
showStudyTypes: true,
showTags: true,
showCitedBy: true,
showAiSummary: true,
showActions: true,
showBadges: true,
showSummary: true,
showPubmed: true,
}
function loadSettings(): DisplaySettings {
try {
const raw = localStorage.getItem(STORAGE_KEY)
if (raw) {
const parsed = JSON.parse(raw)
return { ...defaults, ...parsed }
}
} catch { /* ignore */ }
return { ...defaults }
}
const settings = reactive<DisplaySettings>(loadSettings())
watch(
() => ({ ...settings }),
(val) => {
localStorage.setItem(STORAGE_KEY, JSON.stringify(val))
},
{ deep: true },
)
export function useDisplaySettings() {
return settings
}
@@ -0,0 +1,113 @@
import { ref } from 'vue'
const STORAGE_KEY = 'pub_search_history'
const MAX_ENTRIES = 50
export interface HistoryEntry {
id: string
query: string
expanded_query: string
result_count: number | null
timestamp: string
}
function loadAll(): HistoryEntry[] {
try {
const raw = localStorage.getItem(STORAGE_KEY)
return raw ? JSON.parse(raw) : []
} catch {
return []
}
}
function saveAll(entries: HistoryEntry[]) {
localStorage.setItem(STORAGE_KEY, JSON.stringify(entries))
}
/** 把 #N 引用替换为 expanded_query(加括号保护优先级) */
export function resolveQuery(query: string, entries: HistoryEntry[]): string {
return query.replace(/#(\d+)/g, (_m, num) => {
const found = entries.find(e => e.id === `#${num}`)
return found ? `(${found.expanded_query})` : _m
})
}
/** 递归展开所有 #N 引用为纯查询 */
export function expandQuery(query: string, entries: HistoryEntry[]): string {
let prev = ''
let current = query
for (let i = 0; i < 10; i++) {
if (current === prev) break
prev = current
current = resolveQuery(current, entries)
}
return current
}
export function useSearchHistory() {
const entries = ref<HistoryEntry[]>(loadAll())
function getAll(): HistoryEntry[] {
return [...entries.value]
}
function add(query: string, resultCount?: number | null): HistoryEntry {
const all = loadAll()
const nextNum = all.length > 0
? Math.max(...all.map(e => parseInt(e.id.slice(1), 10))) + 1
: 1
const id = `#${nextNum}`
const expanded = expandQuery(query, all)
const entry: HistoryEntry = {
id,
query,
expanded_query: expanded,
result_count: resultCount ?? null,
timestamp: new Date().toISOString(),
}
if (all.length >= MAX_ENTRIES) {
all.sort((a, b) => a.timestamp.localeCompare(b.timestamp))
all.shift()
}
all.push(entry)
saveAll(all)
entries.value = [...all]
return entry
}
function remove(id: string) {
const all = loadAll().filter(e => e.id !== id)
saveAll(all)
entries.value = all
}
function clear() {
localStorage.removeItem(STORAGE_KEY)
entries.value = []
}
function download() {
const all = loadAll()
const lines = all.map(e =>
`${e.id}\t${e.result_count ?? ''}\t${e.timestamp}\t${e.query}`
)
const blob = new Blob([lines.join('\n')], { type: 'text/plain;charset=utf-8' })
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = `pubmed-search-history-${new Date().toISOString().slice(0, 10)}.tsv`
a.click()
URL.revokeObjectURL(url)
}
return {
entries,
getAll,
add,
remove,
clear,
download,
}
}