diff --git a/.gitignore b/.gitignore index 1185f16..5e5db46 100644 --- a/.gitignore +++ b/.gitignore @@ -39,7 +39,7 @@ eslint-report.json .DS_Store Thumbs.db -# ²ο¼ÓõĵÚÈý·½ԴÂë°ü£¨Éè¼Æ token ÒÑÌáȡµ½ docs/winisland-design-tokens.md£© +# 设计参考压缩包(令牌见 docs/dev/winisland-design-tokens.md) WinIsland-master.zip releases/ @@ -51,3 +51,5 @@ packages/server/prisma/database-settings.json # 文档站构建产物(pnpm docs:build 生成,由 GitHub Pages 发布;见 docs/) docs/_site/ + +packages/data/courseware/ diff --git a/AGENTS.md b/AGENTS.md index fde2d4e..b383a9f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,1719 +1,131 @@ # AGENTS.md — ClassHelper(Class Helper) -本文件写给在本仓库里干活的 AI 编码代理。先读这里,再动代码。 +本文件供本仓库的 AI 编码代理使用。先读这里,再改代码。当前工作目录为 `F:\ClassHelper`。 +现行规则按工作区源码维护;产品说明见 `docs/app/`,开发契约见 `docs/dev/`,部署见 `docs/management/`。 +查证路径见 `docs/reference.md`。文档与代码冲突时,阅读实现及其校验规则后同步修正文档,不沿用历史报告中的结论。 -**2026-10-16 更新**:当前仓库位于 `F:\ClassHelper`,F 盘可用。ClassIsland 联动插件、设备令牌、提醒通道与镜像接口已退役;离线课表 JSON 导入和界面设计参考保留。下文历史联动部署记录不再适用。 -客户端维护只走 `/api/clients/*`,页面为 `ClientsView.vue`;默认权限按数据库中对应角色的默认组生效。 -**2026-10-17 本批功能约定**:课表业务真值为 `ClassPlan / ClassPlanEntry / TimeLayoutItem`, -旧 `Schedule` 仅保留迁移审计来源,`/api/schedules/*` 是新模型的兼容门面。统一解析器在 -`lib/class-plan.ts`,不得在客户端另写在线启用规则。自定义岛方案在本机 `config.json.islandLayout`, -默认 `layout=fixed`;新尺寸上报仅在 custom 模式执行。详见 `docs/dev/20261017-implementation.md`。 +## 1. 项目与目录 -**2026-10-17 更新**:日常 `verify:desktop` / `verify:packaged` 默认是后台快速检查,不创建可见灵动岛或托盘; -完整桌面动画、透明度与截图验收必须显式加 `--visual`(`pnpm verify:desktop:visual`)。 -下文关于置顶验证窗口、鼠标穿透与截图时序的历史约定仅适用于视觉模式。不要为了日常修改反复占用用户桌面。 -联网冒烟显式启用,使用临时教师和班级;禁止回退到真实班级、重置其密码或向其发送测试通知。 +pnpm monorepo,根 `package.json` 是版本单一来源。文档、注释、界面文案及提交描述使用中文。 +远端为 `LoveChengke/ClassHelper`,默认分支 `master`。 -本次 PowerShell 会话的 PATH 不含 pnpm;受限工具进程读取启动器可能需要工具级提权,不能据此判断未安装。 -本次 PowerShell 会话的 PATH 不含 pnpm:需要先把 `C:\Users\hck16\AppData\Local\deepseek-harness\bin` -加到 PATH 前面,再按下文设置 `DSH_NODE` 和 `DSH_PNPM_BIN`。Git Bash 外层找不到 pnpm 时可执行: +| 目录 | 用途 | +| -------------------------- | --------------------------------------------------------- | +| `packages/shared/src/` | 三端共享类型、权限、常量、动效、岛方案与课件契约 | +| `packages/server/` | Express、Prisma、Socket.IO;API 前缀 `/api` | +| `packages/web-admin/` | Vue Web 管理端,PWA | +| `packages/desktop-client/` | Electron 主进程、preload、主窗口与独立灵动岛渲染进程 | +| `scripts/` | 版本、构建、验证、打包、图标与 Pages 脚本 | +| `deploy/` | Windows/Linux/Docker 部署与运维模板 | +| `docs/` | docfx 文档;`images/` 为展示图,`screenshots/` 为回归截图 | +| `website/` | 纯静态官网与下载页 | +| `releases/` | 按组件和版本存放本地交付物,gitignored | -```bash -export DSH_NODE='D:\Node.js\node.exe' -export DSH_PNPM_BIN='C:\Users\hck16\AppData\Local\node\corepack\v1\pnpm\11.8.0\bin\pnpm.cjs' -node "C:/Users/hck16/AppData/Local/node/corepack/v1/pnpm/11.8.0/bin/pnpm.cjs" typecheck +不要在文档中手抄模块、表、权限键、测试用例的总数;分别查注册表、schema、共享目录与本次验证输出。 + +## 2. 服务端与数据边界 + +- 模块由 `packages/server/src/modules/registry.ts` 注册;新增或下线功能改注册表,不另改 `app.ts` 挂载。 +- 路由用 `middleware/validate.ts` 与 zod;响应复用 `lib/http.ts`,错误交给统一中间件。 +- 学生是 `Student` 名单记录,没有账号、角色或密码。教师/管理员存 `User`;班级登录读取 `Class.code/passwordHash`,会话角色 `CLASS_DEVICE`,主体 id 为班级 id。 +- 任课关系只在 `Course(classId, name, teacherId)`。班主任不自动获得别的科目的作业、成绩写权限,统一走 `assertCanManageSubjectContent`。班级端可录作业,不可写成绩;员工还须对应功能权限。 +- 功能权限取 `lib/permissions.ts` 的 `loadEffectivePermissions()`:已分组取并集;未分组使用数据库对应角色的可编辑默认组;管理员永远保留 `permission:manage/system:manage`;班级端为空集。 +- 岗位组不新增 `User.role`。班主任/教师/级长/部长仍为 TEACHER;范围在 `PermissionGroup.scope/scopeValue`,由 `lib/permission-scope.ts` 收口,空年级/部范围拒绝。设备写权限只合并含对应键的分组范围,范围不替代科目授权。 +- 只有一份 `prisma/schema.prisma`,切库改 provider;保持 SQLite/MySQL 通用,不加 Prisma enum 或 `@db.*`。模型变动同步迁移、`lib/snapshot.ts` 的 `SNAPSHOT_TABLES` 与旧库兼容逻辑。 +- `deleteTeacher` 必须保护班主任、课程任课、发布的作业/通知和所属课件,避免级联删除业务内容。 +- 作业和通知按本地 `assignDate` 归类,使用 `dayKeyLocal/isDayKey/shiftDayKey`,不用 UTC 日期切片。 +- 批量通知/作业逐班返回结果;跨班作业按科目名匹配既有课程,找不到则该班失败,不自动把发布者设为任课老师。 +- 两个登录接口共用登录限流。附件链接校验 scheme;表格文件上限 8 MB、JSON 请求体 12 MB,超限映射 413。 +- AI 助手只解析待确认动作,前端确认后调既有发布接口;参数重新校验,Key 加密存储且不回传。自动化用 mock,不访问外网。 + +## 3. 课表、周次、成绩与学年 + +- 课表真值是 `ClassPlan/ClassPlanEntry/TimeLayoutItem`;旧 `Schedule` 与 `TimeLayout.items` 保留迁移审计用途,不双写或回读复活旧业务。 +- `/api/schedules/*` 是新模型兼容门面。启用解析统一走 `lib/class-plan.ts`,在线客户端不得另写启用优先级。 +- 时间点有上课、课间、分割线、行动四种;课程显式引用时间点外键。排序不能靠数组下标代替关联。 +- 支持 1–9 周轮换、群组、预定及临时层;完整档案导入走 `profile-import.service.ts`,不经过单双周有损格式。 +- 全校时间表写入用 `timetable:manage`;班级日课表写入还须班主任/管理员断言。离线整套课表导入可建立班级自有作息,不把 JSON 的 TeacherName 当授权。 +- 周次统一 `loadTermContext/resolveCurrentWeek`:班级逐周区间优先于全校区间,缺省再线性推算。开学日会参考班级设置、第 1 周区间、全校设置与环境兜底,详见 `lib/term.ts`。 +- 全校周数上限存 `term.maxWeeks`,默认 30、范围 1–60;服务端 `getMaxTermWeeks()`,shared 做归一化与离线兜底。班级周数不超全校上限。 +- 关闭个人成绩查询后,班级端仍可看服务端集体聚合;个人明细、原始全班成绩和排名拒绝读取。稀疏样本及补集抑制,不下发原始成绩后在客户端聚合。 +- 总分开关属于 `Exam`;排名同学生同科取最高分,并列排名为 1、2、2、4。客户端只展示,不能修改成绩查询或考试配置。 +- 年度升级由 `term.promotionAt` 配置,`PromotionRun.year` 保证幂等,升级前保存快照。运行期到点自动执行,启动漏跑先预览补跑;开发机启动不得擅自升级业务数据。 +- 高三归档,其余升一级;新班仅建骨架,不复制班主任/任课关系。转出过滤复用 `lib/students.ts`,学号不复用,归档页保留历史与撤回转出。 + +## 4. 客户端、灵动岛与课件 + +- preload 仅暴露 contextBridge 白名单,不暴露 ipcRenderer;跨桥接传纯数据,不传 Vue Proxy。契约写在 `src/types/desktop.d.ts`。 +- 保留 CSP、导航同源校验与弹窗限制。安装包不能含凭据,保留 `check-bundle-secrets` 构建门禁。 +- 离线缓存按服务端与班级隔离,缓存故障不应阻断在线读取。退出、切服务器时清理会话状态,设备 `deviceKey` 保持不变,渲染层不能改写它。 +- 设备维护只走 `/api/clients/*`,管理页 `ClientsView.vue`。心跳约 30 秒,90 秒窗口判在线;未登录不报,401/403 退避,切配置重置计时并隔离迟到响应。 +- 远控须 `remote:control`,只允许 shutdown/restart/logoff/lock,客户端立即执行、持久去重并回执。冒烟始终假执行,不得真关机/重启/注销/锁屏。 +- 更新写入口用 `client:update`;服务端缓存包并登记 SHA256,教室机从本服务下载;无哈希拒收,按立即/放学后/空闲策略执行,关闭自动更新不得强装。冒烟不运行安装器。 +- 灵动岛方案保存在本机 `config.json.islandLayout`,默认 `layout=fixed`;`custom` 当前即常驻模式,旧 `residentEnabled` 是兼容字段。新尺寸上报仅在 custom 模式执行。 +- `LayoutIslandApp.vue` 选择经典 `IslandApp.vue` 或 `CustomIslandRuntime.vue`,共用一个原生岛窗口;课件传输状态优先显示 `CoursewareTransfer.vue`。 +- 经典模式:普通通知/作业上课暂存;普通叫人保留可点击胶囊;紧急消息立即展开。自定义常驻模式:消息先提示铃铛数量,用户主动展开;上课也可主动查看。不要把两个模式规则混写。 +- 自定义方案的常态 `rows` 与无消息展开 `expandedRows` 分开编辑。课程、时钟、容器等使用 shared 组件契约;测量自然内容并按工作区约束适配。 +- 保留经典岛连续圆角、投影常驻、触摸输入、失焦宽限与固定窗口几何护栏。不要调用 `setBackgroundMaterial()` 破坏逐像素透明。左右边距仅左/右停靠生效。 +- 消息按 id 去重,通知/叫人双播不得重复;列表布局复用 shared 度量。崩溃恢复保持限流,冒烟不得自重启。 +- 课件权限和任课范围独立校验:拥有文件、文件可见范围与本人授课班级共同约束推送,岗位范围不代替任课授权。 +- 课件最大 512 MB,流式收发、临时文件、SHA256 校验;客户端不自动运行文件,每设备独立回执。JSON 快照只含元数据,完整备份另带文件目录。 + +## 5. 实时、界面与资源 + +- 事件名与房间名取 shared 常量。业务用 `emitToClass/emitToSession`,不直接依赖 Socket.IO 实例。 +- 会话房间为 `session:{id}`;教师/管理员 id 为账号 id,班级端为班级 id。`students` 是班级端广播房间,不是个人学生账号房间。 +- 新实时事件同步服务端、Web 订阅、客户端订阅。成绩实时载荷不可泄漏个人成绩。 +- Web 菜单分组在 `config/navigation.ts`,标题/图标/权限取路由;守卫与服务端都检查权限,前端隐藏不能代替鉴权。 +- 侧栏折叠用 CSS 保持 DOM,关怀模式放大字号/控件,不改变几何令牌。使用语义色,动效统一 shared 令牌;图标新增后检查注册表。 +- 图标源是 `build/classhelper.png` 与设计导出的 `.ico`,`pnpm icons` 派生其余资源。ICO 原样透传,客户端兜底 `build/icon.*` 同步保留。 +- Web 品牌图用 `/logo.png`,客户端用 `BRAND_LOGO_URL`,避免 file:// 绝对路径问题。 +- 官网和文档站共用一个 Pages 站点,`scripts/build-pages.mjs` 拼装根站与 `/docs/`;文档不写指向 docs 树外的相对链接,源码用 GitHub 绝对链接。 +- 展示图引用 `docs/images/`,回归图放 `docs/screenshots/`。官网 `.reveal-grad` 不拆逐字节点,动效改 token 表。 +- ClassIsland 联动插件、设备令牌、提醒渠道与镜像接口已退役;保留离线 JSON 导入和设计参考,不恢复在线联动依赖。 + +## 6. 本机命令与验证 + +脚本以根/各包 `package.json` 为准;端口:后端 4000,Web dev 5173,客户端 dev 5174,官网 5180,文档 5181。 +先构建 shared 再启动下游。新环境按需执行 install → db:generate → server db:deploy → build:shared;`db:seed/db:reset` 会清业务表,只用于明确选择的演示/隔离库。 + +本机 Node 路径为 `D:\Node.js\node.exe`。受限终端的 pnpm 可能解析到运行时兜底版本,先看 `Get-Command pnpm` 与 `pnpm --version`。 +仓库锁定 pnpm 11.8.0,可用 `C:\Users\hck16\AppData\Local\node\corepack\v1\pnpm\11.8.0\bin\pnpm.cjs`。 +经 DSH 转发壳时可设置: + +```powershell +$env:PATH = 'C:\Users\hck16\AppData\Local\deepseek-harness\bin;' + $env:PATH +$env:DSH_NODE = 'D:\Node.js\node.exe' +$env:DSH_PNPM_BIN = 'C:\Users\hck16\AppData\Local\node\corepack\v1\pnpm\11.8.0\bin\pnpm.cjs' +Remove-Item Env:DSH_PNPM -ErrorAction SilentlyContinue +pnpm --version ``` -外层直接调用用于解决找不到命令;环境变量让内部嵌套的 pnpm 仍使用真实运行时。 -文中「本机环境」「已知坑」两节按**实测**写成;若与本机现状不符,以本机实测为准并顺手改掉这里。 - ---- - -## 1. 这是什么 - -班级信息管理系统,**pnpm monorepo**,一份代码产出多个交付物: - -| 交付物 | 位置 | 形态 | -| ---------- | ------------------------- | -------------------------------------------------------- | -| 后端服务 | `packages/server` | Express 5 + Prisma 7 + Socket.IO,`/api` 前缀 | -| Web 管理端 | `packages/web-admin` | Vue 3 + Vite + Element Plus(教师/管理员用,PWA 可安装) | -| 桌面客户端 | `packages/desktop-client` | Electron + Vue 3(学生用,含「灵动岛」浮窗) | -| 共享契约 | `packages/shared` | 三端共用的类型 / 常量 / 权限 / 工具函数 | - -核心链路: - -``` -教师在 Web 端发布内容 - │ REST API(JWT 鉴权) - ▼ - 后端服务(Express + Prisma) - │ 写库 + 按班级房间广播 - ▼ - Socket.IO ──► class:{classId} 房间 ──► 学生桌面客户端实时更新 UI - -``` - -- 远端:`LoveChengke/ClassHelper`(仓库名大小写不敏感,本地 remote 存的是小写 `classhelper.git`) -- 默认分支:`master` -- 语言:**文档、注释、提交信息、界面文案一律中文** - ---- - -## 2. 目录速查 - -``` -package.json 根脚本(dev / build / db:* / verify:* / dist:* / icons) -pnpm-workspace.yaml workspace + allowBuilds(pnpm 11 依赖构建白名单,勿随手删条目) -tsconfig.base.json 共享 TS 基础配置(ESM + NodeNext + strict) -eslint.config.mjs ESLint 扁平配置(TS + Vue);ignores 含 releases/ / release-server/ / src/generated/ -.prettierrc.json 单引号 / printWidth 110 / trailingComma all / LF / 2 空格 -build/classhelper.{png,ico} 图标源(**设计导出、手工放入**;`pnpm icons` 从它派生其余全部图标,见 §5 第 62 条) -scripts/ - use-database.mjs SQLite ⇄ MySQL 切换(**改写 schema.prisma 里的 provider**,没有第二份 schema) - generate-icons.mjs 启动 Electron 跑 icons/render.cjs(= `pnpm icons`) - icons/render.cjs 把 classhelper.png 重采样成各端图标(CommonJS,见 §5 第 62 条) - dist-server.mjs 服务端 + Web 管理端打包(产物落在 releases/server/<版本>/) - nsis/server-installer.nsi 服务端 NSIS 安装脚本 - verify-packaged.mjs 对**打包后/已安装**的客户端 EXE 复跑冒烟 - check-icons.mjs 图标注册表静态门禁(漏注册会静默渲染空白,见 §5 第 53 条) - version.mjs 版本号单一来源的读写与一致性校验(= `pnpm version:*`,见 §4.4) - ui-smoke/{run,main.cjs,live-probe.mjs} Web 管理端真实点击回归(Electron 驱动) - lib/{electron-env,node-runtime}.mjs 启动 Electron / 定位真实 node.exe -deploy/ Dockerfile / docker-compose{,.sqlite}.yml / nginx.conf / package.runtime.json - install.sh(Linux 一键安装器,TUI)/ install-linux.sh(转发壳) - classhelper(运维命令本体)/ verify-linux.sh(Linux 验收,自统计 N/N) - tools/admin-cli.mjs(离线改密 / SQLite 一致性快照) - profile.d/ logrotate.d/ systemd/(装到 /etc 的模板) - Dockerfile.linux-package(安装包内那份,--mode docker 用) - classhelper.service(systemd 模板,5 个占位符)/ .env{,.sqlite}.example -docs/ **文档站**(docfx 项目;不进 pnpm workspace) - ⚠ 与官网(website/)共用**同一个** GitHub Pages 站点:官网在根、 - 文档站在 /docs/ 下。一个仓库在 Pages 上只有一个站点, - 拼装由 scripts/build-pages.mjs 完成,见 §4.4「官网与文档站怎么发布」。 - docfx.json / toc.yml(顶栏)+ 各章节自己的 toc.yml(侧栏) - get-started/ app/ management/ dev/(四章内容) - management/{production,linux-deploy,mysql}.md(部署与换库) - dev/winisland-design-tokens.md - images/(**展示图**:文档站 / README / 官网共用的同一份,1× 逻辑像素) - screenshots/(回归留档的截图:island / client / web-mobile / classisland, - 页面不展示;两者分工见 docs/dev/testing.md「截图留档」) - templates/classhelper/(模板覆盖:_master.tmpl + public/main.css + 图标) - build.mjs(构建,注入版本号与 404 的 base)/ serve.mjs(本地预览 :5181) - _site/(构建产物,gitignored) - ⚠ 站点的顶栏版本徽标取自根 package.json(build.mjs 用 --metadata 注入), - 不要在文档里再写一份版本号。 - ⚠ 页面里的照片一律指向 images/ —— 它就是 README 与官网吃的那一份。 - 别再往 screenshots/ 引图:那是冒烟脚本滚动的留档,随时被下一次验证覆盖。 - ⚠ 模板目录里的 logo.png / favicon.png 由 `pnpm icons` 派生(见 §5 第 62 条), - 别手工替换;改图标只改 build/classhelper.png 再跑 pnpm icons。 - ⚠ 文档站只发布 docs/ 这一棵树,页面里**不要**写指向 docs/ 之外的相对链接 - (如 ../AGENTS.md)—— 发布出去全是 404,要用 GitHub 的绝对 URL。 -website/ 产品官网(**纯静态、无构建步骤**,不进 pnpm workspace) - index.html(门面)/ download.html(下载页,首页的「立即下载」跳到它) - assets/{styles.css,motion.js,main.js,download.js,icon.png,shots/} - serve.mjs(本地预览)/ tools/capture-shots.mjs(实机图采集) - 404.html(**站点根** 404,含 {{BASE}} 占位符,由 build-pages.mjs 注入) - ⚠ serve.mjs 把 ../docs/_site 挂在 /docs/ 下,为的是让本地与线上同形 —— - 否则页脚那几个「文档站」链接在本地全是 404。文档站得先 pnpm docs:build。 - ⚠ 首屏标题第二行是 .reveal-grad:**它不参与逐字揭示** - (main.js 会跳过它)。拆成 per-char 的 inline-block 后每个字各自建一层合成, - 祖先的 background-clip: text 就穿不过去,渐变会整行失效。 - ⚠ assets/motion.js 是动效层:曲线与弹簧 token 逐条取自 beUI - (beui.dev/components/motion,见 docs/reference.md 的「官网」一节)。 - 改官网动效请改 token 表,不要在组件里另写 duration / cubic-bezier。 - ⚠ assets/shots/ 里全是**跑起来截的**实机图,重采集步骤见 - docs/dev/testing.md 的「截图留档」;客户端那两张要配合冒烟的 - ELECTRON_CLIENT_SHOTS_DIR / ELECTRON_SMOKE_ISLAND_* 开关。 - ⚠ download.html 的版本清单是**当场**读 GitHub Releases 的 - (assets/download.js):版本分段控件 → 一行状态 → 「装什么」分段控件 - (客户端 / 服务端 / 插件,**默认停在客户端**)→ 卡片。卡片是拉到数据之后 - 才建的,建完会派发一次 ch:content,由 main.js 补挂按压反馈 - (新增动态内容照这个来)。两个分段控件共用同一套指示器实现。 - 卡片排版照 ClassIsland 官网那页:图标 + 名称 + 系统要求 + 主按钮 + 变体下拉 - (
,点别处或 Esc 收起);插件那张只有一个文件,不摆点不开的箭头。 - 读不到时退回 并按发布命名约定拼直链 —— - 那个 meta 是 version.mjs 的落点之一,发版必须一起改。 - 命名约定与 README「开始使用」表里那三个名字要对齐 - (ClassHelper-<版本>-x64-client-setup.exe / -client-portable.exe / - -x64-server-setup.exe),它们是离线兜底链接的唯一依据。 -packages/shared/src/ types.ts / constants.ts / permissions.ts / motion.ts / utils.ts / index.ts -packages/server/ - prisma/schema.prisma 数据模型(29 个 model,无 enum、无 @db.*;连接串在 prisma.config.ts) - prisma/migrations/ 17 个迁移(到 add_schedule_source 为止是旧模型;之后 6 个是 - 2026-10-06 的角色与权限重构:add_student_table / add_student_grade_query / - add_grade_level / add_classhelper_heartbeat / add_archives / add_term_weeks; - 最后 1 个是 2026-10-10 的 add_permission_groups_and_clients: - 权限组 3 表 + AppSetting + 客户端设备/命令/安装包 + AiAudit + - Notification.assignDate + User.careMode) - prisma/seed.ts 种子数据(会清空业务表后重建演示数据) - prisma.config.ts Prisma 7 配置:schema / migrations / seed / 连接串 - scripts/verify-e2e.mjs 后端端到端验收(条数由脚本统计,2026-10-06 实测 205 项) - scripts/verify-classisland.mjs 联动链路验收(实测 44 项) - scripts/apply-column-migrations.cjs 旧库补列(手工覆盖 dist 部署时用) - src/app.ts Express 装配(探针 → 限流 → 模块挂载 → 静态托管 → 兜底) - src/index.ts 启动入口(自检 + HTTP + Socket.IO + 优雅退出) - src/config/env.ts zod 环境变量校验 + 生产自检 - src/lib/ access(RBAC) class-account db db-bootstrap http jwt logger mappers - password schemas session term version(读 package.json) web-static - src/middleware/ auth validate error security - src/realtime/ socket.ts(房间)+ bus.ts(事件总线) - src/modules/ 20 个功能模块 + registry.ts + module.types.ts - (含 archives 毕业归档、term 学期周次、permissions 权限组、 - clients 客户端心跳与远控、system 开发者模式/开机自启、ai 大模型助手) -packages/web-admin/src/ api stores router layouts views(18) components(5) composables styles config.ts - (含 ArchivesView 毕业归档、TermWeeksView 学期周次、PermissionsView 权限组、 - ClientsView 客户端设备、AssistantView/aiSettingsView 大模型助手) -packages/desktop-client/ - src/main/ 主进程:index / config(含作业短语与看板偏好)/ ipc / island / tray / update / logger / smoke - src/preload/ contextBridge 白名单桥(index.ts + island.ts,**不暴露 ipcRenderer 本体**) - src/island/ 灵动岛渲染进程:IslandApp.vue / spring.ts / squircle.ts / main.ts - src/renderer/ ClassHelper 班级端渲染进程:api / cache(IndexedDB) / stores / views / island / router - src/types/desktop.d.ts 主进程 ↔ 渲染进程契约 - scripts/ build-main / dev / smoke / dist-win -``` - ---- - -## 3. 本机环境(2026-09-30 实测) - -| 项 | 状态 | -| ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Node | **v22.15.0**(`D:\Node.js\node.exe`;根 `engines` 要求 ≥ 20.19,Prisma 7 的 engine 约束是 `^20.19 \|\| ^22.12 \|\| >=24.0`,v22.15 都满足) | -| pnpm | 11.8.0(`packageManager` 锁定 11.8.0)。本机可用的本体在 `C:\Users\hck16\AppData\Local\node\corepack\v1\pnpm\11.8.0\bin\pnpm.cjs`;PATH 里那个 `pnpm.ps1` 是 DSH 桌面端的转发壳,**别绕它**(见下) | -| npm registry | `https://registry.npmjs.org/`(仓库内无 `.npmrc`,取全局配置) | -| 依赖 | 已装(707 包,`pnpm install` 实测 2m45s) | -| 后端 `.env` | 已由 `.env.example` 生成(**gitignored**),`JWT_SECRET` 为本机随机值,`TERM_START_DATE=2026-09-07` | -| Prisma Client | 已生成到 `packages/server/src/generated/prisma`(gitignored) | -| 数据库 | SQLite `packages/server/prisma/dev.db`:10 个迁移已应用、种子已写入(用户 18 / 班级 3 / 课程 54 / 课表 93 / 作业 15 / 通知 12 / 成绩 90) | -| `shared/dist` | 已构建(`pnpm build:shared`)—— **它是三端与 seed 的前置产物,缺了会 `ERR_MODULE_NOT_FOUND`** | -| Electron 二进制 | 已手动装好(`node_modules/.pnpm/electron@44.3.0/node_modules/electron/dist/electron.exe`,246 MB) | -| .NET SDK | ✅ **8.0.425**(`C:\Program Files\dotnet\sdk`,`dotnet --list-sdks` 有输出)⇒ 插件可编译/打包;**别再照旧文档说"没装"**(`dotnet` 在 PATH 里,`--list-sdks` 才是权威判据) | -| ClassIsland | **2.1.0.1,装在 `D:\Classisland`**(启动器 `D:\Classisland\ClassIsland.exe`,实际进程 `D:\Classisland\app-2.1.0.1-0\ClassIsland.Desktop.exe`) | -| ClassIsland 数据 | 数据根 **`D:\Classisland\data`**:`Config/`、`Profiles/`、`Plugins/`(当前为空,尚未装本插件)、`Logs/`、`Cache/`、`Settings.json` | -| 桌面会话 | 有(`ClassIsland.Desktop.exe` 跑在 session 1);**Electron GUI 在 AI 会话里也能跑**(需先修完整性标签,见 §7 第 7 条) | - -> **旧文档里的 `F:\classisland` / `F:\data` 已失效**:本机只有 `C:` 与 `D:`,`F:` 盘不存在。 -> 另有一份旧副本在 `C:\Users\胡\Desktop\classisland`(数据在它自己的 `data/` 下,2026-09-18 用过),当前以 `D:\Classisland` 为准。 - -#### AI 会话里跑 pnpm:别撞上 DSH 的转发壳(2026-10-11 实测) - -在 DSH 的 shell 工具里直接写 `pnpm xxx` 会拿到 -`pnpm.ps1 : Node.js runtime not found. Please run DeepSeek Harness Desktop to install it first.`, -而同一台机器上人在普通终端里跑得好好的。原因与绕过办法: - -- PATH 里排在前面的是 **DSH 桌面端的转发壳** `C:\Users\hck16\AppData\Local\deepseek-harness\bin\pnpm.ps1` - (`.cmd` 同理)。它有两道会挡住的判据:① 认 `$env:DSH_NODE` / PATH 上的 `node`, - **但要求 `major ≥ 24` 或 `22.19+`**,本机的 `v22.15.0` 卡在门外; - ② 它自己的兜底 `%APPDATA%\dsh-tauri\runtime\node.exe` 与 bundled pnpm - `%APPDATA%\dsh-tauri\dependencies\pnpm\bin\pnpm.cjs` **本机都不存在**。 -- **正确解法是喂给这个壳两个环境变量**(它自己就认这两个覆盖开关,见 shim 第 10、52 行), - 把 `node` 与 `pnpm` 都指到本机真实的那份,然后就**照常写 `pnpm xxx`**: - - ```powershell - $env:DSH_NODE = 'D:\Node.js\node.exe' - $env:DSH_PNPM_BIN = 'C:\Users\hck16\AppData\Local\node\corepack\v1\pnpm\11.8.0\bin\pnpm.cjs' - pnpm typecheck # 与人在终端里跑完全等价 - pnpm build:shared - ``` - - - `DSH_NODE` 必须是**绝对路径**:壳拿到它就不再走"发现 PATH 上的 node 再卡版本"那段。 - - `DSH_PNPM_BIN` 是**本体 .cjs 的路径**(不是目录,也不是 `pnpm.ps1`);壳会 `& $node $pnpmBin @args` - 直接执行它。它同时替掉那条会失败的 `pnpmBin` 存在性检查。 - - **`pnpm` 的嵌套调用**(`typecheck` = `build:shared && pnpm -r --no-bail typecheck`)靠这两个变量就够了: - 子进程继承同一份环境,于是又落进同一个壳、又被同一对覆盖接住。 - - **这三条都是踩过的坑**(同一个会话里依次试错,前两条各失败一次): - - 不要用 `DSH_PNPM`,那是"用户 pnpm 的可执行路径":壳转发给它时**仍然先过 ② 的版本门槛**, - 而且**残留的 `DSH_PNPM` 会污染后面所有调用**——它与 `DSH_PNPM_BIN` 同时存在时壳会转发进 - corepack 的 `pnpm.ps1`,那个壳又把它重置回不存在的 bundled 路径,于是原本能跑的 - `typecheck` 报出 `[pnpm] pnpm not found`。要么别设,要么先 `Remove-Item Env:DSH_PNPM`。 - - 也不要用 `node - -