Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4c61b9f1ec | ||
|
|
0daa912ee1 |
No files matched your search
+182
-49
@@ -1,10 +1,55 @@
|
|||||||
# ClockLab · 开发文档
|
# ClockLab · 开发文档
|
||||||
|
|
||||||
面向**要改这个项目**的人。使用者请看 [README.md](./README.md)。
|
面向**要改这个项目**的人(包括接手的新会话)。使用者请看 [README.md](./README.md)。
|
||||||
|
|
||||||
|
> **接手先读这三处**:第〇节(五分钟了解现状)→ 第六节(**两个分支与 Windows 7 限制,最容易踩**)→ 第八节(踩坑记录,能省几个小时)。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 一、项目结构
|
## 〇、接手先读
|
||||||
|
|
||||||
|
### 这是什么
|
||||||
|
|
||||||
|
一个用于**演练/演示的桌面时钟**:一个**可调速的时钟**(走快、走慢、暂停、**倒着走**、一键快进)+ 一个**无边框的满屏时钟窗口**。Electron 应用,两个窗口(控制窗口 / 时钟窗口),**没有服务端、没有数据库**。
|
||||||
|
|
||||||
|
### 当前状态(截至最后一次提交)
|
||||||
|
|
||||||
|
| 项 | 状态 |
|
||||||
|
| --- | --- |
|
||||||
|
| 功能 | **全部完成**:两窗口、各自置顶、数字/表盘两种样式、实时设定时间、倍率(正负 0 小数)、暂停/继续、快进五档、跳转动画 |
|
||||||
|
| 测试 | **152 条全过**(6 个文件,见第五节) |
|
||||||
|
| 平台 | Windows x64(含**Windows 7**,见第六节)、Linux ARM64 |
|
||||||
|
| 产物 | 见下面「已有的包」 |
|
||||||
|
| 未做 | Windows 的 portable 单文件 exe(卡在第八节 ①);表盘刻度数字 |
|
||||||
|
|
||||||
|
### 已有的包
|
||||||
|
|
||||||
|
```
|
||||||
|
dist/ClockLab-1.0.0-win7-x64.zip Windows 7/10/11 x64(Electron 22)★ 会议室那台用这个
|
||||||
|
dist/ClockLab-1.0.0-win-x64.zip Windows 10/11 x64(Electron 38,主线)
|
||||||
|
dist/ClockLab-1.0.0-linux-arm64.tar.gz Linux ARM64(飞腾 D2000 等)
|
||||||
|
```
|
||||||
|
|
||||||
|
`dist/` 与 `node_modules/` 都在 `.gitignore` 里,**不进仓库** —— 包按需重新打(第七节)。
|
||||||
|
|
||||||
|
### 第一件事:跑测试
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm install --ignore-scripts # ⚠️ 必须 --ignore-scripts,原因见第八节 ④
|
||||||
|
npm test # 期望:152 条全过、退出码 0
|
||||||
|
```
|
||||||
|
|
||||||
|
**`--ignore-scripts` 不是可选项**:本机网络下 electron 的 postinstall 会失败,进而把整个 `install` 拖垮。跳过脚本不影响跑测试(测试是纯 Node)。
|
||||||
|
|
||||||
|
### 目录一览
|
||||||
|
|
||||||
|
```bash
|
||||||
|
main.js preload.js renderer/ src/ test/ build/ README.md DEVELOP.md
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一、项目结构与分层
|
||||||
|
|
||||||
```
|
```
|
||||||
main.js 主进程:建两个窗口、持有走时模型、注册全部 IPC、夹窗口尺寸
|
main.js 主进程:建两个窗口、持有走时模型、注册全部 IPC、夹窗口尺寸
|
||||||
@@ -19,10 +64,11 @@ src/
|
|||||||
selftest.js GUI 自检(electron . --selftest),跑完出截图,只在开发机上用
|
selftest.js GUI 自检(electron . --selftest),跑完出截图,只在开发机上用
|
||||||
test/ 6 个文件、152 条用例(见第五节)
|
test/ 6 个文件、152 条用例(见第五节)
|
||||||
build/icon.ico, icon.png 程序图标
|
build/icon.ico, icon.png 程序图标
|
||||||
.npmrc、pnpm-workspace.yaml 镜像与 pnpm 11 的依赖构建放行(见第六节,**别删**)
|
.npmrc 依赖镜像(electron 运行时下载也走它)
|
||||||
|
pnpm-workspace.yaml pnpm 11 的依赖构建放行表(见第八节 ④,**别删**)
|
||||||
```
|
```
|
||||||
|
|
||||||
**分层原则**:凡是能用纯函数表达的(走时、改大小几何、呈现时间)都**单独成模块**,页面里只做接线。这样 152 条用例里有 103 条不需要浏览器环境就能跑。
|
**分层原则**:凡是能用纯函数表达的(走时、改大小几何、呈现时间)都**单独成模块**,页面里只做接线。所以 152 条用例里有 **103 条不需要浏览器环境**就能跑(改这三块时不用启动 Electron)。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -87,13 +133,14 @@ const animate = layer.snapshot !== null && snapshot.rate !== 0
|
|||||||
## 三、环境与命令
|
## 三、环境与命令
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm install # 依赖
|
pnpm install --ignore-scripts # 依赖(见第〇节,必须带这个参数)
|
||||||
npm start # 起应用
|
npm start # 起应用
|
||||||
npm test # 152 条用例(纯逻辑 + DOM 桩)
|
npm test # 152 条用例(纯逻辑 + DOM 桩)
|
||||||
npm run selftest # GUI 自检:electron . --selftest,跑完出截图
|
npm run selftest # GUI 自检:electron . --selftest,跑完出截图
|
||||||
```
|
```
|
||||||
|
|
||||||
⚠️ **本机(Linux 无显示环境)跑 `npm start` 要 Xvfb**,而且 GPU 会刷 MESA 报错 —— 那是正常的。
|
- **本机(Linux)跑 `npm start` 要有显示环境**:无头时用 `Xvfb :99` + `--no-sandbox`,且 GPU 会刷一堆 MESA 报错 —— **那是正常的**(没有显卡),只要主进程不抛 JS 异常就算过。
|
||||||
|
- **纯逻辑测试不依赖显示环境**,随时能跑。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -101,7 +148,10 @@ npm run selftest # GUI 自检:electron . --selftest,跑完出截图
|
|||||||
|
|
||||||
- **纯逻辑尽量往模块里放**(`src/clock-model.js`、`renderer/resize.js`、`renderer/presented-time.js`),页面只接线 —— 这样测试不用浏览器。
|
- **纯逻辑尽量往模块里放**(`src/clock-model.js`、`renderer/resize.js`、`renderer/presented-time.js`),页面只接线 —— 这样测试不用浏览器。
|
||||||
- **改行为就同时改用例**,但**改断言之前先想清楚**:现有 152 条里有不少是钉边界的(例如"拖西边超过最小宽度时右边界不动"),它们是有意为之。
|
- **改行为就同时改用例**,但**改断言之前先想清楚**:现有 152 条里有不少是钉边界的(例如"拖西边超过最小宽度时右边界不动"),它们是有意为之。
|
||||||
- **数字输入相关的坑**:HTML 里 `type="number"` 的 `v-model`/取值在不同框架下会给 `number` 而不是字符串,本项目一律显式 `String(x)` 再用;**另一个坑是补零** —— `String(0)` 是 `"0"`,而时钟输入框按 `HH:MM:SS` 解析,所以必须 `pad2`(这个 bug 只在**凌晨 9 点前**才露出来,白天怎么测都是绿的)。
|
- **数字输入的两个坑**:
|
||||||
|
1. HTML/框架里 `type="number"` 的值可能是 `number` 而不是字符串 —— 本项目一律显式 `String(x)` 再用;
|
||||||
|
2. **补零**:`String(0)` 是 `"0"`,而时钟输入框按 `HH:MM:SS` 解析,所以必须 `pad2`。
|
||||||
|
⚠️ 这个 bug 只在**上午 9 点前**才露出来(`getHours()` 返回个位数),白天怎么测都是绿的 —— **凡是跟"当前时间"耦合的逻辑,测试里都要当心**(我们就在这里翻过一次车)。
|
||||||
- 注释写**为什么**,不写"这里做了什么"。
|
- 注释写**为什么**,不写"这里做了什么"。
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -117,89 +167,172 @@ npm run selftest # GUI 自检:electron . --selftest,跑完出截图
|
|||||||
| `test/renderer-clock-anim.dom-test.js` | 26 | 跳转动画全链路:参数下发 → 呈现时间 → 数字 + 指针 |
|
| `test/renderer-clock-anim.dom-test.js` | 26 | 跳转动画全链路:参数下发 → 呈现时间 → 数字 + 指针 |
|
||||||
| `test/renderer-control.dom-test.js` | 29 | 控制窗口:各按钮发出的调用、输入夹紧、置顶开关 |
|
| `test/renderer-control.dom-test.js` | 29 | 控制窗口:各按钮发出的调用、输入夹紧、置顶开关 |
|
||||||
|
|
||||||
|
DOM 桩的做法是:把渲染进程的 JS 读进 `vm` 跑,喂一套最小 DOM 实现,断言它发出的 `clockApi` 调用。**不必启动 Electron**。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 六、构建与打包
|
## 六、分支策略与 Windows 7 ★
|
||||||
|
|
||||||
### 6.1 Windows(在 Windows 构建机上)
|
### 两个分支,只差 Electron 版本
|
||||||
|
|
||||||
**推荐在真 Windows 上打**,不要在这台 Linux 上跨平台打。踩过的坑见 6.3,能跑的流程是这样:
|
| 分支 | Electron | 给谁 | 备注 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| **`main`** | **38.2.2** | Windows 10 / 11、Linux ARM64 | 主线,功能开发在这里做 |
|
||||||
|
| **`win7`** | **22.3.27** | **会议室那台 Windows 7 x64** | 也照样能在 Win10/11 上跑 |
|
||||||
|
|
||||||
|
**代码本身两个分支完全一样**(连测试都是同一套)—— 只有 `package.json` 里的 Electron 版本不同。所以**新功能在 `main` 上做完之后,把代码摘到 `win7` 上(cherry-pick 或直接合)重新出包即可**,不需要维护两份逻辑。
|
||||||
|
|
||||||
|
### 为什么锁在 Electron 22
|
||||||
|
|
||||||
|
**Electron 23 起彻底不支持 Windows 7 / 8 / 8.1**。官方在 [Windows 7/8/8.1 弃用公告](https://electronjs.org/blog/windows-7-to-8-1-deprecation-notice) 里写明:23 用的 Chromium 调用了 Win7 没有的系统 API,程序在 Win7 上一启动就报:
|
||||||
|
|
||||||
|
```
|
||||||
|
The procedure entry point GetPackageFamilyName could not be located
|
||||||
|
in the dynamic link library KERNEL32.dll
|
||||||
|
```
|
||||||
|
|
||||||
|
`GetPackageFamilyName` 这类是 Windows 8+ 才有的入口,Win7 的 `kernel32.dll` 里没有 → 加载器直接失败。
|
||||||
|
|
||||||
|
**Electron 22.3.27 是最后一个支持 Win7 的版本**(Chromium 108 / Node 16)。本项目要跑在会议室那台 **Windows 7 x64** 上,所以 `win7` 分支锁在这一版。
|
||||||
|
|
||||||
|
**升级 Electron 之前先确认那台机器还需不需要 Win7** —— 还想保 Win7 就不能越过 22。
|
||||||
|
|
||||||
|
### 怎么判断一个包能不能在 Windows 7 上跑(三条,从易到难)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# ① 看包内的 Electron 版本(不是看 package.json!)
|
||||||
|
type <解压目录>\version # 期望:22.x.x
|
||||||
|
|
||||||
|
# ② 看 PE 的子系统版本(这条最直观)
|
||||||
|
file <解压目录>/ClockLab.exe
|
||||||
|
# Electron 22 → "PE32+ executable for MS Windows 5.02 (GUI)" ← 兼容到 XP 时代 ✓
|
||||||
|
# Electron 38 → "PE32+ executable for MS Windows 10.00 (GUI)" ← 声明需要 Win10 ✗
|
||||||
|
|
||||||
|
# ③ 看导入表里有没有 Win8+/Win10+ 专有 API(最硬的一条)
|
||||||
|
pip install --break-system-packages pefile # 一次就够
|
||||||
|
python3 - <<'PY'
|
||||||
|
import pefile
|
||||||
|
pe = pefile.PE('ClockLab.exe')
|
||||||
|
names = {(e.dll.decode().lower(), i.name.decode()) for e in pe.DIRECTORY_ENTRY_IMPORT for i in pe.imports if i.name}
|
||||||
|
for t in ['GetPackageFamilyName','SetProcessDpiAwarenessContext','GetDpiForWindow','SetThreadDescription']:
|
||||||
|
hits = [d for d, n in names if t in n]
|
||||||
|
print(('FOUND ' if hits else 'absent '), t, hits)
|
||||||
|
PY
|
||||||
|
# 22 版的实测结果:这四条全部 absent(644 个导入条目里一个都没有)
|
||||||
|
```
|
||||||
|
|
||||||
|
> ⚠️ **别用"在文件里搜字符串"判断** —— Chromium 会**运行时动态解析**大量 API,这些名字出现在文件字节里很正常,搜到不代表会在启动时被调用(我第一次就是这么误报的,见第八节 ⑥)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 七、构建与打包
|
||||||
|
|
||||||
|
### 7.1 Windows(在 Windows 构建机上)
|
||||||
|
|
||||||
|
**必须在真 Windows 上打**,不要在这台 Linux 上跨平台打。踩过的坑见第八节,能跑的流程:
|
||||||
|
|
||||||
```bat
|
```bat
|
||||||
:: 1) 依赖:必须 --ignore-scripts
|
:: 1) 依赖:必须 --ignore-scripts
|
||||||
:: electron 的 postinstall 会去下载运行时,网络不通时会把整个 install 拖垮
|
|
||||||
pnpm install --ignore-scripts
|
pnpm install --ignore-scripts
|
||||||
|
|
||||||
:: 2) 运行时:把 electron-v38.2.2-win32-x64.zip 手工解压到位
|
:: 2) 运行时:把对应版本的 electron zip 手工解压到一个目录
|
||||||
:: ⚠️ node_modules/electron 是 pnpm 的**符号链接**,tar 拒绝"穿过符号链接"解压
|
:: ⚠️ node_modules/electron 是 pnpm 的**符号链接**,tar 拒绝"穿过符号链接"解压
|
||||||
:: → 先解到临时目录,再 robocopy 进去
|
:: → 先解到临时目录,再 robocopy 进去(或像下面这样直接用独立目录)
|
||||||
mkdir D:\tmp\electron-dist
|
mkdir D:\tmp\electron-dist
|
||||||
cd /d D:\tmp\electron-dist && tar -xf D:\tmp\electron-win-x64.zip
|
cd /d D:\tmp\electron-dist && tar -xf D:\tmp\electron-v22.3.27-win32-x64.zip
|
||||||
robocopy D:\tmp\electron-dist <项目>\node_modules\electron\dist /E
|
|
||||||
|
|
||||||
:: 3) 出包:直接调 electron-builder 的 CLI(`pnpm run` 会先做依赖检查、可能 EPERM)
|
:: 3) 出包:直接调 electron-builder 的 CLI(`pnpm run` 会先做依赖检查、可能 EPERM)
|
||||||
:: 用 --config.electronDist 明确告诉它运行时在哪,别让它走下载分支
|
:: 用 --config.electronDist 明确告诉它运行时在哪,别让它走"下载"那条分支
|
||||||
node node_modules\electron-builder\out\cli\cli.js --win --x64 ^
|
node node_modules\electron-builder\out\cli\cli.js --win --x64 ^
|
||||||
--config.electronDist=D:\tmp\electron-dist
|
--config.electronDist=D:\tmp\electron-dist
|
||||||
|
|
||||||
:: 4) 压缩:electron-builder 的 zip target 会去下 7zip 工具(见 6.3),自己压更稳
|
:: 4) 压缩:它的 zip target 会去下 7zip 工具(见第八节 ①),自己压更稳
|
||||||
cd dist && tar -a -c -f ClockLab-1.0.0-win-x64.zip win-unpacked
|
cd dist && ren win-unpacked ClockLab-1.0.0-win7-x64
|
||||||
|
tar -a -c -f ClockLab-1.0.0-win7-x64.zip ClockLab-1.0.0-win7-x64
|
||||||
```
|
```
|
||||||
|
|
||||||
产物:`dist\win-unpacked\`(目录版)+ 自己压的 zip。
|
- 第 3 步跑完,`dist\win-unpacked\` 就是**可运行的完整程序**(第 4 步只是把它压成 zip);
|
||||||
|
- 第 4 步里那个 `ren` 是**必须的**(第 4 步要在 `dist` 目录里执行)—— 顺手把目录名带上平台,免得两份包解压出来混淆。
|
||||||
|
- **运行时下载**:`https://npmmirror.com/mirrors/electron/<版本>/electron-v<版本>-win32-x64.zip`(浏览器/curl 都行;**别依赖 `ELECTRON_MIRROR` 环境变量**,它的安装脚本可能因为要抓校验文件而 404)。
|
||||||
|
|
||||||
### 6.2 Linux ARM64(在本机打)
|
### 7.2 Linux ARM64(在本机打)
|
||||||
|
|
||||||
**手工拼**,不要用 electron-builder:
|
**手工拼**,不要用 electron-builder(它在 Linux 上有一条必崩的路径,见第八节 ①):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 1) 复制 Electron 运行时(本机是 arm64,node_modules/electron/dist 就是对的版本)
|
cp -a node_modules/electron/dist/. dist/ClockLab-1.0.0-linux-arm64/ # 运行时
|
||||||
cp -a node_modules/electron/dist/. dist/ClockLab-1.0.0-linux-arm64/
|
|
||||||
mv dist/ClockLab-1.0.0-linux-arm64/electron dist/ClockLab-1.0.0-linux-arm64/clocklab
|
mv dist/ClockLab-1.0.0-linux-arm64/electron dist/ClockLab-1.0.0-linux-arm64/clocklab
|
||||||
|
mkdir -p dist/ClockLab-1.0.0-linux-arm64/resources/app # 应用代码
|
||||||
# 2) 应用代码进 resources/app/
|
|
||||||
mkdir -p dist/ClockLab-1.0.0-linux-arm64/resources/app
|
|
||||||
cp main.js preload.js package.json dist/ClockLab-1.0.0-linux-arm64/resources/app/
|
cp main.js preload.js package.json dist/ClockLab-1.0.0-linux-arm64/resources/app/
|
||||||
cp -a src renderer dist/ClockLab-1.0.0-linux-arm64/resources/app/
|
cp -a src renderer dist/ClockLab-1.0.0-linux-arm64/resources/app/
|
||||||
|
|
||||||
# 3) 打 tar.gz
|
|
||||||
tar -C dist -czf dist/ClockLab-1.0.0-linux-arm64.tar.gz ClockLab-1.0.0-linux-arm64
|
tar -C dist -czf dist/ClockLab-1.0.0-linux-arm64.tar.gz ClockLab-1.0.0-linux-arm64
|
||||||
```
|
```
|
||||||
|
|
||||||
**验证**:解压后 `./clocklab` 直接能跑(本机就是 arm64,Xvfb 下起得来即说明链接与资源都对)。
|
**验证**:本机就是 arm64,解压后 `./clocklab` 起得来即说明链接与资源都对(无头时配 Xvfb)。
|
||||||
|
|
||||||
### 6.3 踩过的坑(**看这一节能省几个小时**)
|
---
|
||||||
|
|
||||||
**① electron-builder 在"需要下载"这条分支上必崩**
|
## 八、踩坑记录
|
||||||
|
|
||||||
|
> **看这一节能省几个小时。** 这些都是实际撞过的,不是转述。
|
||||||
|
|
||||||
|
### ① electron-builder 在"需要下载"这条分支上必崩
|
||||||
|
|
||||||
```
|
```
|
||||||
⨯ Cannot read properties of undefined (reading 'ReadWrite')
|
⨯ Cannot read properties of undefined (reading 'ReadWrite')
|
||||||
at resolveCacheMode (app-builder-lib/src/util/electronGet.ts:131)
|
at resolveCacheMode (app-builder-lib/src/util/electronGet.ts:131)
|
||||||
```
|
```
|
||||||
根因:`ElectronDownloadCacheMode` 在运行环境里是 `undefined`,代码却直接读它的 `.ReadWrite`。**26.15.3 与 26.16.1 都有**,Windows 与 Linux 都复现。触发点是"它认为需要下载 Electron 运行时/7zip 工具" —— 所以:
|
|
||||||
|
根因:`ElectronDownloadCacheMode` 在运行环境里是 `undefined`,代码却直接读它的 `.ReadWrite`。**26.15.3 与 26.16.1 都有**,Windows 与 Linux 都复现。触发点是"它认为需要下载 Electron 运行时 / 7zip 工具",所以:
|
||||||
|
|
||||||
- 用 `--config.electronDist=<运行时目录>` 让它**不需要下载**;
|
- 用 `--config.electronDist=<运行时目录>` 让它**不需要下载**;
|
||||||
- 打 zip 时它仍要 7zip 工具,那条路也会崩 → **压缩自己来**。
|
- 打 zip 时它仍要 7zip 工具,那条路也会崩 → **压缩自己来**。
|
||||||
|
|
||||||
**② 手工放运行时还不够,还得让它"知道"运行时在哪**
|
### ② 手工放运行时还不够,得让它"知道"运行时在哪
|
||||||
|
|
||||||
- electron 包装里的 `path.txt` **不是** electron-builder 的判据(它走 `@electron/get` 的缓存机制);
|
- electron 包装里的 `path.txt` **不是** electron-builder 的判据(它走 `@electron/get` 的缓存机制);
|
||||||
- 直接给它 `electronDist` 才是正解。
|
- 正解是给它 **`electronDist`**。
|
||||||
|
|
||||||
**③ 网络:这台环境从官方源下 Electron 很难**
|
### ③ 网络:下 Electron 运行时的几种办法与各自的坏法
|
||||||
- 直连 GitHub:`%TEMP%` 里会堆一排 `electron-download-*` 重试目录直到失败;
|
|
||||||
- npmmirror 镜像:`ELECTRON_MIRROR` 可用,但它在拼 `SHASUMS256.txt` 时可能 404;
|
|
||||||
- gh 加速站(`gh-proxy.com`):实测 403;
|
|
||||||
- **最稳**:**在能上网的机器上把 zip 下好**(`https://npmmirror.com/mirrors/electron/38.2.2/electron-v38.2.2-<平台>.zip`),再传过去手工放。
|
|
||||||
|
|
||||||
**④ pnpm 11 的 `strictDepBuilds`**
|
- **直连 GitHub**:可能失败(`%TEMP%` 里堆一排 `electron-download-*` 重试目录);**但也不一定** —— 我们后来实测同一条网络两个源都通 200,所以**先试直连,失败再绕**;
|
||||||
只要有依赖的构建脚本被跳过,`pnpm install` 会**直接以退出码 1 失败**(`ERR_PNPM_IGNORED_BUILDS`),而 electron 的 postinstall 正是下载运行时那一步。放行写在 `pnpm-workspace.yaml` 的 `allowBuilds`(`package.json` 的 `pnpm` 字段与 `.npmrc` 的 `onlyBuiltDependencies` 在 pnpm 11 已不生效),**这个文件别删**。
|
- **`ELECTRON_MIRROR` 指 npmmirror**:可能 404(它的安装脚本会去抓校验文件,镜像上没有);
|
||||||
|
- **gh 加速站**:实测可能 403;
|
||||||
|
- **最稳的兜底**:**在能上网的机器上把 zip 下好**(下载跟平台无关,Windows 版的 zip 在 Linux 上下是一模一样的),再传过去手工放。
|
||||||
|
|
||||||
|
### ④ pnpm 11 的 `strictDepBuilds`
|
||||||
|
|
||||||
|
只要有依赖的构建脚本被跳过,`pnpm install` 会**直接以退出码 1 失败**(`ERR_PNPM_IGNORED_BUILDS`),而 electron 的 postinstall 正是下载运行时那一步。放行写在 `pnpm-workspace.yaml` 的 `allowBuilds`(`package.json` 的 `pnpm` 字段与 `.npmrc` 的 `onlyBuiltDependencies` 在 pnpm 11 已不生效)—— **这个文件别删**。
|
||||||
|
|
||||||
|
### ⑤ `pnpm run` 会先做依赖状态检查
|
||||||
|
|
||||||
**⑤ `pnpm run` 会先做依赖状态检查**
|
|
||||||
`pnpm run dist:win` 可能触发一次 `install`,在 `node_modules` 被动过的目录上会 `ERR_PNPM_EPERM`(rename 失败)。**直接调 CLI**(`node node_modules/electron-builder/out/cli/cli.js …`)可绕过。
|
`pnpm run dist:win` 可能触发一次 `install`,在 `node_modules` 被动过的目录上会 `ERR_PNPM_EPERM`(rename 失败)。**直接调 CLI**(`node node_modules/electron-builder/out/cli/cli.js …`)可绕过。
|
||||||
|
|
||||||
|
### ⑥ 判断 Win7 兼容性:**别搜文件字节,要读导入表**
|
||||||
|
|
||||||
|
第一次我用"在整个 exe 里搜 `GetPackageFamilyName`"来判断,**误报**了 —— 那个字符串确实在文件里,但**不在导入表**里。Chromium 大量使用运行时动态解析(`GetProcAddress`)来兼容老系统,字符串出现很正常。
|
||||||
|
|
||||||
|
**判据是导入表**(见第六节末尾那段脚本),再配上 `file` 读出的 PE 子系统版本,两条一起看。
|
||||||
|
|
||||||
|
### ⑦ 时间相关的 bug 只在特定时段露头
|
||||||
|
|
||||||
|
`String(0)` 是 `"0"` 而输入框要 `HH:MM:SS` —— 这个 bug **白天测全绿、上午 9 点前必挂**(`getHours()` 返回个位数)。凡是与"当前时间"耦合的逻辑,写测试时要格外小心(要么注入时钟,要么至少知道它在什么时段会失败)。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 七、已知可以再做的
|
## 九、已知可以再做的
|
||||||
|
|
||||||
- **Windows 的 portable 单文件 exe** 还没做成(卡在 6.3 ① 的 7zip 工具那条路)—— 绿色版 zip 功能等价,只是多一步解压;
|
- **Windows 的 portable 单文件 exe** 还没做成(卡在 ⑧① 的 7zip 工具那条路)—— 绿色版 zip 功能等价,只是多一步解压;
|
||||||
- **飞腾平台(LoongArch 不是 ARM64)不支持** —— 本项目产出的是 ARM64,龙芯需要另找运行时;
|
- **表盘没有刻度数字**(只有刻度线与指针);
|
||||||
- 表盘目前没有"刻度数字"(只有刻度线与指针)。
|
- **`main` 与 `win7` 的代码同步目前靠手工** —— 如果以后两边都要频繁改,可以考虑用一个构建期替换(例如把 Electron 版本做成环境变量)来避免分叉;
|
||||||
|
- **飞腾/龙芯等国产平台的适配**:本项目的 Linux 包是 **ARM64**(飞腾 D2000 可用);**龙芯是 LoongArch,Electron 没有那个版本**,跑不了。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 十、变更记录(简)
|
||||||
|
|
||||||
|
| 时间 | 内容 |
|
||||||
|
| --- | --- |
|
||||||
|
| 2026-09-14 深夜 | 项目建立:两窗口、走时模型、无边框窗口的拖动与改大小、152 条测试;Linux ARM64 包 |
|
||||||
|
| 2026-09-15 | 加了**跳转动画**(呈现时间层,数字与指针共用);修了 `pad2` 那个时段性 bug |
|
||||||
|
| 2026-09-15 | 得知会议室是 **Windows 7** → 开 `win7` 分支降到 **Electron 22.3.27** 并出包(`main` 保持 38 不变) |
|
||||||
@@ -64,7 +64,14 @@
|
|||||||
|
|
||||||
### Windows
|
### Windows
|
||||||
|
|
||||||
解压 `ClockLab-1.0.0-win-x64.zip`,双击里面的 **`ClockLab.exe`**。绿色版,不写注册表、不需要安装。
|
解压对应的包,双击里面的 **`ClockLab.exe`**。绿色版,不写注册表、不需要安装。
|
||||||
|
|
||||||
|
| 你的系统 | 用哪个包 |
|
||||||
|
| --- | --- |
|
||||||
|
| **Windows 7**(会议室那台) | `ClockLab-1.0.0-win7-x64.zip` —— 运行时锁在 Electron 22,**最后一个支持 Win7 的版本** |
|
||||||
|
| Windows 10 / 11 | 上面那个也能跑;`ClockLab-1.0.0-win-x64.zip`(Electron 38,主线)同样可以 |
|
||||||
|
|
||||||
|
> ⚠️ **别拿 Win10/11 那份去 Win7 上跑**:它会报 `GetPackageFamilyName … KERNEL32.dll` 找不到入口点 —— 那是 Electron 23+ 的已知表现(详见 [DEVELOP.md](./DEVELOP.md) 第六节)。
|
||||||
|
|
||||||
### Linux(ARM64)
|
### Linux(ARM64)
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -14,8 +14,8 @@
|
|||||||
"dist:linux": "electron-builder --linux --arm64"
|
"dist:linux": "electron-builder --linux --arm64"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"electron": "38.2.2",
|
"electron": "22.3.27",
|
||||||
"electron-builder": "26.15.3"
|
"electron-builder": "26.16.1"
|
||||||
},
|
},
|
||||||
"build": {
|
"build": {
|
||||||
"appId": "com.askasoft.clocklab",
|
"appId": "com.askasoft.clocklab",
|
||||||
|
|||||||
Generated
+17
-24
@@ -9,8 +9,8 @@ importers:
|
|||||||
.:
|
.:
|
||||||
devDependencies:
|
devDependencies:
|
||||||
electron:
|
electron:
|
||||||
specifier: 38.2.2
|
specifier: 22.3.27
|
||||||
version: 38.2.2(supports-color@7.1.0)
|
version: 22.3.27(supports-color@7.1.0)
|
||||||
electron-builder:
|
electron-builder:
|
||||||
specifier: 26.16.1
|
specifier: 26.16.1
|
||||||
version: 26.16.1(electron-builder-squirrel-windows@26.16.1)(supports-color@7.1.0)
|
version: 26.16.1(electron-builder-squirrel-windows@26.16.1)(supports-color@7.1.0)
|
||||||
@@ -130,8 +130,8 @@ packages:
|
|||||||
'@types/ms@2.1.0':
|
'@types/ms@2.1.0':
|
||||||
resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==}
|
resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==}
|
||||||
|
|
||||||
'@types/node@22.20.2':
|
'@types/node@16.11.26':
|
||||||
resolution: {integrity: sha512-xlvWf4Vs9n1PEVYwP1n4vvG07M6y8WgvJ2t0vbrWTmijsIHp1cS+uJ2kMIRdY3nHZK0nCYKrPeD171+SzF4/zw==}
|
resolution: {integrity: sha512-GZ7bu5A6+4DtG7q9GsoHXy3ALcgeIHP4NnL0Vv2wu0uUB/yQex26v0tf6/na1mm0+bS9Uw+0DFex7aaKr2qawQ==}
|
||||||
|
|
||||||
'@types/node@26.5.1':
|
'@types/node@26.5.1':
|
||||||
resolution: {integrity: sha512-CzNm2FezW4VR/LjG6yUdiEgLE/rAQ9Slj5gCu/C2VrdcW7I0ahNZ8DRbHT7zOZ6r3ONgd/bsQIeSaoDGrd1C6g==}
|
resolution: {integrity: sha512-CzNm2FezW4VR/LjG6yUdiEgLE/rAQ9Slj5gCu/C2VrdcW7I0ahNZ8DRbHT7zOZ6r3ONgd/bsQIeSaoDGrd1C6g==}
|
||||||
@@ -411,8 +411,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-bO3y10YikuUwUuDUQRM4KfwNkKhnpVO7IPdbsrejwN9/AABJzzTQ4GeHwyzNSrVO+tEH3/Np255a3sVZpZDjvg==}
|
resolution: {integrity: sha512-bO3y10YikuUwUuDUQRM4KfwNkKhnpVO7IPdbsrejwN9/AABJzzTQ4GeHwyzNSrVO+tEH3/Np255a3sVZpZDjvg==}
|
||||||
engines: {node: '>=8.0.0'}
|
engines: {node: '>=8.0.0'}
|
||||||
|
|
||||||
electron@38.2.2:
|
electron@22.3.27:
|
||||||
resolution: {integrity: sha512-OXSaVNXDlonXDjMRsFNQo1j5tzTKwKXh5/m46IjAFccBcZJZMISI+EjSI07oexIuhvKM8AZLuFuihVn4YjWWrA==}
|
resolution: {integrity: sha512-7Rht21vHqj4ZFRnKuZdFqZFsvMBCmDqmjetiMqPtF+TmTBiGne1mnstVXOA/SRGhN2Qy5gY5bznJKpiqogjM8A==}
|
||||||
engines: {node: '>= 12.20.55'}
|
engines: {node: '>= 12.20.55'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
@@ -1035,9 +1035,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==}
|
resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
undici-types@6.21.0:
|
|
||||||
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
|
|
||||||
|
|
||||||
undici-types@8.9.0:
|
undici-types@8.9.0:
|
||||||
resolution: {integrity: sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==}
|
resolution: {integrity: sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==}
|
||||||
|
|
||||||
@@ -1279,7 +1276,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@types/http-cache-semantics': 4.2.0
|
'@types/http-cache-semantics': 4.2.0
|
||||||
'@types/keyv': 3.1.4
|
'@types/keyv': 3.1.4
|
||||||
'@types/node': 22.20.2
|
'@types/node': 26.5.1
|
||||||
'@types/responselike': 1.0.3
|
'@types/responselike': 1.0.3
|
||||||
|
|
||||||
'@types/debug@4.1.13':
|
'@types/debug@4.1.13':
|
||||||
@@ -1300,18 +1297,16 @@ snapshots:
|
|||||||
|
|
||||||
'@types/keyv@3.1.4':
|
'@types/keyv@3.1.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 22.20.2
|
'@types/node': 26.5.1
|
||||||
|
|
||||||
'@types/minimatch@6.0.0':
|
'@types/minimatch@6.0.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
minimatch: 3.1.5
|
minimatch: 10.2.6
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@types/ms@2.1.0': {}
|
'@types/ms@2.1.0': {}
|
||||||
|
|
||||||
'@types/node@22.20.2':
|
'@types/node@16.11.26': {}
|
||||||
dependencies:
|
|
||||||
undici-types: 6.21.0
|
|
||||||
|
|
||||||
'@types/node@26.5.1':
|
'@types/node@26.5.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -1319,11 +1314,11 @@ snapshots:
|
|||||||
|
|
||||||
'@types/responselike@1.0.3':
|
'@types/responselike@1.0.3':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 22.20.2
|
'@types/node': 26.5.1
|
||||||
|
|
||||||
'@types/yauzl@2.10.3':
|
'@types/yauzl@2.10.3':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 22.20.2
|
'@types/node': 26.5.1
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@xmldom/xmldom@0.8.15': {}
|
'@xmldom/xmldom@0.8.15': {}
|
||||||
@@ -1686,10 +1681,10 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
electron@38.2.2(supports-color@7.1.0):
|
electron@22.3.27(supports-color@7.1.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@electron/get': 2.0.0(supports-color@7.1.0)
|
'@electron/get': 2.0.0(supports-color@7.1.0)
|
||||||
'@types/node': 22.20.2
|
'@types/node': 16.11.26
|
||||||
extract-zip: 2.0.1(supports-color@7.1.0)
|
extract-zip: 2.0.1(supports-color@7.1.0)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
@@ -2052,11 +2047,11 @@ snapshots:
|
|||||||
|
|
||||||
node-abi@4.35.0:
|
node-abi@4.35.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
semver: 7.7.4
|
semver: 7.8.5
|
||||||
|
|
||||||
node-api-version@0.2.1:
|
node-api-version@0.2.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
semver: 7.7.4
|
semver: 7.8.5
|
||||||
|
|
||||||
node-gyp@12.4.0:
|
node-gyp@12.4.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -2065,7 +2060,7 @@ snapshots:
|
|||||||
graceful-fs: 4.2.11
|
graceful-fs: 4.2.11
|
||||||
nopt: 9.0.0
|
nopt: 9.0.0
|
||||||
proc-log: 6.0.0
|
proc-log: 6.0.0
|
||||||
semver: 7.7.4
|
semver: 7.8.5
|
||||||
tar: 7.5.22
|
tar: 7.5.22
|
||||||
tinyglobby: 0.2.17
|
tinyglobby: 0.2.17
|
||||||
undici: 6.28.1
|
undici: 6.28.1
|
||||||
@@ -2333,8 +2328,6 @@ snapshots:
|
|||||||
type-fest@0.13.1:
|
type-fest@0.13.1:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
undici-types@6.21.0: {}
|
|
||||||
|
|
||||||
undici-types@8.9.0: {}
|
undici-types@8.9.0: {}
|
||||||
|
|
||||||
undici@6.28.1: {}
|
undici@6.28.1: {}
|
||||||
|
|||||||
Reference in new issue
Block a user