---
phase: 01
plan: 02
type: execute
wave: 1
depends_on: []
files_modified:
  - public/assets/js/backend/history.js
  - application/admin/view/history/index.html
autonomous: true
requirements: [OMIT-01, OMIT-02, OMIT-03]
must_haves:
  truths:
    - "用户在 history 页面能看到'遗漏号码'按钮"
    - "点击按钮后弹出 Layer 模态窗口"
    - "弹窗内有期数输入框（默认值 10）和查询按钮"
    - "点击查询后显示结果区域，包含遗漏号码、遗漏期数、波色球"
  artifacts:
    - path: "application/admin/view/history/index.html"
      provides: "history 页面 toolbar 区域的'遗漏号码'按钮"
      contains: "btn-missingnum"
    - path: "public/assets/js/backend/history.js"
      provides: "按钮点击处理、Layer 弹窗、AJAX 请求、结果渲染"
      exports: ["showMissingNumDialog", "queryMissingNum", "renderMissingNum"]
  key_links:
    - from: "application/admin/view/history/index.html"
      to: "public/assets/js/backend/history.js"
      via: "toolbar 按钮 class .btn-missingnum 绑定 click 事件"
      pattern: "btn-missingnum"
    - from: "public/assets/js/backend/history.js"
      to: "history/missingNum"
      via: "$.ajax 请求后端接口"
      pattern: "history/missingNum"
    - from: "public/assets/js/backend/history.js"
      to: "Controller.api.getColorByNum()"
      via: "渲染波色球时复用已有颜色函数"
      pattern: "getColorByNum"
---

<objective>
在 history 页面添加"遗漏号码"按钮和 Layer 弹窗 UI，包含期数输入框、查询按钮、结果展示区域和波色球渲染，复用已有的 getColorByNum() 颜色函数。

Purpose: 提供用户交互界面（per D-01: 按钮+弹窗形式，不新增独立页面/菜单；per D-03: Layer 弹窗展示）
Output: toolbar 按钮 + Layer 弹窗 HTML + 结果渲染逻辑
</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
@D:/code/php/amlhc/application/admin/view/history/index.html
</context>

<interfaces>
<!-- Key types and contracts the executor needs. Extracted from codebase. -->

From public/assets/js/backend/history.js:
```javascript
define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefined, Backend, Table, Form) {
    var Controller = {
        index: function () { ... },
        add: function () { ... },
        edit: function () { ... },
        api: {
            colorMap: {},           // 波色映射缓存 {num: colorText}
            colorMapLoaded: false,  // 是否已加载
            loadColorMap: function (callback) { ... },  // 异步加载，完成后调用 callback
            getColorByNum: function (num) { ... },      // 返回 CSS 颜色值字符串
            formatter: {
                numBall: function (value, row, index) { ... }  // 表格内号码球渲染
            },
            bindevent: function () { ... }
        }
    };
    return Controller;
});
```

From application/admin/view/history/index.html:
- toolbar 区域 id="toolbar"，已有刷新按钮
- 使用 Bootstrap 3 样式类：btn btn-primary, form-control, text-center, text-danger 等
- Layer 对象全局可用（通过 requirejs 加载 fastadmin-layer）

FastAdmin AJAX response format:
```json
{ "code": 1, "msg": "查询成功", "data": [...] }  // success
{ "code": 0, "msg": "错误信息" }                   // error
```
code === 1 表示成功。

Layer.open() API:
```javascript
Layer.open({
    type: 1,           // 1 = page 类型，使用 content 中的 HTML
    title: '标题',
    area: ['650px', '550px'],  // [width, height]
    content: html,     // HTML 字符串
    shadeClose: true   // 点击遮罩关闭
});
```
</interfaces>

<tasks>

<task type="auto" tdd="false">
  <name>Task 1: 在 history 页面 toolbar 添加"遗漏号码"按钮</name>
  <files>application/admin/view/history/index.html</files>
  <read_first>
    - application/admin/view/history/index.html（当前 toolbar 结构）
  </read_first>
  <action>
在 application/admin/view/history/index.html 的 toolbar 区域（id="toolbar"）中添加"遗漏号码"按钮。

当前 toolbar 内容（第 8-11 行）：
```html
<div id="toolbar" class="toolbar">
    <a href="javascript:;" class="btn btn-primary btn-refresh" title="{:__('Refresh')}" ><i class="fa fa-refresh"></i> </a>
<!--                        <a href="javascript:;" class="btn btn-success btn-add ...
</div>
```

在刷新按钮之后、注释的添加按钮之前，插入遗漏号码按钮：
```html
<a href="javascript:;" class="btn btn-warning btn-missingnum" title="{:__('Missing Number Analysis')}"><i class="fa fa-search"></i> {:__('Missing Number Analysis')}</a>
```

完整 toolbar 变为：
```html
<div id="toolbar" class="toolbar">
    <a href="javascript:;" class="btn btn-primary btn-refresh" title="{:__('Refresh')}" ><i class="fa fa-refresh"></i> </a>
    <a href="javascript:;" class="btn btn-warning btn-missingnum" title="{:__('Missing Number Analysis')}"><i class="fa fa-search"></i> {:__('Missing Number Analysis')}</a>
<!--                        <a href="javascript:;" class="btn btn-success btn-add {:$auth->check('history/add')?'':'hide'}" title="{:__('Add')}" ><i class="fa fa-plus"></i> {:__('Add')}</a>-->
</div>
```

关键实现细节：
- 使用 `btn-warning` 样式（黄色按钮，区别于刷新按钮的蓝色）
- class 必须包含 `btn-missingnum`（JS 事件绑定选择器）
- 图标使用 `fa fa-search`（搜索图标，符合查询语义）
- 文本使用 `{:__('Missing Number Analysis')}` 通过 i18n 函数获取（对应 plan 01 task 3 添加的语言键）
- title 属性同样使用 i18n
</action>
  <acceptance_criteria>
    - application/admin/view/history/index.html 的 toolbar 中包含 class="btn-missingnum" 的 <a> 元素
    - 按钮 class 包含 btn-warning
    - 按钮包含 {:__('Missing Number Analysis')} 文本调用
    - 按钮图标 class 为 fa fa-search
    - 按钮位于 btn-refresh 之后
  </acceptance_criteria>
  <verify>
    <automated>grep "btn-missingnum" application/admin/view/history/index.html</automated>
  </verify>
  <done>history 页面 toolbar 出现黄色"遗漏号码"按钮，使用 i18n 文本和搜索图标</done>
</task>

<task type="auto" tdd="false">
  <name>Task 2: 在 history.js 中添加按钮事件绑定、Layer 弹窗和结果渲染逻辑</name>
  <files>public/assets/js/backend/history.js</files>
  <read_first>
    - public/assets/js/backend/history.js（当前 JS 结构，特别是 Controller.api 对象）
    - public/assets/js/backend/command.js（参考 Layer.alert / Layer.open 使用模式）
    - public/assets/js/backend/general/config.js（参考 Layer 弹窗内 HTML + 事件绑定模式）
  </read_first>
  <action>
在 public/assets/js/backend/history.js 中添加遗漏号码弹窗相关代码。

**位置 1：在 Controller.index() 函数内，Table.api.bindevent(table) 调用之后，添加按钮事件绑定：**

在 `Table.api.bindevent(table);` 这一行之后添加：
```javascript
// 遗漏号码按钮事件
$(document).off('click', '.btn-missingnum').on('click', '.btn-missingnum', function () {
    Controller.api.showMissingNumDialog();
});
```
使用 `off().on()` 防止重复绑定（FastAdmin 在 tab 切换时可能重复初始化）。

**位置 2：在 Controller.api 对象中添加三个新方法（在 bindevent 方法之前）：**

```javascript
/**
 * 显示遗漏号码分析弹窗
 */
showMissingNumDialog: function () {
    var html = '<div style="padding:20px;">' +
        '<div class="form-group">' +
        '  <label>' + __('Query Periods') + '：</label>' +
        '  <input type="number" id="missing-periods" class="form-control" value="10" min="1" max="100" style="width:120px;display:inline-block;">' +
        '  <button class="btn btn-primary" id="btn-missing-query" style="margin-left:10px;"><i class="fa fa-search"></i> ' + __('Query') + '</button>' +
        '</div>' +
        '<div id="missing-result" style="margin-top:15px;"></div>' +
        '</div>';

    Layer.open({
        type: 1,
        title: __('Missing Number Analysis'),
        area: ['650px', '550px'],
        content: html,
        shadeClose: true,
        success: function (layero, index) {
            // 绑定查询按钮事件
            $('#btn-missing-query', layero).on('click', function () {
                var periods = parseInt($('#missing-periods', layero).val()) || 10;
                Controller.api.queryMissingNum(periods, layero);
            });
        }
    });
},

/**
 * 查询遗漏号码
 */
queryMissingNum: function (periods, layero) {
    // 确保颜色映射已加载
    if (!Controller.api.colorMapLoaded) {
        Controller.api.loadColorMap(function () {
            Controller.api._doQueryMissingNum(periods, layero);
        });
    } else {
        Controller.api._doQueryMissingNum(periods, layero);
    }
},

/**
 * 执行遗漏号码查询（内部方法）
 */
_doQueryMissingNum: function (periods, layero) {
    $('#missing-result', layero).html('<div class="text-center"><i class="fa fa-spinner fa-spin"></i> ' + __('Loading') + '</div>');
    $.ajax({
        url: 'history/missingNum',
        type: 'GET',
        data: { periods: periods },
        dataType: 'json',
        success: function (ret) {
            if (ret.code == 1) {
                Controller.api.renderMissingNum(ret.data, periods, layero);
            } else {
                $('#missing-result', layero).html('<div class="alert alert-danger">' + (ret.msg || __('Query failed')) + '</div>');
            }
        },
        error: function () {
            $('#missing-result', layero).html('<div class="alert alert-danger">' + __('Query failed') + '</div>');
        }
    });
},

/**
 * 渲染遗漏号码结果
 */
renderMissingNum: function (data, periods, layero) {
    if (!data || data.length === 0) {
        $('#missing-result', layero).html('<div class="alert alert-info">' + __('No missing numbers found') + '</div>');
        return;
    }
    var html = '<div style="display:flex;flex-wrap:wrap;gap:12px;">';
    for (var i = 0; i < data.length; i++) {
        var color = Controller.api.getColorByNum(data[i].num);
        html += '<div style="text-align:center;">' +
            '<span class="num-ball" style="display:inline-block;width:48px;height:48px;line-height:48px;text-align:center;border-radius:50%;color:#fff;background-color:' + color + ';font-weight:bold;font-size:18px;">' + data[i].num + '</span>' +
            '<div style="margin-top:5px;font-size:12px;color:#666;">' + __('Missing') + ' ' + data[i].omit + ' ' + __('periods') + '</div>' +
            '</div>';
    }
    html += '</div>';
    $('#missing-result', layero).html(html);
},
```

关键实现细节：
- 弹窗 HTML 使用 Bootstrap 3 form-group 和 form-control 样式
- 输入框 type="number"，默认值 10，min=1，max=100，宽度 120px 内联显示
- 查询按钮在输入框右侧，margin-left:10px
- 结果区域 id="missing-result"，初始为空
- Layer.open 使用 type:1（page 类型），area: ['650px', '550px']，shadeClose:true（点击遮罩关闭）
- success 回调中绑定查询按钮事件，使用 layero 作为上下文选择器根（`$('#btn-missing-query', layero)`）
- queryMissingNum 先检查 colorMapLoaded，未加载则先调用 loadColorMap
- AJAX 使用标准 $.ajax，url 为 'history/missingNum'，type 为 'GET'
- 响应判断 `ret.code == 1`（FastAdmin 成功标志）
- 加载状态显示 spinner + 文字
- 渲染时使用 flex 布局（display:flex; flex-wrap:wrap; gap:12px）展示球网格
- 每个球 48x48px，圆角 50%，白色文字，背景色来自 getColorByNum()
- 球下方显示"遗漏 X 期"文字，12px 灰色字体
- 所有文本使用 __('key') 获取 i18n
- 空结果时显示 alert-info 提示

不要修改现有的 Controller.index、Controller.add、Controller.edit 方法签名。只在 index 函数内追加按钮事件绑定，在 api 对象内追加新方法。
  </action>
  <acceptance_criteria>
    - public/assets/js/backend/history.js 的 Controller.index() 中包含 `$(document).off('click', '.btn-missingnum').on('click', '.btn-missingnum'`
    - Controller.api 对象包含 `showMissingNumDialog` 方法
    - Controller.api 对象包含 `queryMissingNum` 方法
    - Controller.api 对象包含 `_doQueryMissingNum` 方法
    - Controller.api 对象包含 `renderMissingNum` 方法
    - showMissingNumDialog 调用 Layer.open({type: 1, ...})
    - Layer.open 的 content 包含 input type="number" id="missing-periods"
    - queryMissingNum 检查 colorMapLoaded 状态
    - _doQueryMissingNum 使用 $.ajax 请求 url: 'history/missingNum'
    - renderMissingNum 使用 flex-wrap 布局渲染球网格
    - renderMissingNum 调用 Controller.api.getColorByNum() 获取颜色
    - 结果球显示数字 + 下方"遗漏 X 期"文字
  </acceptance_criteria>
  <verify>
    <automated>grep -c "showMissingNumDialog\|queryMissingNum\|renderMissingNum\|btn-missingnum" public/assets/js/backend/history.js | awk '$1 >= 4'</automated>
  </verify>
  <done>history.js 有完整的遗漏号码按钮处理、Layer 弹窗（含期数输入和查询按钮）、AJAX 请求、结果渲染（波色球+遗漏期数）逻辑</done>
</task>

</tasks>

<threat_model>
## Trust Boundaries

| Boundary | Description |
|----------|-------------|
| User input (number field) → AJAX request | 用户可能在浏览器开发者工具中修改 periods 值 |
| Layer dialog HTML injection | 弹窗 HTML 由 JS 拼接，无外部输入注入风险 |

## STRIDE Threat Register

| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-01-05 | Tampering | 前端 periods 输入值 | mitigate | 后端已有 range 校验 1-100；前端 HTML input min/max 为 UX 辅助，不依赖前端校验 |
| T-01-06 | Information Disclosure | Layer 弹窗内容 | accept | 仅展示遗漏号码统计数据，无敏感信息 |
</threat_model>

<verification>
- 在浏览器中访问 admin history 页面，确认 toolbar 出现黄色"遗漏号码"按钮
- 点击按钮，确认 Layer 弹窗打开，包含期数输入框（默认 10）和查询按钮
- 输入 10 点击查询，确认出现加载状态 → 结果网格（带颜色的球 + 遗漏期数文字）
- 输入 0 或 200 点击查询，确认后端返回错误提示
- 确认结果球颜色与 history 表格中的波色球一致（复用 getColorByNum）
- 确认结果按遗漏期数从大到小排列（视觉上最大 omit 的球在最左）
- 点击弹窗遮罩，确认弹窗关闭
</verification>

<success_criteria>
- [ ] history 页面 toolbar 有"遗漏号码"按钮（btn-warning 样式）
- [ ] 点击按钮弹出 Layer 弹窗，标题为"遗漏号码分析"
- [ ] 弹窗内有期数输入框（默认值 10，范围 1-100）和查询按钮
- [ ] 点击查询后显示加载状态，然后显示结果
- [ ] 结果以 flex 网格展示，每个球显示号码、波色、遗漏期数
- [ ] 波色球颜色与表格中一致（复用 getColorByNum）
- [ ] 无遗漏号码时显示友好提示
- [ ] 点击遮罩可关闭弹窗
</success_criteria>

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