Files
aurora-admin/run_tests.py
T
aurora-admin f1fbfc2ddb
Regression / regression (push) Canceled after 0s
feat(品牌标识): 几何 K 图标(favicon/顶栏标记/theme-color) + 并行会话成果入库
## 品牌标识(本次会话)

起因:品牌此前没有任何图形标识 —— 唯一 favicon 是内联 data-URI 里的字母「A」,
那是 v2.0.0「Aurora Admin → Kole UI」改名漏掉的一处(PC 顶栏也是「A」,
移动端站已是「K」;移动端文档站则完全没有 favicon)。

- 几何:24 网格三个互不接触的笔画(竖 + 两斜),圆头描边;
  描边 2.25 → 16px 标签页尺寸下正好 1.5px = 规范原文「描边1.5px」
- 取色分两套(刻意):favicon 硬编码品牌蓝/白(渲染在浏览器标签栏,不继承 kole-dark);
  顶栏标记走 currentColor(实测暗色下自动转 rgb(20,22,28))
- 新增 theme-color 双条(light #FFFFFF / dark #1C1F26,取 --kole-color-card-bg)
- 修 site/app.js hero 标语 KOLE ADMIN → KOLE UI(改名变形残留)
- 移动端 7 个模板补 favicon(此前计数 0)

验收:门禁 9 条全 OK(site-routing/site-routes/mobile-docs/mobile-site/isolation/
theme/nav/i18n/icons);PC 回归 1464/1464 · 移动端 807/807,各连跑 8 次一致;
两端 favicon 405 字节逐字节一致;PC 站控制台错误 1→0。

## 并行会话成果(本次一并入库)

- 图标系统:2576 图标(TDesign/Element Plus,MIT)+ 11 端注入 + 5 个构建门禁工具
  + IconPreview 预览页 + ICON-SPEC.md 冻结规格
- 移动端平台:47 组件 × 6 端 + 文档站 53 页 + 隔离门禁
- PC 组件:103 个大后台组件 / 组件11 批次
- uni-app:PC 端试点 + 移动端端实现 + 真实编译验证

## 工程

- .gitignore 补 .scratch/ 与 .zcode-preexisting-*.txt(会话中间产物,实测 9.1MB,不入库)
- CHANGELOG 补品牌标识条目
- ROADMAP 登 S8-P4(品牌标识任务包 + og:image/apple-touch-icon 未做部分)
2026-09-21 10:05:48 +08:00

168 lines
9.7 KiB
Python

# -*- coding: utf-8 -*-
"""run_tests.py — 批量生成 tests/<slug>.html + tests/index.html
与 run-tests.ps1 互为镜像;Python 版无 PS5.1 BOM/编码问题。
用法:python run_tests.py
依赖:无(标准库)"""
import os
import re
import json
import sys
import html
ROOT = os.getcwd()
LIB = os.path.join(ROOT, ".design_library/kole-ui")
TESTS_DIR = os.path.join(ROOT, "tests")
TEMPLATE = os.path.join(TESTS_DIR, "_template.html")
with open(os.path.join(LIB, "components/index.json"), encoding="utf-8") as f:
idx = json.load(f)
CAT_MAP = {
"general": ["button","buttongroup","tag","card"],
"navigation": ["tabs","steps","breadcrumb","sidemenu","topmenu","mixednavigation","quicknav","anchornav","enhancedtabnav","dropdown"],
"input": ["input","select","cascader","colorpicker","rangequickpicker","numberrangeinput","switchgroup","dragupload","signaturepad","codeinput","passwordinput","phoneinput","bankcardinput","idcardinput","plateinput","autocomplete","mention","transfer","rate","slider","listpicker","radiocard","checkboxcard","segmented"],
"display": ["table","treetable","expandabletable","mergedcelltable","summaryrowtable","fixedcolumntable","cardlist","timelinelist","steplist","chartpanel","dashboardcard","calendar","carousel","imagepreview","qrcode","countdown","tree","collapse","watermark","employeecard"],
"feedback": ["modal","confirmmodal","alertmodal","formmodal","fullscreenmodal","messagepro","notificationpro","progressvariants","skeletonpro","emptypro","loadingoverlay","resultvariants","exceptionvariants","popconfirm","bottomsheet"],
"system": ["themeswitcher","layoutswitcher","densityswitcher","shortcutpanel","backtotop","pagetransition"]
}
SLUG_CAT = {s: k for k, vs in CAT_MAP.items() for s in vs}
CAT_ZH = {"general": "通用", "navigation": "导航", "input": "数据录入", "display": "数据展示", "feedback": "反馈", "system": "工具与系统"}
CAT_ORDER = ["general", "navigation", "input", "display", "feedback", "system"]
def get_snapshot(prefix):
fw = os.path.join(ROOT, "frameworks", prefix + ".html")
if not os.path.isfile(fw):
return '<p style="color: var(--kole-color-text-placeholder)">\uff08\u65e0 H5 \u6f14\u793a\u9875\uff09</p>'
with open(fw, encoding="utf-8") as f:
raw = f.read()
m = re.search(r"<body[^>]*>(.*?)</body>", raw, flags=re.S)
if not m:
return '<p style="color: var(--kole-color-text-placeholder)">\uff08\u65e0 body \u5185\u5bb9\uff09</p>'
body = m.group(1)
body = re.sub(r"<script[\s\S]*?</script>", "", body, flags=re.I)
body = re.sub(r"<style[\s\S]*?</style>", "", body, flags=re.I)
body = re.sub(r'\son[a-z]+="[^"]*"', "", body, flags=re.I)
if len(body) > 1200:
body = body[:1200] + "..."
return body
def split_name(name):
parts = name.split(" ")
return parts[0], parts[-1]
def main():
os.makedirs(TESTS_DIR, exist_ok=True)
with open(TEMPLATE, encoding="utf-8") as f:
template = f.read()
count = 0
for c in idx["components"]:
slug = c["slug"]
name = c["name"]
zh, en = split_name(name)
cat = SLUG_CAT.get(slug, "general")
batch = int(c["specBatch"])
prefix = c["frameworksPrefix"]
demo = get_snapshot(prefix)
html_doc = template
html_doc = html_doc.replace("SLUG_PLACEHOLDER", slug)
html_doc = html_doc.replace("SLUG_ZH_PLACEHOLDER", zh)
placeholder_demo = '<p style="color: var(--kole-color-text-placeholder)">\uff08\u751f\u6210\u5668\u5c06\u586b\u5145\uff09</p>'
if placeholder_demo in html_doc:
html_doc = html_doc.replace(placeholder_demo, demo)
placeholder_info = 'slug\uff1a<code>SLUG_PLACEHOLDER</code> \u00b7 \u7c7b\u76ee\uff1a<code>general</code> \u00b7 \u89c4\u683c\u6279\u6b21\uff1a\u7b2c <code>1</code> \u6279'
if placeholder_info in html_doc:
html_doc = html_doc.replace(placeholder_info, f"slug\uff1a<code>{slug}</code> \u00b7 \u7c7b\u76ee\uff1a<code>{cat}</code> \u00b7 \u89c4\u683c\u6279\u6b21\uff1a\u7b2c <code>{batch}</code> \u6279")
# \u79fb\u9664\u6a21\u677f\u5185\u7f6e\u7684\u811a\u672c\u5757\uff0c\u6539\u4e3a\u5916\u90e8 _runtime.js
# \u6a21\u677f\u4e2d\u7684\u811a\u672c\u5757\u4ece `/* \u6d4b\u8bd5\u9875\u901a\u7528 runtime */` \u5f00\u59cb\uff0c\u5230 `</script>` \u4e3a\u6b62
script_marker_start = '<script>\n/* \u6d4b\u8bd5\u9875\u901a\u7528 runtime */'
script_idx = html_doc.find(script_marker_start)
if script_idx >= 0:
end_idx = html_doc.find('</script>', script_idx)
if end_idx >= 0:
# \u7528 __SLUG__ \u521d\u59cb\u5316 + \u5916\u90e8 _runtime.js \u811a\u672c\u4ee3\u66ff
replacement = (
'<script>window.__SLUG__ = ' + json.dumps(slug) + ';</script>\n'
'<script src="../tests/_runtime.js"></script>'
)
html_doc = html_doc[:script_idx] + replacement + html_doc[end_idx + len('</script>'):]
target = os.path.join(TESTS_DIR, slug + ".html")
with open(target, "w", encoding="utf-8") as f:
f.write(html_doc)
count += 1
by_cat = {k: [] for k in CAT_ORDER}
for c in idx["components"]:
cat = SLUG_CAT.get(c["slug"], "general")
by_cat.setdefault(cat, []).append(c)
body = ""
for cat in CAT_ORDER:
items = by_cat.get(cat, [])
if not items:
continue
zh_cat = CAT_ZH[cat]
body += f' <div class="cat">\n <h2>{zh_cat} <span class="en">{cat}</span></h2>\n <div class="grid">\n'
for c in items:
slug = c["slug"]
name = c["name"]
zh2, en2 = split_name(name)
body += f' <a class="card" data-slug="{html.escape(slug)}" href="{html.escape(slug)}.html"><div class="zh">{html.escape(zh2)}</div><div class="en">{html.escape(en2)}</div></a>\n'
body += " </div>\n </div>\n"
overview = (
'<!DOCTYPE html>\n<html lang="zh-CN">\n<head>\n<meta charset="UTF-8">\n'
'<meta name="viewport" content="width=device-width, initial-scale=1.0">\n'
'<title>\u6d4b\u8bd5\u603b\u89c8 \u2014 Kole UI \u7ec4\u4ef6\u5e93</title>\n'
'<link rel="stylesheet" href="../.design_library/kole-ui/colors_and_type.css">\n'
'<link rel="stylesheet" href="../site/style.css">\n'
'<style>\nbody { margin: 0; font-family: var(--kole-font-family); color: var(--kole-color-text-body); background: var(--kole-color-page-bg); }\n'
'.shell { max-width: 1280px; margin: 0 auto; padding: 24px; }\n'
'.shell h1 { margin: 0 0 4px; font-size: 24px; color: var(--kole-color-text-title); }\n'
'.shell .desc { margin: 0 0 20px; color: var(--kole-color-text-secondary); font-size: 14px; }\n'
'.summary { display: flex; gap: 12px; margin-bottom: 24px; flex-wrap: wrap; }\n'
'.pill { height: 28px; padding: 0 12px; border-radius: var(--kole-radius-base); background: var(--kole-color-card-bg); border: 1px solid var(--kole-color-border); display: inline-flex; align-items: center; font-family: var(--kole-font-family-num); font-size: 13px; color: var(--kole-color-text-body); }\n'
'.pill strong { margin-right: 4px; color: var(--kole-color-brand); }\n'
'.cat { margin: 24px 0 12px; }\n'
'.cat h2 { margin: 0 0 10px; font-size: 16px; color: var(--kole-color-text-title); }\n'
'.cat h2 .en { font-family: var(--kole-font-family-num); font-size: 12px; color: var(--kole-color-text-secondary); margin-left: 8px; }\n'
'.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 12px; }\n'
'.card { background: var(--kole-color-card-bg); border: 1px solid var(--kole-color-border); border-radius: var(--kole-radius-medium); padding: 12px; transition: border-color .15s, box-shadow .15s; cursor: pointer; text-decoration: none; display: block; }\n'
'.card:hover { border-color: var(--kole-color-brand); box-shadow: var(--kole-shadow-low); }\n'
'.card .zh { font-size: 14px; color: var(--kole-color-text-title); }\n'
'.card .en { font-family: var(--kole-font-family-num); font-size: 11px; color: var(--kole-color-text-secondary); }\n'
'</style>\n</head>\n<body>\n<div class="shell">\n'
'<h1>\u6d4b\u8bd5\u603b\u89c8 <a href="../site/index.html" style="font-size:13px; margin-left:8px;">\u2190 \u6587\u6863\u7ad9</a></h1>\n'
'<p class="desc">\u4e3a\u6bcf\u4e2a\u7ec4\u4ef6\u751f\u6210\u72ec\u7acb\u6d4b\u8bd5\u9875\uff08\u65ad\u8a00\u534f\u8bae + \u901a\u7528\u6d4b\u8bd5\u77e9\u9635\uff09\u3002\u70b9\u51fb\u5361\u7247\u8fdb\u5165\u6d4b\u8bd5\u9875\uff1b\u901a\u8fc7\u7387\u4f1a\u5199 localStorage[kole-test-log]\u3002</p>\n'
'<div class="summary">\n'
f' <span class="pill"><strong>{count}</strong> \u4e2a\u7ec4\u4ef6\u6d4b\u8bd5</span>\n'
' <span class="pill" id="passed">\u901a\u8fc7\uff1a\u2014</span>\n'
' <span class="pill" id="failed">\u5931\u8d25\uff1a\u2014</span>\n'
'</div>\n'
+ body +
'</div>\n<script src="../site/logger.js"></script>\n<script>\n'
'(function () {\n'
' if (!window.koleLogger) return;\n'
' var s = window.koleLogger.summary();\n'
" var p = document.getElementById('passed');\n"
" var f = document.getElementById('failed');\n"
" if (p) p.textContent = '\u901a\u8fc7\uff1a' + s.testPass;\n"
" if (f) f.textContent = '\u5931\u8d25\uff1a' + s.testFail;\n"
'})();\n'
'</script>\n</body>\n</html>\n'
)
with open(os.path.join(TESTS_DIR, "index.html"), "w", encoding="utf-8") as f:
f.write(overview)
print("generated %d test pages" % count)
print("generated tests/index.html")
print("done.")
if __name__ == "__main__":
main()