# ek-ui
ek 生态的常驻主界面 crate(DV-1:egui/eframe 0.33)。**UI 即插件**:观测窗不是宿主外壳,
与 `dm`/`oms`/`ctp` 同列的一个插件——总线面收事件维护共享快照,eframe 窗口面只读渲染。
(0.2.10 BD-UI-38 用户裁定:`sim` 纸面网关退场,ctp/SimNow 两套前置满足需求。)
**零配置口径(2026-10-17 定盘)**:ek-ui 不读任何配置文件,界面本身无设置;
宿主侧同样不预读配置。插件结构只由 `--from` 清单或 demo 缺省决定。需要配置的
插件(唯一 = ek-ctp 虚拟账号),配置面长在插件自身——菜单「账户」面板提供
**手工输入位**(8 格:别名 / broker / td 前置 / td 用户 / td 密码 / AppID / AuthCode / md 前置,
密码掩码;md 账号与密码不单列 = 留空回落 td 凭据,九期 BD-UI-62;无订阅字段)与账户清单,经 ask `{gw}.configure` 进插件
(网关注册名由 broker + 账号派生 `ctp_<broker>_<账号>`,不写配置文件;ek-ctp 自带配置口:
0.3.0 引入,0.5.0 补 configure 强制重连);宿主与界面都不代读、不转发。
实连 CTP 需 `--features ctp-native`(默认关,日常构建不碰 C++)。
**界面框架(v7,0.4.1 BD-UI-54 拆 egui_dock 回归原生容器;前身 v4 = BD-UI-27/28 面板化、
v5 = 0.3.0 数据区 dock、v6 = 0.4.0 整窗 dock)**:
六所固定 tab 条(标签 = **交易所中文名** 上期所/大商所/郑商所/中金所/广期所/能源中心,照 Python
叫法;条在**表之下**且不越进数据区 —— 九期 BD-UI-62)—— 当前所行情表,tick 唯一源,
单击行带入下单面板,双击行弹 K线窗)+ **下单 SidePanel**(可拖,缺省 300 =
Python `setFixedWidth(300)`;9 字段 + 「自动开平」勾选 + 委托/全撤 + 盘口)+
**数据区 CentralPanel**:2×2 = 自写薄 QSplitter 两刀(份额可拖 0.15..0.85,恢复布局
回 50:50 = Python `right_splitter`);四组容器照 Python 四个 QTabWidget:「委托|
未成交」「成交」「持仓」「资金|日志」(列面逐值抄录 Python 蓝图:委托 17 列含
名称/交易所/类型/来源,成交 12 列(方向上色),持仓 16 列;撤单 = 行钮或双击委托行);
全表行选中跳色(行内任意格单击 = 选中,悬停淡底,每表一行;行情选中 = 当前合约;
行情盘口四列带入带方向:买1 价/量 → 卖,卖1 价/量 → 买,价格列顺带填价,其余列方向
保持默认)——
查询合约窗与 K 线窗是**独立 OS 窗**(原生最小化/最大化/关闭,可拖出主窗放
屏幕任意处;不支持多窗的后端自动回退主窗内弹窗);运维/账户窗同为独立 OS 窗
(BD-UI-33 四窗一型),都不占主界面 tab。默认窗 1280×860。
(0.4.0 曾整窗 egui_dock:其「浮窗」实为主窗内 egui Window,拖不出 OS 窗、拖放态无
接管点 —— 详见 DESIGN BD-UI-54 的源码定位;经普查裁定拆除,窗能力归 eframe 视口。)
**表头固定与表头带(0.2.5/0.2.6,BD-UI-34;0.2.27,BD-UI-41 补 chrome;0.4.1 BD-UI-55
表格层换 `egui_table 0.7.0`)**:七张表(行情格/委托/成交/持仓/**资金**/日志/查询合约)
表头固定 —— 纵向滚动表头钉顶、横向滚动表头同移(库的粘性表头 + SplitScroll;0.2.8 起那块
「滚动区之后重画覆盖带 + 重画表头文字」的 DIY 随旧库一起退役,**带底与 1px 下边线仍由我们
在表前整行画**)。**空表表头照常在场**(0.4.0 起由 s73 钉死:八张表的独有表头在全空状态下
必须在)。表头带 = 带底 + 1px 下边线(旧口径照 qdarkstyle `QHeaderView::section`;0.4.1
四期 BD-UI-57 起带底 `#262626`、边线 = `border` 白 10%,两支色分开),表头字号比正文小
一档(Small 14 vs Body 16,Python 侧正是表头 13px < 正文 ≈14.7px);浮动窗里三张短表
(账户清单/运维插件表/服务目录)走 `header_band` 同一配色。**网格线 = 只有行线(0.4.1 五期
BD-UI-58 立「全部表都有网格线」,七期 BD-UI-60 按用户裁定收窄成「只要行的分割线,所有列分割线
都去掉」)**:表格层每张表每行一根行线;列线两个来源都撤 —— 短表层 `grid_chrome` 的列线循环直接
删掉,表格层库里那根(只在可拖列边界上画,`resizable` 恒开才有)改法 = 表的 scope 里把
`widgets.noninteractive.bg_stroke` 的**常态**描边改成全透明,hover / 拖列时仍走 `hovered` /
`active` 描边 → 拖列宽看得见反馈。Python 侧交易表 `setShowGrid(False)` 这一条按裁定反过来,
不再以 Python 为准。**空态不画「(无数据)」这类提示(0.4.1 六期 BD-UI-59,用户裁定「它里面
就是空的,大家都能看到」)**:表头恒在 + 0 行,行线没有(0 行没有行底可画)。**表宽 = 所在区域
100%(0.4.1 七期 BD-UI-60)**:空态按表头文字宽的比例填满(`header_proportional_widths`,按
`n_rows == 0` 现场判定),有数据之后列按内容长(库的 grow-only),**剩余宽补给最后一列**
(= Qt `stretchLastSection` 等价,`widget.py:646` + 默认 True),列总宽已超可用宽时**不压缩**、
保持横向滚动;短表层吃 `min_col_width = 区域宽 / 列数`(下限,内容更宽时照样长,不截断)。
斑马纹改跟主题(`visuals.striped` = false,egui_table 根本没有斑马这条路,行底色只剩
「选中 / 悬停吃主题」)。
**虚拟化口径(0.4.1 BD-UI-55,记死)**:egui_table 按视口剔行剔列 —— 滚出视口的**体表格**
不进 AccessKit 语义树(表头恒在)。视觉不变(横/纵滚动条照旧),但「辅助技术一次读到整张宽表
的全部列」不再成立,这是虚拟化的代价(DESIGN BD-UI-55「口径变更」)。
**日志格从「monospace 行 + 定高 180px」换成带「时间/信息」表头的表** → 窗口最大化它跟着长。
**页签回归自绘 `tab_label`**(0.4.1 BD-UI-54;0.4.0 曾短暂归 dock):未选中 BG4 头底 /
选中 BG5 底 + ACCENT4 3px 下划线(s67 判据),且现在是原生控件 —— 进 AccessKit 可点可测。
K 线窗打开即最大化,ESC 快捷关闭。主题只有一套(0.2.26 BD-UI-40 引入,0.2.30 BD-UI-46/47 起
**唯一**,「一键循环五套配色」钮已删):0.2.26–0.4.1 三期是 Qt深色 QDarkStyle 3.2.3 复刻,
**0.4.1 四期(BD-UI-57)换成 shadcn/ui Nova 深色** —— 页面底 `#0A0A0A`、卡片 `#171717`、
控件底 `#262626` → hover `#404040` → pressed/选中 `#525252`、一切描边 = `border` 白 10%、
圆角两档(卡片/窗/菜单 10px、控件 8px)、描边 1px 不变、正文 `#FAFAFA`、次级 `#A1A1A1`、
强调(下划线/链接)`#E5E5E5`;语义色(多红空绿等)跨主题恒定不变。字号阶梯 22/16/14 与
行高 26 是 BD-UI-32 用户裁定,**不随皮肤退到 shadcn 的 13/14/15**。
**薄下单者(0.1.1,BD-UI-30)**:下单/撤单不直发网关——全部走 `oms.submit_order`
五段管线(拆单/风控/逐腿发送/记账)与 `oms.cancel_order`(守卫不可绕过);
「自动开平」勾选 = 拆单四态的 Auto/None 逐请求开关(风控与守卫不可旁)。
**订阅两入口(订阅不在账户面板定死;一律路由到行情元账号)**:下单面板代码框回车即订阅(带 `EX.` 前缀
自动剥成裸码);菜单「查询合约」
弹窗(查询框留空 = 全部 + 勾选 + 订阅选中)。合约流由 ek-ctp 登录后自动查询,
UI 只收不催。多账号面只有一个**行情元账号**(账户面板指定,已装配后锁定),
订阅只发它 —— 纯 TD 账号收到订阅会静默成功却永远不来 tick,所以路由在界面侧定死。
**账户状态列与『断开』(0.2.19,随 ek-ctp 0.9.0)**:账户面板状态列是**两段**——
实例装配位(`svctl.list` 在场)+ **登录态**(每 2 秒向每个已装配且在场的账户 ask 一次
`{gw}.status` 折出来):`已装配·在线` / `·掉线` / `·登录失败` / `·查询断供` / `·已断开` /
`·登录态未知`。界面只认网关的 `td_logged_in`(纯行情账户认 `md_logged_in`),
**「句柄已建」不当「在线」用**、**"从未回报过"不当"0 秒前回报过"用**;
「查询断供」= 查询连续被拒达 3 次 **或** 资金回报超 30 秒未更新(阈值在界面,可覆盖)。
有话可说的行在面板底部给一句 `状态细节 <派生名>:<原因>`(带次数/秒数)。
『断开』走 `{gw}.disconnect` = **停会话**(实例、配置、订阅、状态计数全留,『连接』可恢复);
『移除』走 `svctl.remove` = **停实例** —— 两个动作两条口,不共用。
**三条口径写进界面(0.2.20,随 ek-ctp 0.9.1)**:① **收录勾选只管该网关自己**收录并上抛的合约,
公用合约表 = 在场各网关的并集,改勾选须『连接/重试』才下发(面板底部与配置弹窗各写明一次);
② **运维窗不再对账户清册里的实例提供『移除』/『重注入』** —— 那一格改写成「起停归『账户』面板
(带撤单守卫)」,实例仍列在表里;清册外的 `ctp_*` 实例仍留这条出口;③ 日志溢出时同一行接上
「日志窗是窗口不是历史 —— 登录/掉线/查询被拒看状态列与状态细节,别回扫这里」。
状态细节另接 ek-ctp 0.9.1 的分项:合约查询被拒 N 次(与断供句并存,不重复点名)。
**「从未到手」也会超时(0.2.21)**:「查询断供」三条判据 —— 连续被拒达 3 次 / 距最后一次资金回报
超 30 秒 / **登录后超 30 秒资金回报一次都没到手**(实连读数照出来的:登录着、零被拒、`None` 一直不变
的账户,原来永远显示「在线」)。刚登录几秒不算;真到手即回在线。
**中文渲染(BD-UI-26)**:启动时自动从系统字体目录装载 CJK 回退字体
(simhei 等,**只收纯 .ttf/.otf**);装载证据行 stderr 可 grep
(`[ek-ui] CJK 回退字体已装载: <path>`)。候选全空时中文显形为豆腐块并
在 stderr 警告——属环境侧字体缺失,非界面缺陷。
## 快速开始
```powershell
cargo run # 无参:dm + oms + ui demo 窗(网关经运维窗 svctl 激活)
cargo run -- --pick # 启动器:勾选插件 → 写清单 → spawn 子进程 → boot-wait
```
```rust
use ek_station::StationConfig;
let mut cfg = StationConfig::demo(); // = dm + oms
cfg.plugins.push("ui".into());
ek_ui::run_station_ui(&cfg, &ek_ui::runtime::RunOpts::plain())?;
```
launcher 协议(bin级):`ek-ui --from <清单> --ready-file <标记>`,start 成功即写
`ready\n`;`ek-station --from ...` 同 mate(清单由 `--pick` 窗生成)。
`ek-ui --pick` 走选窗 → 工作线程 spawn(30s 就绪上限;失败退 2,成功子进程分离脱管)。
## 自动化测试(S1b 起,人工核查 0 项)
- L1:`tests/ui_state.rs` —— 环形日志/快照/revision/停止位(线程面状态机)。
- L2:`render_body(ctx, &UiShared)` 单渲染入口(真实 App 与测试共用;面板组合
在入口内,0.4.1 起 = 菜单 TopBottomPanel + 行情带 TopBottomPanel + 下单
SidePanel + 数据区 CentralPanel(2×2 薄 QSplitter)+ 弹窗最后,BD-UI-54;
0.4.0 那块整窗 dock 已拆)。
- L3:`tests/ui_kittest.rs`(**109 钉,0 ignore**;0.4.0 起新增 s73「空态不吞表头」,
0.4.1 BD-UI-55 新增 s72「行线落在相邻两行之间、横跨带宽」,五期新增 s74「空态表头填满当前
表宽」/ s75「全部表都有网格线」/ s76「短表层同一套网格」,六期新增 s77「空态(0 行)也有列线」,
七期 BD-UI-60 把 s71 / s75 / s76 / s77 四颗**反向重钉成「列线必须为 0」**(计数走任意色,
换描边颜色不算过)并新增 s78「表宽 = 区域 100% + 滚动条 5 px 暗把手 + 表头上下居中」,
八期 BD-UI-61 新增 s79「所有表头带高同一档 29 ± 1 + 表头字号全表 = Small + 带里居中」,
九期 BD-UI-62 新增 s80「行情带 tab = 中文名 + 在表下 + 不压数据区 tab 头」/
s81「悬停高亮 = 整行宽(含行尾空余)」/ s82「数据区竖分隔条 = 一根贯穿线」,并把
`header_bands` 补上**圆角闸**(表头带方角 vs tab 头底圆角,同色同实心),
十期 BD-UI-63 新增 s83「登录被拒时柜台原因跟着红字打在账户行上(`已装配·登录失败·不合法的登录(3)`),
整句仍在『状态细节』那一行给全,恢复在线后原因不留在行上」——
`egui_kittest` 官方建构(ctx 级 `build`,
render_body 自开顶层面板):AccessKit 节点断言、
`清空日志` 就地交互、下单面板入列(委托钮/坏价红字/全撤扇出)、代码框回车
订阅(s20:focus+type_text+key_press 全链)、委托行双击撤单(s11:建构用
`with_step_dt(0.1)`,kittest 每事件一帧口径,BD-UI-28⑥)、行情格单击带入
下单面板、查询合约窗勾选订阅、菜单开窗(运维/账户/查询合约)、行情带切换
(s8:六所 tab 现在是原生控件,直接点标签 or 走 `set_selected_exchange` 状态口
都可断)、静态布局结构钉 s3(0.4.0 那版的基础上按原生面板复核)与满表列面钉
s21(委托 17/成交 12/持仓 16 列,布局泄漏与列面回归当场红)。「画出来但没有
标签」的那一类
(表头带、tab 下划线、行线、toast、斑马缺席)走**本帧绘制指令**判据:
`egui::FullOutput.shapes` 里的实心矩形 / 线段 / 文字形状 + 裁剪矩形。
**0.3.2 起不出位图、不读像素、没有快照基线**:`tests/snapshots/` 已删,
`egui_kittest` 的 `snapshot` / `wgpu` feature 已撤(口径见 DESIGN.md
「口径变更」与交接报告 §17);点击一律 `click_accesskit`
(坐标点击只打可见钮,横向滚动面外控件用 AccessKit 动作),建构面统一
`harness_for()` 实窗尺寸,见 BD-UI-23/27/28/29);**读靠右列的钉走宽夹具
`harness_wide_for()`(2600×900)** —— 0.4.1 BD-UI-55 起 egui_table 不建构视口外的
体表格,1280 宽时委托/成交表第 10 列往后根本不在语义树里(s11/s21/s23/s38/f2/w1/w8)。
- 列面口径(0.3.3 Wave F,BD-UI-51):成交表列序照 Python `TradeMonitor`;判据 f1 钉
「表体第 i 格 == 表头第 i 列」(F1)、持仓成本/均价取值(F2)、列序(F3)、
展示格式只住 `src/format.rs`(F4)。委托表/持仓表/行情带都吃「点表头排序」。
三处列差集(行情表交易所列 / 委托表操作列 / 枚举显示语言)的裁定写在 CHANGELOG 0.3.3。
- 装配链:`tests/ui_runtime.rs` —— ui 工厂可装配、eTimer 1s 快照回路、
关窗协议(stop 位 → 观察线程 close_all → 全体 on_stop)。
- 行情面:`tests/ui_market.rs` —— DM 金样本回放逐位、回灌闭环(帧统计三钉)。
- 交易面:`tests/ui_converter_chain.rs`(3 钉;0.2.10 BD-UI-38 改写)—— oms 五段
管线全链(Auto 拆 2 腿今先/冻结/账本清零/parent 聚合)、mode None 单腿旁路、
手数闸拒 = 网关零接触;网关位 = 测试本地 "ctp" 受理夹具(受理形制忠实 ek-ctp,
成交按 CTP 回报行直发注入)。纸面网关 `tests/ui_sim.rs`(9 钉)与 ek-paper 引用
已随 sim 退场撤除(网关档案见 [ek-paper](https://crates.io/crates/ek-paper),
历史 BD-UI-36;退场裁定 BD-UI-38)。
- 运维面:`tests/ui_ops.rs` —— 命令队列往返/两步确认/检疫检测(u0–u2)+
移除重启环与未知工厂拒装(t0/t1)。
- 启动器:`tests/launch.rs` + `tests/pick_ui.rs` —— spawn/ready 协议、退出码、
选窗勾选写清单与派生。
- 字体面:`src/fonts.rs` 单测 f0(22 常用汉字 has_glyph)+ kittest s16 汉字密集页
排版钉(BD-UI-26 乱码根因钉:字体装上了 + 这一帧真把整串中文排出宽度;
无 CJK 候选环境显形失败,属设计)。
- CTP 配置面:`tests/ui_ctp.rs` —— ui→ask `ctp.configure`/`ctp.subscribe` 总线
闭环(空地址守卫日志、服务目录 5 口、校验拒面、ctp 不在场失败不炸链)+
kittest s13(手工表单入列逐位、窗面无订阅字段口径)。
- CTP 实连(手工):`tests/ui_ctp_live.rs` —— `--features ctp-native` + `--ignored`
才跑;12 步全流程(判据三态 PASS/OBSERVE/FAIL,账号 env 手工注入)。
## 门禁
```powershell
powershell -ExecutionPolicy Bypass -File scripts/gate.ps1
```
五闸:fmt --check / test / clippy -D warnings / doc / check-package(strict)。
## 依赖纪律
ek 生态件一律 crates.io 装版引用(运行时七件直接依赖:core/event/data/dm/ctp/oms/station;
0.1.1 起运行时还经 station 传递吃到第八件 ek-risk 0.1.x(风控闸装配,含多账号面的合约预检闸,见 CHANGELOG);
dev-dep converter 0.1.1 已随 0.1.1 退役——拆单器入驻 oms 管线,见 CHANGELOG);
三方库只在本 crate(eframe = egui = egui_kittest = 0.33.3 四件套锁定,见 DESIGN BD-UI-7;
表格层 = `egui_table 0.7.0`(rerun-io,egui ^0.33 同代)—— 0.2.29 起用的 `egui_extras`
于 0.4.1 BD-UI-55 退役,`[patch.crates-io]` vendor 副本也一并拆掉,不许留)。
remote 授权(2026-10-17 建仓):`https://cnb.cool/enkeep/ek-ui`(本仓)与
`https://cnb.cool/enkeep/ek-station`;此前为纯本地提交(方案 §3 双启动矩阵:
ek-dm 同域纪律)。