本文件是 xinghen.work 项目的长期协作规则、功能事实基线和变更确认记录。所有在本仓库中工作的开发者与 AI Agent 都必须遵守。
它的核心目的不是记录“做过什么动作”,而是保存:为什么这样改、功能实际确认到了什么程度、哪些判断以后不能在没有新证据时随意推翻。
AGENTS.md,子目录文件可补充局部规则,但不得绕过本文件的变更记录和验证要求。星痕·智能课程工坊是一个支持个人与组织工作区的课程生产系统。
以上是当前产品基线,不代表所有路径都已经完成端到端验证。实际确认状态以本文末尾的记录为准。
3001,通过外部反向代理网络提供域名和 HTTPS。src/:前端页面、组件、上下文、API 客户端和类型。server/:接口、数据库连接、迁移/建表和服务端业务逻辑。public/:公开静态资源。uploads/:本地上传目录;业务文件不得当作源码处理。deployment/、deploy.sh、docker-compose*.yml:部署入口和生产编排。docs/:面向项目的专项说明。以下判断是修改相关功能时必须主动核对的约束。若确需改变,应先说明影响,再在变更记录中写明新结论替代了哪一条。
课程、积分、账单、成员权限等数据都必须能追溯到个人或某个组织工作区。不得仅靠前端当前选中的工作区推断服务端数据权限;服务端必须校验登录身份、成员关系和角色。
已有课程默认继续属于个人工作区,已有个人积分继续属于个人账户。旧的用户“单位”资料不是组织成员关系,不能据此自动建立权限。
组织停用后,应阻止新成员加入和新建组织课程;历史课程、成员、账单与审计信息必须保留。恢复组织后仍应能继续使用已有数据。
积分余额变化必须有流水记录。普通成员只能看到其本人课程产生的组织积分消耗;组织管理员可以查看组织完整账单。调整余额时不能只改余额而不留流水。
上传素材和交付文件必须通过鉴权接口读取。不得重新开放可猜测的 /uploads/ 公共静态路径。新增下载、预览或图片展示能力时,必须验证未登录、无成员关系和跨组织访问均会被拒绝。
组织管理员现场创建老师账号时,当前初始密码为 888888,首次登录必须修改密码。前端提示不能代替服务端限制;任何绕过强制改密的业务接口都属于缺陷。
服务启动会执行建表/迁移逻辑。修改 server/schema.sql、数据库初始化或查询代码时,必须考虑已有生产数据库,而不是只验证空库。危险或不可逆的数据迁移必须提供备份、回滚或明确的人工步骤。
部署和同步操作必须保护 .env、上传文件以及 PostgreSQL/上传持久卷。使用删除式同步、重建容器或修改挂载前,要先核对真实目标和持久化边界。容器启动成功不等于新代码已部署成功。
涉及部署时,至少区分并记录:本地构建通过、镜像构建通过、容器健康、HTTPS/反向代理正常、真实页面/API 可用、新版本特征可见。未完成最后几项时不得声称“已上线”。
依赖管理以 pnpm 为准:使用 pnpm install / pnpm install --frozen-lockfile 安装依赖,提交 pnpm-lock.yaml 与 pnpm-workspace.yaml,不得提交 package-lock.json。新增或升级依赖后必须刷新 pnpm-lock.yaml 并验证 pnpm install --lockfile-only --frozen-lockfile 通过;.gitignore 已忽略 package-lock.json。
.env 或用户上传数据。npm run checknpm run buildC-YYYYMMDD-NN;同一天从 01 递增。已确认:目标用户路径已通过实际功能验证,并有可复述的结果。部分确认:只完成了部分层级,例如类型检查/构建通过,但没有完成浏览器、接口、权限、数据库或公网验证。未确认:实现或现状存在,但没有足够证据证明功能成立。失败:验证明确未通过,必须保留失败现象和后续处理建议。已替代:历史结论已被后续编号明确取代;旧记录仍保留。复制以下模板到本文件末尾并填写,不保留空泛占位文字:
### C-YYYYMMDD-NN|简短标题
- 日期:YYYY-MM-DD
- 类型:功能 / 修复 / 重构 / 数据库 / 部署 / 文档 / 仅决策
- 需求与原因:用户要解决什么问题,为什么需要修改。
- 修改范围:列出实际修改的功能、接口、数据结构和关键文件。
- 保留/新增判断:引用既有 `D-xxx`;若形成新判断,写清适用边界。
- 功能确认状态:已确认 / 部分确认 / 未确认 / 失败 / 已替代
- 验证证据:执行了什么检查,验证了哪些用户路径,实际结果是什么。
- 未确认与风险:明确尚未验证、环境受限、兼容性或回滚风险;没有则写“无已知未确认项”。
- 后续修改提示:未来改到本功能时必须先确认什么。
- 关联:相关提交、任务、Issue 或被替代的记录编号;没有则写“无”。
不合格:
完成了组织功能,测试正常。
合格:
新增组织停用接口,并在创建组织课程和审批成员接口中校验组织状态。
npm run check、npm run build通过;使用管理员账号确认停用后两个接口均返回 403,恢复后可再次创建课程。未验证生产数据库上的旧组织数据,因此状态为“部分确认”。
根据改动范围选择并记录,不要求与本次无关的验证,但不得省略相关层级。
| 改动范围 | 最低验证要求 |
|---|---|
| 纯文案/样式 | 目标页面实际查看;桌面端和受影响的窄屏布局;构建 |
| React 页面/状态 | 主路径、空状态、加载、错误、刷新后状态;类型检查与构建 |
| API 客户端/服务端接口 | 成功请求、参数错误、未登录、无权限、资源不存在 |
| 登录/权限 | 不同角色、首次改密、跨用户/跨组织越权、会话失效 |
| 组织/课程/积分 | 个人与组织归属、角色可见性、余额与流水一致性、组织停用状态 |
| 上传/下载 | 文件类型与大小、鉴权访问、跨组织拒绝、文件不存在、持久化 |
| 数据库结构 | 空库初始化、已有库升级、重复执行、约束、备份/回滚考虑 |
| 部署 | 脚本语法、Compose 配置、镜像、容器健康、HTTPS/API、版本特征 |
uploads/ 中的文件属于业务数据,不得为了测试批量删除或纳入版本控制。一次代码任务只有同时满足以下条件,才能向用户报告完成:
如果因环境、账号、生产权限或外部依赖无法完成验证,应报告“部分确认”或“未确认”,说明缺口,不得用构建通过代替功能完成。
变更索引(按编号顺序):C-01 协作规范 · C-02 蓝色品牌色 · C-03 扁平按钮 · C-04 课程时间排序 · C-05 数字讲师资产库 · C-06 统一创建入口 · C-07 禁止删除已用讲师 · C-08 创建流程优化 · C-09 手机拍照录音 · C-10 独立创建编辑页 · C-11 搜索备注收起 · C-12 照片参考样例 · C-13 待制作队列 · C-14 取消制作详情页 · C-15 平台/用户隔离 · C-16 讲师默认所有者 · C-17 顶部栏管理员匹配与千分位。 验证状态:C-01 已确认;C-02/C-03 为样式“部分确认”;C-04~C-16 均为功能/修复“部分确认”(仅过 check/build 及接口隔离测试,未做公网与生产库验收)。
AGENTS.md;整理当前产品与技术基线、九条关键判断、修改前后流程、验证矩阵、记录状态和统一追加模板。README.md、package.json、docker-compose.prod.yml、.gitignore 和工作区文件结构核对文档中的项目名称、技术栈、常用命令、生产端口、持久卷及主要功能描述;确认本次只新增该文档,未修改现有业务代码。#55B0F7;调整 src/styles.css 的主色、悬停色、蓝色浅底、边框、阴影和常用状态色,并同步课程、首页与管理台组件中的蓝色行内样式。#55B0F7;浅蓝背景上的按钮文字使用深蓝,交互悬停使用较深蓝与白字以保持辨识度。npm run check、npm run build 通过;浏览器检查了本地营销首页桌面和窄屏呈现,主按钮、标题强调及进度条已显示新蓝色。仓库检索未发现主要旧蓝色值残留。--blue、--blue-dark 和对应蓝色浅底;在有文字的浅蓝按钮上检查对比度。进入登录后页面或部署时继续做实际页面核对。src/styles.css 的主按钮、强调按钮、次按钮、导航和选中态。品牌蓝 #55B0F7 继续用于视觉强调;承载白字的操作按钮改用同色系深蓝 #0875BD,悬停色为 #075F9B;移除按钮阴影与上浮效果,圆角收至 6px,增加键盘焦点轮廓。npm run check、npm run build、git diff --check 通过;本地浏览器查看营销首页,主按钮呈现为平面深蓝底白字,导航注册按钮与标题强调有明确层级。.primary-button、.secondary-button,品牌蓝 --blue 用于强调,按钮面使用 --blue-action;勿恢复主按钮阴影和上浮位移。server/db.ts(mapCourse 增加 updatedAt)、server/organizations.ts(补充 c.updated_at 与 c.created_at 查询字段)、server/index.ts(资产上传/删除同步触发 courses.updated_at = NOW())。src/types.ts(Course 增加 updatedAt?: string)、新增 src/utils/date.ts 规范化时间格式化。src/pages/OrganizationManage.tsx:课程管理表格增加“最后更新时间”与“创建时间”两列,支持表头点击升降序排序与下拉框快捷切换;src/pages/Dashboard.tsx:课程卡片底部展示创建日期与最后更新时间,并在顶栏支持按更新时间/创建时间升降序排序;src/pages/OrganizationPage.tsx:课程进度表格增加最后更新时间与创建时间列,并支持表头点击排序与下拉选择;src/pages/CourseWorkspace.tsx:工作区头部标题栏下方展示创建时间与最后更新时间;src/pages/admin/AdminCourses.tsx 与 AdminOrganizations.tsx:补充创建时间与最后更新时间列与详情展示。updated_at;排序默认采用最后更新时间最新优先(降序)。npm run check(TypeScript 类型检查无报错)、npm run build(Vite 生产构建成功,无模块冲突)。created_at、updated_at 字段已在 courses 表中存在),无数据迁移风险;公网尚未部署。updated_at = NOW()。digital_instructors、digital_instructor_files、digital_instructor_events 及课程讲师引用和提交快照;增加幂等旧讲师迁移;新增资产库、统一选择器、个人复制到组织、所有者转移、文件补充/替换、平台管理员维护入口;课程创建、编辑和模板流程改用工作区讲师,移除旧课程级讲师写入接口;更新文件鉴权、提示和帮助文案。核心文件为 server/schema.sql、server/digital-instructors.ts、server/index.ts、server/db.ts、src/components/DigitalInstructorPicker.tsx、src/pages/DigitalInstructorsPage.tsx、src/pages/admin/AdminDigitalInstructors.tsx、src/pages/NewCourse.tsx、src/pages/CourseWorkspace.tsx、src/api.ts。npm run check、npm run build、git diff --check 通过;在独立 PostgreSQL 测试库执行空库建表、插入旧课程讲师后重复运行迁移,旧讲师只迁移一次并关联旧课程;隔离服务中实际验证资料不全创建和选课、提交返回 409、补齐文件后提交成功;验证组织成员查看及选择、非所有者编辑返回 403、组织管理员和平台管理员编辑成功、所有者转移后新所有者可编辑、跨工作区选课返回 400、未登录及越权文件访问返回 401/403、个人讲师复制后照片和录音文件名均独立;删除已提交课程引用的讲师后,资产库隐藏但历史快照仍可读取。浏览器查看个人资产库、课程创建选择器及平台管理员组织讲师维护页。src/components/DigitalInstructorPicker.tsx 移除内嵌创建流程,改为仅选择并提供资产库入口;src/pages/DigitalInstructorsPage.tsx 整理独立创建表单、搜索、空状态和工作区标题,移除组织权限说明文案,并避免创建与编辑表单同时显示;src/pages/CourseWorkspace.tsx 更新选择文案;src/styles.css 补齐页面样式;src/pages/OrganizationPage.tsx、src/pages/OrganizationManage.tsx 将课程排序 useMemo 移到条件返回之前,修复状态变化时的 Hook 调用顺序问题。npm run check、npm run build、git diff --check 通过;本地浏览器检查数字讲师、组织页面及组织管理页面,组织课程、成员、积分、邀请和管理区块正常展示;独立 PostgreSQL 测试库实际验证未登录、成员、管理员和非成员的访问边界,申请及审批、邀请加入及停用、成员创建和改名、首次改密限制、积分调整与流水可见性、组织停用和恢复后的接口行为,断言均通过。测试库已删除。server/digital-instructors.ts 删除时在事务中锁定讲师并检查全部课程引用,命中时返回 409;新增同工作区复制接口,复制讲师资料和独立上传文件,记录来源与事件,复制件默认归操作人所有;src/api.ts 增加复制客户端;src/pages/DigitalInstructorsPage.tsx 和 src/pages/admin/AdminDigitalInstructors.tsx 增加复制操作。npm run check、npm run build、git diff --check 通过;独立 PostgreSQL 测试库验证复制后 ID、来源、所有者及照片文件路径不同,未登录复制返回 401,草稿和已完成课程引用均阻止删除,未使用的复制件可以删除;本地浏览器确认资产库中出现“复制讲师”按钮。测试库已删除。src/components/DigitalInstructorCreateForm.tsx,将创建流程分为讲师资料、照片、录音三个区块,增加拍摄/录音提示、格式大小校验、照片预览和录音试听;调整资产库和平台管理页的编辑字段为名称及备注;更新 src/api.ts、src/types.ts 和 server/digital-instructors.ts 的输入输出与复制逻辑;server/schema.sql 新增备注列并把旧简介一次性迁入备注,新增组织内名称去重触发器,保留历史重名数据;src/styles.css 补齐表单与窄屏导航样式。npm run check、npm run build、git diff --check 通过;独立 PostgreSQL 测试库验证组织同名创建/改名/个人复制入组织返回 409、组织内复制自动递增副本名、个人库不受组织重名限制、备注字段正确返回;重复执行建表脚本成功;模拟旧库缺少备注列后重跑迁移,旧简介保留到备注。浏览器查看桌面创建表单、输入已有名称的即时提示和禁用保存,390px 视口下无页面横向溢出。测试库已删除。organization、introduction 数据列暂留作兼容,不再出现在新讲师界面。src/components/DigitalInstructorCreateForm.tsx 的照片选择改为分批追加、去除重复选择、单张移除和最多 19 张限制;窄屏提供后置摄像头拍照入口;新增基于浏览器 getUserMedia 和 MediaRecorder 的录音、计时、结束、取消、试听与重录流程,并在离开表单时释放麦克风。server/digital-instructors.ts 接受浏览器录音常见的 WebM、Ogg MIME 类型;讲师编辑及平台管理页的文件选择同步允许这些格式;src/styles.css 调整桌面与手机上传布局。npm run check、npm run build、git diff --check 通过;390px 浏览器视口下显示拍照入口,页面无横向溢出;通过浏览器文件选择器先选两张测试照片,再从拍照入口追加第三张,已选数量由 2 变为 3 且前两张仍在;独立 PostgreSQL 测试库验证 multipart 请求保存两张照片及一份 WebM 录音,讲师返回素材齐备。测试库已删除。src/pages/DigitalInstructorEditorPage.tsx、src/components/DigitalInstructorCreateForm.tsx、src/pages/DigitalInstructorsPage.tsx、src/pages/admin/AdminDigitalInstructors.tsx、server/digital-instructors.ts、src/api.ts、src/types.ts、src/App.tsx 和 src/styles.css。npm run check、npm run build、git diff --check 通过;独立 PostgreSQL 测试库及本地服务验证两张照片加录音创建、非所有者编辑返回 403、组织管理员一次编辑姓名备注及所有者并移除一张旧照片、增加封面照片和更换录音、课程引用数量变化、已使用讲师删除返回 409;平台管理员管理接口及引用查询通过。浏览器查看资产卡片、课程使用弹层、操作菜单、独立创建与编辑页,390px 视口无横向溢出且底部操作可见。测试服务已停止,测试库已删除。src/pages/DigitalInstructorsPage.tsx 将搜索和素材状态筛选折叠到搜索按钮,筛选生效时在按钮上提示并提供清除操作;src/components/DigitalInstructorCreateForm.tsx 将备注改为按需展开,已有备注在编辑时自动展开;已保存和待上传照片均支持点击查看大图及 Escape 关闭;src/styles.css 补充搜索面板、按钮、完整照片缩略图和全屏预览的桌面/手机样式。npm run check、npm run build、git diff --check 通过;浏览器确认资产库搜索默认收起、点击展开并聚焦输入框;编辑页照片点击后出现完整大图及关闭按钮;创建页备注默认收起,点击后出现文本框;390px 浏览器视口查看资产库搜索入口和创建页布局,无明显横向溢出。src/components/DigitalInstructorCreateForm.tsx 增加照片样例入口和弹层、固定朗读稿、获取麦克风后 3 秒倒数、实际开始录制后计时及 10 秒绿色时长提示,倒数期间可取消并释放麦克风;src/styles.css 增加样例、朗读稿与录音状态样式;public/guides/digital-instructor-photo-reference.png 保存用户提供的示例材料,弹层按适合和不适合两张画面展示,并可展开完整说明。MediaRecorder;10 秒绿色仅说明本次录音时长达到提示线,不代表声音质量或未来 AI 检验合格,也不新增服务端时长限制。用户仍可上传现有录音文件。npm run check、npm run build、git diff --check 通过;本地浏览器确认创建页出现样例入口和朗读稿,点击样例后显示照片对照;390px 视口查看两张对照照片可辨认、弹层可滚动,随后恢复默认视口。src/pages/admin/AdminProductionQueue.tsx 与 /admin/production-queue 路由;管理导航增加“待制作课程”,概览入口指向队列;队列逐课展示提交人、工作区、提交时间、分集与讲稿数量、素材文件下载、提交时的数字讲师快照照片和录音文件;src/styles.css 增加桌面与窄屏布局。WAITING_PRODUCTION 状态;讲师资料以提交快照为准;文件继续通过鉴权接口下载,不公开静态文件路径;完整制作操作仍进入现有课程详情页。npm run check、npm run build、git diff --check 通过;核对现有管理员课程列表接口按状态过滤且由 adminAuth 保护,文件接口校验登录及管理员身份。未改服务端或数据库结构。/admin/courses/:id 平台路由及各管理页面对此路径的链接;待制作队列卡片加入完整讲稿、提交人资料,以及接单操作;同页增加“制作中”切换、交付文件上传与下载、实际积分调整和完成交付。全部课程页保留为只读总览。AdminCourseDetail 组件仍供组织管理员课程路径使用,不作为平台管理员页面。/admin/production-queue,受原管理员接口鉴权约束。替代 C-20260923-13 中“完整制作操作仍进入现有课程详情页”的判断。npm run check、npm run build、git diff --check 通过;检索确认平台管理端没有残留指向 /admin/courses/:id 的链接,组织管理路径仍引用原组件。platform_admins、platform_admin_sessions 独立表;后台登录和后台鉴权改用独立管理员会话;普通登录只使用 users 与普通会话;启动迁移旧 users.role='ADMIN' 记录并保留管理员审计归属;数据库触发器禁止普通表出现非 USER 角色,也禁止管理员表与普通用户手机号重叠;移除用户管理中的升降级操作;管理员操作的审计、积分流水、组织审批和数字讲师事件增加管理员归属字段;平台管理员不能创建普通用户数字讲师资产。users 只保存普通用户,platform_admins 只保存平台管理员;平台管理员与普通用户没有角色转换接口、共享会话或共享手机号;旧管理员若拥有课程、组织成员关系或数字讲师业务数据,启动迁移会停止并要求人工处理,不自动删除或转移业务归属。npm run check、npm run build、git diff --check 通过;隔离 PostgreSQL 库验证普通注册、管理员注册手机号拦截、普通登录、管理员登录、交叉登录拒绝、普通会话访问 /api/me、管理员会话访问 /api/me、普通会话访问后台返回 401、管理员访问后台统计/课程/用户/组织/数字讲师接口成功、管理员退出后会话失效、角色转换接口返回 403;实际操作验证用户创建/重置密码、组织创建/更新、成员授权/移除、组织积分、申请审批、课程制作状态/积分/交付/结单、数字讲师查看/编辑/复制/删除;浏览器实际验证管理员登录后台、管理员退出、普通用户登录课程首页;迁移库验证旧管理员从 users 迁入 platform_admins 且普通用户表不再保留管理员记录;数据库触发器验证三种手机号/角色越界写入均被拒绝;生产模式服务启动成功。adminAuth,普通用户接口不得接受管理员会话;审计、积分流水、审批和资产事件涉及操作人时应分别写入 admin_actor_id 或普通用户字段;不得恢复 users.role='ADMIN' 或升降级接口。生产升级前备份数据库和上传目录,并先核对旧管理员是否拥有业务数据。src/components/DigitalInstructorCreateForm.tsx 确认创建时不展示所有者字段;编辑组织讲师时,非管理员显示只读禁用的所有者姓名并提示仅管理员可修改,管理员提供成员下拉框支持更换;保存时非管理员不传 ownerId;src/pages/DigitalInstructorEditorPage.tsx 补充系统管理员与组织管理员判断并传递 canManageOwner;src/styles.css 补充输入框禁用样式。服务端原有时序与权限(创建直接设为创建者自身、编辑非管理员改所有者报 403)保持生效。uid(req);编辑时所有者修改权严格限定为组织管理员及平台管理员,普通所有者编辑时字段只读。npm run check、npm run build、git diff --check 全部通过。src/components/Layout.tsx:将 isOrgManager 从全局匹配任一组织角色(organizations.some)改为仅匹配当前工作区(currentOrganization?.role === 'MANAGER');若处于个人工作区或非管理员组织,则不渲染管理员徽标;src/components/Layout.tsx:积分徽章文案更新为“积分余额:xxx”,并使用 .toLocaleString('zh-CN') 统一千分位逗号展示;src/pages/OrganizationManage.tsx:组织积分余额数字同样增加 .toLocaleString('zh-CN') 格式化,保持全站一致。npm run check、npm run build、git diff --check 全部通过。npm run check、npm run build、git diff --check 通过;核对原上传接口支持 multipart 多文件、每次最多 20 份且单份最大 200 MB。src/components/CourseAssetPreview.tsx 新增全屏预览,图片按原比例展示、视频使用浏览器播放控件、PDF 逐页渲染,PPTX 与 Word 调用鉴权转换接口后逐页渲染;分集和通用素材列表增加预览及下载入口,文件选择器标明支持格式。server/index.ts 新增课程素材鉴权预览接口,LibreOffice 按需转 PDF 并在私有上传卷缓存,删除素材或课程时清理对应缓存;Dockerfile 安装 LibreOffice 和中文字体。src/components/LectureNotesDialog.tsx 与 src/styles.css 修复弹窗整体白色容器、滚动和窄屏布局,增加实时字数与按每分钟约 260 字计算的时长;src/api.ts 的组织课程预览请求携带当前工作区标识。.previews 目录,不开放公共静态路径。时长仅为按字数估算,不代表成片实际时长。npm run check、npm run build、git diff --check 与本地 Docker 镜像构建通过;在隔离数据库、服务和上传目录中批量上传 PPTX 与图片,另上传 PDF、DOCX、MP4。PPTX 转换得到 6 页 PDF,浏览器实际从第 1 页翻到第 2 页;PDF 同样翻页,Word 显示转换后页面,图片全屏展示,MP4 显示播放控件;生产镜像内分别实际转换 PPTX 和 DOCX 并返回 PDF。未登录、其他用户、目标素材不存在分别返回 401、403、404;组织课程正确工作区访问返回 200,其他组织工作区返回 404,即使指定目标工作区但不属于该组织也返回 403。浏览器确认讲稿弹窗各区域在同一白色容器内,输入后字数与时间实时变化,取消后文字未保存。server/index.ts 的 LibreOffice 调用支持 SOFFICE_PATH,并在本机运行时提供现有依赖路径作为回退;新增 server/pptx-notes.ts 读取 PPTX 备注正文;上传单份 PPTX 时,对指定分集或仅有一个分集的课程,在口播稿为空时写入逐页备注。上传结果返回导入页数,课程详情显示导入提示。增加 jszip、fast-xml-parser 依赖。npm run check、npm run build、git diff --check 通过。SOFFICE_PATH;如需覆盖已有口播稿,应增加用户确认与预览差异。处理特殊 PPTX 时应按 presentation 关系映射真实页序。Dockerfile 在安装 LibreOffice 后检查 /usr/bin/soffice 可执行;docker-compose.prod.yml 显式设置 SOFFICE_PATH;server/index.ts 在 macOS 转换时提供系统中文字体目录与微软雅黑到冬青黑体的替代配置,预览缓存升级为 v2 以跳过已生成的缺字 PDF,删除素材时一并清理两版缓存;src/components/CourseAssetPreview.tsx 与 src/styles.css 加入预览差异提示。linux/amd64 架构构建生产镜像成功;容器中 /usr/bin/soffice --version 正常,fc-match 微软雅黑 匹配 Noto Sans CJK;以用户 PPTX 在容器内转换成功,PDF 第一页正文完整可见且文字层含中文。本机原配置生成的 PDF 中文缺失;显式加载 macOS 字体目录与替代规则后,同文件转换的第一页中文完整可见。package-lock.json(npm)与未跟踪的 pnpm-lock.yaml / pnpm-workspace.yaml,包管理器混用会导致依赖不一致与部署失败。用户确认统一使用 pnpm。package-lock.json;将 pnpm-lock.yaml 与 pnpm-workspace.yaml 纳入版本控制;.gitignore 新增忽略 package-lock.json;本文件新增 D-010 工程判断。pnpm-lock.yaml,并用 pnpm install --lockfile-only --frozen-lockfile 校验一致。pnpm install --lockfile-only --frozen-lockfile 返回 exit 0,确认 pnpm-lock.yaml 与当前 package.json 完全一致;npm 阶段新增的四个依赖(jszip、fast-xml-parser、qrcode、@types/qrcode)均已存在于 lock 的 packages 与 importers 段。corepack enable 或全局安装 pnpm)。node_modules 当前仍为 npm 格式,首次 pnpm install 会将其转为 pnpm 符号链接结构,属本地可逆变更。pnpm-lock.yaml 已更新且 package-lock.json 不出现在 git status。src/pages/OrganizationPage.tsx 将管理员视角的课程名称改为课程详情链接;点击时切换到该机构工作区。/courses/:id 路由和课程详情接口允许所属组织管理员访问;npm run check、npm run build、git diff --check 通过。landing.html 与 src/landing/(main.tsx、LandingPage.tsx、landing.css);vite.config.ts 改为双入口构建;生产服务端新增 /lp、/lp/* 显式路由,返回 dist/landing.html。server/schema.sql 新增 lead_requests 表(姓名/手机/微信/需求类型/体量/描述/来源/状态/备注/关联用户/时间)。server/index.ts 新增 POST /api/public/leads(公开提交、按 IP 60 秒限流、同手机号 24 小时内合并更新、可选密码注册)、GET /api/admin/leads、PATCH /api/admin/leads/:id(均 adminAuth)。src/pages/admin/AdminLeads.tsx、/admin/leads 路由与「落地页线索」导航项;src/api.ts 增加 admin.listLeads / admin.updateLead;src/types.ts 增加 Lead / LeadStatus;src/styles.css 仅新增 .status-filter-select 与 .lead-message 两个类。lp- 前缀);线索属公开写入数据,必须限流并在服务端剥离 HTML;注册只能由用户显式填写密码触发,不得自动建号;同手机号 24 小时内重复提交按更新处理,避免线索重复。npm run check、npm run build 通过,产物含独立 dist/landing.html 与 landing-*.css|js,主站 dist/index.html 不引用任何 landing 资源(grep 计数 0)。以 NODE_ENV=production 启动本地服务(端口 3011)实测:/lp 与 /lp/anything 返回 200 且标题为「数字人课程制作 · 星痕智能课程工坊」,/dashboard 仍走主站 SPA 兜底;POST /api/public/leads 正常 201、非法手机号 400、缺微信号 400、同 IP 60 秒内 429、带密码提交返回 token 且用该 token 调 /api/me 返回 200(姓名与微信号已写入)、同手机号重复注册 409;同手机号 24 小时内再次提交经 SQL 核对仍为 1 行且字段为最新值;<script>alert(1)</script><b>加粗</b> 落库为「加粗正常内容」。GET /api/admin/leads 管理员 200 且带 userName,未登录与普通用户 token 均 401,PATCH 更新状态与备注返回 200。测试数据已清理(3 个测试账号、5 条线索、1 条临时管理员会话)。/lp 依赖 NODE_ENV=production 分支,本地开发需访问 /landing.html;lead_requests 建表仅在本地 dev 库执行过,未在生产库验证(语句为 CREATE TABLE IF NOT EXISTS + ADD COLUMN IF NOT EXISTS,可重复执行,但未经生产库实测);本次未新增依赖,pnpm-lock.yaml 无需变更,验证使用的是 npm scripts 而非 pnpm。src/landing/ 与 landing.css,禁止 import 主站 styles.css 或 src/api.ts;新增线索字段须同步 leadSchema、mapLead、Lead 类型与后台表格;投放链接建议带 ?from= 或 utm_source 以保留渠道归因;对外微信号与二维码位于 LandingPage.tsx 的 WECHAT_ID 常量与 src/assets/contact-group-qrcode.jpg。currentTarget 而显示 reset 报错;用户要求老师首次登录直接进入工作区。src/pages/OrganizationManage.tsx 在异步请求前保存表单元素,成功后重置表单并更新提示;server/organizations.ts 创建老师时将强制改密标记设为 FALSE;server/index.ts 与 src/App.tsx 移除旧强制改密拦截;src/pages/Profile.tsx 保留自愿改密入口但移除强制提示;server/schema.sql 幂等清除已有账号的旧强制改密标记;同步更新 README.md。888888,首次登录可直接进入工作区;用户可在账户安全自行改密。服务端仍须校验登录身份及组织权限(D-001)。npm run check、npm run build、git diff --check 通过。迁移语句仅更新标记为 TRUE 的账号,重复执行不再产生变化。xinghen.work 可访问;授权必要时覆盖服务器数据库数据。Dockerfile 改为按 pnpm-lock.yaml 冻结安装、构建和启动;deploy.sh 同步时排除 uploads/;将当前代码构建为 linux/amd64 镜像并上传至 47.93.193.127:/opt/xinghen,重建应用容器。保留现有 PostgreSQL 与上传持久卷,未执行数据库覆盖。部署前数据库备份位于服务器 /opt/xinghen/.deploy/pre-deploy-20260923.dump。pnpm run check、pnpm run build、生产镜像构建、Compose 配置检查、脚本语法检查通过;新容器运行镜像 SHA 589c7600dc81faf8f563db789fa5fb6200fe56b862cd944434f6e08c52e4fddf。公网首页与 /lp 均返回 200,且返回本次构建的主站及落地页资源名;资源请求返回 200;/api/health 返回 200 与 {"ok":true};HTTP 跳 HTTPS 返回 301;证书 SAN 包含 xinghen.work,有效期至 2026-12-12;未登录访问 /api/me 与 /api/admin/leads 均返回 401。容器内 LibreOffice 可执行,上传卷仍挂载。迁移后普通用户 5 条、平台管理员 1 条、课程 5 条,lead_requests 表存在。 ;课程详情选择素材后还需再次点击上传。server/pptx-notes.ts 仅将备注中的十进制或十六进制换行字符引用转为真实换行;src/utils/lectureNotes.ts 对以“第 1 页”开头的旧版纯文本导入稿在展示、复制和打开编辑器时做相同规范化;src/pages/CourseWorkspace.tsx 选择分集素材或通用附件后立即上传,移除待上传清单及二次上传按钮,增加上传期间禁用重复选择,并在失败后刷新素材列表、提示核对后重选。pnpm run check、pnpm run build 通过;使用合成 PPTX 备注验证 &#10; 经解析变成实际换行,而无关的 &amp; 仍保持原文本;检查两处文件选择入口均直接调用上传函数,且上传期间禁用再次选择。fcf1e84;构建 linux/amd64 生产镜像并部署到 47.93.193.127:/opt/xinghen,仅重建应用容器,保留数据库和上传持久卷。重部署前备份数据库至 /opt/xinghen/.deploy/pre-redeploy-fcf1e84.dump。pnpm run check、pnpm run build、pnpm install --lockfile-only --frozen-lockfile、脚本语法检查、镜像构建通过;生产应用容器运行镜像 SHA cc0632a94d3520f130c26d5aec3ee9e2cf6b2ee092c3736d05ec97c0c3e53d9f,PostgreSQL 健康。https://xinghen.work/ 和 /lp 返回 200,首页引用本次构建的 main-8LOcnJ-3.js 且资源返回 200;/api/health 返回 200 与 {"ok":true},未登录后台接口返回 401,HTTP 返回 301 跳转 HTTPS,证书 SAN 包含 xinghen.work 且有效期至 2026-12-12。对照本次部署前备份,课程记录部署前后均为 4 条;普通用户 5 条、平台管理员 1 条;上传卷仍挂载。fcf1e84。Cannot read properties of null (reading 'reset') 并在错误栏显示红字;排查确认系 React 异步事件处理函数中在 await 之后直接访问 e.currentTarget.reset() 导致(恢复执行时 SyntheticEvent 的 currentTarget 已被清空为 null);同时全面排查项目中其他表单提交逻辑与异步事件处理。src/pages/admin/AdminOrganizations.tsx 在 await 前将 e.currentTarget 赋值给本地变量 formElement,成功后调用 formElement.reset();在 create、setMember、points、rename、updateSlug、review 等表单与操作提交前统一清空旧的 error 和 message 状态;优化成员与积分表单成功后的重置体验。reset() 时,必须在首个 await 之前保存 DOM 元素引用。src/ 下所有 .reset()、currentTarget、target 及 onSubmit 异步函数,确认无其他类似跨 await 访问空引用的隐患;pnpm run check(tsc -b)通过,pnpm run build(vite build)通过。await 之前提取数据和保存表单引用(或使用受控组件/React Hook Form)的范式。17b3aff;构建 linux/amd64 镜像并部署到 47.93.193.127:/opt/xinghen,仅重建应用容器;部署前数据库备份在 /opt/xinghen/.deploy/pre-redeploy-17b3aff.dump,保留 PostgreSQL 和上传持久卷。pnpm run check、pnpm run build、git diff --check 与生产镜像构建通过;生产容器运行镜像 SHA dcc9fe5ed14c0084c1e15620749d27d8a4bf7ec865a3e84bbc0a483e9fc36596,PostgreSQL 健康。公网首页与 /lp 均返回 200,首页引用新版 main-B1p2drP9.js 且资源请求返回 200;/api/health 返回 200 与 {"ok":true},未登录后台接口返回 401,HTTP 返回 301 跳转 HTTPS,证书 SAN 包含 xinghen.work 且有效期至 2026-12-12。课程部署前后均为 4 条,普通用户 5 条、平台管理员 1 条;上传卷仍挂载。17b3aff。docs/现场演示-slides.html,包含 9 页可键盘、按钮和触屏翻页的单文件幻灯片;内嵌现有联系二维码与纯粹星辰官方 SVG Logo;采用主站品牌蓝、宋体标题、浅灰背景及扁平卡片。git diff --check 通过。file:// 页面被浏览器工具安全策略阻止,尚未完成桌面及窄屏目视放映;正式演示前须实际打开幻灯片检查排版,并准备已制作课程样片。output/pdf/星痕现场演示.pdf,共 9 页 16:9 横版;沿用幻灯片的内容、品牌配色、纯粹星辰 Logo 与联系二维码。pdfinfo 确认 9 页、960×540 pt;逐页渲染 PNG 并检查九宫格缩略图,中文字体、Logo、二维码和卡片均可见;pypdf 确认标题、逐字稿与联系方式可提取。output/pptx/星痕现场演示-可编辑版-v2.pptx,沿用 9 页 HTML/PDF 的内容、配色与纯粹星辰 Logo;标题、正文、卡片和色块保留为原生可编辑幻灯片元素,Logo 与二维码保留为图片素材。src/components/CourseContentView.tsx,在成员课程内容页及新平台单课制作页共用分集、讲稿、分集素材、通用备注与素材、数字讲师展示;成员提交后仍可查看课程内容。新增 src/pages/admin/AdminProductionWorkspace.tsx 与 /admin/production-queue/:id 路由,将接单、联系人、实际积分、按分集上传及删除交付文件、交付说明和结单放入独立制作操作区。制作队列改为简要入口;全部课程增加单课查看入口;调整桌面和窄屏样式。pnpm run check、pnpm run build、git diff --check 通过;用同一份模拟课程数据在本地浏览器打开成员与平台管理员页面,核对分集顺序、讲稿、每集素材、通用备注和讲师资料相同;桌面及 390px 窄屏目视检查,修复窄屏两列挤压问题;管理员制作进度页签可切换,交付区与课程内容视觉分离,未上传文件时结单按钮禁用。src/pages/CourseWorkspace.tsx 将制作中状态面板移动到“课程内容”页签的最前面,课程内容仍在其后;src/components/CourseContentView.tsx 移除数字讲师录音文件名与下载按钮,保留鉴权试听;src/styles.css 清理不再使用的录音文件名样式。共用内容组件的改动同时作用于成员与平台管理员页面。pnpm run check、pnpm run build、git diff --check 通过;在本地已登录的真实课程页面目视确认“制作中”卡片位于课程分集内容之前,数字讲师卡片只显示“试听”按钮,页面可访问性树中不再出现录音文件名和录音下载按钮。server/index.ts 新增仅平台管理员可调用、仅 IN_PRODUCTION 可写的课程资料、分集增删改、通用与分集素材上传删除、数字讲师快照同步及本课程专用编辑接口;server/schema.sql 新增 course_instructor_files 保存本课程专用照片和录音元数据,并接入鉴权文件读取;src/api.ts、src/components/AdminCourseContentEditor.tsx、src/pages/admin/AdminProductionWorkspace.tsx、src/styles.css 增加后台编辑模式。课程预览仍使用成员与平台共用的 CourseContentView。分集删除前校验至少保留一集及无关联交付文件;增删分集重算预计积分,不改实际积分或已扣流水。pnpm run check、pnpm run build、git diff --check 通过;独立 PostgreSQL 测试库从空库启动并重复执行建表脚本通过。实际 API 验证管理员修改课程资料、分集、素材上传,成员刷新读取相同结果;未登录及普通成员无法调用后台编辑接口,其他个人账号无法读取文件;非法分集、唯一分集删除、错误讲师照片 ID、完成课程编辑均被拒绝。测试本课讲师名称、备注、照片和录音替换后成员可读取,资产库原讲师未变;重新同步后本课专用文件元数据被清理。隔离浏览器中核对后台编辑入口、保存回显、讲师编辑和预览,以及成员端显示管理员修改的讲师备注。src/pages/CourseWorkspace.tsx 将已完成课程的交付提示和等待制作课程的排期提示移到共用课程内容视图之前;此前已前置的制作中提示保留原位。草稿和驳回状态目前没有独立状态提示框,本次未新增文案或状态流程。pnpm run check、pnpm run build、git diff --check 通过;独立测试库中的成员课程分别设为等待制作、制作中、已完成,浏览器可访问性树均显示状态提示标题在“课程分集与素材”之前。制作中课程还核对了讲师备注仍与后台保存结果一致。server/index.ts、server/schema.sql 增加平台管理员制作中课程编辑接口、课程讲师文件鉴权与元数据结构;src/components/AdminCourseContentEditor.tsx、src/components/CourseContentView.tsx、src/pages/admin/AdminProductionWorkspace.tsx 提供编辑器、共用视图和制作工作区;更新 src/App.tsx、src/api.ts、src/pages/CourseWorkspace.tsx、src/pages/admin/AdminCourses.tsx、src/pages/admin/AdminLayout.tsx、src/pages/admin/AdminProductionQueue.tsx 与 src/styles.css 接入页面和样式。pnpm run check、pnpm run build 与 git diff --check 通过;具体业务路径及数据库/API 验证沿用 C-20260924-06 记录,本次未重复执行端到端验证。866412f 对应镜像到 47.93.193.127:/opt/xinghen,仅重建 xinghen-app-1;部署前数据库备份保存为 /opt/xinghen/.deploy/pre-redeploy-866412f-20260924113445.dump。未修改反向代理配置;PostgreSQL 与上传卷保留。xinghen_postgres_data 与上传卷 xinghen_uploads_data 不重建。pnpm run check、pnpm run build、docker buildx build --platform linux/amd64 --pull --load 通过;生产应用容器运行镜像 SHA 0ff8774dd3195b561027c6907fcfcee91d5cf296d384a7050b941332acd2c849,状态为 running;PostgreSQL 健康,上传卷仍挂载到 /app/uploads。https://xinghen.work/api/health 返回 200 与 {"ok":true},首页返回本次构建静态资源 main-fVQOLHwP.js 和 main-CEdAkTL-.css,HTTP 跳转 HTTPS 为 301,证书 SAN 包含 xinghen.work。866412f。