Regression / regression (push) Canceled after 0s
## 品牌标识(本次会话) 起因:品牌此前没有任何图形标识 —— 唯一 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 未做部分)
1138 lines
107 KiB
HTML
1138 lines
107 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<script>
|
||
(function () {
|
||
var m = 'auto';
|
||
try { var v = localStorage.getItem('kole-mode'); if (v === 'light' || v === 'dark' || v === 'auto') m = v; } catch (e) {}
|
||
var dark = m === 'dark' || (m === 'auto' && window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches);
|
||
document.documentElement.classList.toggle('kole-dark', !!dark);
|
||
document.documentElement.style.colorScheme = dark ? 'dark' : 'light';
|
||
})();
|
||
</script>
|
||
<link rel="icon" href="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%3E%3Crect%20width='24'%20height='24'%20rx='6'%20fill='%232F54EB'/%3E%3Cg%20fill='none'%20stroke='%23FFFFFF'%20stroke-width='2.25'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Cpath%20d='M7%205.5V18.5'/%3E%3Cpath%20d='M17%205.5l-6.25%204.06'/%3E%3Cpath%20d='M10.75%2014.44L17%2018.5'/%3E%3C/g%3E%3C/svg%3E">
|
||
<meta name="theme-color" content="#FFFFFF" media="(prefers-color-scheme: light)">
|
||
<meta name="theme-color" content="#1C1F26" media="(prefers-color-scheme: dark)">
|
||
<title>更新日志 — Kole UI Mobile</title>
|
||
<meta name="description" content="Kole UI 移动端的变更记录:按时间倒序,来自仓库根 CHANGELOG.md 中与移动端相关的段落(构建时提取,不手抄)。">
|
||
<link rel="stylesheet" href="../../.design_library/kole-ui-mobile/colors_and_type.css">
|
||
<link rel="stylesheet" href="style.css">
|
||
</head>
|
||
<body>
|
||
<header class="m-top">
|
||
<div class="m-top-inner">
|
||
<a class="m-logo" href="index.html">
|
||
<span class="m-logo-mark" aria-hidden="true"><svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.25" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M7 5.5V18.5"/><path d="M17 5.5l-6.25 4.06"/><path d="M10.75 14.44L17 18.5"/></svg></span>
|
||
<span class="m-logo-text">Kole UI<em>移动端</em></span>
|
||
</a>
|
||
<div class="m-ver-picker" id="m-ver-picker">
|
||
<button id="m-ver-trigger" class="m-ver-trigger" type="button" data-single="1" aria-haspopup="listbox" aria-expanded="false" aria-label="选择版本" title="文档版本(清单来自部署根的 versions.json)">
|
||
<span id="m-ver">v1.0.0</span>
|
||
<svg class="m-ver-caret" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg>
|
||
</button>
|
||
<div id="m-ver-menu" class="m-ver-menu hidden" role="listbox" aria-label="文档版本">
|
||
<div class="m-ver-menu-head" id="m-ver-head">文档版本</div>
|
||
</div>
|
||
</div>
|
||
<nav class="m-nav"> <a href="index.html">组件总览</a>
|
||
<a href="guide.html">快速开始</a>
|
||
<a href="design.html">设计规范</a>
|
||
<a href="faq.html">常见问题</a>
|
||
<a href="changelog.html" class="active" aria-current="page">更新日志</a></nav>
|
||
<div class="m-mode">
|
||
<button id="m-mode-trigger" class="m-mode-trigger" type="button" aria-haspopup="listbox" aria-expanded="false" aria-label="主题模式" title="主题模式">
|
||
<svg class="m-mode-icon" id="m-icon-moon" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
|
||
<svg class="m-mode-icon" id="m-icon-sun" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" style="display:none"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41"/></svg>
|
||
<svg class="m-mode-icon" id="m-icon-auto" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" style="display:none"><rect x="3" y="4" width="18" height="13" rx="2"/><path d="M8 21h8M12 17v4"/></svg>
|
||
<span id="m-mode-cur">自动模式</span>
|
||
<svg class="m-mode-caret" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg>
|
||
</button>
|
||
<div id="m-mode-menu" class="m-menu hidden" role="listbox" aria-label="主题模式">
|
||
<div class="m-menu-head">主题模式</div>
|
||
<button class="m-menu-opt" type="button" role="option" data-mode="light" aria-selected="false"><span data-mode-label="light">日间模式</span></button>
|
||
<button class="m-menu-opt" type="button" role="option" data-mode="dark" aria-selected="false"><span data-mode-label="dark">夜间模式</span></button>
|
||
<button class="m-menu-opt" type="button" role="option" data-mode="auto" aria-selected="true"><span data-mode-label="auto">自动模式</span></button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
<div class="m-body">
|
||
<details class="m-side" open>
|
||
<summary><span>移动端导航</span><span class="cur">更新日志</span></summary>
|
||
<nav>
|
||
<div class="m-side-section"><span>平台</span></div>
|
||
<a class="m-side-link m-side-platform" href="../index.html"><span class="sp-top"><span class="sp-name">PC 端组件</span></span><span class="sp-sub">103 个中后台组件 · 5 端</span></a>
|
||
<a class="m-side-link m-side-platform" href="index.html"><span class="sp-top"><span class="sp-name">移动端组件</span><span class="sp-badge">当前</span></span><span class="sp-sub">47 个组件 · 6 端 · 含 uni-app</span></a>
|
||
<div class="m-side-section"><span>开发指南</span></div>
|
||
<a class="m-side-link" href="index.html">组件总览</a>
|
||
<a class="m-side-link" href="guide.html">快速开始</a>
|
||
<a class="m-side-link" href="design.html">设计规范</a>
|
||
<a class="m-side-link" href="faq.html">常见问题</a>
|
||
<a class="m-side-link is-active" href="changelog.html" aria-current="page">更新日志</a>
|
||
<a class="m-side-link" href="platform.html">平台与端<em>移动端</em></a>
|
||
<a class="m-side-link" href="../../tests/mobile/index.html" target="_blank">测试与回归<em>移动端</em></a>
|
||
<div class="m-side-section"><span>组件</span><span class="cnt">47</span></div>
|
||
<div class="m-side-group"><span>通用</span><span class="cnt">5</span></div>
|
||
<a class="m-side-link" href="component/mobile-button.html">按钮<em>Button</em></a>
|
||
<a class="m-side-link" href="component/mobile-divider.html">分割线<em>Divider</em></a>
|
||
<a class="m-side-link" href="component/mobile-icon.html">图标<em>Icon</em></a>
|
||
<a class="m-side-link" href="component/mobile-layout.html">布局<em>Layout</em></a>
|
||
<a class="m-side-link" href="component/mobile-link.html">链接<em>Link</em></a>
|
||
<div class="m-side-group"><span>导航</span><span class="cnt">6</span></div>
|
||
<a class="m-side-link" href="component/navbar.html">顶部导航栏<em>NavBar</em></a>
|
||
<a class="m-side-link" href="component/tabbar.html">底部标签栏<em>TabBar</em></a>
|
||
<a class="m-side-link" href="component/mobile-grid.html">宫格<em>Grid</em></a>
|
||
<a class="m-side-link" href="component/mobile-steps.html">步骤条<em>Steps</em></a>
|
||
<a class="m-side-link" href="component/mobile-segmented.html">分段器<em>Segmented</em></a>
|
||
<a class="m-side-link" href="component/mobile-sticky.html">吸顶容器<em>Sticky</em></a>
|
||
<div class="m-side-group"><span>数据录入</span><span class="cnt">16</span></div>
|
||
<a class="m-side-link" href="component/mobile-numberkeyboard.html">数字键盘<em>NumberKeyboard</em></a>
|
||
<a class="m-side-link" href="component/mobile-datepicker.html">日期选择器<em>DatePicker</em></a>
|
||
<a class="m-side-link" href="component/mobile-input.html">输入框<em>Input</em></a>
|
||
<a class="m-side-link" href="component/mobile-search.html">搜索框<em>Search</em></a>
|
||
<a class="m-side-link" href="component/mobile-stepper.html">步进器<em>Stepper</em></a>
|
||
<a class="m-side-link" href="component/mobile-switch.html">开关<em>Switch</em></a>
|
||
<a class="m-side-link" href="component/mobile-textarea.html">多行文本框<em>Textarea</em></a>
|
||
<a class="m-side-link" href="component/mobile-checkbox.html">多选框<em>Checkbox</em></a>
|
||
<a class="m-side-link" href="component/mobile-form.html">表单<em>Form</em></a>
|
||
<a class="m-side-link" href="component/mobile-radio.html">单选框<em>Radio</em></a>
|
||
<a class="m-side-link" href="component/mobile-rate.html">评分<em>Rate</em></a>
|
||
<a class="m-side-link" href="component/mobile-slider.html">滑动选择器<em>Slider</em></a>
|
||
<a class="m-side-link" href="component/mobile-cascader.html">级联选择器<em>Cascader</em></a>
|
||
<a class="m-side-link" href="component/mobile-colorpicker.html">颜色选择器<em>ColorPicker</em></a>
|
||
<a class="m-side-link" href="component/mobile-picker.html">选择器<em>Picker</em></a>
|
||
<a class="m-side-link" href="component/mobile-upload.html">上传<em>Upload</em></a>
|
||
<div class="m-side-group"><span>数据展示</span><span class="cnt">8</span></div>
|
||
<a class="m-side-link" href="component/cell.html">单元格<em>Cell</em></a>
|
||
<a class="m-side-link" href="component/mobile-badge.html">徽标<em>Badge</em></a>
|
||
<a class="m-side-link" href="component/mobile-tag.html">标签<em>Tag</em></a>
|
||
<a class="m-side-link" href="component/mobile-avatar.html">头像<em>Avatar</em></a>
|
||
<a class="m-side-link" href="component/mobile-list.html">列表<em>List</em></a>
|
||
<a class="m-side-link" href="component/mobile-collapse.html">折叠面板<em>Collapse</em></a>
|
||
<a class="m-side-link" href="component/mobile-table.html">表格<em>Table</em></a>
|
||
<a class="m-side-link" href="component/mobile-typography.html">排版<em>Typography</em></a>
|
||
<div class="m-side-group"><span>反馈</span><span class="cnt">12</span></div>
|
||
<a class="m-side-link" href="component/actionsheet.html">动作面板<em>ActionSheet</em></a>
|
||
<a class="m-side-link" href="component/pullrefresh.html">下拉刷新<em>PullRefresh</em></a>
|
||
<a class="m-side-link" href="component/swipecell.html">滑动单元格<em>SwipeCell</em></a>
|
||
<a class="m-side-link" href="component/mobile-popup.html">弹出层<em>Popup</em></a>
|
||
<a class="m-side-link" href="component/mobile-toast.html">轻提示<em>Toast</em></a>
|
||
<a class="m-side-link" href="component/mobile-dialog.html">对话框<em>Dialog</em></a>
|
||
<a class="m-side-link" href="component/mobile-noticebar.html">通知栏<em>NoticeBar</em></a>
|
||
<a class="m-side-link" href="component/mobile-loading.html">加载<em>Loading</em></a>
|
||
<a class="m-side-link" href="component/mobile-progress.html">进度条<em>Progress</em></a>
|
||
<a class="m-side-link" href="component/mobile-message.html">消息通知<em>Message</em></a>
|
||
<a class="m-side-link" href="component/mobile-overlay.html">遮罩层<em>Overlay</em></a>
|
||
<a class="m-side-link" href="component/mobile-popover.html">弹出气泡<em>Popover</em></a>
|
||
</nav>
|
||
</details>
|
||
<main class="m-main">
|
||
<section class="m-hero">
|
||
<h1>更新日志</h1>
|
||
<p>移动端的变更记录,<b>构建时从仓库根 <code>CHANGELOG.md</code> 提取</b>(标题含「Mobile / 移动端」的段落),按时间倒序 —— 不手抄,避免站点与仓库两处说法不一致。完整历史(含 PC 侧)在仓库的 CHANGELOG.md。</p>
|
||
</section>
|
||
<section class="m-block">
|
||
<h2 id="entries">移动端相关变更</h2>
|
||
<h3 class="m-h3" style="margin-top: var(--kole-space-20);">图标系统(PC 5 端 + 移动端 6 端 + 全量图标库 + 预览页)</h3>
|
||
<p class="m-note">起因:用户要求「图标组件参考 TDesign / Element 等主流组件库,看他们的 icon 是怎么专门设计的、</p>
|
||
<p class="m-note">设计内容有哪些,覆盖他们全部拥有的 icon 图标和功能」。</p>
|
||
<p class="m-note">落地:<strong>2576 个图标</strong>(TDesign 线性 1334 + 面性 1020 + Element Plus 293,均 MIT),</p>
|
||
<p class="m-note">11 个端实现全部接通,新增 5 个构建/门禁工具与 1 个图标预览页。</p>
|
||
<p class="m-note">冻结规格:<code>.design_library/kole-ui/icons/ICON-SPEC.md</code>。</p>
|
||
<p class="m-sub">#### 新增:图标数据管线(离线可复现,上游校验和钉死)</p>
|
||
<ul class="m-steps">
|
||
<li><code>tools/fetch-icon-sources.mjs</code> — 拉取并<strong>归一化</strong>上游 SVG → <code>sources.json</code>。</li>
|
||
</ul>
|
||
<p class="m-sub"> 逐包校验 sha256(防「某天图标悄悄变了」);纯函数可单独 import(无副作用)。</p>
|
||
<ul class="m-steps">
|
||
<li><code>tools/build-icons.mjs</code> — 编译出 <code>manifest.json</code>(清单)/ <code>registry.json</code>(渲染数据)/</li>
|
||
</ul>
|
||
<p class="m-sub"> <code>viewbox.js</code>(viewBox 例外表)/ <code>core.js</code>(内联运行时)/ <code>ATTRIBUTION.md</code>(MIT 归属,分发必带)。</p>
|
||
<ul class="m-steps">
|
||
<li><code>tools/build-icon-aliases.mjs</code> — 别名层,让 <strong>Element UI 全部 282 个历史图标名</strong>与</li>
|
||
</ul>
|
||
<p class="m-sub"> 移动端既有 9 个字形名都能落到真实图标(<strong>旧调用点零破坏</strong>)。</p>
|
||
<ul class="m-steps">
|
||
<li><code>tools/inject-icon-table.mjs</code> — 把 core 表注入 11 个端实现的标记块,杜绝「11 份手抄表必然漂移」。</li>
|
||
<li><code>tools/verify-icons.mjs</code> — <strong>11 条静态断言</strong>(产物一致 / 无 <code>view-box</code> 残留 / 描边全 1.5 /</li>
|
||
</ul>
|
||
<p class="m-sub"> core 可渲染 / 别名不遮蔽 / 各端都能查到名 / 分组覆盖率 / 路径数字与源逐值一致)。</p>
|
||
<p class="m-sub">#### 修正:两个上游缺陷在构建期抹平(此处是本仓库的增量,不是照抄)</p>
|
||
<p class="m-sub">1. <strong><code>view-box</code> 拼写错误</strong>:TDesign <strong>全部 2356 个</strong> SVG 把属性写成 <code>view-box</code>(无效属性,</p>
|
||
<p class="m-sub"> 浏览器忽略 → 图标按默认视口缩放)。构建期两种拼写都读,产物统一写回规范的 <code>viewBox</code>。</p>
|
||
<p class="m-sub">2. <strong>描边宽度 2 → 1.5</strong>:上游为 <code>stroke-width="2"</code>(24 网格),本仓库规范</p>
|
||
<p class="m-sub"> (<code>组件1.txt</code>「图标规范:线性图标,描边1.5px,尺寸16/20/24px」)要求 1.5,构建期归一。</p>
|
||
<p class="m-sub"> 另丢弃 <code>fill="transparent"</code> 命中区路径(那是热区不是视觉内容)。</p>
|
||
<p class="m-sub">#### 修正:自测发现的数据损坏缺陷(构建期把坐标改错)</p>
|
||
<ul class="m-steps">
|
||
<li><strong>现象</strong>:归一化把路径数字四舍五入到 2 位小数,看似无害;但 SVG path 允许「隐式分隔」——</li>
|
||
</ul>
|
||
<p class="m-sub"> <code>q-13.005.48-22.5</code> 是<strong>三个</strong>数(<code>-13.005</code>、<code>.48</code>、<code>-22.5</code>,靠 <code>-</code> 和 <code>.</code> 当分隔符)。</p>
|
||
<p class="m-sub"> 把 <code>-13.005</code> 舍成 <code>-13</code> 后,后面的 <code>.48</code> <strong>粘成</strong> <code>-13.48</code> —— 那是另一个坐标,</p>
|
||
<p class="m-sub"> 图形画错<strong>且不报任何错</strong>。实测踩到 Element Plus 的 <code>quartz-watch</code>(靠浏览器控制台才发现)。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>修法</strong>:原本有小数位的数字,输出<strong>至少保留 1 位小数</strong>(<code>-13.005</code> → <code>-13.0</code>),</li>
|
||
</ul>
|
||
<p class="m-sub"> 隐式相连的 <code>.48</code> 不可能再被吸收;并在 <code>optimizePath</code> 内<strong>复核数字序列</strong>,不一致就原样返回。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>防复发</strong>:新增门禁断言 <strong>V8「路径数字与源逐值一致」</strong>,比对全部 2576 个图标。</li>
|
||
</ul>
|
||
<p class="m-sub"> 已做<strong>变异测试</strong>:把该 bug 重新注入 → V8 当场 FAIL(<code>数字个数 461 ≠ 源 462</code>),判据有区分力。</p>
|
||
<ul class="m-steps">
|
||
<li>代价:<code>registry.json</code> 707KB(修前 677KB,+30KB 换正确性)。</li>
|
||
</ul>
|
||
<p class="m-sub">#### 修正:非 <code><path></code> 图形被静默丢弃(覆盖率审计发现)</p>
|
||
<ul class="m-steps">
|
||
<li><strong>现象</strong>:解析器只认 <code><path d></code>,而上游有 <strong>36 个文件</strong>用 <code><circle cx cy r></code> / <code><ellipse></code></li>
|
||
</ul>
|
||
<p class="m-sub"> 表达图形(TDesign 的 <code>circle</code> / <code>round</code> / <code>brightness</code> / <code>image</code> 等)。这些图形被<strong>静默丢掉</strong> ——</p>
|
||
<p class="m-sub"> 图标页面上"渲染成功"却是空的,构建与回归都不报错。TDesign 的 <code>circle</code> 与 <code>round</code> 两个图标</p>
|
||
<p class="m-sub"> 因此整个消失,是<strong>覆盖率审计</strong>(对三个参考库逐名比对)才暴露的。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>修法</strong>:解析器同时处理 <code><path></code> / <code><circle></code> / <code><ellipse></code>,把圆与椭圆等价换算成</li>
|
||
</ul>
|
||
<p class="m-sub"> path 的两段 arc(<code>M cx cy-r A r r 0 1 1 cx cy+r A r r 0 1 1 cx cy-r Z</code>)。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>结果</strong>:图标数 2574 → <strong>2576</strong>;对三家参考库的覆盖率现为</li>
|
||
</ul>
|
||
<p class="m-sub"> <strong>TDesign 2356/2356、Element Plus 293/293(各 100%)</strong>、Element UI 280/282(余 2 个是解析碎片名)。</p>
|
||
<ul class="m-steps">
|
||
<li>校验:两个恢复的图标在浏览器实测有真实几何(path 长度 63 / 44,分别对应 r=10 与 r=7 的圆)。</li>
|
||
</ul>
|
||
<p class="m-sub">#### 分层与体积(按实测决定,不是拍脑袋)</p>
|
||
<ul class="m-steps">
|
||
<li><strong>core 151 个内联</strong>(<code>core.js</code> 35KB),<code>standard</code> 1989 / <code>extended</code> 434 按需加载。</li>
|
||
<li><strong>为何不全内联</strong>:全量 670KB,内联等于给每一页都加 670KB,直接违背 ROADMAP 的性能预算精神。</li>
|
||
</ul>
|
||
<p class="m-sub">#### 组件 API(PC 5 端 + 移动端 6 端,语义统一)</p>
|
||
<ul class="m-steps">
|
||
<li>props:<code>name</code> / <code>size</code>(关键字 <code>small|default|large|xlarge</code> <strong>或任意 CSS 长度</strong>,对齐 TDesign 双模式)/</li>
|
||
</ul>
|
||
<p class="m-sub"> <code>tone</code>(<code>default|brand|secondary|danger|success|warning</code>)/ <code>spin</code> / <code>label</code>。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>向后兼容</strong>:PC 既有 <code>icon</code> prop(字形字符,默认 <code>'★'</code>)与移动端既有 9 名字形表全部保留,<code>name</code> 优先。</li>
|
||
<li>无障碍:<code>label</code> 有值 → <code>role="img"</code> + <code>aria-label</code>,无值 → <code>aria-hidden</code>;</li>
|
||
</ul>
|
||
<p class="m-sub"> <code>spin</code> 在 <code>prefers-reduced-motion: reduce</code> 下停转;语义色对比度 ≥ 3:1(实测 5.10–5.87:1)。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>viewBox 按「路径串 → 网格」反查而非按名</strong>:别名指向的 1024 网格图标若按名查会取错网格</li>
|
||
</ul>
|
||
<p class="m-sub"> (实测影响 160 个名字,会导致图标拉伸变形)。</p>
|
||
<p class="m-sub">#### 新增:图标预览页 <code>site/icon-preview.html</code></p>
|
||
<p class="m-sub">规范 <code>组件5.txt</code>「图标预览 IconPreview:展示系统所有图标 / 网格布局,支持搜索 /</p>
|
||
<p class="m-sub">点击图标复制名称或代码 / 可调整图标大小」声明已久但从未实现。本次补齐:</p>
|
||
<p class="m-sub">2576 图标网格、搜索、点击复制名称、Shift+点击复制内联 SVG、四档尺寸切换、仅线性过滤;</p>
|
||
<p class="m-sub">分两段加载(索引 51KB 先出骨架,路径数据按需);</p>
|
||
<p class="m-sub">入口挂在图标组件页代码条 footer,i18n 双语(<code>图标预览</code> / <code>Icon gallery</code>)。</p>
|
||
<p class="m-sub">#### 已知未覆盖 / 遗留</p>
|
||
<ul class="m-steps">
|
||
<li>移动端 uni-app 端用 <strong>CSS mask(data-URI SVG)</strong> 承载字形(小程序无 DOM、无内联 SVG)。</li>
|
||
</ul>
|
||
<p class="m-sub"> H5 目标已实测;<strong>mp-weixin / app-plus 的遮罩渲染未在真机验证</strong>,已登记进契约 <code>unknowns</code>。</p>
|
||
<ul class="m-steps">
|
||
<li><code>verify-emits-parse.mjs</code> 的文件数期望值陈旧(158,应为 103×2=206)—— <strong>本次之前就存在</strong></li>
|
||
</ul>
|
||
<p class="m-sub"> (<code>git show HEAD:</code> 确认),已登记为 ROADMAP <code>S8-P1</code>。</p>
|
||
<ul class="m-steps">
|
||
<li><code>verify-cross-platform.mjs</code> 把注入的图标数据表误判为组件类名(540 条里 533 条是噪声)——</li>
|
||
</ul>
|
||
<p class="m-sub"> 已登记为 ROADMAP <code>S8-P2</code>(判据不放宽,让门禁学会跳过数据块)。</p>
|
||
<ul class="m-steps">
|
||
<li><code>组件5.txt</code> 声明的 47 个工具/模板组件里 <strong>46 个仍未实现</strong>(本次只补了 IconPreview)——</li>
|
||
</ul>
|
||
<p class="m-sub"> 已登记为 ROADMAP <code>S8-P3</code>,含「走 <code>frameworks/</code> 正式组件还是 <code>site/</code> 工具页」的决策点。</p>
|
||
<h3 class="m-h3" style="margin-top: var(--kole-space-20);">Mobile · 顶栏对齐 PC + 平台入口去重(移动端站导航栏改造)</h3>
|
||
<p class="m-note">起因:用户要求「移动端组件的导航栏参考 PC 端组件导航栏优化(上面跳转 PC 和移动的下拉框也重复了)」。</p>
|
||
<p class="m-note">实测确认两条问题,逐项修掉。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>去重:平台切换从顶栏移除</strong>。移动端站顶栏原有一个 <code>[PC 端] [移动端]</code> 分段胶囊,与同一页左栏的</li>
|
||
</ul>
|
||
<p class="m-sub"> 「平台」组指向同一跳转(实测 DOM:<code>a.mp-item</code> 与 <code>a.m-side-link.m-side-platform</code> 的 href 均为</p>
|
||
<p class="m-sub"> <code>../index.html</code>)—— 同一件事两处入口。PC 站顶栏从来没有这个控件(只有左栏一处),</p>
|
||
<p class="m-sub"> 故删顶栏那份,两端现在都是<strong>左栏唯一入口</strong>。改动同时断言「删的是重复的那个,不是删光」:</p>
|
||
<p class="m-sub"> <code>verify-mobile-docs</code> 的 E7 从「断言顶栏有平台切换」改为「断言左栏有平台入口」。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>顶栏逐项对齐 PC</strong>(此前只有结构同名,控件实现不同源):</li>
|
||
<li>高度 56px → <strong>60px</strong>;内容加 <strong>1180px 居中容器</strong>(<code>.m-top-inner</code>,与 <code>.m-body</code> 同宽,</li>
|
||
</ul>
|
||
<p class="m-sub"> logo 与左栏左边缘对齐;此前内容铺满视口、与下方内容左边缘错位)。</p>
|
||
<ul class="m-steps">
|
||
<li>激活项补 <strong>2px 底部品牌指示条</strong>(<code>.m-nav a.active::after</code>,PC <code>.topnav a.active::after</code> 同款);</li>
|
||
</ul>
|
||
<p class="m-sub"> 此前只有文字变色。</p>
|
||
<ul class="m-steps">
|
||
<li>版本角标 → <strong>可点版本下拉</strong>:<code><span class="m-ver"></code> 换成 <code>#m-ver-trigger</code> + <code>#m-ver-menu</code>,</li>
|
||
</ul>
|
||
<p class="m-sub"> 清单从部署根 <code>versions.json</code> / <code>site/versions.json</code> 两份按序取(不并行 —— 快照页那份必然 404,</p>
|
||
<p class="m-sub"> 并行会把 404 记进控制台,而站点门禁的零控制台错误是硬断言);拿不到清单时</p>
|
||
<p class="m-sub"> <code>data-single="1"</code> 退回不可点角标(与 PC 同一语义)。路径校验只放行 <code>".."</code> 与 <code>x.y.z</code>,防清单把人带去外站。</p>
|
||
<ul class="m-steps">
|
||
<li>主题触发器补 <strong>三态图标</strong>(月亮 / 太阳 / 显示器,PC <code>.theme-icon</code> 同款):显式选择时按时态给图标,</li>
|
||
</ul>
|
||
<p class="m-sub"> 自动模式恒为显示器图标;此前只有文字标签。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>零运行时依赖不变</strong>:新增的版本/主题逻辑是构建期内联脚本(<code>COPY_SCRIPT</code>),不引包。</li>
|
||
<li><strong>顺手修掉一处过期硬编码</strong>:移动端站文案里的 PC 组件数写死为「79」,</li>
|
||
</ul>
|
||
<p class="m-sub"> 而 S6-P21 组件族参数化后 PC 索引已是 <strong>103</strong>(实测 <code>components/index.json</code>)。改为构建期从 PC 索引实测读取</p>
|
||
<p class="m-sub"> (模板占位符由 <code>build-mobile.mjs</code> 灌入),拿不到索引时退回中性表述而非编造数字。</p>
|
||
<p class="m-sub"> 涉及:总览页、FAQ、平台与端页的正文与覆盖矩阵、以及 <code>dist/mobile/README.md</code> 与 manifest 的 <code>isolatedFrom</code>。</p>
|
||
<p class="m-sub"><strong>验收(原样)</strong>:</p>
|
||
<div class="m-code"><pre><code>node tools/verify-mobile-docs.mjs → [OK] 12 条断言 · 53 页(E7 判据已同步去重)
|
||
REG_BASE=http://127.0.0.1:3457 node tools/verify-mobile-site.mjs → [OK] 263 条断言 · 50 页 · 控制台错误 0 · 演示帧 281/281
|
||
REG_BASE=http://127.0.0.1:3457 node tools/run-mobile-regression.mjs → passRate 100% | pages 47 (all-pass 47) | assertions 804/804 | N/A 10
|
||
REG_BASE=http://127.0.0.1:3457 node tools/run-regression.mjs → passRate 100% | pages 103 (all-pass 103) | assertions 1405/1405 | N/A 50
|
||
PC 与移动端回归各连跑 8 次,结果逐次一致(40/40 全绿)</code></pre></div>
|
||
<p class="m-sub">浏览器实测(Playwright,1440px):顶栏 60px / 内容容器实测 1180px / 激活项 <code>::after</code> 实测</p>
|
||
<p class="m-sub"><code>2px rgb(47, 84, 235)</code> / 顶栏内 <code>.m-platform</code> 计数 0 / 版本下拉实测 1 个选项且 <code>data-single="0"</code> /</p>
|
||
<p class="m-sub">切夜间后图标 <code>m-icon-sun:block</code> 且 <code>html.kole-dark</code> 生效;320–768px 四档无横向溢出、零控制台错误。</p>
|
||
<p class="m-sub">四道移动端门禁全绿:隔离 31 条 / uni-app 9 条 × 50 SFC / 文档 12 条 · 53 页 / 站点 263 条 · 50 页。</p>
|
||
<h3 class="m-h3" style="margin-top: var(--kole-space-20);">Mobile · 批次 E/F:移动端组件 36 → 47(两批子 agent 并行 + 新增「磁盘↔索引」双向门禁)</h3>
|
||
<p class="m-note">继续 <code>/son</code> 派活推进 TDesign 清单覆盖。批次 E(typography / segmented / sticky / overlay / popover / message)</p>
|
||
<p class="m-note">与批次 F(picker / cascader / colorpicker / upload / table)并行产出 11 个组件 × 8 文件 = 88 文件,</p>
|
||
<p class="m-note">规格 §37–§47。索引 36 → <strong>47</strong>,实现 216 → <strong>282</strong>,移动端回归 597 → <strong>804 条断言</strong>。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>给每个组件预分配规格编号</strong>(§37–§47):上一轮两个子 agent 并存时存在编号撞号风险(撞号会被合并脚本按幂等规则整节跳过),</li>
|
||
</ul>
|
||
<p class="m-sub"> 本轮在派单里逐组件指定编号,合并时零冲突、规格 §1–47 连续。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>新增反向门禁 B1b(两个子 agent 独立报的同一条缺口)</strong>:<code>verify-mobile-isolation.mjs</code> 原先只查</li>
|
||
</ul>
|
||
<p class="m-sub"> 「索引里的组件有没有实现文件」,<strong>不查「磁盘上的实现文件有没有进索引」</strong>——于是子 agent 产出的组件在合并前</p>
|
||
<p class="m-sub"> 完全不被任何门禁覆盖(不在索引里 = 不被检查),属静默缺口。现补反向断言:磁盘前缀集合必须等于索引前缀集合,</p>
|
||
<p class="m-sub"> 不一致直接报「未登记:X, Y(跑 node tools/merge-mobile-batch.mjs 合并)」。断言数 30 → <strong>31</strong>。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>修掉两处探针误判(都是工具侧,不是内容缺陷)</strong>:</li>
|
||
<li><code>verify-mobile-site.mjs</code> 的总览页在 47 个卡片时只渲染 43 个 —— 预览帧 <code>loading="lazy"</code>,</li>
|
||
</ul>
|
||
<p class="m-sub"> <strong>一次 <code>scrollTo</code> 到底会跳过中间帧</strong>。改为逐段滚动(每 800px 一次)后再判定,实测 281/281。</p>
|
||
<ul class="m-steps">
|
||
<li>同一脚本把收集器的 <code>net::ERR_ABORTED</code> 记为控制台错误 —— 那是 <code>_collect.html</code> 在帧装载完成后</li>
|
||
</ul>
|
||
<p class="m-sub"> <strong>主动</strong> <code>removeAttribute('src')</code> 释放帧造成的在途请求中断,是设计行为。判据改为忽略 <code>ERR_ABORTED</code>,</p>
|
||
<p class="m-sub"> 其余(404 / ERR_INVALID_URL 等)仍然计错。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>子 agent 自修的真实缺陷(都带证据)</strong>:<code>Sticky</code> 的 <code>is-stuck</code> 判定在默认偏移下滚动 0 就已贴合(改为与容器 padding box + 偏移量比较,含 1px 边框补偿);</li>
|
||
</ul>
|
||
<p class="m-sub"> <code>Message.jsx</code> 把 <code>onClose</code> 写进 <code>useEffect</code> 依赖 → 宿主传内联箭头函数时计时器永远不到点(回调移到 <code>useRef</code>);</p>
|
||
<p class="m-sub"> <code>Sticky</code>/<code>Message</code> 演示页缺实际执行的监听器致 <code>data-behavior</code> 静默失效;</p>
|
||
<p class="m-sub"> <code>Table</code> 可点行不可聚焦(真实引擎报 <code>matrix:keyboard-reachable</code>)→ 补 <code>tabindex</code>/<code>role</code>/<code>aria-selected</code>/Enter 键路径;</p>
|
||
<p class="m-sub"> <code>ColorPicker</code> 的色板 hex 写在行内 style 被 <code>no-hardcode-hex</code> 判据扫到 → 改走行内自定义属性(CSS 零 hex,色值只存数据层)。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>规划偏差(子 agent 主动对齐既有约定)</strong>:<code>disabled</code> 从变体维度降为状态类 —— 全仓 47 份契约无一把它当变体,</li>
|
||
</ul>
|
||
<p class="m-sub"> 同意;<code>picker</code>/<code>cascader</code>/<code>segmented</code> 的初判分类经我核对后修正为 input/navigation(PC 同源六分类口径)。</p>
|
||
<p class="m-sub"><strong>验收(原样)</strong>:</p>
|
||
<div class="m-code"><pre><code>node tools/verify-mobile-isolation.mjs → [OK] 31 条断言(含新增 B1b 反向断言)
|
||
node tools/verify-uniapp.mjs → [OK] 9 条断言 · 50 个 SFC
|
||
node tools/verify-mobile-docs.mjs → [OK] 12 条断言 · 53 页
|
||
REG_BASE=… verify-mobile-site.mjs → [OK] 263 条断言 · 50 页(0 控制台错误、演示帧 281/281)
|
||
node tools/run-mobile-regression.mjs → 100% | 804/804 | 47/47 页 | N/A 10 | 0 超时
|
||
node tools/run-regression.mjs → 100% | 1405/1405 | 103/103 页(PC 侧未被污染)
|
||
node tools/pack-deploy.mjs --tar → OK(282 实现 / 47 文档页 / 47 测试页)</code></pre></div>
|
||
<p class="m-sub"><strong>部署</strong>:暂存树哈希与本地一致(<code>936742e98d061a30</code>)→ 替换重建 → 五项验收全过、11 个新增组件页全 200、容器 0 error;</p>
|
||
<p class="m-sub">公网 <code>data.mobile.json</code> 报 <strong>47 个组件</strong>。</p>
|
||
<p class="m-sub"><strong>剩余 23 个</strong>(已写进 ROADMAP S7-P23):config-provider / Fab / BackTop / Drawer / Indexes / SideBar / Tabs /</p>
|
||
<p class="m-sub">Calendar / DateTimePicker / TreeSelect / CountDown / Empty / Footer / Image / ImageViewer / QRCode / Result /</p>
|
||
<p class="m-sub">Skeleton / Swiper / Watermark / DropdownMenu / Guide / PullDownRefresh。</p>
|
||
<h3 class="m-h3" style="margin-top: var(--kole-space-20);">Mobile · 批次推进:移动端组件 5 → 36(三批子 agent + 合并脚本 + 修行为断言静默缺口)</h3>
|
||
<p class="m-note">起因:用户要求「参考 TDesign 移动端组件清单,按批次补全缺的组件」,随后 <code>/son</code>(派子 agent)、<code>/agi</code>(全自主)。</p>
|
||
<p class="m-note">参考清单 67 个组件,本仓库已覆盖 36 个(含此前的 18 个);流程按 <code>PLATFORMS.md</code> §四「六步」固化。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>三批子 agent 产出 18 个组件</strong>(每个 = 6 端实现 + 规格片段 + 契约 = 8 文件,共 144 文件):</li>
|
||
<li>批次 A:<code>mobile-icon</code> / <code>mobile-layout</code> / <code>mobile-link</code> / <code>mobile-loading</code></li>
|
||
<li>批次 B:<code>mobile-avatar</code> / <code>mobile-list</code> / <code>mobile-collapse</code> / <code>mobile-progress</code></li>
|
||
<li>批次 C:<code>mobile-input</code> / <code>mobile-search</code> / <code>mobile-switch</code> / <code>mobile-stepper</code> / <code>mobile-textarea</code></li>
|
||
<li>批次 D:<code>mobile-form</code> / <code>mobile-radio</code> / <code>mobile-checkbox</code> / <code>mobile-slider</code> / <code>mobile-rate</code></li>
|
||
</ul>
|
||
<p class="m-sub"> 规格从 §19 连续到 §36(共 36 节);索引 18 → <strong>36 个组件</strong>,实现文件 108 → <strong>216</strong>。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>新增可复用的合并脚本 <code>tools/merge-mobile-batch.mjs</code></strong>:子 agent 一律不写 <code>index.json</code>(避免并发写同一文件),</li>
|
||
</ul>
|
||
<p class="m-sub"> 由主 agent 用该脚本统一合并 —— 它做三件事且幂等:① 按编号把 <code>spec/parts/*.md</code> 拼进规格原文(同编号则只归档不拼接);</p>
|
||
<p class="m-sub"> ② 把 <code>frameworks-mobile/</code> 里未登记的组件(校验 6 端文件齐全 + 契约存在)写进索引;③ 已拼接的片段归档到 <code>parts/_merged/</code>。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>修掉一个静默缺口(子 agent 复核时发现,价值最高的一条)</strong>:<code>tests/mobile/_behaviors.js</code> 的 <code>PILOTS</code> 是<strong>硬编码白名单</strong></li>
|
||
</ul>
|
||
<p class="m-sub"> (只有最初 5 个组件),于是批次新增组件即使在演示页写了 <code>data-behavior</code>,引擎也直接 <code>return []</code> ——</p>
|
||
<p class="m-sub"> <strong>行为断言静默不跑,断言总数悄悄少一截</strong>。修法:作用范围改为「演示页里<strong>真的</strong>写了 <code>[data-behavior]</code> 的组件」</p>
|
||
<p class="m-sub"> ∪ 兜底名单,由 <code>build-mobile.mjs</code> 在生成测试页时把派生结果注入 <code>window.__koleBehaviorSlugs</code>。</p>
|
||
<p class="m-sub"> 实测:行为断言覆盖 5 → <strong>31 个组件</strong>,断言条数 8 → <strong>34 条</strong>(全部 pass)。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>修掉一处判据过窄(上一轮遗留)</strong>:<code>pack-deploy.mjs</code> 的「frameworks-mobile 实现文件数」按索引动态算,</li>
|
||
</ul>
|
||
<p class="m-sub"> 但打包时批次文件已在磁盘而尚未合并进索引 → 断言误报。现在按「索引组件数 × 端数」与磁盘实际双向核对,</p>
|
||
<p class="m-sub"> 不一致时直接指出差额(本次实测:156 vs 186,正是未合并的批次 C)。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>修掉一处探针误判</strong>:<code>verify-mobile-site.mjs</code> 统计总览页演示帧时只看到 20/26 ——</li>
|
||
</ul>
|
||
<p class="m-sub"> 预览帧是 <code>loading="lazy"</code>,<strong>视口外的帧不会加载</strong>(浏览器行为,不是缺陷)。判据改为先滚到底再判定,</p>
|
||
<p class="m-sub"> 实测 26/26、现在 36/36 全部正常渲染。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>子 agent 自修的真实缺陷</strong>(都在报告里给了证据):<code><button></code> 嵌在 <code><label></code> 里(非法嵌套);</li>
|
||
</ul>
|
||
<p class="m-sub"> Stepper 根 <code>overflow:hidden</code> 裁掉小尺寸档的热区外扩(命中测试证实);同一状态块挂两条 <code>data-behavior</code> 互相污染;</p>
|
||
<p class="m-sub"> Textarea 计数示例是死数据;Slider 根元素 <code>cursor:pointer</code> 让数值文案被判「鼠标专用交互」(真实引擎报的 <code>matrix:keyboard-reachable</code>)。</p>
|
||
<p class="m-sub"><strong>验收(原样)</strong>:</p>
|
||
<div class="m-code"><pre><code>node tools/verify-mobile-isolation.mjs → [OK] 30 条断言
|
||
node tools/verify-uniapp.mjs → [OK] 9 条断言 · 39 个 SFC
|
||
node tools/verify-mobile-docs.mjs → [OK] 12 条断言 · 42 页(站内链接全可解析)
|
||
REG_BASE=… verify-mobile-site.mjs → [OK] 208 条断言 · 39 页(0 控制台错误、演示帧 220/220、320–768px 无溢出)
|
||
node tools/run-mobile-regression.mjs → 100% | 597/597 | 36/36 页 | N/A 10 | 0 超时
|
||
node tools/run-regression.mjs → 100% | 1405/1405 | 103/103 页(PC 侧未被污染)
|
||
node tools/pack-deploy.mjs --tar → OK(2349 文件 / 36 组件 × 6 端 = 216 实现 / 文档页 36 / 测试页 36)</code></pre></div>
|
||
<p class="m-sub"><strong>部署</strong>:暂存树哈希与本地一致(<code>719d66f2fce6875a</code>)→ 替换重建 → 服务器端五项验收全过、容器 0 error;</p>
|
||
<p class="m-sub">公网 <code>data.mobile.json</code> 报 <strong>36 个组件</strong>,抽查 8 个新增组件页全 200。</p>
|
||
<h3 class="m-h3" style="margin-top: var(--kole-space-20);">Mobile · 移动端组件第二批收尾(弹出层系 + 展示系 + 输入系):Toast / Dialog / Grid / Steps / NoticeBar / NumberKeyboard / DatePicker(索引 11 → 18)</h3>
|
||
<p class="m-note">起因:按批次继续开工,把 ROADMAP S7-P23 的剩余 7 个组件做完 —— 规格 §12~§18 本轮新写</p>
|
||
<p class="m-note">(契约的唯一授权来源必须先于实现),再按 <code>PLATFORMS.md</code> §四 六步流程落地为 6 端实现。</p>
|
||
<p class="m-note">索引 11 → <strong>18</strong>,<code>frameworks-mobile/</code> 66 → <strong>108</strong> 个文件(18 × 6 端)。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>七个新组件</strong>(规格小节 → slug / 前缀):</li>
|
||
<li><strong>轻提示 Toast</strong>(§12 → <code>mobile-toast</code>):<code>tone</code> 五语气 + <code>position</code> 三位置 + <code>mask</code>;</li>
|
||
</ul>
|
||
<p class="m-sub"> 容器 <code>role="status"</code> + <code>aria-live="polite"</code>(结果朗读一次不反复打断);<code>tone=loading</code> 时图标旋转并标 <code>aria-busy</code>。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>对话框 Dialog</strong>(§13 → <code>mobile-dialog</code>):<code>variant</code> = confirm(两按钮等宽)/ alert(单按钮铺满)+ <code>tone</code> = danger + <code>round</code>;</li>
|
||
</ul>
|
||
<p class="m-sub"> <code>closeOnMask</code> 可关;<code>role="dialog"</code> + <code>aria-modal</code> + <code>aria-labelledby</code>;按钮热区 ≥44px。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>宫格 Grid</strong>(§14 → <code>mobile-grid</code>):<code>columns</code> 2/3/4 + <code>border</code> + <code>square</code>;可点格子是<strong>原生 button</strong>(整块热区 + 键盘可达),</li>
|
||
</ul>
|
||
<p class="m-sub"> <code>static</code> 格子是 div 且不绑点击 —— 直接规避「鼠标专用交互」这类无障碍缺口;按下反馈一律 <code>:active</code>(触屏没有悬停)。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>步骤条 Steps</strong>(§15 → <code>mobile-steps</code>):<code>direction</code> 横/纵 + <code>status</code> 四态;<code>role="list"</code> / <code>role="listitem"</code> + <code>aria-current="step"</code>;</li>
|
||
</ul>
|
||
<p class="m-sub"> <strong>状态不只靠颜色</strong>(进行中加粗、已完成用勾选字符、失败用感叹号)。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>通知栏 NoticeBar</strong>(§16 → <code>mobile-noticebar</code>):四种语气 + <code>scrollable</code> 跑马灯 + <code>closable</code>;</li>
|
||
</ul>
|
||
<p class="m-sub"> <strong>无障碍硬要求落地</strong>:<code>prefers-reduced-motion: reduce</code> 下停止滚动并改为换行显示;滚动内容<strong>不用</strong> <code>aria-live</code>(反复朗读会干扰)。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>数字键盘 NumberKeyboard</strong>(§17 → <code>mobile-numberkeyboard</code>):<code>type</code> = number/digit + <code>showDelete</code> + <code>showConfirm</code> + <code>confirmDisabled</code>;</li>
|
||
</ul>
|
||
<p class="m-sub"> 键盘<strong>不持有输入值</strong> —— 只 emit 按键事件(数字键回传该字符 / 删除键回传 <code>'delete'</code> / 确认键回传 <code>'confirm'</code>),写入哪个输入框由宿主决定。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>日期选择器 DatePicker</strong>(§18 → <code>mobile-datepicker</code>):<code>mode</code> = date/month + <code>round</code> + <code>closeOnMask</code>;三列 <code>role="listbox"</code> / <code>role="option"</code> + <code>aria-selected</code>;</li>
|
||
</ul>
|
||
<p class="m-sub"> 年份范围由宿主传入(<code>years</code> / <code>months</code> / <code>days</code>),<strong>不发明</strong>可用年份区间(契约 unknowns 已登记)。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>配色零发明</strong>(全部既有令牌,脚本实测亮/暗两态):Toast 深底 + 反色文字 15.78:1 / 15.00:1;</li>
|
||
</ul>
|
||
<p class="m-sub"> Dialog 卡片底 + 正文色 15.13:1 / 10.34:1;Grid / Steps / NoticeBar 沿用既有语义对(浅底 + 同族深字,均 ≥4.5:1)。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>判据有效性用变异探针验证(5/5 当场捕获,不是「跑了绿灯」就算数)</strong>:</li>
|
||
</ul>
|
||
<p class="m-sub"> ① 向 CSS 注入 <code>#FF0000</code> → B3 报出文件与色值;② 契约加一个源码没有的 prop → E4 逐端报「源码里没有」;</p>
|
||
<p class="m-sub"> ③ 把有默认值的 prop 标成必传 → E4b 逐端报「应相反」;④ uni-app 端注入 <code>document.querySelectorAll</code> → U5 报出两条规则;</p>
|
||
<p class="m-sub"> ⑤ uni-app 端去掉全部 <code>rpx</code> → U9 报「未使用 rpx」。每个探针都当场还原并复跑确认绿灯。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>过程中发现并修掉三处真问题</strong>:</li>
|
||
</ul>
|
||
<p class="m-sub"> ① 契约 <code>related</code> 里写了裸名(<code>toast</code> / <code>noticebar</code>)而非真实 slug → E6c 逐条报「不存在」,已补 <code>mobile-</code> 前缀(5 处);</p>
|
||
<p class="m-sub"> ② <code>tools/pack-deploy.mjs</code> 输出文案与 <code>site/m/_design_template.html</code> 里<strong>写死了组件数</strong>(前一批 8 个组件落地时漏改)</p>
|
||
<p class="m-sub"> → 分别改为读索引与构建期占位符注入;</p>
|
||
<p class="m-sub"> ③ <code>Dialog</code> 初版的圆角写在基础类上(<code>round</code> 变体于是无意义)→ 改为由 <code>--round</code> 类控制,契约 <code>variantClasses</code> 与 CSS 双向对上。</p>
|
||
<p class="m-sub"><strong>验收(原样)</strong>:</p>
|
||
<div class="m-code"><pre><code>node tools/build-mobile.mjs → 18 组件 × 6 端(data.mobile.json 412 KB)· 18 测试页 · 18 文档页 · dist/mobile 全端
|
||
node tools/verify-mobile-isolation.mjs → [OK] 隔离门禁全部通过(30 条断言)— B1 18 × 6 端 108 文件 / B8 报告 18 = 索引 18
|
||
node tools/verify-uniapp.mjs → [OK] uni-app 门禁全部通过(9 条断言 · 21 个 SFC)
|
||
node tools/verify-mobile-docs.mjs → [OK] 文档完整性门禁全部通过(12 条断言 · 24 页)
|
||
REG_BASE=… verify-mobile-site.mjs → [OK] 移动端站点全部通过(118 条断言 · 21 页;演示帧 101/101 正常,0 控制台错误)
|
||
REG_BASE=… node tools/run-mobile-regression.mjs → passRate 100% | pages 18 (all-pass 18) | assertions 270/270 | N/A 6
|
||
node tools/run-regression.mjs → passRate 100% | pages 103 (all-pass 103) | assertions 1405/1405 | N/A 50(PC 零污染)
|
||
node tools/pack-deploy.mjs → OK — mobile 108 个实现文件(18 组件 × 6 端)/ 文档页 18 / 测试页 18</code></pre></div>
|
||
<p class="m-sub"><strong>未执行</strong>:部署(只改工作区,未走 AGENTS §九 发布流程);uni-app 真实编译(既有状况 S7-P25,需 <code>@dcloudio</code> 依赖)。</p>
|
||
<h3 class="m-h3" style="margin-top: var(--kole-space-20);">Mobile · 移动端组件第二批(弹出层基座 + 展示系):Badge / Tag / Popup(索引 8 → 11)</h3>
|
||
<p class="m-note">起因:批次推进。移动端规格已写到 §9 / §10 / §11(上一轮写好但未实现),本轮按 <code>PLATFORMS.md</code> §四</p>
|
||
<p class="m-note">的六步流程把三节规格落地为 6 端实现 —— 组件数 8 → 11,<code>frameworks-mobile/</code> 48 → 66 个文件。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>三个新组件</strong>(规格小节 → slug / 前缀):</li>
|
||
<li><strong>徽标 Badge</strong>(§9 → <code>mobile-badge</code>):<code>shape</code> = <code>dot</code> / <code>number</code> / <code>text</code> + <code>standalone</code>;</li>
|
||
</ul>
|
||
<p class="m-sub"> 状态 <code>overflow</code>(超过 <code>max</code> 显示 <code>99+</code>)与 <code>hidden</code>(值为 0 且未开 <code>showZero</code> 时不渲染)。</p>
|
||
<p class="m-sub"> 角标绝对定位、不改变被包裹元素的布局尺寸;红点 <code>aria-hidden</code>,数字角标带 <code>aria-label</code>。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>标签 Tag</strong>(§10 → <code>mobile-tag</code>):<code>tone</code> 五语气(default / primary / success / warning / danger)</li>
|
||
</ul>
|
||
<p class="m-sub"> + <code>size</code>(default / small)+ <code>closable</code>。关闭按钮是<strong>原生 button</strong>、24×24 热区(小尺寸标签内用负外边距扩展),</p>
|
||
<p class="m-sub"> <code>aria-label</code> 带上标签文字(否则一串「✕」读屏无法区分);<code>disabled</code> 时置灰且不响应。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>弹出层 Popup</strong>(§11 → <code>mobile-popup</code>):<code>placement</code> 五方向(center / bottom / top / left / right)+ <code>round</code></li>
|
||
</ul>
|
||
<p class="m-sub"> (贴边方向在靠内容一侧切圆角,用逻辑属性,RTL 安全);<code>closeOnMask</code> 可关;内容超长时 <strong>body 内部滚动</strong>、</p>
|
||
<p class="m-sub"> 遮罩不滚;滑入 240ms <code>cubic-bezier(.32,.72,0,1)</code>;浮层 <code>role="dialog"</code> + <code>aria-modal</code>,遮罩 <code>aria-hidden</code>。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>配色零发明(全部既有令牌,脚本实测两态)</strong>:实心底 + <code>--kole-color-text-inverse</code></li>
|
||
</ul>
|
||
<p class="m-sub"> 亮色 5.57–5.87:1 / 暗色 5.64–8.24:1;Tag 的浅底 + 同族字色亮色 4.68–5.38:1 / 暗色 4.92–6.91:1。</p>
|
||
<p class="m-sub"> <strong>未新增任何色值</strong>,因此不需要碰 PC 令牌文件。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>过程发现并修掉两处陈旧计数</strong>(都是前一批 8 个组件落地时漏改的):</li>
|
||
</ul>
|
||
<p class="m-sub"> ① <code>tools/pack-deploy.mjs</code> 的输出文案写死「5 组件 × 6 端」(数字本身是算出来的,只有说明文字陈旧)→ 改为读索引;</p>
|
||
<p class="m-sub"> ② <code>site/m/_design_template.html</code> 的「移动端 5 个组件」写死在 HTML 里 → 改为构建期占位符(由 <code>build-mobile.mjs</code> 注入实际组件数)。</p>
|
||
<p class="m-note">注:本条初版把占位符原文写进了日志正文,而 <code>changelog.html</code> 是<strong>从本文件正文提取</strong>渲染的,</p>
|
||
<p class="m-note">于是页面里出现了未替换的占位符 —— 被 <code>verify-mobile-docs.mjs</code> 的 E7(无残留占位符)当场拦下。</p>
|
||
<p class="m-note">同一条纪律:日志正文里不要出现占位符形状的字面量。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>修掉一处误报 skip</strong>:Badge 的「数值为 0(hidden)」演示块把 <code>data-assert</code> 放在<strong>被隐藏的角标</strong>上,</li>
|
||
</ul>
|
||
<p class="m-sub"> 断言引擎按「默认隐藏(交互后可见)」记为 skip —— 但该块本身就是「隐藏态+可见对照」的设计。</p>
|
||
<p class="m-sub"> 已把断言移到可见的容器上,skip 3 → 且该页断言 16 条全 pass(skip 只剩两条静态组件固有的 N/A)。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>顺带修正 ROADMAP S7-P23 的失败判据</strong>:其中写的 PC 断言总数是 1017,实际当前为 <strong>1405</strong>(103 组件 × 5 端后未更新)。</li>
|
||
</ul>
|
||
<p class="m-sub"><strong>验收(原样)</strong>:</p>
|
||
<div class="m-code"><pre><code>node tools/build-mobile.mjs → 11 组件 × 6 端(data.mobile.json 238 KB)· 11 测试页 · 11 文档页 · dist/mobile 全端
|
||
node tools/verify-mobile-isolation.mjs → [OK] 隔离门禁全部通过(30 条断言)— B1 11 × 6 端 66 文件 / B8 报告 11 = 索引 11
|
||
node tools/verify-uniapp.mjs → [OK] uni-app 门禁全部通过(9 条断言 · 14 个 SFC)
|
||
node tools/verify-mobile-docs.mjs → [OK] 文档完整性门禁全部通过(12 条断言 · 17 页;E8 站内链接 657 条)
|
||
REG_BASE=… verify-mobile-site.mjs → [OK] 移动端站点全部通过(83 条断言 · 14 页;演示帧 64/64 正常,0 控制台错误)
|
||
REG_BASE=… node tools/run-mobile-regression.mjs → passRate 100% | pages 11 (all-pass 11) | assertions 172/172 | N/A 4
|
||
node tools/run-regression.mjs → passRate 100% | pages 103 (all-pass 103) | assertions 1405/1405 | N/A 50(PC 零污染)
|
||
node tools/pack-deploy.mjs → OK — mobile 66 个实现文件(11 组件 × 6 端)/ 文档页 11 / 测试页 11
|
||
浏览器实测(1280×1000) → 三页各 14 个小节 / 演示块 6·5·6 / 预览帧全部有内容(0 空帧)/ 0 控制台错误
|
||
几何:Badge 角标 absolute + translate(50%,-50%)(不占位);Tag 24px 高;Popup fixed 被展示框 transform 包含块约束(关闭态位移 143.6px)</code></pre></div>
|
||
<h3 class="m-h3" style="margin-top: var(--kole-space-20);">Mobile · 组件批次推进:18 个组件 × 6 端(108 实现文件)+ 文档站 24 页,全部门禁与回归通过</h3>
|
||
<p class="m-note">起因:用户要求「参考 TDesign 移动端的组件清单,按批次补全当前组件库缺的组件」。</p>
|
||
<p class="m-note">参考页给出 72 个组件的清单,本仓库按「规格 → 契约 → 6 端实现 → 文档页 → 门禁」的既有流水线分批推进。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>组件从 5 扩到 18</strong>(<code>frameworks-mobile/</code> 108 个文件 = 18 × 6 端),规格 19 节、契约 18 份:</li>
|
||
</ul>
|
||
<p class="m-sub"> 按 PC 同源分类:导航 4 · 反馈 7 · 通用 2 · 数据展示 3 · 数据录入 2。完整清单见 <code>site/m/index.html</code> 的覆盖矩阵与侧栏。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>本会话直接产出的一批(A)</strong>:<code>mobile-button</code>(三档高度 44/36/28、loading 阻止重复触发、<code>aria-busy</code>)、</li>
|
||
</ul>
|
||
<p class="m-sub"> <code>cell</code>(整行热区 ≥56px、按行分隔线、可点用原生 button / 纯展示用 div)、<code>mobile-divider</code>(水平/垂直/虚线/</p>
|
||
<p class="m-sub"> 三种文字对齐,纯装饰 <code>role=separator + aria-hidden</code>)。三者的 6 端实现、契约(含演示分组、相似组件、</p>
|
||
<p class="m-sub"> 必传列、CSS 变量)与规格 §6–§8 均由本会话写入;其余批次与并发会话合并完成。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>合并时修掉两处判据问题(都是「判据太窄」而不是放宽)</strong>:</li>
|
||
<li><strong>slug 允许与 PC 同名</strong>:<code>build-mobile.mjs</code> 与 <code>verify-mobile-isolation.mjs</code> 原先要求「移动端 slug 不得与</li>
|
||
</ul>
|
||
<p class="m-sub"> PC 撞名」,实际两个平台各自有「按钮」「分割线」是正常的 —— 真正要证明的是「同名也各自独立」。</p>
|
||
<p class="m-sub"> 改为:报告同名清单,并断言同名组件的实现文件<strong>不在</strong> <code>frameworks/</code> 下。</p>
|
||
<ul class="m-steps">
|
||
<li><strong><code>dist/mobile/manifest.json</code> 的判据</strong>从「无 PC slug」改为「slug 集合与索引完全一致」——</li>
|
||
</ul>
|
||
<p class="m-sub"> 后者能同时抓到漏项与串入,比原来更严。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>移动端令牌层补一条全局焦点环</strong>:<code>:focus-visible</code> 落在移动端令牌层(不能只依赖 PC 令牌文件里那条 ——</li>
|
||
</ul>
|
||
<p class="m-sub"> 断言引擎读的是<strong>直接 link 的样式表</strong>的 <code>cssRules</code>,<code>@import</code> 进来的规则不在其中)。</p>
|
||
<p class="m-sub"> 这既是让静态组件过 <code>focus-visible-defined</code> 断言,也是真实的 a11y 改进。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>门禁全绿</strong>:隔离 30 条 · uni-app 9 条(21 个 SFC)· 文档完整性 12 条(24 页,站内链接 1121 条全可解析)·</li>
|
||
</ul>
|
||
<p class="m-sub"> 移动端站点 <strong>118 条</strong>(21 页浏览器实测:0 控制台错误、演示帧 101/101 正常渲染、320–768px 无溢出)。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>回归</strong>:移动端 <strong>100%(270 通过 / N/A 6 / 18 页)</strong> · PC <strong>100%(1405 通过 / N/A 50 / 103 页)</strong>。</li>
|
||
</ul>
|
||
<p class="m-sub"><strong>部署(AGENTS §九)</strong>:<code>pack-deploy --tar</code>(2187 文件 / 1.65 MB gzip)→ 暂存树哈希与本地一致</p>
|
||
<p class="m-sub">(<code>a962d7c4caa853b2</code>)→ 替换重建 → 服务器端 specs 404 / 根 302 / sitemap 200 / healthz / SPA 深链全过、</p>
|
||
<p class="m-sub">容器 0 error;公网抽查 5 个页面全 200,<code>site/m/index.html</code>、<code>site/m/component/mobile-button.html</code>、</p>
|
||
<p class="m-sub"><code>site/m/data.mobile.json</code> 逐字节与本地一致。</p>
|
||
<h3 class="m-h3" style="margin-top: var(--kole-space-20);">Mobile docs · 顶栏与左栏对齐 PC(平台 / 开发指南 / 组件 N + 主题三态跨站生效)+ 补常见问题与更新日志页</h3>
|
||
<p class="m-note">起因:用户指出「导航栏需要和 PC 一致,左侧栏也是」。移动端文档站此前是自成一体的简版顶栏</p>
|
||
<p class="m-note">(品牌 + 平台分段 + 3 个链接)与自创分组的左栏(开发指南 5 项自定名 + 手势/反馈分类),</p>
|
||
<p class="m-note">与 PC 文档站的信息架构不一致。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>顶栏与 PC 同结构</strong>:<code>logo(标记块 + Kole UI + 副标题)</code> + <code>版本角标 v1.0.0</code>(读 <code>data.mobile.json</code> 的</li>
|
||
</ul>
|
||
<p class="m-sub"> <code>meta.version</code>)+ <code>平台切换</code> + <code>主导航 5 项</code>(<strong>组件总览 / 快速开始 / 设计规范 / 常见问题 / 更新日志</strong>,</p>
|
||
<p class="m-sub"> 与 PC 同名同序,组件页高亮「组件总览」= PC 在组件页高亮「组件」的同一口径)+ <code>主题模式三态</code>。</p>
|
||
<p class="m-sub"> PC 特有的三件<strong>没有搬</strong>,原因写明在 <code>site/m/style.css</code> 注释里:搜索框(PC 79 个组件才需要)、</p>
|
||
<p class="m-sub"> 技术栈选择器(PC 5 端各自成页;移动端 6 端同页展示)、语言选择器(移动端无 i18n 字典)。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>主题三态与 PC 完全同一约定</strong>:<code>localStorage['kole-mode'] ∈ light|dark|auto</code>、<code>auto</code> 跟随系统、</li>
|
||
</ul>
|
||
<p class="m-sub"> 反色靠 <code>html.kole-dark</code>(令牌两端共用)。实测<strong>跨站生效</strong>:在移动端站选夜间 → FAQ 页仍是夜间 →</p>
|
||
<p class="m-sub"> PC 站同一键也是夜间;<code><head></code> 里有同步 bootstrap,避免首帧先白后黑(PC 的 S2-P5 同款处理)。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>左侧栏三段式与 PC 侧栏一致</strong>:「<strong>平台</strong>」组(PC 端组件 / 移动端组件【当前】,各带副标题)→</li>
|
||
</ul>
|
||
<p class="m-sub"> 「<strong>开发指南</strong>」组(与 PC 同名同序的 5 项;移动端特有的「平台与端 / 测试与回归」置后并标「移动端」)→</p>
|
||
<p class="m-sub"> 「<strong>组件 N</strong>」按 <strong>PC 的六分类</strong>分组与计数(通用 / 导航 / 数据录入 / 数据展示 / 反馈 / 工具与系统)。</p>
|
||
<p class="m-sub"> 组件重新归类:navbar、tabbar → 导航;actionsheet、pullrefresh、swipecell → 反馈;</p>
|
||
<p class="m-sub"> 分类口径写进移动端索引的 <code>categories</code> 块(真源),构建脚本与文档站都读它。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>补两页</strong>:</li>
|
||
<li><code>faq.html</code> 常见问题:6 组 20 条(接入 / 令牌 / 触控 / uni-app / 排障 / 口径),每条对应仓库里可复现的</li>
|
||
</ul>
|
||
<p class="m-sub"> 实测或门禁断言;含「uni-app 端真的编译过吗 → 没有」这类如实回答。</p>
|
||
<ul class="m-steps">
|
||
<li><code>changelog.html</code> 更新日志:<strong>构建时从仓库根 <code>CHANGELOG.md</code> 提取</strong>标题含「Mobile / 移动端」的段落</li>
|
||
</ul>
|
||
<p class="m-sub"> (自带一个极小的 Markdown 渲染:标题 / 列表 / 引用 / 代码块 / 行内 code 与 strong),不手抄 ——</p>
|
||
<p class="m-sub"> 站点与仓库两处说法不一致是这类页面的典型失败模式。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>改名对齐</strong>:<code>设计令牌</code> → <code>设计规范</code>(<code>tokens.html</code> → <code>design.html</code>),与 PC 的「设计规范」同名。</li>
|
||
<li><strong>门禁 11 → 12 条</strong>:E3 从「侧栏列全组件」扩为「<strong>顶栏与左栏与 PC 同结构</strong>」(平台组与「当前」角标、</li>
|
||
</ul>
|
||
<p class="m-sub"> 开发指南 5 项同名同序、顶栏 7 个必需元素、主导航 5 项);新增 <strong>E8 站内文件链接全部可解析</strong>(332 条)。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>E8 当场抓到两个真问题(已修)</strong>:① 改名后 <code>_index_template.html</code> 与 <code>_guide_template.html</code> 还指向</li>
|
||
</ul>
|
||
<p class="m-sub"> <code>tokens.html</code>(快速开始卡片与页脚);② <code>changelog.html</code> 的测试链接少退一级(<code>../tests/…</code> 应为 <code>../../tests/…</code>)。</p>
|
||
<p class="m-sub"> E8 已双向验证(临时改坏一处链接 → FAIL)。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>修</strong>:更新日志页在窄屏横向溢出(375px 溢出 88px、414px 溢出 49px)—— 提取的正文含 64 位哈希、</li>
|
||
</ul>
|
||
<p class="m-sub"> 长路径这类不可断词的串;给正文/列表/行内 code 补 <code>overflow-wrap: anywhere</code>(表格此前已有同类规则)。</p>
|
||
<p class="m-sub"> 修后 7 页 × 4 宽度(320/375/414/768)<strong>0 溢出</strong>。</p>
|
||
<p class="m-sub"><strong>验收(原样)</strong>:</p>
|
||
<div class="m-code"><pre><code>node tools/verify-mobile-docs.mjs → [OK] 12 条断言 · 11 页
|
||
node tools/verify-mobile-isolation.mjs → [OK] 29 条断言 node tools/verify-uniapp.mjs → [OK] 9 条(8 SFC)
|
||
REG_BASE=… verify-mobile-site.mjs → [OK] 53 条断言(8 页 + 窄屏) npm run verify:i18n → OK(16 checks)
|
||
浏览器实测:顶栏 = Kole UI 移动端 / v1.0.0 / [PC 端][移动端] / 组件总览·快速开始·设计规范·常见问题·更新日志 / 自动模式
|
||
左栏 = 平台(PC 端组件、移动端组件【当前】)/ 开发指南 5 项 + 2 项移动端特有 / 组件 5(导航 2 · 反馈 3)
|
||
主题:选夜间 → html.kole-dark + localStorage[kole-mode]=dark;跳 FAQ 仍夜间;PC 站同键亦夜间;0 控制台错误
|
||
node tools/run-mobile-regression.mjs → 100% | 77/77 | 5/5 页
|
||
node tools/run-regression.mjs → 100% | 1026/1026 | N/A 34 | 79/79 页
|
||
node tools/pack-deploy.mjs --tar → OK(39 条必需项,含 6 张移动端根页;组件数改为从索引读取)</code></pre></div>
|
||
<h3 class="m-h3" style="margin-top: var(--kole-space-20);">Deploy · 移动端组件页(TDesign 范式)已上线</h3>
|
||
<p class="m-sub">按 AGENTS §九 发布:<code>pack-deploy --tar</code> → 上传 → 暂存<strong>归一化树哈希与本地一致</strong>(<code>f00d3c4ac2e50861</code>,1531 文件)→ 替换 → <code>docker compose build && up -d</code>。</p>
|
||
<p class="m-sub">验收:specs 404 / 根 302→<code>/site/</code> / sitemap 200 / healthz 有 content-type / SPA 深链 200 / 缺快照 404 / 容器日志 0 error;</p>
|
||
<p class="m-sub">公网 <code>https://kole-ui.mymoyu.top</code> 抽查 6 个文件(<code>site/app.js</code>、组件薄壳、<code>site/data.json</code>、移动端文档页与演示页)<strong>逐字节与本地一致</strong>。</p>
|
||
<p class="m-note">并发提示:本轮期间另一会话把 <code>package.json</code> 版本重置为 <strong>1.0.0</strong> 并重建站点(<code>data.json</code> 的 <code>generated=2026-09-20 07:36</code>、</p>
|
||
<p class="m-note"><code>versions.json</code> 单版本)。线上现已与该状态一致 —— 版本号本身不是本次改动;如需回到 2.0.0,请改 <code>package.json</code></p>
|
||
<p class="m-note">后重跑 <code>npm run build:site</code> 并再发布一次。</p>
|
||
<p class="m-note">另:本轮第一次发布(07:26 打包)后实测到线上落后工作区 906 个文件(并发会话 07:36 重建了薄壳与 <code>app.js</code>),</p>
|
||
<p class="m-note">已按 §九 的约定「等稳定后重发」补发一次,现线上与工作区逐字节一致。</p>
|
||
<h3 class="m-h3" style="margin-top: var(--kole-space-20);">Mobile docs · 组件页对齐 TDesign 范式(演示分组 / 独立预览 + 原文代码 / 必传 / CSS 变量 / 相似组件)</h3>
|
||
<p class="m-note">起因:用户给了参考页 <https://tdesign.tencent.com/mobile-vue/components/link>。用浏览器渲染后提取其结构:</p>
|
||
<p class="m-note">演示按 <code>01 组件类型</code> / <code>02 组件状态</code> 分组;每个演示独立成块(标题 + 预览 + 代码);</p>
|
||
<p class="m-note">API 分 Props(<strong>名称 / 类型 / 默认值 / 描述 / 必传</strong>)、Events、<strong>CSS Variables</strong>(名称 / 默认值 / 描述);</p>
|
||
<p class="m-note">另有「何时使用 / 组件搭配使用 / 推荐慎用示例 / 相似组件」。按这套范式重排了移动端组件页。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>演示改为「分组 + 独立块」</strong>:契约新增 <code>demos</code>(id / group / title / desc / variant),5 个组件共 <strong>20 个演示块</strong></li>
|
||
</ul>
|
||
<p class="m-sub"> (navbar 3、tabbar 4、actionsheet 4、pullrefresh 4、swipecell 4),按 <code>01 组件类型</code> / <code>02 组件状态</code> 分组。</p>
|
||
<p class="m-sub"> 每块含:标题 + 变体标记 + 说明 + <strong>375 宽单演示预览帧</strong> + 「查看代码」(演示页<strong>原文</strong>,行数标注,可复制)。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>演示页支持 <code>?demo=<id></code> 单块模式</strong>:5 个演示页的每个演示包成</li>
|
||
</ul>
|
||
<p class="m-sub"> <code><section class="demo-block" data-demo="…"></code>,并加过滤脚本(只显示该块、隐藏帧内标题行、取消 <code>min-height:100vh</code>)。</p>
|
||
<p class="m-sub"> <strong>无参数路径完全不变</strong> —— 测试页与回归走无参数,实测移动端回归仍 100%(77/77)。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>预览帧高度自适应</strong>:按 <code>body.scrollHeight</code> 收紧到 140–360px。</li>
|
||
</ul>
|
||
<p class="m-note">踩坑:先用 <code>max(body.scrollHeight, documentElement.scrollHeight)</code> 量,结果<strong>四个演示都锁在 308px</strong> ——</p>
|
||
<p class="m-note"><code>documentElement.scrollHeight</code> 等于<strong>帧视口高度</strong>(html 撑满视口),于是「帧高 → 视口高 → 量到的高度」</p>
|
||
<p class="m-note">形成反馈环,永远收敛在初始值。只量 body 后:swipecell 140 / navbar 140–166 / tabbar 222 / pullrefresh 242 /</p>
|
||
<p class="m-note">actionsheet 262,<strong>0 个演示被裁</strong>。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>API 补两列/一表</strong>:Props 增「<strong>必传</strong>」列(严格定义:实现里<strong>没有默认值</strong>时才为 Y,门禁逐条核对 4 个框架端);</li>
|
||
</ul>
|
||
<p class="m-sub"> 新增「<strong>CSS 变量</strong>」表(从组件样式表扫描组件级变量:<code>--kole-m-swipecell-offset</code> /</p>
|
||
<p class="m-sub"> <code>--kole-m-swipecell-action-width</code> / <code>--kole-m-pullrefresh-threshold</code> / <code>--kole-m-pullrefresh-offset</code>)。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>新增「相似组件」表</strong>(契约 <code>related</code>:组件 + 「何时用它而不是本组件」的区分说明,共 10 条),</li>
|
||
</ul>
|
||
<p class="m-sub"> 并把「用法要点」按参考页改名为「<strong>何时使用</strong>」。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>组件页新顺序</strong>:演示 → API(Props / 事件 / 插槽 / CSS 变量)→ 何时使用 → 交互与触控 → 无障碍 →</li>
|
||
</ul>
|
||
<p class="m-sub"> 相似组件 → 规格未定 → 结构 → 变体类名映射 → 代表变体 → 令牌 → 6 端源码 → 测试与回归 → 契约。</p>
|
||
<p class="m-sub"> 另在 hero 下加「引入」代码块(令牌 + 本组件样式)。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>门禁从 8 条扩到 11 条</strong>:新增 E4b(必传 ⇔ 实现默认值)、E6b(CSS 变量表 ↔ 组件 CSS 双向)、</li>
|
||
</ul>
|
||
<p class="m-sub"> E6c(相似组件 slug 真实存在 + 说明非空);E5 扩为「每个演示块 ↔ 演示页 <code>data-demo</code> 双向一致 +</p>
|
||
<p class="m-sub"> 预览帧带 <code>?demo=</code> + 代码区含<strong>转义后</strong>的演示页原文 + 每个演示代码块非空」。</p>
|
||
<p class="m-sub"><strong>验收(原样)</strong>:</p>
|
||
<div class="m-code"><pre><code>node tools/verify-mobile-docs.mjs → [OK] 11 条断言 · 9 页
|
||
node tools/verify-mobile-isolation.mjs → [OK] 29 条断言 node tools/verify-uniapp.mjs → [OK] 9 条(8 SFC)
|
||
REG_BASE=… verify-mobile-site.mjs → [OK] 53 条断言(8 页 + 窄屏)
|
||
npm run verify:i18n → OK(16 checks)
|
||
浏览器实测(1280×1000):分组 01/02 ✓ · 20 个演示块 ✓ · 每块预览帧只显示对应演示(4/4)✓ ·
|
||
每块有代码折叠 ✓ · 帧高自适应 0 裁剪 ✓ · 0 控制台错误
|
||
node tools/run-mobile-regression.mjs → 100% | 77/77 | 5/5 页(演示页改动后复跑)
|
||
node tools/run-regression.mjs ×2 → 100% | 1026/1026 | N/A 34 | 79/79 页</code></pre></div>
|
||
<h3 class="m-h3" style="margin-top: var(--kole-space-20);">Deploy · 公网站更新到当前构建(移动端文档站四页 + 逐组件页 + PC 顶栏改动)</h3>
|
||
<ul class="m-steps">
|
||
<li>按 AGENTS §九 流程发布:<code>SITE_URL_BASE=https://kole-ui.mymoyu.top/ npm run build:site</code> → <code>build-mobile</code> + <code>build-uniapp</code></li>
|
||
</ul>
|
||
<p class="m-sub"> → <code>pack-deploy --tar</code>(1531 文件 / 5.8 MB,1.0 MB gzip)→ 上传 → 备份(目录 <code>/opt/aurora-admin.prev-20260920-0712</code></p>
|
||
<p class="m-sub"> + 镜像 <code>kole-ui-showcase:pre-20260920-0710</code>)→ 暂存目录<strong>逐文件哈希对账 0 差异(1531/1531)</strong> → 替换 → <code>docker compose build && up -d</code>。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>验收(服务器 127.0.0.1:3311)</strong>:specs 目录 404 ✓ / 根 302→<code>/site/</code> ✓ / sitemap 200 ✓ / healthz 有 content-type ✓ /</li>
|
||
</ul>
|
||
<p class="m-sub"> SPA 深层路由 <code>/site/component/button/h5</code> 200 ✓;移动端与 uni-app 路径 8 条全 200(含此前 404 的</p>
|
||
<p class="m-sub"> <code>site/m/guide.html</code>、<code>platform.html</code>、<code>tokens.html</code>);安全响应头 3 条在位。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>公网 <code>https://kole-ui.mymoyu.top</code></strong>:<code>/site/</code>、<code>/site/m/</code>、<code>/site/m/guide.html</code>、</li>
|
||
</ul>
|
||
<p class="m-sub"> <code>/site/m/component/actionsheet.html</code>、<code>/versions.json</code>、<code>/sitemap.xml</code> 全 200;</p>
|
||
<p class="m-sub"> <code>data.json</code> → <code>lib=kole-ui</code> / <code>version=2.0.0</code> / <code>generated=2026-09-20 07:09</code>(= 本次构建);</p>
|
||
<p class="m-sub"> 抽查 <code>site/m/guide.html</code> 与 <code>site/m/component/actionsheet.html</code> 的公网字节哈希与本地包<strong>逐字节一致</strong>。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>过程发现并修掉一个会让构建直接失败的仓库状态问题</strong>:本地 <code>Dockerfile</code> 曾带 <code>COPY 1.4.1 /usr/share/nginx/html/1.4.1</code></li>
|
||
</ul>
|
||
<p class="m-sub"> 行,而仓库根已无 <code>1.4.1/</code> 快照目录(并发改动期间快照被清理)——<code>docker compose build</code> 报</p>
|
||
<p class="m-sub"> <code>failed to calculate checksum of ref …: "/1.4.1": not found</code>。该行与其后注释块("以后归档后在补一行")</p>
|
||
<p class="m-sub"> 的自洽性由 <code>tools/verify-versions.mjs</code> 双向卡住(磁盘快照 ↔ COPY 行),现已通过(33 checks)。</p>
|
||
<p class="m-note">注:本轮第一次发布正是<strong>卡在这条</strong>(打包与 Dockerfile 之间差了几十秒的并发改动),已重新打包发布成功。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>修掉发布过程中暴露的 500</strong>:快照被移除后 <code>/1.4.1/site/</code> 由「归档站」变成 <strong>500</strong>(容器日志:</li>
|
||
</ul>
|
||
<p class="m-sub"> <code>rewrite or internal redirection cycle while internally redirecting to "/1.4.1/site/index.html"</code>)——</p>
|
||
<p class="m-sub"> <code>nginx.conf</code> 里版本段的 <code>try_files</code> 回落目标又匹配回同一 location,形成内部重定向环;</p>
|
||
<p class="m-sub"> 且深链 <code>/1.4.1/site/component/button/h5</code> 同样 500。修法:末位补 <code>=404</code>,前三个参数退化为文件存在性检查</p>
|
||
<p class="m-sub"> (快照在 → 发它自己的壳;不在 → 干净 404)。<strong>并给这条失败模式加了门禁</strong>:<code>tools/verify-versions.mjs</code></p>
|
||
<p class="m-sub"> 新增「versioned fallback terminates with =404」,双向验证过(改动后 34 checks 全过;把 <code>=404</code> 去掉立刻 FAIL)。</p>
|
||
<p class="m-sub"> 实测:<code>/1.4.1/site/</code> 404、<code>/1.4.1/</code> 302(再 404)、其余验收全绿、容器日志 0 条 error。</p>
|
||
<p class="m-note">归档的 1.4.1 快照内容本身没有被删除——它仍在服务器上的 <code>/opt/aurora-admin.prev-20260920-0712/1.4.1/</code></p>
|
||
<p class="m-note">与 <code>kole-ui-showcase:pre-20260920-0710</code> 镜像里;仓库当前按「不归档任何历史版本」的状态(<code>versions.json</code></p>
|
||
<p class="m-note">只列 2.0.0)发布,这是并发会话清理快照后的既定状态。</p>
|
||
<h3 class="m-h3" style="margin-top: var(--kole-space-20);">Mobile docs · 补全左侧栏与页面内容(API / 6 端源码 / 令牌 / 回归状态)</h3>
|
||
<p class="m-note">起因:用户指出「移动端的左侧栏内容不完整,页面内容也不够完整」。移动端文档站此前只有顶部导航</p>
|
||
<p class="m-note">与 6 个小节的组件页,没有左侧导航,也没有 API、源码、令牌、回归状态这些"读文档的人真正要用的东西"。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>左侧栏</strong>:所有页面共用一份 IA —— 「开发指南」5 项(移动端总览 / 快速开始 / 平台与端 / 设计令牌 /</li>
|
||
</ul>
|
||
<p class="m-sub"> 测试与回归)+「组件 5」按分类分组(导航 / 手势 / 反馈)与计数,当前页/当前组件高亮并带 <code>aria-current</code>。</p>
|
||
<p class="m-sub"> 桌面恒定展开;<strong>≤1000px 自动收起为可折叠面板</strong>(点标题展开)。</p>
|
||
<p class="m-note">踩坑记录:左栏用 <code><details></code> 承载时,<strong>不能</strong>用"去掉 <code>open</code> + CSS 强制展开"的写法 ——</p>
|
||
<p class="m-note">不带 <code>open</code> 的 <code><details></code> 高度按关闭态算成 <strong>0</strong>,<code>overflow:auto</code> 会把里面的 nav 整块裁掉</p>
|
||
<p class="m-note">(DOM 里有 10 条链接、屏幕上一片空白,实测截图抓到)。改为 <code>open</code> 默认展开 + 断点脚本切换。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>新增三页</strong>:</li>
|
||
<li><code>guide.html</code> 快速开始:三步接入、按端引入路径表(6 端)、6 端最小示例(React / Vue 3 / Vue 2 / uni-app</li>
|
||
</ul>
|
||
<p class="m-sub"> 可拷贝代码)、与 PC 端的边界、5 条常见问题(样式不生效 / <code>--kole-m-*</code> 取不到值 / 安全区 / 手势不触发 / 多 fixed 叠层)。</p>
|
||
<ul class="m-steps">
|
||
<li><code>platform.html</code> 平台与端:两轴覆盖矩阵、uni-app 两列对照、12 行目录与命名映射、6 条隔离规则、</li>
|
||
</ul>
|
||
<p class="m-sub"> 6 条门禁命令(各自检查什么)、新增组件的六步流程。</p>
|
||
<ul class="m-steps">
|
||
<li><code>tokens.html</code> 设计令牌:15 个 <code>--kole-m-*</code> 表(名 / 值 / 用途)+ 组件实际引用到的继承令牌表(构建时扫描)、</li>
|
||
</ul>
|
||
<p class="m-sub"> 单位与安全区与明暗模式三条约定、"改令牌的正确姿势"示例。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>组件页 6 节 → 14 节</strong>:新增 API(props / 事件 / 插槽三张表)、6 端源码(每端一个折叠块 + 复制按钮)、</li>
|
||
</ul>
|
||
<p class="m-sub"> 用到的令牌(构建时从该组件 CSS 扫描,区分自有 / 继承 / 组件级变量)、交互与触控、无障碍、</p>
|
||
<p class="m-sub"> 变体维度 × 类名映射、测试与回归(<strong>逐组件断言数</strong>,取自回归报告的 <code>pageResults</code>)、设计契约(内嵌原始 JSON)、</p>
|
||
<p class="m-sub"> 相关组件。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>契约补 4 类字段</strong>:<code>interaction</code> / <code>accessibility</code>(逐字取自规格 §x.5 / §x.6)、</li>
|
||
</ul>
|
||
<p class="m-sub"> <code>api</code>(props / events / slots,写明"实现接口"的来源)、<code>variantClasses</code>(变体取值 → 类名/变量)。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>新门禁 <code>npm run verify:mobile-docs</code>(8 条断言)</strong>:页面齐全 / 组件页 14 小节 / 侧栏列全组件且恰好一个高亮 /</li>
|
||
</ul>
|
||
<p class="m-sub"> <strong>契约 API 与 4 个框架端源码逐名一致(正向 + 反向)</strong> / 每个声明的端都有非空代码块 /</p>
|
||
<p class="m-sub"> <code>variantClasses</code> 的类与变量在组件 CSS 里真实存在 / 页面壳完整且无残留占位符。</p>
|
||
<p class="m-sub"> Rationale:本站是生成物,生成器的 bug 只会让页面<strong>悄悄少一块</strong>,不会报错 —— 这类"缺内容"必须靠断言抓。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>门禁当场抓到的真实漂移(已修)</strong>:契约声明 <code>PullRefresh.threshold</code>,但 <strong>uni-app 端没有这个 prop</strong>(它用的是</li>
|
||
</ul>
|
||
<p class="m-sub"> 模块常量)。已给 uni-app 端补上 <code>threshold</code>(rpx 默认 120),并把「浏览器端 px / 本端 rpx」的单位差异写进契约描述。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>回归报告新增 <code>pageResults</code></strong>(逐页 total/pass/fail):组件页据此显示「断言 N 条 · 全部通过」。</li>
|
||
</ul>
|
||
<p class="m-sub"> PC 侧报告缺这一块(正是 S5-P17「失败落不了盘」的痛点),移动端先补。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>令牌计数统一为 15</strong>:安全区两条在 <code>@supports</code> 里各出现两次(0px 默认 + <code>env()</code> 覆盖),</li>
|
||
</ul>
|
||
<p class="m-sub"> 此前构建日志/数据显示 17 而映射表显示 15;现按<strong>唯一令牌名</strong>计,并给这两条补上用途说明。</p>
|
||
<p class="m-sub"><strong>验收(原样)</strong>:</p>
|
||
<div class="m-code"><pre><code>node tools/verify-mobile-docs.mjs → [OK] 8 条断言 · 9 页(含 API ↔ 源码逐名一致)
|
||
node tools/verify-mobile-isolation.mjs → [OK] 29 条断言
|
||
node tools/verify-uniapp.mjs → [OK] 9 条断言(8 个 SFC)
|
||
REG_BASE=… node tools/verify-mobile-site.mjs → [OK] 53 条断言(8 页 + 窄屏 3 页 × 4 宽度)
|
||
npm run smoke:site / verify:i18n → OK(16 checks)
|
||
浏览器实测:桌面左栏高度 500px / 10 条链接 / 恰好 1 个高亮;窄屏自动收起、点开可见、放大自动展开;
|
||
复制按钮写入剪贴板 2233 字符且文案变「已复制」;#api 锚点可定位;0 控制台错误
|
||
node tools/run-regression.mjs ×2 → 100% | 1026/1026 | N/A 34 | 79/79 页 | 0 超时
|
||
node tools/run-mobile-regression.mjs → 100% | 77/77 | 5/5 页 | 0 超时(逐页 14/15/16/15/17)</code></pre></div>
|
||
<h3 class="m-h3" style="margin-top: var(--kole-space-20);">Docs site · 平台入口打通(PC ↔ 移动端)+ 技术栈选择器美化 + 本地访问方式</h3>
|
||
<p class="m-note">起因:用户要求「做一个合理的访问方式」与「当前的组件框架选择的样式需要美化一下」。</p>
|
||
<p class="m-note">前者指:移动端文档站上线后没有入口,且本地起服务时端口被旧实例占用会静默失败(实测 403);</p>
|
||
<p class="m-note">后者指顶栏那个裸 <code><select></code>(系统原生下拉、无悬停/展开态,与旁边的语言/主题选择器不同源)。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>技术栈选择器改为「胶囊触发器 + 卡片菜单」</strong>(<code>site/index.html</code> + <code>site/style.css</code> + <code>site/app.js</code>):</li>
|
||
</ul>
|
||
<p class="m-sub"> 图标(层次符号)+ 当前值 + 箭头;菜单沿用语言选择器的卡片样式(标题分隔线、每项带<strong>端文件名角标</strong></p>
|
||
<p class="m-sub"> <code>html / jsx / vue2 / vue3</code>、选中项对勾、头部右侧显示当前端文件名)。键盘全路径:<code>↓/↑</code> 移动、</p>
|
||
<p class="m-sub"> <code>Home/End</code> 首尾、<code>Enter/Space</code> 选中、<code>Esc</code> 关闭并回焦、<code>Tab</code> 关闭不抢焦点、点击外部关闭;</p>
|
||
<p class="m-sub"> 展开态旋转箭头 + 品牌色焦点环;暗色与 <code>prefers-reduced-motion</code> 均适配。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>原生 <code><select></code> 保留为状态真源</strong>(<code>.fw-native</code>:1px 视觉隐藏,不进 Tab 序列):</li>
|
||
</ul>
|
||
<p class="m-sub"> 选中项写回它的 <code>value</code> 并派发 <code>change</code>,既有的「状态更新 / localStorage / 路由跳转」管道</p>
|
||
<p class="m-sub"> 与既有验证脚本(<code>run-site-smoke</code> 读 <code>inputValue()</code> + <code>selectOption()</code>)<strong>一行未改</strong>。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>侧栏新增「平台」分组</strong>:<code>PC 端组件</code>(带「当前」角标与副标题)/ <code>移动端组件</code>(副标题写清</li>
|
||
</ul>
|
||
<p class="m-sub"> 5 组件 × 6 端含 uni-app)。移动端入口是<strong>整页跳转</strong>(指向站点根下的 <code>m/index.html</code>)——</p>
|
||
<p class="m-sub"> SPA 的路由拦截器只接管无扩展名的地址,带 <code>.html</code> 的链接交回浏览器,两个站各自独立加载。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>移动端文档站顶栏加平台切换</strong> <code>[PC 端] [移动端]</code>(当前侧实心品牌底),与 PC 侧形成双向入口。</li>
|
||
<li><strong>本地访问方式</strong>(<code>site/dev-server.js</code>):端口被占用时<strong>自动向上找空闲端口</strong>(最多 10 档)并说明,</li>
|
||
</ul>
|
||
<p class="m-sub"> 不再 <code>EADDRINUSE</code> 直接退出;<code>KOLE_PORT</code> 显式指定时不回退(CI/脚本依赖固定端口)。</p>
|
||
<p class="m-sub"> 启动即打印 6 个入口:PC 文档站 / 移动端文档站 / 组件总览 / PC 测试总览 / 移动端测试总览 /</p>
|
||
<p class="m-sub"> 两个回归收集器,并给出 <code>REG_BASE=</code> 提示。</p>
|
||
<p class="m-note">实测踩到过:3311 上挂着<strong>改动前启动</strong>的实例时,新目录一律 403,日志里只有一句 <code>EADDRINUSE</code>,</p>
|
||
<p class="m-note">极易误判成「构建没产出」。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>修:移动端文档站在小屏上横向溢出</strong>(实测 375px 溢出 128px、414px 溢出 89px)—— 一个讲移动端的</li>
|
||
</ul>
|
||
<p class="m-sub"> 文档站自己在手机上横向滚动说不过去。元凶两处:表格里<strong>不可断行的长路径</strong></p>
|
||
<p class="m-sub"> (<code>.design_library/kole-ui-mobile/components/</code> 把表格最小宽度顶到 462px)与组件页固定 375px 的设备帧。</p>
|
||
<p class="m-sub"> 修法:单元格与 <code><code></code> 允许长 token 断行、设备帧取 <code>min(375px, 100%)</code>、顶栏 ≤720px 换行并把副导航移到第二行。</p>
|
||
<p class="m-sub"> 修后实测 320 / 375 / 414 / 768 / 1280 全部 0 溢出、0 控制台错误。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>新增窄屏门禁</strong>:<code>tools/verify-mobile-site.mjs</code> 加「窄屏无横向溢出」一节(3 类页面 × 4 个宽度 + 控制台错误),</li>
|
||
</ul>
|
||
<p class="m-sub"> 断言数 38 → <strong>53</strong>。只靠"在桌面宽度看一眼"发现不了这类问题。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>修</strong>:移动端文档站与规格文件里对 <code>MOBILE.md</code> 的失效引用 → <code>PLATFORMS.md</code>(4 处源码,</li>
|
||
</ul>
|
||
<p class="m-sub"> 生成物已重跑)。该文档在交付时最终命名为 PLATFORMS.md。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>验证脚本同步</strong>:<code>verify-nav-responsive.mjs</code> 的字体容忍度元素清单随 UI 更新</li>
|
||
</ul>
|
||
<p class="m-sub"> (<code>.fw-label</code>/<code>#fw-select</code> → <code>.fw-cur</code>/<code>.fw-menu .fw-opt</code>)—— 清单不跟着改会把余量报得偏乐观。</p>
|
||
<p class="m-sub"> 实测余量 5.8%(英文 · 1367px)。</p>
|
||
<p class="m-sub"><strong>验收(原样)</strong>:</p>
|
||
<div class="m-code"><pre><code>浏览器功能断言(Chromium 1440×900) 25/25 PASS,全程 0 控制台错误
|
||
├ 触发器/菜单/4 选项/端文件名角标/选中态/aria-expanded
|
||
├ 选择 Vue 3 → 触发器文案 + #fw-select value + localStorage + 路由 /component/button/vue3 同步
|
||
├ 键盘:Esc 关闭、↓ 打开并聚焦当前项、↓ 环形移动、Enter 生效
|
||
├ 侧栏平台入口 2 项、href=/site/m/index.html、整页跳转到移动端站、移动端站回链 ../index.html
|
||
└ 暗色模式菜单底色 rgb(28,31,38)(跟随主题)
|
||
node tools/verify-mobile-isolation.mjs → [OK] 29 条断言
|
||
node tools/verify-uniapp.mjs → [OK] 9 条断言(8 个 SFC)
|
||
REG_BASE=… node tools/verify-mobile-site.mjs → [OK] 53 条断言(8 页 + 3 页窄屏 × 4 宽度)
|
||
npm run smoke:site → OK — all checks passed
|
||
npm run verify:nav → OK — all checks passed(余量 5.8%)
|
||
npm run verify:playground → OK — all checks passed
|
||
node tools/run-regression.mjs ×3 → 100% | 1026/1026 | N/A 34 | 79/79 页 | 0 超时
|
||
node tools/run-mobile-regression.mjs → 100% | 77/77 | 5/5 页 | 0 超时</code></pre></div>
|
||
<h3 class="m-h3" style="margin-top: var(--kole-space-20);">Mobile · 移动端平台上线:与 PC 端物理隔离(平台 × 端两轴,含 uni-app 端)</h3>
|
||
<p class="m-note">起因:用户要求「在组件库额外添加移动端组件,移动端和 PC 端隔离」,并明确「PC 和移动是两个大分类,</p>
|
||
<p class="m-note">移动端后面是 H5 移动端、Vue 移动端,而且 uni-app 也会经常用到(uni-app 移动端 / uni-app PC 端)」。</p>
|
||
<p class="m-note">据此把组件库组织成<strong>两个正交轴</strong>:平台 <code>pc | mobile</code> × 端 <code>css | html | jsx | vue2 | vue3 | uniapp</code>。</p>
|
||
<p class="m-note">新增 <strong><code>PLATFORMS.md</code></strong>(两轴定义、目录与命名映射、隔离规则、新增组件六步流程)。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>移动端 5 个组件 × 6 端 = 30 个实现文件</strong>(<code>frameworks-mobile/</code>):<code>navbar</code>(顶部导航栏)、</li>
|
||
</ul>
|
||
<p class="m-sub"> <code>tabbar</code>(底部标签栏)、<code>actionsheet</code>(动作面板)、<code>pullrefresh</code>(下拉刷新)、<code>swipecell</code>(滑动单元格)。</p>
|
||
<p class="m-sub"> 每组件六端:CSS / H5 演示页 / React / Vue 2 / Vue 3 / <strong>uni-app</strong>。类名前缀 <code>kole-m-</code>,导出名 <code>KoleM*</code>。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>移动端规格原文(本仓库自撰)</strong>:<code>.design_library/kole-ui-mobile/spec/移动端规格.md</code>。</li>
|
||
</ul>
|
||
<p class="m-sub"> PC 端规格来自外部交付的 <code>组件1~10.txt</code>;移动端<strong>没有</strong>外部规范,故契约 <code>sourceKind: authored-spec</code>、</p>
|
||
<p class="m-sub"> <code>provenance: authored-in-repo</code>——不冒认外部来源;契约的 <code>doNotInvent</code> / <code>unknowns</code> 逐条对应规格条目。</p>
|
||
<p class="m-sub"> 规格共 5 节(另有 §〇 隔离总则),5 份契约全覆盖。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>令牌层</strong>:<code>.design_library/kole-ui-mobile/colors_and_type.css</code> = <code>@import</code> PC 令牌(颜色/字体/圆角/阴影同源)</li>
|
||
</ul>
|
||
<p class="m-sub"> + 15 个 <code>--kole-m-*</code>(触控 44px 最小热区、<code>@supports (env())</code> 安全区、移动端字号、手势动效时值)。</p>
|
||
<p class="m-sub"> <strong>PC 令牌文件零改动</strong>,改一处令牌两端同时生效。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>隔离边界</strong>(每一条都有断言,见下):实现目录(<code>frameworks-mobile/</code> vs <code>frameworks/</code>)、契约目录、</li>
|
||
</ul>
|
||
<p class="m-sub"> 类名前缀、令牌前缀、测试页(<code>tests/mobile/</code> vs <code>tests/</code>)、回归报告(<code>tests/mobile-report.json</code> vs</p>
|
||
<p class="m-sub"> <code>tests/report.json</code>)、文档站(<code>site/m/</code> 静态站 vs <code>site/</code> SPA,移动端<strong>不进</strong> PC 路由表)、</p>
|
||
<p class="m-sub"> 分发产物(<code>dist/mobile/*</code> vs <code>dist/*</code>)。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>uni-app 端两处</strong>:移动端 × uni-app(5 个,目标 <code>app-plus</code> / <code>mp-weixin</code> / <code>h5</code>,用 uni 基础组件 +</li>
|
||
</ul>
|
||
<p class="m-sub"> <code>rpx</code> + <strong>touch 事件</strong>——小程序与 App 端无 PointerEvent);<strong>PC × uni-app 试点 3 个</strong></p>
|
||
<p class="m-sub"> (<code>frameworks-uniapp-pc/{Button,Input,Card}.uniapp.vue</code>,类名与 PC 的 <code>frameworks/*.css</code> 逐字一致、</p>
|
||
<p class="m-sub"> <code>--kole-*</code> 令牌、px 尺寸、目标 H5/PC 容器),覆盖率 3 / 79 并登记为 ROADMAP S7-P26。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>构建(Node,跨平台,不碰 Windows-only 的 PS 链)</strong>:<code>tools/build-mobile.mjs</code>(导出 <code>site/m/data.mobile.json</code></li>
|
||
</ul>
|
||
<p class="m-sub"> 自包含 102 KB = 5 组件 × 6 端源码、<code>site/m/index.html</code> 总览、<code>site/m/component/<slug>.html</code> 逐组件页、</p>
|
||
<p class="m-sub"> <code>tests/mobile/<strong></code>、<code>dist/mobile/</strong></code>)与 <code>tools/build-uniapp.mjs</code>(<code>dist/uniapp-pc/**</code>)。</p>
|
||
<p class="m-sub"> 两个脚本都带<strong>写入守卫</strong>:越界路径直接抛错(移动端脚本只允许写 <code>site/m/</code> <code>tests/mobile/</code> <code>dist/mobile/</code>)。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>测试</strong>:移动端测试页以 <strong>375×640 设备帧</strong> iframe 载真实演示页;断言引擎与 PC <strong>共用</strong> <code>tests/_runtime.js</code>;</li>
|
||
</ul>
|
||
<p class="m-sub"> 行为库为移动端专属 <code>tests/mobile/_behaviors.js</code>,新增 3 个触控动词 <code>swipe-sets-class</code> / <code>swipe-sets-attr</code> /</p>
|
||
<p class="m-sub"> <code>pull-triggers</code>(合成 <code>pointerdown → pointermove → pointerup</code> 手势,断言类/属性真实变化)。</p>
|
||
<p class="m-sub"> <strong>两端行为库互不加载</strong>,PC 侧 6 试点组件不受影响。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>门禁与 CI</strong>:<code>tools/verify-mobile-isolation.mjs</code>(<strong>28 断言</strong>:PC 零污染 / 移动端自洽 / 分发隔离)、</li>
|
||
</ul>
|
||
<p class="m-sub"> <code>tools/verify-uniapp.mjs</code>(<strong>9 断言</strong>:SFC 三段 / <code>node --check</code> 语法 / 标签配平 / 禁 DOM API /</p>
|
||
<p class="m-sub"> 手势必须 touch / 只用 uni 基础组件 / 前缀隔离 / 单位策略)、<code>tools/run-mobile-regression.mjs</code>。</p>
|
||
<p class="m-sub"> <code>regression.yml</code> 新增:构建分发产物 → <strong>移动端生成物可复现性断言</strong>(<code>git diff --quiet</code> 卡"改了索引忘重跑构建")</p>
|
||
<p class="m-sub"> → 两条门禁 → 移动端回归 → 报告上传与摘要(PC 与移动端两段)。</p>
|
||
<ul class="m-steps">
|
||
<li><strong>部署</strong>:<code>tools/pack-deploy.mjs</code> 的 <code>CONTENT_DIRS</code> 增 <code>frameworks-mobile</code>、<code>frameworks-uniapp-pc</code>,</li>
|
||
</ul>
|
||
<p class="m-sub"> 新增 6 条硬断言(移动端 30 实现 / 5 文档页 / 5 测试页 / 3 试点);<code>Dockerfile</code> 增两条 <code>COPY</code>;</p>
|
||
<p class="m-sub"> <code>.dockerignore</code> 显式排除 <code>.design_library/kole-ui-mobile/spec</code>(移动端规格原文与 PC 规格同策略不进镜像);</p>
|
||
<p class="m-sub"> <code>site/dev-server.js</code> 白名单增两个顶层目录。</p>
|
||
<p class="m-sub"><strong>验收(原样)</strong>:</p>
|
||
<div class="m-code"><pre><code>node tools/verify-mobile-isolation.mjs → [OK] 隔离门禁全部通过(28 条断言)
|
||
node tools/verify-uniapp.mjs → [OK] uni-app 门禁全部通过(9 条断言 · 8 个 SFC)
|
||
node tools/run-mobile-regression.mjs → passRate 100% | pages 5 (all-pass 5) | assertions 77/77
|
||
node tools/run-regression.mjs ×8 → run 1..8 全为 100% | pass 1017 | fail 0 | na 34 | pages 79/79 | timedOut 0
|
||
node tools/pack-deploy.mjs → OK:components 79 薄壳/395 实现 · mobile 30 实现/5 文档页/5 测试页 · uniapp-pc 3 试点</code></pre></div>
|
||
<p class="m-sub"><strong>未执行</strong>(写明并给复现命令,见 ROADMAP S7-P25):uni-app 真实编译(H5 / 微信小程序 / App)——</p>
|
||
<p class="m-sub">需 <code>@dcloudio/vite-plugin-uni</code>,与零运行时依赖不冲突但会污染主回归 job 的依赖,故本次只做静态门禁;</p>
|
||
<p class="m-sub">复现命令写在 <code>tools/verify-uniapp.mjs</code> 头部。</p>
|
||
</section>
|
||
|
||
<footer class="m-foot">
|
||
<p>生成物:本页由 <code>node tools/build-mobile.mjs</code> 从 <code>CHANGELOG.md</code> 提取生成,请勿手改。</p>
|
||
<p>相关:<a href="platform.html">平台与端</a> · <a href="../../tests/mobile/index.html">测试与回归</a> · PC 侧更新日志见 <a href="../changelog">PC 文档站</a></p>
|
||
</footer>
|
||
</main>
|
||
</div>
|
||
<script>
|
||
(function () {
|
||
/* 左栏:≤1000px 默认收起(手机上不让导航占满一屏),跨断点时同步一次;不监听 resize,
|
||
免得跟用户手动的展开/收起抢状态。桌面不设 open 之外的动作。 */
|
||
var side = document.querySelector('details.m-side');
|
||
if (side && window.matchMedia) {
|
||
var mq = window.matchMedia('(max-width: 1000px)');
|
||
var sync = function () { if (mq.matches) side.removeAttribute('open'); else side.setAttribute('open', ''); };
|
||
sync();
|
||
if (mq.addEventListener) mq.addEventListener('change', sync);
|
||
else if (mq.addListener) mq.addListener(sync);
|
||
}
|
||
/* 单演示预览帧按内容高度自适应(同源可读 contentDocument;上限 360px、下限 140px)。
|
||
固定高度会在内容少的演示上留一大片空白(实测 SwipeCell 单个演示只用 ~120px)。 */
|
||
function fitDemoFrames() {
|
||
var frames = document.querySelectorAll('.m-demo-stage iframe');
|
||
Array.prototype.forEach.call(frames, function (f) {
|
||
var fit = function () {
|
||
try {
|
||
var d = f.contentDocument;
|
||
if (!d || !d.body) return;
|
||
/* 只量 body.scrollHeight:documentElement.scrollHeight 等于**帧视口高度**(html 撑满视口),
|
||
拿它做 max 会形成「帧高→视口高→量到的高度」反馈环,永远收敛在初始值(实测锁在 308px)。 */
|
||
var h = d.body.scrollHeight;
|
||
if (!h) h = d.documentElement ? d.documentElement.scrollHeight : 0;
|
||
if (!h) return;
|
||
f.style.height = Math.min(360, Math.max(140, h + 8)) + 'px';
|
||
} catch (e) { /* 跨源或未就绪:保持 CSS 兜底高度 */ }
|
||
};
|
||
f.addEventListener('load', function () { fit(); setTimeout(fit, 300); });
|
||
fit();
|
||
});
|
||
}
|
||
fitDemoFrames();
|
||
window.addEventListener('load', fitDemoFrames);
|
||
/* 主题模式(三态):与 PC 完全同一约定 —— localStorage[kole-mode] ∈ light|dark|auto,
|
||
auto 跟随系统,反色靠 html.kole-dark(令牌文件里已写好该组,两端共用一份令牌)。
|
||
用户在 PC 站选夜间,进移动端站也是夜间。 */
|
||
var MODE_KEY = 'kole-mode';
|
||
var MODE_LABEL = { light: '日间模式', dark: '夜间模式', auto: '自动模式' };
|
||
function readMode() {
|
||
try { var v = localStorage.getItem(MODE_KEY); if (v === 'light' || v === 'dark' || v === 'auto') return v; } catch (e) {}
|
||
return 'auto';
|
||
}
|
||
function systemDark() {
|
||
return !!(window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches);
|
||
}
|
||
function applyMode(mode) {
|
||
var dark = mode === 'dark' || (mode === 'auto' && systemDark());
|
||
document.documentElement.classList.toggle('kole-dark', dark);
|
||
document.documentElement.style.colorScheme = dark ? 'dark' : 'light';
|
||
var cur = document.getElementById('m-mode-cur');
|
||
if (cur) cur.textContent = MODE_LABEL[mode] || MODE_LABEL.auto;
|
||
Array.prototype.forEach.call(document.querySelectorAll('.m-menu-opt'), function (o) {
|
||
o.setAttribute('aria-selected', o.getAttribute('data-mode') === mode ? 'true' : 'false');
|
||
});
|
||
/* 三态图标与 PC 同口径:显式选择时按时态给图标(日间=月亮 / 夜间=太阳),
|
||
自动模式下恒为「跟随系统」的显示器图标(此时两个显式图标都不显示)。 */
|
||
var moon = document.getElementById('m-icon-moon');
|
||
var sun = document.getElementById('m-icon-sun');
|
||
var auto = document.getElementById('m-icon-auto');
|
||
if (moon) moon.style.display = (mode === 'light') ? '' : 'none';
|
||
if (sun) sun.style.display = (mode === 'dark') ? '' : 'none';
|
||
if (auto) auto.style.display = (mode === 'auto') ? '' : 'none';
|
||
}
|
||
function bindModeMenu() {
|
||
var trigger = document.getElementById('m-mode-trigger');
|
||
var menu = document.getElementById('m-mode-menu');
|
||
if (!trigger || !menu) return;
|
||
var close = function (refocus) {
|
||
menu.classList.add('hidden'); trigger.setAttribute('aria-expanded', 'false');
|
||
if (refocus) trigger.focus();
|
||
};
|
||
trigger.addEventListener('click', function () {
|
||
var open = menu.classList.toggle('hidden');
|
||
trigger.setAttribute('aria-expanded', open ? 'false' : 'true');
|
||
});
|
||
trigger.addEventListener('keydown', function (e) {
|
||
if (e.key === 'Escape') close(true);
|
||
});
|
||
Array.prototype.forEach.call(menu.querySelectorAll('.m-menu-opt'), function (opt) {
|
||
opt.addEventListener('click', function () {
|
||
var mode = opt.getAttribute('data-mode');
|
||
try { localStorage.setItem(MODE_KEY, mode); } catch (e) {}
|
||
applyMode(mode);
|
||
close(true);
|
||
});
|
||
});
|
||
document.addEventListener('click', function (e) {
|
||
if (!menu.classList.contains('hidden') && !menu.contains(e.target) && !trigger.contains(e.target)) close(false);
|
||
});
|
||
document.addEventListener('keydown', function (e) {
|
||
if (e.key === 'Escape' && !menu.classList.contains('hidden')) close(true);
|
||
});
|
||
/* 自动模式下跟随系统切换 */
|
||
if (window.matchMedia) {
|
||
var mq = window.matchMedia('(prefers-color-scheme: dark)');
|
||
var onChange = function () { if (readMode() === 'auto') applyMode('auto'); };
|
||
if (mq.addEventListener) mq.addEventListener('change', onChange);
|
||
else if (mq.addListener) mq.addListener(onChange);
|
||
}
|
||
applyMode(readMode());
|
||
}
|
||
bindModeMenu();
|
||
/* 版本下拉:与 PC 顶栏同一实现口径 —— 清单从部署根取(<prefix>/versions.json 与
|
||
<prefix>/site/versions.json 两份按序尝试,取有可用条目的那份),拿不到就退回不可点的
|
||
纯角标(data-single="1"),不留一个点不开的下拉。
|
||
清单不许把用户带去外站:path 只允许 ".."(站点根自身)或 x.y.z 快照目录名。 */
|
||
var VERSION_KEY_PATH = '..';
|
||
function isValidVersionPath(p) {
|
||
if (typeof p !== 'string' || !p.length || p.indexOf('\\') >= 0 || p.indexOf('//') >= 0) return false;
|
||
if (p === VERSION_KEY_PATH) return true;
|
||
return /^\d+\.\d+\.\d+$/.test(p);
|
||
}
|
||
/* 清单里的 path 相对部署根,".." = /site/、x.y.z = /x.y.z/site/。
|
||
当前页可能不在部署根下(快照页),故先还原部署根绝对路径再算回相对当前页。 */
|
||
function versionHref(absSite) {
|
||
var i = location.pathname.indexOf('/site/');
|
||
var prefix = i >= 0 ? location.pathname.slice(0, i) : '';
|
||
if (!prefix || absSite.indexOf(prefix + '/') !== 0) return absSite;
|
||
var rest = absSite.slice(prefix.length + 1);
|
||
var up = '';
|
||
var segs = prefix.slice(1).split('/');
|
||
for (var k = 0; k < segs.length; k++) up += "../";
|
||
return up + rest;
|
||
}
|
||
function buildVersionList(doc) {
|
||
var raw = doc && doc.versions;
|
||
if (!raw || !raw.length) return [];
|
||
var i = location.pathname.indexOf('/site/');
|
||
var prefix = i >= 0 ? location.pathname.slice(0, i) : '';
|
||
var list = [];
|
||
for (var k = 0; k < raw.length; k++) {
|
||
var v = raw[k] || {};
|
||
var ver = v.version ? String(v.version) : '';
|
||
var path = v.path ? String(v.path) : '';
|
||
if (!ver || !isValidVersionPath(path)) continue;
|
||
var absSite = (path === VERSION_KEY_PATH ? '' : '/' + path) + '/site/';
|
||
list.push({ version: ver, date: v.date ? String(v.date) : '', latest: !!v.latest, href: versionHref(absSite), absSite: absSite, current: false });
|
||
}
|
||
if (list.length) {
|
||
/* 当前版本 = 绝对路径上命中的最长前缀(最具体的那个)——与 PC 的判定一致,
|
||
快照页与根站点都能算对。 */
|
||
var here = location.pathname;
|
||
var best = -1;
|
||
for (var j = 0; j < list.length; j++) {
|
||
if (here.indexOf(list[j].absSite) === 0 && list[j].absSite.length > best) best = list[j].absSite.length;
|
||
}
|
||
for (var m = 0; m < list.length; m++) list[m].current = best >= 0 && list[m].absSite.length === best;
|
||
if (best < 0 && list[0]) list[0].current = true;
|
||
}
|
||
return list;
|
||
}
|
||
function bindVersionMenu() {
|
||
var trigger = document.getElementById('m-ver-trigger');
|
||
var menu = document.getElementById('m-ver-menu');
|
||
var chip = document.getElementById('m-ver');
|
||
if (!trigger || !menu) return;
|
||
var close = function (refocus) {
|
||
menu.classList.add('hidden'); trigger.setAttribute('aria-expanded', 'false');
|
||
if (refocus) trigger.focus();
|
||
};
|
||
var interactive = false;
|
||
var render = function (list) {
|
||
interactive = !!(list && list.length);
|
||
/* 只有清单拿不到才退回纯角标;只要有一版就是下拉(里面至少能看清当前是哪个版本) */
|
||
trigger.setAttribute('data-single', interactive ? '0' : '1');
|
||
if (!interactive) { menu.classList.add("hidden"); return; }
|
||
var cur = null;
|
||
for (var k = 0; k < list.length; k++) if (list[k].current) cur = list[k];
|
||
if (chip) chip.textContent = 'v' + ((cur && cur.version) || VERSION);
|
||
/* 重建选项(幂等:重复调用不会叠加) */
|
||
Array.prototype.slice.call(menu.querySelectorAll('.m-ver-opt')).forEach(function (n) { n.remove(); });
|
||
list.forEach(function (it) {
|
||
var opt = document.createElement('button');
|
||
opt.type = 'button'; opt.className = 'm-ver-opt'; opt.setAttribute('role', 'option');
|
||
opt.setAttribute('data-version', it.version);
|
||
opt.setAttribute('aria-selected', it.current ? 'true' : 'false');
|
||
var tag = it.current ? '当前浏览' : (it.latest ? '最新版' : (it.date || ''));
|
||
opt.innerHTML = '<span class="m-ver-opt-num">v' + it.version + '</span><span class="m-ver-opt-tag">' + tag + '</span>';
|
||
if (it.href) opt.setAttribute('data-href', it.href);
|
||
opt.addEventListener("click", function () {
|
||
if (!it.current && it.href) location.href = it.href;
|
||
else close(true);
|
||
});
|
||
menu.appendChild(opt);
|
||
});
|
||
};
|
||
trigger.addEventListener('click', function () {
|
||
if (!interactive) return;
|
||
if (menu.classList.contains('hidden')) { menu.classList.remove('hidden'); trigger.setAttribute('aria-expanded', 'true'); }
|
||
else close(false);
|
||
});
|
||
trigger.addEventListener('keydown', function (e) {
|
||
if (!interactive) return;
|
||
if (e.key === 'ArrowDown' || e.key === 'ArrowUp') { e.preventDefault(); menu.classList.remove('hidden'); trigger.setAttribute('aria-expanded', 'true'); }
|
||
else if (e.key === 'Escape') close(true);
|
||
});
|
||
document.addEventListener('click', function (e) {
|
||
if (!menu.classList.contains('hidden') && !menu.contains(e.target) && !trigger.contains(e.target)) close(false);
|
||
});
|
||
document.addEventListener('keydown', function (e) {
|
||
if (e.key === 'Escape' && !menu.classList.contains('hidden')) close(true);
|
||
});
|
||
/* 两份清单按序尝试(不并行:快照页那份必然 404,并行会把 404 记进控制台,
|
||
而站点门禁的「零控制台错误」是硬断言)。 */
|
||
if (!window.fetch || location.protocol === 'file:') return;
|
||
var i = location.pathname.indexOf('/site/');
|
||
if (i < 0) return;
|
||
var prefix = location.pathname.slice(0, i);
|
||
var urls = [prefix + '/versions.json', prefix + '/site/versions.json'];
|
||
var attempt = function (idx) {
|
||
if (idx >= urls.length) return;
|
||
fetch(urls[idx], { cache: 'no-store' })
|
||
.then(function (r) { if (!r.ok) throw new Error('HTTP ' + r.status); return r.json(); })
|
||
.then(function (doc) { var list = buildVersionList(doc); if (list.length) render(list); else attempt(idx + 1); })
|
||
.catch(function () { attempt(idx + 1); });
|
||
};
|
||
attempt(0);
|
||
}
|
||
bindVersionMenu();
|
||
function done(btn) {
|
||
var old = btn.textContent; btn.textContent = '已复制'; btn.classList.add('is-done');
|
||
setTimeout(function () { btn.textContent = old; btn.classList.remove('is-done'); }, 1200);
|
||
}
|
||
document.addEventListener('click', function (e) {
|
||
var btn = e.target && e.target.closest ? e.target.closest('.m-copy') : null;
|
||
if (!btn) return;
|
||
var src = document.getElementById(btn.getAttribute('data-copy') || '');
|
||
var text = src ? (src.innerText || src.textContent || '') : '';
|
||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||
navigator.clipboard.writeText(text).then(function () { done(btn); }, function () { fallback(btn, text); });
|
||
} else { fallback(btn, text); }
|
||
});
|
||
function fallback(btn, text) {
|
||
try {
|
||
var ta = document.createElement('textarea'); ta.value = text; ta.setAttribute('readonly', '');
|
||
ta.style.position = 'fixed'; ta.style.opacity = '0'; document.body.appendChild(ta);
|
||
ta.select(); document.execCommand("copy"); document.body.removeChild(ta); done(btn);
|
||
} catch (err) { btn.textContent = '复制失败'; }
|
||
}
|
||
})();
|
||
</script>
|
||
</body>
|
||
</html>
|