278 lines
11 KiB
Markdown
278 lines
11 KiB
Markdown
# 高级搜索独立页面设计(PubMed 风格)
|
||||
|
|
|
|||
|
|
> 文档日期:2026-07-24
|
|||
|
|
> 状态:**已规划,暂不实现**(先完成普通搜索 Phase 0 Bug 修复)
|
|||
|
|
> 设计目标:在 `/app/search/advanced` 独立路由上实现 PubMed 风格的 Advanced Search
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 1. 问题
|
|||
|
|
|
|||
|
|
当前系统的"高级搜索"只是 SearchView 上的一个筛选面板(年份范围、期刊等级、标签),与 PubMed 的 Advanced Search(`/pubmed/advanced/`)完全不同。
|
|||
|
|
|
|||
|
|
PubMed Advanced Search 是两个核心功能的组合:
|
|||
|
|
|
|||
|
|
1. **查询构建器(Query Builder)** — 行式字段下拉 + AND/OR/NOT 组合
|
|||
|
|
2. **搜索历史(Search History)** — #1/#2/#3 编号,可点击组合为 `#1 AND #2`
|
|||
|
|
|
|||
|
|
## 2. 目标界面
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
┌─────────────────────────────────────────────────────────┐
|
|||
|
|
│ [Basic Search] [Advanced Search] ← Tab 切换 │
|
|||
|
|
├─────────────────────────────────────────────────────────┤
|
|||
|
|
│ 查询构建器(Query Builder) │
|
|||
|
|
│ ┌───────┬────────────────────────────────────┬──────┐ │
|
|||
|
|
│ │ 字段 │ 查询词 │ × │ │
|
|||
|
|
│ ├───────┼────────────────────────────────────┼──────┤ │
|
|||
|
|
│ │ All │ lung cancer │ × │ │
|
|||
|
|
│ │ AND ▼ │ │ │ │
|
|||
|
|
│ │ MeSH │ lung neoplasms │ × │ │
|
|||
|
|
│ └───────┴────────────────────────────────────┴──────┘ │
|
|||
|
|
│ [+ Add row] [Search] [Details ▼] │
|
|||
|
|
│ ── PubMed 查询字符串 ──────────────────────────────── │
|
|||
|
|
│ (lung cancer[All Fields]) AND (lung neoplasms[MeSH Terms])│
|
|||
|
|
├─────────────────────────────────────────────────────────┤
|
|||
|
|
│ 搜索历史(Search History) │
|
|||
|
|
│ ┌──────┬─────────────────────────┬───────┬──────────┐ │
|
|||
|
|
│ │ # │ Query │ Hits │ Time │ │
|
|||
|
|
│ ├──────┼─────────────────────────┼───────┼──────────┤ │
|
|||
|
|
│ │ #3 │ #1 AND #2 │ 42 │ 10:32 AM │ │
|
|||
|
|
│ │ #2 │ lung neoplasms[MH] │ 128 │ 10:31 AM │ │
|
|||
|
|
│ │ #1 │ lung cancer[TI] │ 215 │ 10:30 AM │ │
|
|||
|
|
│ └──────┴─────────────────────────┴───────┴──────────┘ │
|
|||
|
|
│ [Combine selected] [Clear history] │
|
|||
|
|
├─────────────────────────────────────────────────────────┤
|
|||
|
|
│ 搜索结果区域 │
|
|||
|
|
│ (复用现有 LiteratureCard + Pagination) │
|
|||
|
|
└─────────────────────────────────────────────────────────┘
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 3. 页面布局
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
┌──────────────┬──────────────────────────────────┐
|
|||
|
|
│ 左栏 │ 右栏 │
|
|||
|
|
│ 280px │ flex: 1 │
|
|||
|
|
│ │ │
|
|||
|
|
│ Search │ Query Builder (上区) │
|
|||
|
|
│ History │ + 查询字符串显示 │
|
|||
|
|
│ Panel │ │
|
|||
|
|
│ │ ───────── 分割线 ──────── │
|
|||
|
|
│ │ │
|
|||
|
|
│ │ Search Results (下区) │
|
|||
|
|
│ │ 复用 LiteratureCard │
|
|||
|
|
│ │ + Pagination │
|
|||
|
|
└──────────────┴──────────────────────────────────┘
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 4. 后端改动
|
|||
|
|
|
|||
|
|
### 4.1 新建模型 SearchHistory
|
|||
|
|
|
|||
|
|
| 字段 | 类型 | 说明 |
|
|||
|
|
|------|------|------|
|
|||
|
|
| `id` | UUID PK | |
|
|||
|
|
| `user_id` | FK → users | |
|
|||
|
|
| `query_text` | TEXT | 用户输入的原始查询字符串 |
|
|||
|
|
| `query_json` | JSON | 结构化查询(各字段解析结果)|
|
|||
|
|
| `result_count` | INTEGER | 结果数 |
|
|||
|
|
| `created_at` | TIMESTAMPTZ | 搜索时间 |
|
|||
|
|
|
|||
|
|
**文件**:`backend/app/models/search_history.py`(新建)
|
|||
|
|
**导出**:`backend/app/models/__init__.py` 添加 SearchHistory
|
|||
|
|
**迁移**:`alembic revision --autogenerate -m "add_search_history"`
|
|||
|
|
|
|||
|
|
### 4.2 API 端点
|
|||
|
|
|
|||
|
|
挂载在 `features` router 下(`backend/app/api/v1/features.py`):
|
|||
|
|
|
|||
|
|
| 方法 | 路径 | 说明 |
|
|||
|
|
|------|------|------|
|
|||
|
|
| `POST` | `/features/search/advanced` | **已有**。搜索结果后自动存入 history |
|
|||
|
|
| `GET` | `/features/search/history` | 获取用户最近 20 条搜索历史 |
|
|||
|
|
| `DELETE` | `/features/search/history/{id}` | 删除单条历史 |
|
|||
|
|
| `GET` | `/features/search/details` | 查询翻译详情(ATM 展开后的结构) |
|
|||
|
|
|
|||
|
|
### 4.3 搜索自动入 History
|
|||
|
|
|
|||
|
|
修改 `advanced_search()`:搜索成功后**自动 INSERT** 一条 SearchHistory 记录。
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
# 伪代码
|
|||
|
|
result = await AdvancedSearchEngine.search(db, ...)
|
|||
|
|
history = SearchHistory(
|
|||
|
|
user_id=user.id,
|
|||
|
|
query_text=req.query,
|
|||
|
|
query_json=parsed_query, # 解析后的结构化字段
|
|||
|
|
result_count=result["total"],
|
|||
|
|
)
|
|||
|
|
db.add(history)
|
|||
|
|
await db.commit()
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4.4 查询构建器 → 后端适配
|
|||
|
|
|
|||
|
|
前端行式构建器发送的结构:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"query": "(lung[TI]) AND (lung neoplasms[MH])",
|
|||
|
|
"advanced_builder": {
|
|||
|
|
"rows": [
|
|||
|
|
{"field": "TI", "query": "lung", "operator": "AND"},
|
|||
|
|
{"field": "MH", "query": "lung neoplasms", "operator": null}
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`AdvancedSearchRequest` 新增 `advanced_builder` 可选字段。
|
|||
|
|
|
|||
|
|
### 4.5 查询详情端点
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
@router.get("/search/details")
|
|||
|
|
async def search_details(query: str = Query(...)):
|
|||
|
|
"""返回 ATM 展开后的查询结构(类似 PubMed 'Search details' 面板)"""
|
|||
|
|
parsed = parse_pubmed_query(query)
|
|||
|
|
return {
|
|||
|
|
"parsed": parsed.model_dump(),
|
|||
|
|
"translated": expanded_query, # ATM 引擎(Phase 1.7 后)
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 5. 前端改动
|
|||
|
|
|
|||
|
|
### 5.1 QueryBuilder.vue
|
|||
|
|
|
|||
|
|
**位置**:`frontend/src/components/search/QueryBuilder.vue`
|
|||
|
|
|
|||
|
|
**功能**:行式查询构建器
|
|||
|
|
|
|||
|
|
- 每行:`[NSelect 字段] + [NInput 查询词] + [NSelect AND/OR/NOT] + [删除按钮]`
|
|||
|
|
- 第一行无运算符选择
|
|||
|
|
- 底部 `[+ Add row]` 按钮
|
|||
|
|
- 字段下拉选项(12 个):
|
|||
|
|
|
|||
|
|
| 标签 | 值 |
|
|||
|
|
|------|-----|
|
|||
|
|
| All Fields | ALL |
|
|||
|
|
| Title | TI |
|
|||
|
|
| Title/Abstract | TIAB |
|
|||
|
|
| Author | AU |
|
|||
|
|
| MeSH Terms | MH |
|
|||
|
|
| MeSH Major Topic | MAJR |
|
|||
|
|
| Journal | TA |
|
|||
|
|
| Affiliation | AD |
|
|||
|
|
| Language | LA |
|
|||
|
|
| Publication Type | PT |
|
|||
|
|
| Grant Number | GR |
|
|||
|
|
| Chemical | NM |
|
|||
|
|
|
|||
|
|
- 自动生成 PubMed 语法字符串:`(lung[TI]) AND (lung neoplasms[MH])`
|
|||
|
|
- `[Search]` → 调用 `POST /features/search/advanced`
|
|||
|
|
- `[Add to history]` → 后端自动存,无需手动
|
|||
|
|
|
|||
|
|
**Props**: 无
|
|||
|
|
**Emits**: `@search(query: string, field: string, ...params)`
|
|||
|
|
|
|||
|
|
### 5.2 SearchHistoryPanel.vue
|
|||
|
|
|
|||
|
|
**位置**:`frontend/src/components/search/SearchHistoryPanel.vue`
|
|||
|
|
|
|||
|
|
**功能**:搜索历史表格
|
|||
|
|
|
|||
|
|
- 加载时调用 `GET /features/search/history`
|
|||
|
|
- 表格列:#、Query、Hits、Time
|
|||
|
|
- 点击行 → 将 `#1` 语法插入 Query Box
|
|||
|
|
- 行操作:删除
|
|||
|
|
- `[Combine]` → 选中多条 → 生成 `#1 AND #2`
|
|||
|
|
- `[Clear history]` → 调用批量删除
|
|||
|
|
|
|||
|
|
**Props**: 无
|
|||
|
|
**Emits**: `@useQuery(query: string)`、`@search(query: string)`
|
|||
|
|
|
|||
|
|
### 5.3 AdvancedSearchView.vue
|
|||
|
|
|
|||
|
|
**位置**:`frontend/src/views/app/AdvancedSearchView.vue`
|
|||
|
|
|
|||
|
|
**布局**:
|
|||
|
|
- 左栏 280px:SearchHistoryPanel
|
|||
|
|
- 右栏:上区 QueryBuilder + 下区搜索结果(LiteratureCard + Pagination)
|
|||
|
|
- 顶部 `[Basic Search] / [Advanced Search]` Tab 切换
|
|||
|
|
|
|||
|
|
**依赖**:
|
|||
|
|
- `LiteratureCard.vue`(现有,复用)
|
|||
|
|
- `Pagination`(现有,复用)
|
|||
|
|
|
|||
|
|
### 5.4 路由更新
|
|||
|
|
|
|||
|
|
**文件**:`frontend/src/router/index.ts`
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
{ path: 'search/advanced', name: 'search-advanced',
|
|||
|
|
component: () => import('../views/app/AdvancedSearchView.vue') }
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 5.5 侧边栏
|
|||
|
|
|
|||
|
|
在导航增加入口:`搜索` → `高级搜索`
|
|||
|
|
|
|||
|
|
**文件**:检查侧边栏组件(Sidebar.vue 或布局文件)
|
|||
|
|
|
|||
|
|
### 5.6 TypeScript 类型更新
|
|||
|
|
|
|||
|
|
**文件**:`frontend/src/types/index.ts`
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
export interface AdvancedBuilderRow {
|
|||
|
|
field: string
|
|||
|
|
query: string
|
|||
|
|
operator: 'AND' | 'OR' | 'NOT' | null
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export interface SearchHistoryItem {
|
|||
|
|
id: string
|
|||
|
|
query_text: string
|
|||
|
|
result_count: number
|
|||
|
|
created_at: string
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// SearchRequestBody 补充
|
|||
|
|
export interface SearchRequestBody {
|
|||
|
|
// ...已有字段...
|
|||
|
|
advanced_builder?: { rows: AdvancedBuilderRow[] }
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 6. 依赖关系
|
|||
|
|
|
|||
|
|
| 依赖 | 说明 |
|
|||
|
|
|------|------|
|
|||
|
|
| **Phase 0(必须完成)** | 搜索 Bug(OR 布尔、MH JOIN、recent_subq)不修复则高级搜索结果也不正确 |
|
|||
|
|
| **Phase 1.1(tree_number)** | MeSH Terms 字段的子树展开依赖 |
|
|||
|
|
| **Phase 1.3-1.5(字段标签)** | QueryBuilder 的 AD/LA/EDAT 字段依赖 |
|
|||
|
|
| **Phase 1.7(ATM 引擎)** | 查询详情面板的 "Translated query" 依赖 |
|
|||
|
|
| **无依赖** | QueryBuilder UI + SearchHistory 后端可立即实现(框架先搭好) |
|
|||
|
|
|
|||
|
|
## 7. 工作量估算
|
|||
|
|
|
|||
|
|
| 任务 | 文件 | 预估 |
|
|||
|
|
|------|------|------|
|
|||
|
|
| SearchHistory 模型 + 迁移 | `models/search_history.py` + 迁移脚本 + `__init__.py` | 30min |
|
|||
|
|
| 后端 CRUD 端点 | `features.py`(GET/DELETE history) | 1h |
|
|||
|
|
| 搜索自动入 history | `features.py` advanced_search() | 15min |
|
|||
|
|
| 查询详情端点 | `features.py` | 30min |
|
|||
|
|
| QueryBuilder.vue | `components/search/QueryBuilder.vue` | 2-3h |
|
|||
|
|
| SearchHistoryPanel.vue | `components/search/SearchHistoryPanel.vue` | 2h |
|
|||
|
|
| AdvancedSearchView.vue | `views/app/AdvancedSearchView.vue` | 2h |
|
|||
|
|
| 路由 + 侧边栏 + 类型 | router, Sidebar, types | 30min |
|
|||
|
|
| **合计** | | **~9-10h** |
|
|||
|
|
|
|||
|
|
## 8. 优先级排序
|
|||
|
|
|
|||
|
|
1. **最优先**:Phase 0 Bug 修复(12 项,代码已改待提交)
|
|||
|
|
2. **次优先**:此高级搜索独立页面(9-10h)
|
|||
|
|
3. **后续**:Phase 1-3 字段标签补齐、ATM 引擎、排序优化
|