v1.0.10 两件大事:
1. 界面中英双语(英文界面无中文残留)
- 新增 web/i18n.js(约 4200 条词条),零依赖零构建
- 后端下发中文数据走 bt4() 查表翻译;独立模块(upstream/netdetail/
realtime/update)纳入翻译并注册切语言重绘钩子
- 后端「状态/聚合」接口只回稳定枚举+原始数值,不下发中文文案
- 顺带修一个真实 XSS:顶层 const 不挂 window,独立脚本 esc 静默退化成零转义
2. 修掉「文字竖着排/错位」(多分辨率反复暴露)
- 单元格 min-content 只有 1 字宽 → word-break:keep-all + 表头 nowrap
- 依赖卡片正文被标签/按钮挤成 27px → .dep-body{flex:1 1 200px} + flex-wrap
- 裸 flex:1(下限为0)给正文 → 一律改 flex:1 1 <px>
- .dep-item 横向容器直接塞块级子项 → 退回块级
- 关键陷阱:auto-fill 布局屏越宽列数越多单卡越窄,窄屏反而测不出来,
真机审计推进到「逐视图点按钮 + 1920/2560 宽视口 + 中英双语」
- 守卫 t-layout.py 扩到 35 条,12 处反向验证全红
3. 修真缺陷:_link_since() 重启检测被误改成 if False:(注释还写着「已修」),
uptime 归零后相减得负数 → 恢复比对 boot_id
附带:新增 25 个检查器(t-*.py)、排版审计工具链(shot-*/ocr-*/live-*)、
版本号兜底值同步到 1.0.10、.gitignore 收纳 i18n/排版排查的一次性脚本。
118 lines
4.5 KiB
Python
118 lines
4.5 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""语言切换开关的接线检查。
|
||
|
||
为什么要有这个检查器:
|
||
2026-10-07 真机故障 —— 用户点顶栏「中 / EN」毫无反应。
|
||
根因不是 i18n 坏了,而是 `initLangSwitch()` **定义了却从未被调用**:
|
||
它在 app.js 里只有定义那一处,全项目 0 个调用点 → #btn-lang 的 onclick
|
||
永远是 null。静态语法检查、`node --check`、离线渲染回归全都测不出来
|
||
(它们从不点击按钮)。
|
||
|
||
所以这个检查器只钉三件事:
|
||
① initLangSwitch 被真正调用(定义次数 < 总出现次数)
|
||
② #btn-lang 的 onclick 确实被赋值
|
||
③ i18n 侧被 app.js 用到的 API 都真实存在(少一个 → 点了没反应)
|
||
"""
|
||
import io
|
||
import os
|
||
import re
|
||
import sys
|
||
|
||
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||
APP = os.path.join(ROOT, 'web', 'app.js')
|
||
I18N = os.path.join(ROOT, 'web', 'i18n.js')
|
||
HTML = os.path.join(ROOT, 'web', 'index.html')
|
||
|
||
PASS = FAIL = 0
|
||
|
||
|
||
def chk(name, cond, extra=''):
|
||
global PASS, FAIL
|
||
if cond:
|
||
PASS += 1
|
||
print(' [OK] %s' % name)
|
||
else:
|
||
FAIL += 1
|
||
print(' [NG] %s %s' % (name, extra))
|
||
|
||
|
||
def _strip_comments(src):
|
||
"""粗略剔除 JS 行注释与块注释,只为计数用。
|
||
|
||
⛔ 不做这件事就会假绿:注释里提到函数名会撑大出现次数,
|
||
掩盖「只有定义、没有调用」的死代码。
|
||
"""
|
||
out = []
|
||
i, n = 0, len(src)
|
||
while i < n:
|
||
c = src[i]
|
||
if c == '/' and i + 1 < n:
|
||
nxt = src[i + 1]
|
||
if nxt == '/': # 行注释
|
||
j = src.find('\n', i)
|
||
if j < 0:
|
||
break
|
||
out.append(' ' * (j - i))
|
||
i = j
|
||
continue
|
||
if nxt == '*': # 块注释
|
||
j = src.find('*/', i + 2)
|
||
j = n if j < 0 else j + 2
|
||
out.append(re.sub(r'[^\n]', ' ', src[i:j]))
|
||
i = j
|
||
continue
|
||
out.append(c)
|
||
i += 1
|
||
return ''.join(out)
|
||
|
||
|
||
def main():
|
||
app = io.open(APP, encoding='utf-8').read()
|
||
i18n = io.open(I18N, encoding='utf-8').read()
|
||
html = io.open(HTML, encoding='utf-8').read()
|
||
|
||
print('=== 一、开关必须被调用(死代码是本次真机故障的根因)===')
|
||
# ⛔ 必须**剔除注释**再计数:我在这段代码的注释里写了「initLangSwitch」,
|
||
# 连注释一起数的话,删掉真正的调用后 n_all 仍然 > n_def → 判据假绿
|
||
# (2026-10-07 反向验证实测:删掉调用仍报 OK)。
|
||
app_nc = _strip_comments(app)
|
||
n_all = len(re.findall(r'\binitLangSwitch\b', app_nc))
|
||
n_def = len(re.findall(r'function\s+initLangSwitch\s*\(', app_nc))
|
||
n_call = len(re.findall(r'^\s*initLangSwitch\s*\(\s*\)\s*;', app_nc, re.M))
|
||
chk('initLangSwitch 有且只有一处定义', n_def == 1, '实际 %d 处' % n_def)
|
||
chk('initLangSwitch 至少被调用一次(剔除注释后统计)',
|
||
n_call >= 1,
|
||
'剔除注释后只有 %d 次出现 = 只有定义,没有调用 → onclick 永不绑定' % n_all)
|
||
if n_call:
|
||
for m in re.finditer(r'^[ \t]*initLangSwitch\s*\(\s*\)\s*;?', app_nc, re.M):
|
||
ln = app_nc[:m.start()].count('\n') + 1
|
||
print(' 调用点 L%d: %s' % (ln, m.group(0).strip()))
|
||
|
||
print('\n=== 二、按钮与标签存在 ===')
|
||
chk('index.html 有 #btn-lang', 'id="btn-lang"' in html)
|
||
chk('index.html 有 #lang-label', 'id="lang-label"' in html)
|
||
|
||
print('\n=== 三、onclick 真的被赋值 ===')
|
||
chk('app.js 给 btn.onclick 赋值', re.search(r'btn\.onclick\s*=', app) is not None)
|
||
|
||
print('\n=== 四、i18n 侧 API 齐全(少一个就点了没反应)===')
|
||
for api in ('toggle', 'getLang', 'onChange', 'rerenderAll', 'setLang', 'register'):
|
||
chk('i18n 导出 %s' % api,
|
||
re.search(r'\b%s\s*:' % re.escape(api), i18n) is not None)
|
||
chk('i18n 有 window.i18n 暴露', 'window.i18n' in i18n)
|
||
|
||
print('\n=== 五、切换后要重绘当前页(否则「点了变一半」)===')
|
||
chk('onChange 回调里重绘当前页',
|
||
re.search(r'onChange\s*\(\s*\(\)\s*=>', app) is not None
|
||
and re.search(r'onChange[\s\S]{0,400}?go\(S\.page\)', app) is not None)
|
||
|
||
print('\n========================================')
|
||
print('通过 %d / 失败 %d' % (PASS, FAIL))
|
||
if FAIL:
|
||
sys.exit(1)
|
||
print('LANG_SWITCH_OK')
|
||
|
||
|
||
if __name__ == '__main__':
|
||
main() |