用户要求的三件事(原话):①批量删除;②增加「测试发起时间」;③点击详情用图层展示、
打开归档用新页面展示。
- **批量删除**(`POST /experiments/bulk-delete`,已在上一个提交实现接口):
表头全选(只作用本页,工具条写明范围)+ 每行删除复选框,工具条显示「已选 N 个(待删除)
| 删除所选 | 取消选择」;确认框写清**不可恢复**、结果只存归档这一份、关联作业记录会保留但
读不回结果、想留底先导出 JSON。单次接口上限 200 个 id,超过前端分批;**分批中途失败时
如实报「已删除 X 个,之后失败 —— 原文」**(前面几批是真删了,不能报成一个都没删);
接口返回的 `missing` 单独用警示色报「M 个不存在(未计入删除数)」,不把「不存在」当删成功;
只清掉确实删掉/本来不存在的选中项(没删成的留在选中里可直接重试),并同步清掉已删归档
的详情图层 / 对比选择 / 勾选状态。
- **发起时间列**:`created_at` 用 `new Date()` 转**本地时间** `YYYY-MM-DD HH:mm:ss`
(不直接截 UTC 字符串),空值显示「—」;表头可排序(空值恒排末尾、同秒用 id 兜底)。
- **详情图层**:新增通用 `components/Modal.tsx`(portal / `role="dialog" aria-modal` /
`aria-labelledby` / 锁 body 滚动 / ESC 关闭 / 遮罩 mousedown 关闭而点内容区不关 /
打开聚焦、关闭把焦点还给触发按钮 / Tab 焦点陷阱;样式走 CSS Module,不动 globals.css)。
图层内复用 `ArchiveResultView`(按 kind 分发,回测的净值曲线、成交明细、买卖说明、
因子曲线与「新页面放大」链接都在图层里可用),元信息区列出 id/类型/发起时间/版本/作业/区间/体积;
标题栏含「在新页面打开完整归档」。
- **打开归档**:列表入口改 `target="_blank" rel="noreferrer"`(URL 不变)。
验证(真实浏览器 CDP,读数原文已记录):
- 表头 `发起时间 ↓`,首行 `EXP-8EA2819B / 2026-10-01 17:45:05`;点一次变升序(首行
2026-09-06 17:17:37),再点回降序。
- 图层:`{aria-modal:true, 标题「EXP-8EA2819B 归档详情 · 回测」, canvas:35, tables:10,
body.overflow:hidden}`;点内容区不关、遮罩关闭后焦点回到「详情」、ESC 关闭且恢复滚动。
- 批量删除:造 2 条一次性归档 → 勾选 → 确认「将永久删除 2 个归档」→ 横幅「已删除 2 个归档。」,
行数 59→56;用 `/api/experiments` 逐条集合核对 `before-after=[]`、`after-before=[]`
(只删勾选的);`missing` 分支实测「已删除 0 个归档,1 个不存在(未计入删除数)。」;
进行中态实测「删除中… + disabled」。造出的 4 条自检归档已全部清理(404,关键词命中 0)。
- 打开归档用真实鼠标事件点击:页面目标 `/experiments` → `/experiments/EXP-10581FED`
(合成 `a.click()` 会被 Chrome 弹窗策略拦掉,故用真实鼠标事件验证)。
- 另外把图层关闭按钮调大调亮(34×34、`--text-1`、图标 16px):2 倍放大截图逐字核对,
原先「×」细到几乎看不见,现在与「在新页面打开完整归档」同高同线、清晰可见。
101 lines
2.8 KiB
CSS
101 lines
2.8 KiB
CSS
/*
|
|
* 图层样式(只服务 components/Modal.tsx)。
|
|
*
|
|
* 刻意用 CSS Module 而不是 app/globals.css:全局样式表被多个页面共用,
|
|
* 而这里的选择器名(backdrop/dialog/head/body)太通用,写进全局极易与别处冲突;
|
|
* CSS Module 会在构建时把类名变成局部哈希,天然隔离。
|
|
* 颜色/圆角/间距全部取 globals.css 里已有的设计令牌,保证与全站同一套视觉。
|
|
*/
|
|
|
|
.backdrop {
|
|
position: fixed;
|
|
inset: 0;
|
|
z-index: 1000;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: var(--sp-5);
|
|
/* 半透明遮罩 + 轻微毛玻璃:背后的列表仍可辨认位置,但不会与图层内容抢注意力 */
|
|
background: rgba(4, 8, 18, 0.66);
|
|
backdrop-filter: blur(3px);
|
|
overflow: auto;
|
|
}
|
|
|
|
.dialog {
|
|
display: flex;
|
|
flex-direction: column;
|
|
width: min(1100px, 100%);
|
|
/* 小屏/长内容时图层本身不超过 90vh,滚动交给 .body */
|
|
max-height: 90vh;
|
|
border: 1px solid var(--line-strong);
|
|
border-radius: var(--r-lg);
|
|
background: var(--surface-1);
|
|
box-shadow: var(--shadow-pop);
|
|
/* 对话框用 tabIndex={-1} 聚焦:去掉浏览器默认的聚焦描边,改用下面的 :focus-visible */
|
|
outline: none;
|
|
}
|
|
|
|
.head {
|
|
flex: 0 0 auto;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: var(--sp-3);
|
|
padding: var(--sp-3) var(--sp-4);
|
|
border-bottom: 1px solid var(--line);
|
|
border-radius: var(--r-lg) var(--r-lg) 0 0;
|
|
background: var(--surface-2);
|
|
}
|
|
|
|
.title {
|
|
margin: 0;
|
|
font-size: var(--fs-lg);
|
|
font-weight: 600;
|
|
color: var(--text-1);
|
|
overflow-wrap: anywhere;
|
|
}
|
|
|
|
.tools {
|
|
display: flex;
|
|
align-items: center;
|
|
flex-wrap: wrap;
|
|
gap: var(--sp-2);
|
|
}
|
|
|
|
.close {
|
|
appearance: none;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
/* 图标按钮用标准控件高度(34px)而不是密集行的 28px:图层是浮层,
|
|
关闭是唯一出口,点击目标不该比正文按钮更小;字号也调大一档,
|
|
避免「×」在深色底上细到看不见(截图逐字核对时发现对比度偏低)。 */
|
|
width: var(--ctl-h-md);
|
|
height: var(--ctl-h-md);
|
|
border: 1px solid var(--line-strong);
|
|
border-radius: var(--r-sm);
|
|
background: var(--surface-3);
|
|
color: var(--text-1);
|
|
font-size: var(--fs-md);
|
|
line-height: 1;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.close:hover {
|
|
color: var(--text-1);
|
|
border-color: var(--line-strong);
|
|
}
|
|
|
|
.close:focus-visible {
|
|
outline: 2px solid var(--focus);
|
|
outline-offset: 1px;
|
|
}
|
|
|
|
/* 内容区:归档结果可以很长(净值曲线 + 持仓 + 成交表),所以这里独立滚动,
|
|
标题栏与关闭按钮始终可见(不用把用户滚回顶部才能关掉图层)。 */
|
|
.body {
|
|
flex: 1 1 auto;
|
|
max-height: 90vh;
|
|
overflow: auto;
|
|
padding: var(--sp-4);
|
|
} |