OK,OK,大家好,欢迎大家来到大鹏 AI 教育,我是张大鹏。
给 Django Admin 换一套图标,看起来只是视觉优化,真正落到长期维护时却会碰到四个工程问题。
图标从哪里来、模板怎样复用、主题怎样接管颜色、测试怎样保证调用点与静态资源没有悄悄漂移?
如意 Django CRM 在 v0.1.4 中采用了一个很克制的方案:从 Lucide 精选十个图标,组成项目本地 SVG sprite,再用单点模板、CSS 语义和集合测试把它们串成闭环。
这篇文章不讨论“图标库哪个好看”,只复盘这条链为什么可靠,以及它仍然不能替代哪些浏览器验收。
一、本地图标子集怎样收紧依赖与许可边界
本地化不是把上游文件复制进仓库就结束,而是把来源、规模和许可都变成项目资产。
1.1 精选子集同时控制体积、来源与许可
沿着左侧四步链观察,本地图标从上游进入 Django Admin 前经过了哪些约束?
为什么 sprite 与许可证必须作为一组资产维护?
这条治理链包含四个清晰角色。
- 🧭上游来源:Lucide 提供统一线性风格与可追溯的官方许可。
- 📦本地子集:
ruyi-lucide.svg只保存项目实际需要的十个 symbol。 - 📜许可文件:
LICENSE.lucide.txt随静态资源保留 ISC 与 MIT 文本。 - 🏛️项目调用:Django Admin 只引用本地资源,不依赖运行时外部 CDN。
我在 sprite 中核对到的十个 ID 是house、users-round、handshake、clipboard-check、settings-2、shield-check、boxes、database、plus和ellipsis。
从维护角度看,本地精选子集带来三个直接收益。
- 🪶规模可控:仓库不需要携带整套未使用图标。
- 🔒交付稳定:页面渲染不受第三方 CDN 可用性和版本漂移影响。
- 🧾来源可审:图形与许可同时提交,后续审查能回答资源从哪里来。
Lucide 的官方许可说明可查看 https://lucide.dev/license。
1.2 图标语义来自导航配置而不是模板猜测
域导航图标名称由NAVIGATION_GROUPS配置提供,首页、添加动作和更多动作等固定入口则在模板调用点显式传入名称。
这里必须说清一个边界:icon.html会消费传入的任意字符串,它没有运行时白名单。
- 🗺️配置职责:导航配置决定每个业务域应该使用哪个语义图标。
- 🧷固定职责:
house、plus、ellipsis等固定名称由具体模板调用点维护。 - 🧪测试职责:测试把这些约定收敛成预期集合,发现漏改或多余资源。
所以安全线来自调用约定与回归测试,而不是模板在运行时拒绝未知名称。
二、单点模板怎样完成完整渲染链路
图标名称只有穿过稳定的模板入口,才能真正变成页面中的 SVG。
2.1 SVG use 通过本地静态路径引用 symbol
统一模板的核心结构非常小。
{% load static %} <svg class="ruyi-icon{% if class_name %} {{ class_name }}{% endif %}" aria-hidden="true" focusable="false"> <use href="{% static 'admin/icons/ruyi-lucide.svg' %}#{{ icon }}"></use> </svg>use的工作是把静态资源 URL 与 fragment id 组合起来,引用 sprite 中对应的symbol。
- 🔗资源定位:
{% static %}负责生成本地 sprite 的静态路径。 - 🏷️片段定位:
#{{ icon }}负责选择十个 symbol 中的一个。 - ♿装饰语义:
aria-hidden与focusable=false避免纯装饰图标进入无意义的焦点和朗读流。
SVG 2 对use的标准语义可查看 https://www.w3.org/TR/SVG2/struct.html#UseElement。
2.2 侧边栏与工作台共享同一个模板入口
从左到右观察,名称经过哪些节点后才到达两个页面?
如果静态路径需要调整,为什么不必逐个修改侧边栏和工作台?
主链只有三个责任节点和两个使用出口。
- 🧠名称来源:导航配置与固定调用点提供语义名称。
- 🧩单点模板:include 统一输出 SVG 结构、基础类和可访问属性。
- 🛰️本地资源:sprite URL 与 symbol id 共同定位真实图形。
我在nav_sidebar.html和index.html中看到的都是对同一图标模板的 include,而不是重复 SVG 代码。
因此一次入口修改可以覆盖多个页面位置。
- 🔧路径统一:静态目录变化只需修改一个模板。
- 🎛️类名统一:各页面通过附加语义类调整尺寸,不复制基础规则。
- 🧹维护统一:新增可访问属性或修复引用方式时,改动不会散落到多个模板。
三、样式语义为什么要与图形资源分离
sprite 负责图形轮廓,CSS 负责页面语义。
3.1 currentColor 让图标跟随主题与交互状态
Lucide symbol 使用stroke="currentColor"。
这意味着图标颜色来自当前 CSScolor,不必在 SVG 里复制深色主题、浅色主题、悬停和选中状态。
- 🎨图形资源:路径、线宽、端点和 viewBox 保持稳定。
- 🌗主题状态:外层选择器决定默认色、悬停色与当前项颜色。
- ♻️资源复用:同一个 symbol 可以在不同背景和状态中重复使用。
图形与主题分离之后,换色不需要重做 sprite,换图也不需要重写页面状态规则。
3.2 导航、域卡片与模型卡片按语义分层定尺寸
观察第四张卡片,34×34 指的是外层还是内部 SVG?
为什么模型卡片需要更大的容器,却不需要把图形本身同步放大?
四种尺寸分别服务不同的信息层级。
- 📏基础 SVG:
.ruyi-icon默认宽高是 18×18。 - 🧱导航图标:
.ruyi-icon-nav收紧为 16×16,适配紧凑侧边栏。 - 🗂️域图标:
.ruyi-icon-domain放大为 20×20,强化业务域入口。 - 🧊模型图标:
.ruyi-model-icon是 34×34 外层容器,内部 SVG 仍保持 18×18。
我专门保留“容器包住图形”的画法,是为了避免把 CSS 盒子尺寸误写成 SVG 尺寸。
这个区分带来两个视觉判断。
- 🧘留白可控:大容器提供背景、圆角和呼吸空间,而不是粗暴拉伸线条。
- 🪞线条一致:内部图形维持同一视觉重量,不会因入口层级变化而突然变粗。
四、十个图标怎样形成可回归契约
精选子集真正可维护的关键,是调用集合与文件集合必须相等。
4.1 configured_ids、expected_ids 与 symbol_ids 必须相等
先看三个圆环,再看下方十个名称。
哪一个集合来自 XML,哪一个集合仍需要测试代码人工维护?
三个集合的来源并不相同。
- 🧮configured_ids:导航配置中的名称加上测试中人工列出的固定入口名称。
- 🎯expected_ids:测试明确写下的十个期望 ID。
- 🧬symbol_ids:XML 解析后从 sprite 实际读取的十个 symbol ID。
我核对当前测试时,确认它不是“自动扫描全部模板并提取图标名”。
这个事实决定了契约怎样维护。
- 📝人工入口要同步:新增固定模板调用名时,必须更新 configured_ids 的固定部分。
- 🪚静态资源要同步:sprite 必须包含同名 symbol,且不能遗留未使用图标。
- ✅期望集合要同步:expected_ids 让评审者明确看到项目允许的完整集合。
三者相等时,测试既能发现“页面调用了但 sprite 没有”,也能发现“sprite 留着但项目不再使用”。
4.2 新增图标必须同步调用点、sprite 与测试
新增图标时,最小变更闭环包含三个位置。
- ➕调用点:配置或模板传入新的语义名称。
- 🧰sprite:加入同名 symbol,并保持统一
viewBox="0 0 24 24"。 - 🧫测试:更新期望集合与固定入口集合,继续要求三者相等。
XML 解析同时会发现文件结构损坏,统一 viewBox 断言则避免某个图标在同样 CSS 尺寸下比例异常。
五、怎样验证图标不是请求成功但画面为空
图标链跨越模板、静态文件、HTTP 和浏览器渲染,任何一层都可能让图形消失。
5.1 响应断言只能证明已覆盖的四个引用
当前响应测试检查house、users-round、database和plus四个 sprite 引用。
- 🏠首页引用:
house证明固定首页入口使用 sprite。 - 👥导航引用:
users-round证明至少一个导航域引用进入响应。 - 🗃️模型引用:
database证明工作台模型卡片能够引用图标。 - ✚动作引用:
plus证明新增动作能够引用图标。
这组断言能发现已覆盖入口的模板链断裂,但不能被描述成十个图标的全量页面视觉验收。
5.2 XML 与许可证检查验证静态资产完整性
文件级测试不需要启动浏览器,就能先挡住四类问题。
- 📄XML 可读:sprite 必须能被标准 XML 解析器读取。
- 🔟集合完整:实际 symbol 集合必须与两组项目约定相等。
- 📐视口一致:十个 symbol 都使用
0 0 24 24。 - ⚖️许可齐全:许可文件同时包含 ISC 与 MIT 文本。
本次从backend目录运行聚焦测试,结果是 1 个图标测试通过,10 个无关测试被过滤。
命令如下。
uv run pytest common/tests/test_admin_workspace.py-kicon --no-cov5.3 浏览器验收补上网络、布局与可见性
沿着闭环检查,哪一层会发现“请求是 200,但图形仍然为空”?
为什么单元测试不能替代最后一层?
四层分别发现不同类型的断裂。
- 🪝模板引用:检查 include、静态 URL 和 fragment id 是否进入响应。
- 🧿文件完整:检查 XML、十个 symbol、统一 viewBox 与双许可。
- 📡响应内容:检查四个已覆盖引用是否真实出现在认证页面 HTML 中。
- 🔍浏览器验收:检查网络请求、
use解析、实际尺寸、颜色、焦点状态和控制台。
我把 HTTP 200 放在闭环中心,是因为静态文件请求成功仍可能遇到 symbol 名称不匹配、尺寸为零或主题颜色不可见。
最终结论需要保持两条边界。
- 🧲测试先收敛:文件与响应测试快速挡住确定性回归。
- 👁️浏览器后确认:真实页面验收负责证明图形在目标主题和交互状态中确实可见。
这次升级的价值不只是把旧图标换成 Lucide。
更重要的是,来源、许可、名称、模板、样式与测试已经形成了一条可解释的本地资产链。
当下一次增加图标时,团队不需要重新发明接入方式,只需要沿着同一条契约同步调用点、sprite、测试和浏览器验收。