跳转至

迁移 report 到 uv 管理依赖并修复 MkDocs footer 黑栏对齐

日期: 2026-05-17 目标: 把 report 仓库从无依赖声明的 pip 安装方式迁移到 uv 管理(锁版本、本地/CI 一致),同时修复 Material 主题底部 Built/Commit 信息独立于黑栏的视觉问题。


背景

report 仓库是基于 mkdocs-material 的纯文档站,用 GitHub Actions 自动部署到 Cloudflare Pages(参考早前的 archive-202605011405-report-cloudflare-pages.md)。本次有两个并行问题:

  1. 前端缺陷:用户曾在 overrides/main.html 自行追加了一段 <div> 用于显示 Built: {time} | Commit: {sha},但它落在 Material 默认黑色 md-footer-meta 栏的下方(作为独立的灰色条带),视觉上让黑栏变得很孤立。
  2. 环境缺陷:仓库根目录没有 requirements.txt / pyproject.toml。CI 用一行硬编码 pip install mkdocs-material 安装;新 clone 的人不知道要装什么。本机环境是 anaconda3 Python 3.13,用户希望换 uv 管理,但要求:
  3. 不污染全机器(项目隔离 venv)
  4. 锁定版本(本地与 CI 完全一致)
  5. 指令跨系统通用(Windows / macOS / Linux)
  6. 本机已装 uv(0.10.11),不要重复安装

操作步骤

1. 初始勘察

读取关键文件:

  • mkdocs.yml:确认 custom_dir: overridestheme.name: materialextra.generator: false
  • overrides/main.html:原始 footer override 是 {{ super() }} 后追加一个 <div> —— 这就是它落在黑栏外的根因
  • .github/workflows/deploy.yml:CI 用 actions/setup-python@v5 (3.11) + pip install mkdocs-material,并通过 python3 << 'PYEOF' 注入 build_time / git_shamkdocs.ymlextra:
  • 没有 requirements.txtpyproject.tomluv.lockPipfile

overrides/main.html 从"append div"改为"用 JS 把 div 注入到 .md-footer-meta__inner 内部":

<script>
(function () {
  var inner = document.querySelector('.md-footer-meta__inner');
  if (!inner) return;
  var info = document.createElement('div');
  info.className = 'md-footer-build-info';
  info.innerHTML = 'Built: ... | Commit: <a href="...">...</a>';
  inner.appendChild(info);
})();
</script>

配套 CSS 初版:flex: 1 1 100% + flex-wrap: wrap。这一版让 build-info 进了黑栏,但占满 100% 宽度 → 它独占新一行,跟 GitHub 图标不在同一行。

3. UV 环境搭建

第一次跑 uv init --bare --python 3.11 --vcs none 在 PowerShell 下报 Exit code 1,但没有 stderr 输出。换 bash 重跑:

$ uv init --bare --python 3.11 --vcs none
Initialized project `report`

推测是 PowerShell 流处理问题,不是 uv 本身。pyproject.toml 已成功生成(requires-python = ">=3.11",没有创建 .python-version,对跨版本兼容更友好)。

uv add mkdocs-material 拉取 29 个包并生成 uv.lock,关键依赖:mkdocs==1.6.1、mkdocs-material==9.7.6。uv run mkdocs --version 验证可用。

期间有一条无害 warning:

warning: `VIRTUAL_ENV=...\python\cpython-3.12.13-windows-x86_64-none` does not match the project environment path `.venv` and will be ignored
来自残留的环境变量,uv 已自动忽略并使用 .venv,不需处理。

4. 同步周边文件(并行编辑)

  • .gitignore:在 # Python 区段末尾追加 .venv/(注意保留 uv.lock 不被 ignore,必须提交)
  • .github/workflows/deploy.yml
  • actions/setup-python@v5astral-sh/setup-uv@v4,pin version: "0.10.11" 对齐本地
  • pip install mkdocs-materialuv sync --frozen
  • python3 << 'PYEOF'uv run python << 'PYEOF'(注入 build_time/git_sha 的脚本本体不变)
  • mkdocs builduv run mkdocs build
  • README.md:新增"🛠️ 本地开发"段落,给出 Windows (irm | iex) 和 Unix (curl | sh) 两套 uv 安装命令,统一开发流程为 uv sync + uv run mkdocs serve

把 CSS 改为:

.md-footer-build-info {
  flex: 1 1 auto;     /* 共享一行,不再 100% 撑满 */
  text-align: center;
  ...
}

JS 改用 insertBefore(info, social) 而不是 appendChild,让 build-info 落在 GitHub 图标左侧——视觉顺序变成 [空 copyright] [Built/Commit 居中] [GitHub 图标右对齐]

用户截图反馈:build-info 跟 GitHub 图标横向同行了,但纵向不齐(build-info 偏上、GitHub 图标偏下)。

直接 curl 抓 Material 的 main CSS 看实际规则:

curl -s http://127.0.0.1:8000/assets/stylesheets/main.484c7ddc.min.css \
  | grep -oE '\.md-footer-meta__inner\{[^}]*\}|\.md-social\{[^}]*\}|\.md-copyright\{[^}]*\}'

结果:

.md-footer-meta__inner { display:flex; flex-wrap:wrap; justify-content:space-between; padding:.2rem }
.md-copyright { color:var(--md-footer-fg-color--lighter); font-size:.64rem; margin:auto .6rem; padding:.4rem 0; width:100% }
.md-copyright { width:auto }
.md-social { display:inline-flex; gap:.2rem; margin:0 .4rem; padding:.2rem 0 .6rem }
.md-social { padding:.6rem 0 }

定位根因: - .md-footer-meta__inner 没有 align-items,默认是 stretch —— 子元素会被拉伸到容器高度 - .md-copyright 通过 margin: auto .6rem 用 auto-margin 做垂直居中 - .md-socialpadding: .6rem 0 把图标推到自己 box 的中心 - 我的 .md-footer-build-info 没用这两个技巧,所以被 stretch 后内容靠顶

修法:直接套用 .md-copyright 的 DNA(margin: auto .6rem + padding: .4rem 0 + 同样的 font-size: .64rem)。最终 CSS:

.md-footer-build-info {
  flex: 1 1 auto;
  text-align: center;
  margin: auto 0.6rem;
  padding: 0.4rem 0;
  font-size: 0.64rem;
  color: var(--md-footer-fg-color--lighter);
}
.md-footer-build-info a {
  color: inherit;
  text-decoration: underline;
}

用户重启 serve 后确认成功。

7. 验证 & 提交

uv run mkdocs build --clean 通过(1.79s),证明 uv 管道闭环。

chore: 迁移到 uv 并修复 footer 对齐

提交 SHA 596df89,6 个文件 (+622 / −10),包含新增的 pyproject.tomluv.lock


遇到的问题与解决

问题 1:PowerShell 下 uv init 静默失败

现象: uv init --bare --python 3.11 --vcs none 在 PowerShell 返回 Exit code 1 但无 stderr 输出。

原因: 推测是 PowerShell 对 stderr/stdout 流的处理问题,跟 uv 本身无关。

解决: 直接在 bash 下重跑,正常输出 Initialized project 'report'。后续 uv 命令两边都能跑(uv 跨 shell 兼容),只是初次 init 在 PowerShell 下踩了一脚。

问题 2:mkdocs serve 不热重载 overrides/ 改动

现象: 修改 overrides/main.html 后等了 56 秒,curl http://127.0.0.1:8000/ 仍返回旧 CSS。文件 mtime 早于当前时间。

原因: 实测 mkdocs serve 对 theme.custom_dir 不可靠地监听(文档说应监视,实际未触发)。

解决: 必须手动 Ctrl+C 重启 serve。新增依赖、改 mkdocs.yml 时一般会重载,但改 overrides/ 模板不会。

现象: JS 注入应该把元素放进 .md-footer-meta__inner,但用户截图显示 Built/Commit 像是在黑栏之外(黑文本/浅背景)。

原因(最终判定): 用户当时的浏览器没刷新到最新版(mkdocs serve 没重载,curl 也证实服务端仍是旧版)。一旦 serve 重启,元素就在黑栏内了。

教训: 在调试 overrides 时,永远先确认 curl <url> | grep <my-class> 拿到的是最新版本再判断 CSS 问题。

问题 4:横向同行后纵向偏移

现象: build-info 居中显示在黑栏顶部,GitHub 图标在黑栏底部,纵向不齐。

原因: Material 的 .md-footer-meta__inner 没有 align-items,默认 stretch;不同子元素用了不同的垂直居中技巧(copyright 用 margin: auto,social 用对称 padding),自定义元素若都不用就靠顶。

解决: 套用 .md-copyright 完整的样式 DNA(margin + padding + font-size),让自定义元素行为等价于"一个有内容的 copyright"。


知识清单

<footer class="md-footer">
  <div class="md-footer-meta md-typeset">
    <div class="md-footer-meta__inner md-grid">
      <div class="md-copyright"> ... </div>     <!-- 空时也会渲染容器 -->
      <div class="md-social"> ... </div>        <!-- 包含 social.icons -->
    </div>
  </div>
</footer>
  • .md-footer-meta 整个是黑栏(暗色背景由 theme 控制)
  • .md-footer-meta__innerdisplay: flex; flex-wrap: wrap; justify-content: space-between没有 align-items
  • 想"塞进黑栏"必须 append/insert 到 .md-footer-meta__inner 内部
  • 垂直居中:套用 .md-copyrightmargin: auto .6rem + padding: .4rem 0

uv 项目初始化模式(文档站场景)

uv init --bare --python 3.11 --vcs none   # bare = 无 README/sample,vcs none = 不动 git
uv add mkdocs-material                     # 装 + 锁定 + 同步 .venv
uv run mkdocs serve                        # 后续所有命令前缀 uv run
  • --python 3.11 只写入 requires-python = ">=3.11"生成 .python-version,本地装的 3.13 也能用
  • 强烈建议 uv.lock 入库;CI 用 uv sync --frozen 拿到 bit-exact 的依赖
  • 跨平台(Windows/macOS/Linux)uv CLI 完全一致

deploy.yml 从 pip 改 uv 的最小 diff

-      - name: Setup Python
-        uses: actions/setup-python@v5
-        with:
-          python-version: '3.11'
-      - name: Install dependencies
-        run: pip install mkdocs-material
+      - name: Setup uv
+        uses: astral-sh/setup-uv@v4
+        with:
+          version: "0.10.11"
+          enable-cache: true
+      - name: Install dependencies
+        run: uv sync --frozen
...
-          python3 << 'PYEOF'
+          uv run python << 'PYEOF'
...
-      - name: Build site
-        run: mkdocs build
+      - name: Build site
+        run: uv run mkdocs build

enable-cache: true 让 setup-uv 把下载缓存到 Actions cache。

Flexbox auto-margin 垂直居中

在 flex 容器里给子元素 margin: auto吃掉所有剩余的主轴/交叉轴空间并均分到上下/左右,等效于双向居中。比 align-items: center 更精确(针对单个子元素,不影响其他)。Material 在 footer 里就是这么实现的。

curl 调试已 serve 的 mkdocs 站

# 抓 CSS bundle 路径
curl -s http://127.0.0.1:8000/ | grep -oE 'href="[^"]*\.css"'

# 抓某个 selector 的实际规则
curl -s http://127.0.0.1:8000/<css-url> | grep -oE '\.classname\{[^}]*\}'

# 验证自定义 JS 注入是否到达浏览器
curl -s http://127.0.0.1:8000/ | grep -c 'my-marker-class'

待办 / 遗留

  • [ ] mkdocs build 输出大量 monitor-system/* 文档里的失效相对链接(指向 main.pyswellcamera/*.py 等不在文档树里的代码文件)。本次没动,预计后续单开一轮清理。
  • [ ] 文档相对链接锚点 mojibake:部分 #章节-X 锚点匹配失败(涉及 hermes-session、monitor-system),可能是 mkdocs 对中文锚点的归一化策略差异,需要单独排查。
  • [ ] setup-uv@v4 的版本是从经验选的,未来发布新主版本时需要看是否要升级;同样 uv 本身 pin 在 0.10.11,跟随本机版本,不一定永远是最新。