---
phase: 01
plan: 03
type: execute
wave: 2
depends_on: [01-01, 01-02]
files_modified:
  - public/assets/js/backend/history.js
autonomous: false
requirements: [OMIT-02, OMIT-03, OMIT-04]
must_haves:
  truths:
    - "前后端联调通过：前端请求能正确到达后端接口并获取数据"
    - "结果按遗漏期数从大到小正确渲染"
    - "波色球着色与已有颜色映射一致"
  artifacts:
    - path: "public/assets/js/backend/history.js"
      provides: "联调验证逻辑和边界情况处理"
      contains: "history/missingNum"
  key_links:
    - from: "public/assets/js/backend/history.js"
      to: "application/admin/controller/History.php::missingNum()"
      via: "$.ajax GET history/missingNum?periods=X"
      pattern: "history/missingNum"
    - from: "public/assets/js/backend/history.js::renderMissingNum"
      to: "后端返回 data[].omit"
      via: "按 omit 降序渲染（后端已排序）"
      pattern: "data\\[i\\]\\.omit"
---

<objective>
前后端联调验证：确保 history.js 的 AJAX 请求正确调用 history/missingNum 接口，结果按遗漏期数降序渲染，波色球着色与已有映射一致，并处理边界情况（无数据、加载失败、颜色映射未就绪）。

Purpose: 验证完整功能链路（per D-03: $.ajax 请求遗漏接口）
Output: 联调验证通过，边界情况处理完善
</objective>

<execution_context>
@D:/code/php/amlhc/.claude/get-shit-done/workflows/execute-plan.md
@D:/code/php/amlhc/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/01-omitted-number-analysis/01-RESEARCH.md
@D:/code/php/amlhc/public/assets/js/backend/history.js（plan 01-02 修改后的版本）
@D:/code/php/amlhc/application/admin/controller/History.php（plan 01-01 修改后的版本）
@D:/code/php/amlhc/application/admin/model/History.php（plan 01-01 修改后的版本）
</context>

<interfaces>
<!-- Key types and contracts from plans 01-01 and 01-02. -->

Backend endpoint (from plan 01-01):
```
GET /admin/history/missingNum?periods=X
Response success: {code: 1, msg: "查询成功", data: [{num: int, omit: int, color: string}, ...]}
Response error: {code: 0, msg: "期数范围必须在 1-100 之间"}
```

Frontend API (from plan 01-02):
```javascript
Controller.api.showMissingNumDialog()     // opens Layer dialog
Controller.api.queryMissingNum(periods, layero)  // initiates AJAX
Controller.api.renderMissingNum(data, periods, layero)  // renders result grid
Controller.api.colorMapLoaded             // boolean: color map ready flag
Controller.api.getColorByNum(num)         // returns CSS color string
```
</interfaces>

<tasks>

<task type="auto" tdd="false">
  <name>Task 1: 验证联调链路并完善边界情况处理</name>
  <files>public/assets/js/backend/history.js</files>
  <read_first>
    - public/assets/js/backend/history.js（plan 01-02 修改后的完整文件）
    - application/admin/controller/History.php（plan 01-01 修改后的控制器）
    - application/admin/model/History.php（plan 01-01 修改后的模型）
  </read_first>
  <action>
检查 plan 01-02 创建的 JS 代码，确保以下边界情况已正确处理。如果已有则跳过，如果缺失则补充。

**边界情况 1：colorMap 未加载时的处理**
确认 queryMissingNum 方法在 colorMapLoaded === false 时，先调用 loadColorMap 等待完成再发起请求。代码应类似：
```javascript
queryMissingNum: function (periods, layero) {
    if (!Controller.api.colorMapLoaded) {
        Controller.api.loadColorMap(function () {
            Controller.api._doQueryMissingNum(periods, layero);
        });
    } else {
        Controller.api._doQueryMissingNum(periods, layero);
    }
},
```

**边界情况 2：后端返回空数据（所有号码在最近 X 期都出现过）**
确认 renderMissingNum 方法在 data.length === 0 时显示友好提示而非空白：
```javascript
if (!data || data.length === 0) {
    $('#missing-result', layero).html('<div class="alert alert-info">...</div>');
    return;
}
```

**边界情况 3：AJAX 请求失败（网络错误、服务器 500）**
确认 _doQueryMissingNum 的 error 回调正确显示错误信息：
```javascript
error: function () {
    $('#missing-result', layero).html('<div class="alert alert-danger">请求失败</div>');
}
```

**边界情况 4：波色球颜色兜底**
确认 renderMissingNum 中 getColorByNum 对未映射号码返回灰色 (#95a5a6)——这已在 getColorByNum 方法中实现，此处只需确保调用正确。

**边界情况 5：快速重复点击查询按钮**
在 _doQueryMissingNum 中，发起请求前禁用查询按钮，请求完成后恢复：
```javascript
var $btn = $('#btn-missing-query', layero);
$btn.prop('disabled', true);
$.ajax({
    ...
    complete: function () {
        $btn.prop('disabled', false);
    }
});
```

如果上述边界情况在 plan 01-02 的代码中已经处理，本任务仅做验证性读取确认，不做修改。如果有缺失项，补充对应代码。
  </action>
  <acceptance_criteria>
    - queryMissingNum 包含 `if (!Controller.api.colorMapLoaded)` 检查
    - queryMissingNum 在 colorMapLoaded 为 false 时调用 `Controller.api.loadColorMap(function(){...})`
    - renderMissingNum 包含 `data.length === 0` 的空数据处理分支
    - _doQueryMissingNum 包含 error 回调函数
    - _doQueryMissingNum 的 $.ajax 包含 complete 回调用于恢复按钮状态
    - _doQueryMissingNum 在请求前设置 `$('#btn-missing-query', layero).prop('disabled', true)`
  </acceptance_criteria>
  <verify>
    <automated>grep -c "colorMapLoaded\|data\.length === 0\|\.prop.*disabled\|complete:" public/assets/js/backend/history.js | awk '$1 >= 4'</automated>
  </verify>
  <done>所有边界情况（颜色映射未就绪、空数据、请求失败、按钮防重复点击、波色兜底）均已正确处理</done>
</task>

<task type="checkpoint:human-verify" gate="blocking">
  <name>Task 2: 人工验证完整功能链路</name>
  <files>public/assets/js/backend/history.js, application/admin/controller/History.php, application/admin/model/History.php</files>
  <what-built>
后端 missingNum 接口 + 前端弹窗 UI + AJAX 联调 + 边界情况处理
  </what-built>
  <how-to-verify>
按以下步骤在浏览器中验证（假设 admin 后台地址为 http://localhost/ByZjtVrKok.php）：

1. **登录 admin 后台**，进入 history 页面
2. **检查按钮**：确认 toolbar 出现黄色"遗漏号码"按钮（带搜索图标）
3. **打开弹窗**：点击按钮，确认弹出 Layer 窗口，标题为"遗漏号码分析"
4. **检查弹窗内容**：确认有"查询期数："标签、数字输入框（默认值 10）、查询按钮
5. **正常查询**：保持默认值 10，点击查询
   - 确认出现"查询中..."加载提示（带 spinner）
   - 确认加载完成后显示结果网格：每个球显示号码（带颜色）+ 下方"遗漏 X 期"文字
   - 确认结果从左到右按遗漏期数从大到小排列（最左边 omit 最大）
   - 确认球的颜色与 history 表格中的波色球一致
6. **边界值测试**：
   - 输入 1 点击查询，确认返回结果
   - 输入 100 点击查询，确认返回结果
   - 输入 0 点击查询，确认显示错误提示"期数范围必须在 1-100 之间"
   - 输入 200 点击查询，确认显示错误提示
7. **防重复点击**：点击查询按钮后，确认按钮变灰（disabled），请求完成后恢复可点击
8. **关闭弹窗**：点击弹窗外的遮罩区域，确认弹窗关闭
9. **重复打开**：再次点击"遗漏号码"按钮，确认弹窗正常打开且输入框恢复默认值 10
  </how-to-verify>
  <resume-signal>验证通过请回复"approved"，如有问题请描述具体现象</resume-signal>
</task>

</tasks>

<threat_model>
## Trust Boundaries

| Boundary | Description |
|----------|-------------|
| AJAX response → DOM rendering | 后端返回的数据直接注入 DOM，需确保无 XSS 风险 |
| User rapid-click → multiple AJAX requests | 可能导致竞态条件或服务器负载 |

## STRIDE Threat Register

| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-01-07 | Tampering | renderMissingNum DOM 渲染 | mitigate | 使用 textContent/innerText 或 jQuery .text() 渲染号码数字，不使用 .html() 注入原始数据；球的颜色通过 style.backgroundColor 设置 |
| T-01-08 | Denial of Service | 快速重复点击 | mitigate | 请求期间禁用查询按钮（complete 回调恢复），防止并发请求 |
</threat_model>

<verification>
- 所有边界情况已在代码中处理（grep 验证通过）
- 人工验证 9 个步骤全部通过
- 无 JavaScript 控制台错误
- 后端无 PHP 错误日志
</verification>

<success_criteria>
- [ ] 前端 AJAX 请求正确调用后端 missingNum 接口
- [ ] 后端返回的 {num, omit, color} 数据正确渲染为波色球网格
- [ ] 结果按遗漏期数从大到小排列
- [ ] 波色球颜色与表格中一致
- [ ] 空数据时显示友好提示
- [ ] 参数超出范围时显示错误提示
- [ ] 请求期间按钮被禁用防止重复提交
- [ ] 点击遮罩可关闭弹窗
- [ ] 无 JavaScript 控制台错误
</success_criteria>

<output>
After completion, create `.planning/phases/01-omitted-number-analysis/01-03-SUMMARY.md`
</output>
