Files
2026-04-21 23:02:15 +08:00

356 lines
15 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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",默认值 10min=1max=100,宽度 120px 内联显示
- 查询按钮在输入框右侧,margin-left:10px
- 结果区域 id="missing-result",初始为空
- Layer.open 使用 type:1page 类型),area: ['650px', '550px']shadeClose:true(点击遮罩关闭)
- success 回调中绑定查询按钮事件,使用 layero 作为上下文选择器根(`$('#btn-missing-query', layero)`
- queryMissingNum 先检查 colorMapLoaded,未加载则先调用 loadColorMap
- AJAX 使用标准 $.ajaxurl 为 '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>