Files
qlib/frontend/web/components/Modal.module.css
T
Simon c974415691 feat(web): 实验对比页支持批量删除 / 发起时间列 / 详情图层,打开归档改新页面
用户要求的三件事(原话):①批量删除;②增加「测试发起时间」;③点击详情用图层展示、
打开归档用新页面展示。

- **批量删除**(`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 倍放大截图逐字核对,
  原先「×」细到几乎看不见,现在与「在新页面打开完整归档」同高同线、清晰可见。
2026-10-01 18:31:04 +08:00

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);
}