--- 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" --- 在 history 页面添加"遗漏号码"按钮和 Layer 弹窗 UI,包含期数输入框、查询按钮、结果展示区域和波色球渲染,复用已有的 getColorByNum() 颜色函数。 Purpose: 提供用户交互界面(per D-01: 按钮+弹窗形式,不新增独立页面/菜单;per D-03: Layer 弹窗展示) Output: toolbar 按钮 + Layer 弹窗 HTML + 结果渲染逻辑 @D:/code/php/amlhc/.claude/get-shit-done/workflows/execute-plan.md @D:/code/php/amlhc/.claude/get-shit-done/templates/summary.md @.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 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 // 点击遮罩关闭 }); ``` Task 1: 在 history 页面 toolbar 添加"遗漏号码"按钮 application/admin/view/history/index.html - application/admin/view/history/index.html(当前 toolbar 结构) 在 application/admin/view/history/index.html 的 toolbar 区域(id="toolbar")中添加"遗漏号码"按钮。 当前 toolbar 内容(第 8-11 行): ```html ``` 关键实现细节: - 使用 `btn-warning` 样式(黄色按钮,区别于刷新按钮的蓝色) - class 必须包含 `btn-missingnum`(JS 事件绑定选择器) - 图标使用 `fa fa-search`(搜索图标,符合查询语义) - 文本使用 `{:__('Missing Number Analysis')}` 通过 i18n 函数获取(对应 plan 01 task 3 添加的语言键) - title 属性同样使用 i18n - application/admin/view/history/index.html 的 toolbar 中包含 class="btn-missingnum" 的 元素 - 按钮 class 包含 btn-warning - 按钮包含 {:__('Missing Number Analysis')} 文本调用 - 按钮图标 class 为 fa fa-search - 按钮位于 btn-refresh 之后 grep "btn-missingnum" application/admin/view/history/index.html history 页面 toolbar 出现黄色"遗漏号码"按钮,使用 i18n 文本和搜索图标 Task 2: 在 history.js 中添加按钮事件绑定、Layer 弹窗和结果渲染逻辑 public/assets/js/backend/history.js - 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 + 事件绑定模式) 在 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 = '
' + '
' + ' ' + ' ' + ' ' + '
' + '
' + '
'; 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('
' + __('Loading') + '
'); $.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('
' + (ret.msg || __('Query failed')) + '
'); } }, error: function () { $('#missing-result', layero).html('
' + __('Query failed') + '
'); } }); }, /** * 渲染遗漏号码结果 */ renderMissingNum: function (data, periods, layero) { if (!data || data.length === 0) { $('#missing-result', layero).html('
' + __('No missing numbers found') + '
'); return; } var html = '
'; for (var i = 0; i < data.length; i++) { var color = Controller.api.getColorByNum(data[i].num); html += '
' + '' + data[i].num + '' + '
' + __('Missing') + ' ' + data[i].omit + ' ' + __('periods') + '
' + '
'; } html += '
'; $('#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 对象内追加新方法。
- 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 期"文字 grep -c "showMissingNumDialog\|queryMissingNum\|renderMissingNum\|btn-missingnum" public/assets/js/backend/history.js | awk '$1 >= 4' history.js 有完整的遗漏号码按钮处理、Layer 弹窗(含期数输入和查询按钮)、AJAX 请求、结果渲染(波色球+遗漏期数)逻辑
## 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 | 仅展示遗漏号码统计数据,无敏感信息 | - 在浏览器中访问 admin history 页面,确认 toolbar 出现黄色"遗漏号码"按钮 - 点击按钮,确认 Layer 弹窗打开,包含期数输入框(默认 10)和查询按钮 - 输入 10 点击查询,确认出现加载状态 → 结果网格(带颜色的球 + 遗漏期数文字) - 输入 0 或 200 点击查询,确认后端返回错误提示 - 确认结果球颜色与 history 表格中的波色球一致(复用 getColorByNum) - 确认结果按遗漏期数从大到小排列(视觉上最大 omit 的球在最左) - 点击弹窗遮罩,确认弹窗关闭 - [ ] history 页面 toolbar 有"遗漏号码"按钮(btn-warning 样式) - [ ] 点击按钮弹出 Layer 弹窗,标题为"遗漏号码分析" - [ ] 弹窗内有期数输入框(默认值 10,范围 1-100)和查询按钮 - [ ] 点击查询后显示加载状态,然后显示结果 - [ ] 结果以 flex 网格展示,每个球显示号码、波色、遗漏期数 - [ ] 波色球颜色与表格中一致(复用 getColorByNum) - [ ] 无遗漏号码时显示友好提示 - [ ] 点击遮罩可关闭弹窗 After completion, create `.planning/phases/01-omitted-number-analysis/01-02-SUMMARY.md`