# characterPage 代码风险审查计划

## 审查范围

- `src/views/characterPage/index.vue` - 入口页
- `src/views/characterPage/components/unified/index.vue` - 统一布局
- `src/views/characterPage/components/thinkTankPerson/index.vue` - 智库人员
- `src/views/characterPage/components/techLeader/index.vue` - 科技领袖
- `src/views/characterPage/components/memberOfCongress/index.vue` - 国会议员
- `src/views/characterPage/components/*/components/characterRelationships/index.vue` - 人物关系
- `src/views/characterPage/components/*/components/relevantSituation/index.vue` - 相关情况
- `src/views/characterPage/components/thinkTankPerson/components/historicalProposal/components/NewsTracker.vue` - 新闻追踪（参考实现）
- `src/api/characterPage/characterPage.js` - API 层

---

## 一、样式溢出问题 (CRITICAL/HIGH)

### C1. 全模块固定 1600px 宽度 — 水平溢出 [CRITICAL]

**影响文件**: thinkTankPerson:467, techLeader, memberOfCongress, characterRelationships:467, relevantSituation:499

所有组件使用 `width: 1600px` 固定宽度，小于此宽度的屏幕会水平溢出。

```scss
// 当前代码
.think-tank-person {
  width: 1600px;  // 固定宽度
  margin: 0 auto;
}
```

**修复方案**: 使用 `max-width: 1600px` + `width: 100%` 替代固定宽度。

```scss
// 修复后
.think-tank-person {
  max-width: 1600px;
  width: 100%;
  margin: 0 auto;
  padding: 0 16px;
  box-sizing: border-box;
}
```

**涉及位置**:
| 文件 | 行号 | 当前值 |
|------|------|--------|
| thinkTankPerson/index.vue | 467, 472, 534, 574, 609 | width: 1600px / 1064px / 520px |
| techLeader/index.vue | 类似位置 | 同上 |
| memberOfCongress/index.vue | 类似位置 | 同上 |
| characterRelationships/index.vue | 467 | width: 1600px |
| relevantSituation/index.vue | 499, 504, 629 | width: 1600px / 1068px / 520px |

### C2. 固定高度容器导致内容溢出 [HIGH]

**影响文件**: thinkTankPerson/index.vue:576

```scss
.info-content {
  height: 1933px;  // 固定高度，内容多则溢出，内容少则大片空白
}
.main {
  height: 1435px;  // 固定高度
}
```

**修复方案**: 改为 `min-height`，让容器自适应内容高度。

### C3. .introduction/.description 无文本溢出处理 [MEDIUM]

**影响文件**: 所有三个类型视图的 header 区域

`characterInfo.description` 直接渲染，无 max-height 或 line-clamp 限制。超长描述会将下方内容推到不可见区域。

**修复方案**:
```scss
.introduction {
  max-height: 72px;  // 约3行
  overflow: hidden;
  text-overflow: ellipsis;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
}
```

### C4. scoped 内 `* { margin:0; padding:0 }` 重置不可靠 [LOW]

**影响文件**: thinkTankPerson:459, characterRelationships:462, relevantSituation:493

Vue scoped styles 中的 `*` 选择器行为不符合预期，且可能影响全局样式。

**修复方案**: 移除这些全局重置，用具体选择器替代。

---

## 二、无数据 EMPTY 展示缺失 (HIGH)

### H1. 最新动态列表无空状态 [HIGH]

**影响文件**: thinkTankPerson/index.vue:41-71, techLeader, memberOfCongress

当 `CharacterLatestDynamic` 为空数组时，`.main` 容器直接显示为空，无任何提示。

**修复方案**: 参考 NewsTracker.vue:36 的实现
```html
<el-empty v-if="CharacterLatestDynamic.length === 0" description="暂无动态数据" :image-size="80" />
<template v-else>
  <div v-for="item in CharacterLatestDynamic" ...>
    <!-- 现有列表内容 -->
  </div>
</template>
```

### H2. 词云图表无空状态 [HIGH]

**影响文件**: thinkTankPerson/index.vue:33, techLeader, memberOfCongress

当 `characterView` 无数据时，AnalysisBox 显示空白区域。

**修复方案**:
```html
<div class="echarts">
  <el-empty v-if="characterView.length === 0" description="暂无观点数据" :image-size="80" />
  <WordCloudChart v-else-if="wordLoading" :data="characterView" />
</div>
```

### H3. 人物关系图无空状态 [MEDIUM]

**影响文件**: characterRelationships/index.vue

当 `CharacterRelation` 为空时，仅显示中心节点，无关系提示。用户看到的是一张只有一个点的图。

**修复方案**:
```html
<el-empty v-if="nodes.length <= 1" description="暂无人物关系数据" :image-size="100" />
<div class="mainBox" v-else>
  <div class="graph" id="relGraph"></div>
</div>
```

### H4. 相关情况子列表无空状态 [MEDIUM]

**影响文件**: relevantSituation/index.vue:61-103

`relationBillsList`、`relationAdList`、`relationThinkTankList` 三个列表无空状态。

**修复方案**: 每个列表添加：
```html
<el-empty v-if="relationBillsList.length === 0" description="暂无数据" :image-size="60" />
<template v-else>
  <div v-for="item in relationBillsList" ...>...</div>
</template>
```

### H5. 人物基本信息卡片无空状态 [LOW]

**影响文件**: thinkTankPerson/index.vue:83

`v-if="characterBasicInfo"` 隐藏了整个卡片但无替代提示。

**修复方案**: 添加 `v-else` 显示加载中或空状态。

### H6. 履历卡片无空状态 [LOW]

**影响文件**: thinkTankPerson/index.vue:143, techLeader, memberOfCongress

`ResumeCard` 组件无论数据是否为空都显示，应添加空状态。

---

## 三、代码风险 (CRITICAL → LOW)

### R1. 空 catch 块吞噬错误 [CRITICAL]

**影响文件**: 几乎所有组件

```javascript
// index.vue:50-52
catch(error){
  // 完全空的 catch 块！
}
```

空 catch 块会完全吞掉错误，导致问题无法排查。出现在：
- index.vue:50-52
- thinkTankPerson/index.vue:239-241, 287-289, 318-320, 410-413
- characterRelationships/index.vue:193-196, 355-357
- relevantSituation/index.vue:204-206

**修复方案**: 至少添加 `console.error(error)` 或使用统一错误处理。

### R2. ref 类型初始化错误 [HIGH]

**影响文件**: thinkTankPerson:343, relevantSituation:161-163

```javascript
// 错误：初始化为对象，实际用作数组
const CharacterLatestDynamic = ref({});  // 应为 ref([])
const relationBillsList = ref({});       // 应为 ref([])
const relationAdList = ref({});          // 应为 ref([])
const relationThinkTankList = ref({});   // 应为 ref([])
```

这会导致 `.length` 检查返回 undefined，v-for 遍历对象属性而非数组元素。

**修复方案**: 全部改为 `ref([])`。

### R3. ECharts 双重初始化 — 内存泄漏 [HIGH]

**影响文件**: characterRelationships/index.vue

```javascript
// onMounted 中初始化一次 (line 394)
chart = echarts.init(el);

// getCharacterRelationFn 中又初始化一次 (line 296)
chart = echarts.init(el);  // 覆盖了之前的实例，未 dispose
```

**修复方案**: 在重新 init 前先 dispose 旧实例，或提取为独立方法只调用一次。

### R4. v-for key 使用对象引用 [MEDIUM]

**影响文件**: unified/index.vue, thinkTankPerson:30

```html
<!-- key="tag" 传入的是整个对象，不是唯一标识 -->
<AreaTag v-for="tag in characterInfo.industryList" :key="tag" .../>
```

**修复方案**: 使用 `:key="tag.industryId"` 或 `:key="tag.industryName"`。

### R5. 嵌套结构错误 [MEDIUM]

**影响文件**: thinkTankPerson/index.vue:106-118

```html
<div class="baseInfo-item">         <!-- 外层 -->
  <div class="baseInfo-item">       <!-- 内层嵌套了同类型 -->
    <div class="baseInfo-item-title">教育背景：</div>
    <div class="baseInfo-item-content">...</div>
  </div>
</div>
```

外层 `.baseInfo-item` 只包含一个子 `.baseInfo-item`，应该只有一层。

### R6. API 参数不匹配 [MEDIUM]

**影响文件**: thinkTankPerson/index.vue:364 vs characterPage.js:47-53

```javascript
// 调用时传了 signal
const res = await getCharacterLatestDynamic(params, abortController.value.signal);

// 但 API 定义不接收 signal
export function getCharacterLatestDynamic(params) {
  return request({ method: "GET", url: `...`, params });
}
```

**修复方案**: 在 API 函数中接收并传递 signal 参数。

### R7. personId 独立读取 vs props 传递 [MEDIUM]

**影响文件**: 所有子组件

每个子组件都独立通过 `useRoute().query.personId` 获取 personId，而不是通过 props 接收。这导致：
- 组件与路由耦合
- 难以测试
- 可能出现不一致

**修复方案**: 父组件传递 personId 作为 prop。

### R8. getProxyUrl 函数重复 [LOW]

**影响文件**: thinkTankPerson:215-223, characterRelationships:94-104, memberOfCongress

同一函数在 3 个文件中重复。

**修复方案**: 提取到 `src/utils/imageProxy.js` 中统一导出。

### R9. console.log 残留 [LOW]

**影响文件**: 多处
- index.vue:34 — `console.log("人物全局信息111", res)`
- thinkTankPerson:234,276,334,365,405

**修复方案**: 移除所有调试日志。

### R10. 硬编码数据残留 [LOW]

**影响文件**: relevantSituation/index.vue:215-274

`billList`、`twoList`、`billList2` 三个 ref 定义了但从未在模板中使用（API 数据替代了它们）。

**修复方案**: 删除这些未使用的变量。

### R11. 年份列表硬编码 [LOW]

**影响文件**: thinkTankPerson/index.vue:431

```javascript
const num = ref(["2026","2025", "2024", "2023", "2022", "2021"]);
```

**修复方案**: 动态生成最近5-6年。

### R12. 无加载状态 [LOW]

**影响文件**: 所有列表组件

数据加载过程中无 skeleton 或 spinner 提示，用户看到空白后突然出现内容。

**修复方案**: 添加 `v-loading` 指令或 skeleton 组件。

---

## 四、实施步骤

### Step 1: 修复 ref 类型错误 (R2)
- 将所有 `ref({})` 改为 `ref([])` 用于数组数据
- 涉及: thinkTankPerson:343, relevantSituation:161-163

### Step 2: 修复空 catch 块 (R1)
- 所有空 catch 块添加 `console.error(error)`
- 涉及: 6+ 个文件中的 10+ 处

### Step 3: 修复 ECharts 双重初始化 (R3)
- characterRelationships/index.vue 统一初始化逻辑

### Step 4: 修复 v-for key 问题 (R4) 和嵌套结构 (R5)
- 使用 industryId/industryName 作为 key
- 修复 thinkTankPerson 中的嵌套 baseInfo-item

### Step 5: 添加 EMPTY 空状态展示 (H1-H6)
- 最新动态列表: thinkTankPerson/techLeader/memberOfCongress
- 词云图表: 同上三个组件
- 人物关系图: characterRelationships
- 相关情况列表: relevantSituation
- 基本信息和履历卡片

### Step 6: 修复样式溢出 (C1-C3)
- 1600px 固定宽度改为 max-width
- 固定高度改为 min-height
- .introduction 添加文本溢出限制

### Step 7: 清理代码 (R8-R11)
- 提取 getProxyUrl 到 utils
- 移除 console.log
- 删除硬编码未使用数据
- 动态生成年份列表

---

## 五、关键文件操作矩阵

| 文件 | 操作 | 优先级 | 描述 |
|------|------|--------|------|
| thinkTankPerson/index.vue | 修改 | CRITICAL | ref类型、空状态、样式、key、嵌套 |
| techLeader/index.vue | 修改 | HIGH | 空状态、样式 |
| memberOfCongress/index.vue | 修改 | HIGH | 空状态、样式 |
| characterRelationships/index.vue | 修改 | HIGH | ECharts双初始化、空状态、样式 |
| relevantSituation/index.vue | 修改 | MEDIUM | ref类型、空状态、清理 |
| unified/index.vue | 修改 | MEDIUM | v-for key |
| index.vue (主入口) | 修改 | LOW | catch块、console.log |
| src/utils/imageProxy.js | 新建 | LOW | 提取 getProxyUrl |
| src/api/characterPage/characterPage.js | 修改 | MEDIUM | signal 参数支持 |

---

## 六、风险评估

| 风险 | 缓解措施 |
|------|----------|
| 样式修改可能影响现有布局 | 逐步修改，每次改一个属性，测试验证 |
| 空状态添加需要确认设计规范 | 参考 NewsTracker.vue 已有的 el-empty 实现 |
| ECharts 重构可能影响关系图渲染 | 保留现有 setOption 逻辑，仅统一初始化入口 |
| getProxyUrl 提取需确认所有引用 | grep 确认所有使用点后一次性替换 |

---

## SESSION_ID (for /ccg:execute use)
- CODEX_SESSION: N/A (codex CLI not found in PATH)
- GEMINI_SESSION: N/A (gemini CLI not found in PATH)
