ek-ui 0.9.37

ek 生态常驻主界面(egui/eframe):插件即界面——观测页 + 日志页,车头与 UI 同插件
docs.rs failed to build ek-ui-0.9.37
Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
Visit the last successful build: ek-ui-0.9.33

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): 全局字号 16px 面(apply_ui_style);菜单栏(运维|账户|查询合约一键直达弹窗,帮助保留菜单)

  • 行情带 TopBottomPanel(可拖分隔条,缺省 240 = Python setSizes([240,560]) 上带; 六所固定 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 警告——属环境侧字体缺失,非界面缺陷。

快速开始

cargo run            # 无参:dm + oms + ui demo 窗(网关经运维窗 svctl 激活)
cargo run -- --pick  # 启动器:勾选插件 → 写清单 → spawn 子进程 → boot-wait
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, 历史 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 -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 同域纪律)。