feat: E1002 宽版用量配色改三档绿/黑/红+今日实时与近7天版式重做

- 用量与 pace 配色由「红/黑」二档、黄「注意」档,改为绿(健康)/黑(正常)/红(危险)三档,新增 USAGE_OK/PACE_TOL 阈值,去掉黄色进度条
- Claude/ChatGPT 用量行标签改用 statusline 同款长标签(all 5h/fable 7d 等)
- 今日实时区块改为指标名角标+数值与环比同比整组居中;近7天区块改为真实数据表(取30天趋势缓存最后7天),不画折线
- renderer.py 抽出 _cmp_at/_pace_color 钩子+新增 F36 字体供宽版复用;CLAUDE.md 同步版式与配置项说明

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-10 15:42:17 +08:00
parent 110f710ff0
commit 6dc89b504e
5 changed files with 90 additions and 50 deletions

View File

@@ -7,17 +7,17 @@ reTerminal E1002 渲染层 —— 复用 renderer.DashboardRenderer 的组件(
版式k=112/24/48px 点阵字体,不放大;四段,三道横规):
A 顶部090 :大时钟+日期(左)|天气(右,贴 790——与三色版同一套组件与尺寸。
B 用量96206 :左半 **Claude Usage**5h / 7d / Fable 7d竖规 x=400右半 **ChatGPT Usage**mock5h / 7d / Codex 7d
行组件沿用三色版的紧凑单行 `_usage_row`(行距 31标签列宽按最长标签自适应。
C 今日218314**橘喵·今日实时** 三列全宽(名 / 值 F24/ 环比·同比),版式同三色版
B 用量96206 :左半 **Claude Usage**all 5h / all 7d / fable 7d竖规 x=400右半 **ChatGPT Usage**mockall 5h / all 7d / codex 7d
行组件沿用三色版的紧凑单行 `_usage_row`(行距 31标签用 bar["label"]statusline 同款写法),列宽按最长标签自适应。
C 今日218304**橘喵·今日实时** 三列全宽:指标名 F12 作左上角角标;「值 F36 右侧叠放的环比/同比」整组居中
D 近7天328438**橘喵·近7天** 数据表真实数据30 天趋势缓存的最后 7 天;不画折线)——
列 = 指标名 + 7 个日期 + 合计,行 = 单量 / 流水 / 毛利;合计列伪粗体。
可选470479六色测试色条config.E1002_COLOR_STRIP默认关真机校准色彩量化时打开
六色的语义分工(每种颜色只有一个含义,不为「有」而用):
- 黑/白:结构与正文。红:预警pct≥warn、pace 超前、定位、太阳/闪电。
- 用量进度条「注意」档caution ≤ pct < warn填充边框与百分比仍黑黄字压白底不可读故黄只作填充)。
- 蓝:天气雨滴/雪花。绿:暂未使用(保留给后续语义,例如「节余」)。
- 黑/白:结构与正文。红:危险(用量 ≥ warn 的进度条+边框+百分比、pace ≥ +tol、涨、定位、太阳/闪电。
- 绿:健康(用量 ≤ ok 的进度条+边框、pace ≤ tol。用量与 pace 各自三档:绿 / 黑 / 红(阈值 USAGE_OK / USAGE_WARN / PACE_TOL)。
- 蓝:天气雨滴/雪花。黄:不用(用户明确不要黄色进度条;曾试过 60%80% 黄「注意」档,已移除)。
- 整图只允许六种纯色;渲染后可用 `img.getcolors()` 自查。
为什么是「固定 800x480 + 一张 PNG」而不是自适应 HTML
@@ -30,6 +30,7 @@ import time
import config
from renderer import DashboardRenderer, BLACK, WHITE, RED
import config as _cfg
# E Ink Spectra 6 原生六色。取纯饱和值作为给平台量化器的明确信号(与 ESPHome/GxEPD2 驱动的 BWYRGB 映射一致)。
YELLOW, GREEN, BLUE = (255, 255, 0), (0, 255, 0), (0, 0, 255)
@@ -42,17 +43,28 @@ class E1002Renderer(DashboardRenderer):
def __init__(self, font_path=config.FONT_PATH):
super().__init__(font_path, size=(config.E1002_WIDTH, config.E1002_HEIGHT), k=1)
# ---------- 配色钩子 ----------
# ---------- 配色钩子(三档:绿 健康 / 黑 正常 / 红 危险)----------
_ok, _pace_tol = _cfg.USAGE_OK, _cfg.PACE_TOL # 由 _usage_block 按数据覆盖
def _usage_colors(self, ok, pct, warn, bar):
"""三档≥warn 整体红caution≤pct<warn 黄填充+黑框黑字|其黑。返回(填充,边框,文字)。"""
"""进度条填充与边框同色pct ≥ warn 红pct ≤ ok 绿|其黑。
百分比文字只在红档转红,绿档仍黑——绿在白底上偏淡,做大块填充清楚、做小字发虚。"""
if not ok:
return BLACK, BLACK, BLACK
if pct >= warn:
return RED, RED, RED
if pct >= self._caution:
return YELLOW, BLACK, BLACK
if pct <= self._ok:
return GREEN, GREEN, BLACK
return BLACK, BLACK, BLACK
def _pace_color(self, diff):
"""pace = 用量% 时间%:≥ +tol 红(烧得比时间快,超前过多)|≤ tol 绿(节余充足)|其间黑。"""
if diff >= self._pace_tol:
return RED
if diff <= -self._pace_tol:
return GREEN
return BLACK
# ---------- 组件 ----------
def _color_strip(self, y0, y1, x0=None, x1=None):
"""六色测试色条:六块等宽实心色块,白块加 1px 黑框以便可见。真机校准用。"""
@@ -72,7 +84,8 @@ class E1002Renderer(DashboardRenderer):
right = "示例数据" if usage.get("mock") else (f'快照 {usage["updated"]}' if usage.get("updated") else None)
self._label(y0, usage.get("title", "Usage"), right=right, x0=x0, x1=x1)
warn = usage.get("warn", 80)
bars = usage.get("bars", [])
self._ok, self._pace_tol = usage.get("ok", _cfg.USAGE_OK), usage.get("pace_tol", _cfg.PACE_TOL)
bars = [dict(b, k=b.get("label") or b["k"]) for b in usage.get("bars", [])] # 宽版用长标签all 5h / fable 7d
label_w = max([self._tw(b["k"], self.F13) + 1 for b in bars] + [26]) + 8
y = y0 + 27
for b in bars:
@@ -81,20 +94,28 @@ class E1002Renderer(DashboardRenderer):
return y - 31 + 20
def _realtime(self, y0, mao, x0=8, x1=None):
"""橘喵·今日实时三列(版式同三色版:标题+18=名/+34=值/+64=环比/+82=同比)。返回墨迹底部 y。"""
"""橘喵·今日实时三列(宽版):指标名 F12 贴在每列左上角作角标;
「值 F36 粗 右侧上下叠放的 环比/同比F12」整组在列内水平居中、竖直略偏下给角标让位
(曾试过名在值上方居中——小字压大数字、上半显空;也试过名与值同行——名参与居中会把整组推偏。)返回区块底部 y。"""
x1 = self.W - 8 if x1 is None else x1
self._label(y0, "橘喵·今日实时", right=f'上次更新 {mao["upd"]}', x0=x0 + 2, x1=x1 - 2)
cols = mao["cols"]; n = len(cols)
cw = (x1 - x0) / n
TAG_Y, CY, GAP = y0 + 22, y0 + 56, 14 # 角标顶部;组竖直中心;值↔环比块 间隙
for i in range(1, n):
self.vdash(x0 + cw * i, y0 + 32, y0 + 96)
self.vdash(x0 + cw * i, y0 + 22, y0 + 84)
for i, c in enumerate(cols):
cx = x0 + cw * (i + 0.5)
self.ctext(cx, y0 + 18, c["k"], self.F12)
self.ctext(cx, y0 + 34, c["v"], self.F24, bold=1)
self._cmp_line(cx, y0 + 64, "环比", c["hb"])
self._cmp_line(cx, y0 + 82, "同比", c["tb"])
return y0 + 96
cx0 = x0 + cw * i
cx = cx0 + cw / 2
self.text(cx0 + 12, TAG_Y, c["k"], self.F12) # 左上角角标
vw = self._tw(c["v"], self.F36) + 1
cmpw = max(self._cmp_w("环比", c["hb"]), self._cmp_w("同比", c["tb"]))
gx = cx - (vw + GAP + cmpw) / 2 # 整组左端(不含角标)
self._mid(gx, CY, c["v"], self.F36, bold=1)
_, t, _, b = self.F12.getbbox("环比")
self._cmp_at(gx + vw + GAP, CY - 9 - (t + b) / 2, "环比", c["hb"]) # 两行 F12 叠放(行距 18中心对齐 CY
self._cmp_at(gx + vw + GAP, CY + 9 - (t + b) / 2, "同比", c["tb"])
return y0 + 86
def _week_table(self, y0, week, x0=10, x1=None):
"""橘喵·近7天数据表表头 = 指标名列 + 7 个日期 + 合计;行 = 单量/流水/毛利。
@@ -105,7 +126,7 @@ class E1002Renderer(DashboardRenderer):
if not dates or not rows:
self.ctext((x0 + x1) / 2, y0 + 40, "--", self.F24)
return y0 + 80
NAME_W, ROW = 44, 22
NAME_W, ROW = 44, 24
ncol = len(dates) + 1 # 日期列 + 合计列
cw = (x1 - x0 - NAME_W) / ncol
col_r = lambda i: x0 + NAME_W + cw * (i + 1) - 4 # 第 i 列右端(含右内边距)
@@ -115,6 +136,8 @@ class E1002Renderer(DashboardRenderer):
self.rtext(col_r(len(dates)), hy, "合计", self.F12)
self.hdash(x0, x1, hy + 17, dash=2, gap=3)
y = hy + ROW
# 合计列前一道竖点规,把「逐日」与「合计」分开
self.vdash(x0 + NAME_W + cw * len(dates), hy + 2, hy + ROW * (len(rows) + 1) - 6, dash=2, gap=3)
for name, vals, total in rows:
self.text(x0, y, name, self.F12)
for i, v in enumerate(vals):
@@ -133,7 +156,6 @@ class E1002Renderer(DashboardRenderer):
dt, wt, mao = data["date"], data["weather"], data["mao"]
usage, gpt = data["usage"], data.get("usage_gpt") or {}
self._caution = usage.get("caution", config.USAGE_CAUTION)
# ---- A. 顶部:大时钟+日期(左)/ 天气(右)——与三色版同尺寸 ----
DIV_Y = 90