THE PERSONAL JOURNAL OF JIANGANG KONG
一些思考,一些尝试。
还有尚未定稿的生活。
关于技术、语言与日常的个人记录。
一处持续生长的个人空间,连接知识、工具与生活。
共享视觉令牌采用暖纸白、墨色文字与陶土色强调;首页以衬线大字、流动地景与目录式布局组织内容,工作区以读数与阅读效率为先。导航提供主页、笔记、资产总览,以及登录后可见的雅思口语教练。默认采用纸白主题,保留深色偏好。动态支持暂停与系统减少动态设置。下方 v1 实现记录供维护参考;视觉数值以当前共享样式为准。
| Tab | 回答什么问题 | 核心内容 |
|---|---|---|
| 我和本站 | 这个站怎么做的、怎么改 | 本页:设计规约 |
| 笔记 | 我要记什么 | 左侧目录(文件树 / 大纲)→ 右侧正文(预览 / 编辑) |
| 资产总览 | 我有多少钱、怎么配的、往哪走 | 总资产 → 资产配置(环形图 + 图例)→ 持仓明细 → 年被动收入 / 总资产走势 |
| 内容 | 未登录 | 已登录 |
|---|---|---|
| 资产总览(数据) | 登录卡片 | 完整仪表盘 |
| 笔记(目录与正文) | 可读 | 可读 |
| 笔记(新建 / 编辑 / 删除 / 移动) | 拦截并提示去登录 | 可用 |
| 我和本站(本页) | 可读 | 可读 |
config/users.json 里的密码 + Google Authenticator 动态码(验证器账号在 config/totp.json)。动态码每 30 秒更新,同一码不能重复使用;验证器设备丢失时可用一次性恢复码。色卡的色值实时读取当前主题的 CSS 变量,切换深浅色会自动更新;其余数值为当前代码实测值。本页示例数字均为虚构,不代表真实资产。
--up 红(涨 / 支出 / 风险)、--down 绿(跌 / 收益)、--flat 灰(持平)。本站在涨跌上用的是红涨绿跌(A 股习惯),不要按欧美习惯改成绿涨红跌。--s1…--s4 是四类资产(加密货币 / 股票 / 现金 / 房产)的辨识色,只用于图表、圆点和进度条;--g1…--g4 是配套的辉光色,成对使用。--ink 主文字、--ink-2 次文字、--muted 弱化说明。不要再加中间灰。--border,不要用实色灰 —— 半透明在深浅两套底色上都能自然融合。#0a84ff / 浅色 #007aff),不提供切换开关,视觉语言统一优先。需要强调色的地方一律 var(--accent),不要写死。rgba(var(--glow-rgb), α) —— --glow-rgb 是 --accent 的 RGB 分量形式(rgba() 不认十六进制变量)。--font-sans 把自托管的 Inter 放最前,后面才是 system-ui / -apple-system / PingFang SC。Inter 只含拉丁字母和数字,汉字会自动落到系统字体(Mac 苹方 / Win 雅黑 / Android Roboto)—— 中文在各平台都是原生观感,也不必打包几 MB 的中文字体。--font-mono 是自托管的 JetBrains Mono,只给金额、涨跌幅、时钟和代码块用。tabular-nums,上下几行的金额才能对齐比较。这是本站最不能破的一条。line-height: normal。button 的默认值(13.33px)也算离群值 —— 自己设过的控件要显式写 font-size。styles.css 里显式覆盖了编辑器正文与 h1–h6 的字号。padding / margin / gap 只用 2 的倍数。2 / 4 / 6 / 8 / 10 / 12 / 16 / 20 / 24。出现 5px、7px、11px 这类值视为样式缺陷。| 用途 | 值 | 例子 |
|---|---|---|
| 装饰条 / 微徽章 | 2px | 标题级别徽章、引用条 |
| 小控件 · 内联元素 | 4px | 行内代码、图例行与圆点、仪表框、拖拽手柄 |
| 按钮 · 行 · 中间层 | 6px | 按钮、目录树行、菜单项、站点控件、滚动条滑块 |
| 容器 | 8px | 面板、卡片、正文区、编辑器、笔记侧栏、代码块 |
| 浮层 | 10px | 弹窗、下拉菜单、浮动工具栏 |
var(--border);选中态用 2px var(--accent)(标签页下划线、文档块选中)。不要用更粗的边框做强调。--shadow-pop)。平面内容不用阴影,靠底色与描边分层。text-shadow —— 只给这一处,因为它全站只有一个。0.2s ease-out(快起缓停,接近 macOS 手感)。只做颜色、描边、阴影、透明度、位移,不做缩放弹跳。prefers-reduced-motion 下全部关掉(含扫描线与脉冲)。| 层 | 值 | 内容 |
|---|---|---|
| 背景 | 0 | 点阵与辉光背景层 |
| 内容 | 1 | 页面主体 |
| 悬浮信息 | 10 | 图表 tooltip |
| 页内浮层 | 40 | 新建菜单 |
| 模态 | 100 | 弹窗遮罩与弹窗 |
| 编辑器浮层 | 200 | 斜杠菜单、类型菜单、选中工具栏 |
新增浮层请套用上表已有的层,不要随手写 999。
.console 不设 max-width,内边距 24px。三个 tab 用同一套宽度 —— 所以三个 .tab-panel 必须都放在 .console 里面(2026-10-11 修过一处:「我和本站」的面板曾被落在 .console 外面,于是它既通栏贴边、左边界为 0,又比另两个 tab 多出 40px 顶距 —— 就是 .console 自己的 padding-bottom)。窄屏(≤600px)内边距整体收到 12px(上下左右都是)—— 桌面那 24px 是版心留白,手机上等于白扔掉近 1/5 的宽度(实测:390px 的屏上,词条英文原先要从屏幕左缘 36px 才开始、按钮离右缘 40px);桌面仍是 24px。标签行到内容的距离也由 18px 收到 8px(三个 tab 一致;量出来 tab 行底边到内容是 9px,多出的 1px 是 .topbar 的分隔线本身)。笔记正文的留白收在一个变量里:--notes-pad(桌面 12px 16px、窄屏 12px 12px)。阅读区 .notes-article、飞书文档编辑器 #doc-editor、Vditor 的正文容器三处必须共用它 —— 只改一处,只读↔编辑切换时正文就会横向跳动、宽度缩水(原来靠三处各写一遍 14px 18px 加注释维系,2026-10-11 收成一个变量,顺手把越档的 14/18 换成档内的 12/16)。窄屏为什么要再收到 12px:同一目录里 html 笔记的正文离屏幕左缘 24px、fdoc 却是 31px,来回看就显出来了,收到 12px 后两者对齐。14.45%、最小 145px、宽屏上限 272px;正文吃掉剩下的全部。calc(100vh - 128px),移动端 calc(100dvh - 118px)(dvh 才能躲开手机地址栏)。改头部高度必须同步改这两个数。| 断点 | 改什么 | 为什么 |
|---|---|---|
| ≤ 1199px | 年被动收入 / 总资产走势 由并排改上下 | 走势图等比缩放,再窄坐标轴文字就不可读 |
| ≤ 1049px | 持仓卡片由三列一行改单列 | 三列时每张放不下四列表格,会横向溢出 |
| ≤ 780px | 隐藏头部时钟 | 给标签页与控件让位 |
| ≤ 600px | 笔记页改上下堆叠;目录树 / 大纲高度上限 24vh;目录可整块收起;文档编辑器隐藏块手柄 | 触屏没有 hover,且高度是最稀缺资源 |
| ≥ 601px | 笔记侧栏宽度上限 272px | 避免超宽屏下侧栏吃掉正文 |
.panel:--panel 底 + 1px 描边 + 8px 圆角 + 20px 22px 内边距,块间距 14px。仪表类内容都装在面板里。10px 8px 8px 内边距,与右侧正文卡片同规格,靠 16px 间距分隔(不画分隔线)。文件 · N 项,切大纲时变 大纲 · N 项,带分隔线)→ 列表。三者共用同一套左右内缩,所以选中色块左右对称。--panel-2 的悬停态一眼分开。--guide-x 对齐父级箭头)—— 只靠缩进很难看出从属关系。stroke-width: 1.3、stroke="currentColor"、16 视框,定义在 app.js 的 ICON_*。不要用 emoji 当图标 —— emoji 的字形与粗细由系统决定,跨平台会走样。区分格式靠颜色(飞书文档用品牌蓝 #3370ff),不靠换形状。--material + backdrop-filter;不支持毛玻璃的浏览器自动退回不透明底色(已写 @supports 兜底)。.tab:文字按钮,激活时底边 2px 强调色。新增 tab 只要加 data-tab="名字" 与对应 #tab-名字 面板,切换逻辑自动接管。scrollbar-width: thin + scrollbar-color(轨道透明、滑块半透明),宽度交给各引擎自己的 thin 档,不要再写死像素宽或在局部另写一套。| 状态 | 表现 | 本站实例 |
|---|---|---|
| 默认 | — | — |
| 悬停 | 底色 --panel-2,或描边转强调色 | 目录行、图例行、次按钮 |
| 选中 / 当前 | 列表用强调色实心药丸 + 白字;按钮用 2px 强调色描边 | 当前笔记、正在编辑的按钮 |
| 键盘聚焦 | 3px 强调色光晕(--focus-ring),全站统一 | 所有可交互控件 |
| 拖拽中 | 被拖项半透明跟随光标;悬停到折叠文件夹时自动展开 | 目录树拖拽、文档块排序 |
| 空 | 居中说明 + 下一步怎么做,不是一行灰字 | 「还没有笔记 / 点左上角 + 新建,或用 ⬆ 上传 .md」 |
| 加载 / 失败 | 加载给文字提示不白屏;失败弹窗说清原因(含 HTTP 状态) | 「编辑器加载中…」;保存失败弹窗 |
--focus-ring 光晕,新组件要按 Tab 键确认一遍。--muted 只用于非关键信息。+/− 号;文件格式除了颜色还有图标与文字。title + aria-label,纯装饰图标加 aria-hidden。Tab 进树、↑↓ 移动、←→ 收起/展开(→ 在已展开的文件夹上进入首个子项)、Enter 打开、Home / End 跳首尾。树的 DOM 重建后要恢复焦点(见 renderTree() 里的 hadFocus)。prefers-reduced-motion:动画在系统「减少动态」时全部关闭。¥128,064.50(千分位 + 两位小数,走 Intl.NumberFormat("zh-CN"));美元同理;汇率类加 ≈ 前缀。¥128,064.50),截图同理。视觉语言以 macOS 人机界面指南为参照,但不是照搬:凡是会伤害信息密度或可访问性的,一律不跟。
| 项 | macOS 做法 | 本站 |
|---|---|---|
| 布局栅格 | 4pt / 8pt 栅格 | 跟,取一半:2pt 栅格(信息密度优先) |
| 字体 | SF Pro / SF Mono,正文 13pt | 跟观感:Inter + JetBrains Mono 自托管;正文 15px(比 macOS 大一档,读数字更舒服) |
| 中文字体 | 苹方 | 跟随系统:打包字体不含汉字,Mac 上就是苹方 |
| 主题默认 | 跟随系统外观 | 不跟:本站固定默认浅色(用户切过之后记住选择) |
| 键盘焦点环 | 所有控件都有 | 跟:统一 --focus-ring 光晕 |
| 浮层阴影 | 细描边 + 柔投影 | 跟:--shadow-pop 两段式 |
| 材质 / 毛玻璃 | 侧栏与菜单半透明模糊 | 跟:--material + backdrop-filter |
| 选中样式 | 侧栏选中是强调色实心圆角块 | 跟:列表选中用实心药丸 + 白字 |
| 滚动条 | 浮层细滚动条 | 跟:全站统一透明轨道 + 内缩滑块 |
| 圆角 | 窗口约 10px,连续曲率(squircle) | 跟尺寸(4–10px 分档);曲率不跟 —— CSS border-radius 只能画圆弧 |
| 强调色 | 跟随系统 controlAccentColor | 跟:固定系统蓝,不提供切换(统一优先) |
| 图标 | SF Symbols | 跟规范(等粗笔画 + currentColor + 16 视框),但不是 SF Symbols 的图形 |
| 鼠标指针 | 按钮上仍是箭头 | 不跟:网页里按钮用手型,用户有预期 |
| 涨跌色 | 语义色不分涨跌 | 不跟:红涨绿跌是 A 股习惯 |
| 信息密度 | 桌面控件偏小巧 | 不跟:行高按手指点选设计,触屏也能用 |
<head> 内联脚本里先定好(见 index.html / login.html),否则会先渲染默认色再跳成用户选的色、闪一下。onTap()),不要用 click —— SortableJS 拖拽结束后会用捕获阶段的监听吞掉紧接着的第一次 click,表现为「拖完点不动」。forceFallback: true(原生 DnD 在嵌套列表里不工作)+ fallbackOnBody,触屏再加 delay: 150 防误触。放置目标用 document.elementFromPoint() 判定,不要只信 evt.to。vditor.setTheme(编辑器主题, 内容主题, 高亮主题, 内容主题路径)。Vditor 没有 setContentTheme 这个方法 —— 只传第一个参数会让正文颜色留在旧主题上(深色模式变成深字配深底,正文直接看不见)。keydown 处理前先判 isComposing,合成期间不切块、不重渲染。| 格式 | 文件 | 内容 |
|---|---|---|
| 一篇笔记 | <名字>/<名字>.md | Markdown 正文,用 Vditor 编辑、marked.js 预览 |
| 一篇飞书文档 | <名字>/<名字>.fdoc | JSON:{ version, title, blocks[] },块编辑器自研(www/doc-editor.js) |
| 一篇网页笔记 | <名字>/<名字>.html | 整份自包含 html(自带 CSS/JS),在沙箱 iframe 里只读渲染,可做交互;站内不提供源码编辑 |
| 该笔记的资源 | <名字>/image.png | 与正文同一个目录,正文只存 /media/<笔记路径>/<文件名>。目录里只有这篇笔记的东西 |
| (网页笔记)读音文件 | <名字>/audio/<单词>.mp3 | 子目录不会出现在目录树里(笔记目录不再往下展开),由生成脚本下载。为什么不用浏览器自带的语音合成:它读的是各设备自己装的语音包(iPhone 是 Apple 语音、Windows 是微软语音、安卓是 Google 语音、Linux 常常一个都没有),同一个词四端四个声音。放成固定文件,四端听到的才一样、离线也能用;万一取不到文件,页面会退回语音合成兜底 |
块类型:text 正文 · h1/h2/h3 标题 · bulleted 无序 · numbered 有序 · todo 待办(带 checked)· quote 引用 · callout 高亮块(带 icon)· code 代码块(纯文本存 text)· divider 分割线 · image 图片(存 url + alt)· table 表格(存 rows 二维数组 + header)。行内格式(加粗 / 斜体 / 删除线 / 行内代码 / 链接)存为白名单 HTML。
data/notes/周报/W4-Sep-2026/ 下是 W4-Sep-2026.md + image.png。同名文件自动加序号(截图.png → 截图-1.png)。<目录名>.md / .fdoc / .html)→ 显示为一篇笔记;否则是分组文件夹。所以不需要额外的「资源目录隐藏」规则。/media/<旧路径>/ 前缀。/note/<笔记路径>(如 /note/经历领悟/判断)。站内点链接不刷新页面、浏览器前进后退可用、链接可直接发给别人。后端把 /note/* 交给前端按路径打开。注意:页面里引用静态资源必须写绝对路径(/app.js 而不是 app.js),否则深层 URL 下相对路径会解析错。[[笔记路径]]:markdown 与飞书文档都支持(飞书文档在渲染时统一转换,所以已有内容里的 [[…]] 也会自动变成链接);带别名写 [[路径|显示文字]]。渲染成 <a href="/note/路径">,点击在站内打开目标笔记。编辑态里点链接不跳转(那是在改链接文字)。[[路径#小节]]「#」后面是那篇笔记里的标题文字(不是编号),打开后自动滚到那一节。URL 形如 /note/<路径>#<小节>,可直接分享/收藏;找不到同名标题就停在文档顶部,不报错。别省显示文字——不带 |别名 时,带小节的链接显示的就是小节名(如 [[个人知识库/哲学#延异]] 显示「延异」),这正是概念表里想要的效果。阅读态标题会被写上 id(slug = sec- + 百分号编码的标题文字),fdoc 与 markdown 两条渲染路径共用同一套规则;同一篇里标题文字重复时只有第一个能被锚点命中。<button class="say"></button>,预览态会自动补上喇叭图标(图标由 app.js 的 initSpeech() 注入,源文件保持干净)。点一下用浏览器自带的语音合成朗读,再点一下停。读什么:优先 data-say="…";没有就取所在块的文字,并自动跳过 .zh(中文译文)和按钮本身 —— 所以一句英文只写一遍,译文包在 <span class="zh">…</span> 里即可。音色优先挑英音(en-GB),语速固定 1 倍速(不提供调速,听的就是考试语速)。实例见 IELTS/listening。只在预览态生效,编辑态看到的是源码(Vditor 保存往返不会弄丢这段 HTML)。html):正文是一整份自包含 html(自带 CSS/JS,只用浏览器原生能力、无外部依赖),在阅读区用一个 sandbox="allow-scripts" 的 iframe 渲染 —— 不给 allow-same-origin,所以它碰不到站点页面。站内只读(不提供源码编辑,改内容就改文件),因此也没有站内大纲。iframe 与站点只通过 postMessage 来往:{type:"wordlist-mark", word, value} 上行(只报「哪一个词改成了什么」,不报整张表),由 /api/notes/marks 把正文里的状态块 <script type="application/json" id="marks">…</script> 就地改掉;{type:"note-theme", value} 与 {type:"note-auth", authed} 下行(换主题只通知、不重建 iframe,免得丢掉滚动位置和正在放的声音;登录状态另见下条)。为什么是「只改那一块」而不是整篇保存:浏览器手里的正文副本可能是旧的(笔记更新后那个标签页一直没关),整篇写回会把笔记回退到旧版本 —— 点一下「熟悉」就把整篇笔记还原,实测踩过这个坑。独立打开这个文件也能用(此时标记存在 localStorage)。未登录的访客只给朗读、不给标记按钮(登录状态由父页面写进 <html data-canmark>,首屏就正确,不会先闪出按钮;中途登录/注销用 {type:"note-auth"} 补正)—— 访客仍能按分类浏览笔记里已有的标记。实例:IELTS/奶酪单词(2232 词的词表,由 tools/词表生成/ 的脚本从 PDF 生成,读音由 fetch_audio.py 预下到同目录的 audio/)。根域 | 一级范畴 | 类别 | 概念 | 概念数」,一个类别一行,概念用 [[个人知识库/<一级范畴>#<概念名>]] 平铺罗列。类别按研究领域分(以哲学为例:形而上学与本体论、认识论与逻辑、语言与心智、伦理学、政治哲学、美学、科学哲学与宗教),再按「学派与立场 / 人物 / 著作」组织——不要用「概念簇 A / B / C」这种无意义的编号。同一类别下若还有子维度(如人物的时期),不另起一行,在「概念」格里用 <b>标签(条数)</b> 加分号换行 <br /> 分段,例:人物 一行里分「古希腊与希腊化 / 中世纪至近代早期 / 近代 / 现代与当代」四段。总表只是索引:概念的解释(释义)写在「个人知识库/<一级范畴>」那篇 fdoc 里,一条概念一节,小节标题就是概念名(所以总表的链接能靠锚点落到那一节),每节固定两块:先说一句大白话,再放原释义。实例:个人知识库/PKO 是总表,个人知识库/哲学 是哲学这一范畴的释义。根域、一级范畴这类逐行重复的列只在第一个数据行写值、其余留空,表格块加 "merge": true 后阅读态会自动把空格并进上一格(rowspan);编辑态不合并,模型里始终是完整矩形 —— 否则 syncBlock 靠 data-r/data-c 逐格读回时会串行。;飞书文档里粘贴会插入一个「图片块」(斜杠菜单或块类型菜单里选「图片」也可以),存的是 {type:"image", url, alt}。图片地址只允许 /media/… 与 http(s),data: 内嵌会被丢弃。绝不要用 base64 内嵌图片 —— 一张 800KB 的图内嵌后正文变 1.1MB,实测让「进入编辑」从 150ms 涨到 3.5 秒。周报.md 与 周报.fdoc 会撞路径)。_safe() 把 / \\ . : 等字符替换成下划线,目录分隔只认 /,写文件一律限制在 data/notes/ 内。blocks 数组),不合法返回 400,不会写坏文件。body[data-tab="notes"] → .console → #tab-notes → .notes-layout 串成一条纵向 flex 链(styles.css 里 .notes-layout 下方那段),高度由 flex 分配。早期这里写的是 calc(100vh - 128px)(窄屏 - 118px):顶部实际只占约 80px,于是底部白白空出 48px(手机 38px),看着铺不满,换机型/转屏就更不准。三个别踩的坑:① .console 必须显式 width: 100% —— 它有 margin: 0 auto,成为 flex 项目后自动外边距会吃掉两侧空间、把宽度收缩成内容宽度;② body 要 min-height: 0 —— 基础样式里的 min-height: 100vh 在 iOS 上是「地址栏收起时」的高度,会把 100dvh 顶大、正文反探出屏幕;③ 只挂 body[data-tab="notes"] 下面,资产总览 / 我和本站两个 tab 的整页滚动不能受影响。#del-modal,走 /api/notes/delete):删除不可撤销,正文与该笔记自己的图片会一起删掉,所以除登录外再输一次密码和当前动态码(与改资产、改预期同一套路)。密码或动态码错就留在框里提示,不会关掉让你重来。登录与操作分别记录已用过的步数,同一个码不能在操作里重复使用;删除接口的试错与登录共用同一套节流。| 方法 | 路径 | 可见性 | 说明 |
|---|---|---|---|
| GET | /api/session | 公开 | 当前是否已登录、会话绑定的用户名 + 已登录设备列表 |
| POST | /api/login · /api/logout | 公开 | 密码 + 验证器动态码登录(设 7 天 HttpOnly Cookie)与退出 |
| GET | /api/notes/tree | 公开 | 目录树,每个笔记带 kind(md / doc / html) |
| GET | /api/notes/content?path=&kind= | 公开 | 读单篇笔记内容 |
| POST | /api/notes/save / mkdir / move | 需登录 | 写、建目录、移动,请求体都带 kind |
| POST | /api/notes/delete | 需登录 + 密码 + 验证器动态码 | 删除不可撤销(正文连同该笔记的图片一起删),所以除登录外还要再输一次密码和当前动态码(password、code 字段),试错与登录共用同一套节流 |
| POST | /api/notes/marks | 需登录 | 网页笔记的「难点 / 熟悉」标记:{path, word, value}(value 0 取消 / 1 难点 / 2 熟悉),只改正文里的状态块,其余字节不动 |
| POST | /api/notes/asset | 需登录 | 上传图片(base64 JSON),存进该笔记的资源目录,返回 /media/… 链接 |
| GET | /media/<笔记路径>/<文件名> | 公开 | 读笔记资源(图片 + 音频),文件名不可变可长期缓存。MIME 表是 notes.SERVE_EXT:比 IMAGE_EXT 多 .mp3/.wav —— 后者管的是「上传只允许图片」,两者别混用 |
| GET | /note/<笔记路径> | 公开 | 笔记的独立 URL:返回前端,由前端按路径打开对应笔记(可分享) |
| GET | /api/portfolio | 需登录 | 汇总数据(总资产、配置、年被动收入);带 ?force 跳过缓存 |
| GET | /api/assets · /api/history · /api/projection | 需登录 | 手工维护的资产、月度快照、未来预期 |
| POST | /api/assets / update / delete · /api/projection | 需登录 + 密码 + 验证器动态码 | 增删改资产与未来预期 |
页面与静态资源全部公开;笔记只读公开、写需登录;资产类接口全部需登录。登录只用 config/users.json 的密码 + Google Authenticator 动态码;资产编辑与删除笔记在登录之外还要再输一次密码和当前动态码(防止设备被临时借用时误改)。会话存 data/auth/sessions.json,服务重启不失效。
MY_PROFILE/ ├─ server.py # 路由 + 鉴权 + 静态文件(systemd 跑在 80 端口) ├─ backend/ # auth / assets / history / projection / portfolio / notes ├─ www/ # 前端(无构建,直接跑) │ ├─ index.html # 三个 tab:我和本站 / 笔记 / 资产总览 │ ├─ login.html # 登录页(独立页面,自带主题预置脚本) │ ├─ styles.css # 全部样式与设计变量 │ ├─ fonts.css # 自托管字体 @font-face │ ├─ app.js # 全部前端逻辑 │ ├─ doc-editor.js # 飞书文档块编辑器 │ └─ vendor/ # vditor / marked / sortable / fonts(均自托管) ├─ data/ # 真实数据:assets.json、history.json、projection.json、notes/ └─ tests/ # test_notes.py(后端单测)、serve_test.py(:8081 隔离测试服务)
交付前自检 —— 改完样式至少过一遍:
title / aria-label。