# 高级搜索独立页面设计（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 引擎、排序优化
