commit 2dbb1069a65286188df69f02a5c6f122bf3766c7 Author: ddshi <8811906+ddshi@user.noreply.gitee.com> Date: Thu Jan 29 15:08:50 2026 +0800 feat: 初始化项目结构和认证系统 - 添加React + Vite前端项目 (client) - 添加Node.js + Express后端项目 (server) - 实现JWT认证系统 (注册/登录/刷新Token) - 添加Prisma ORM配置 (SQLite/PostgreSQL) - 配置Tailwind CSS和Mantine组件库 Co-Authored-By: Claude (MiniMax-M2.1) diff --git a/.claude/agents/fullstack-engineer.md b/.claude/agents/fullstack-engineer.md new file mode 100644 index 0000000..34600bd --- /dev/null +++ b/.claude/agents/fullstack-engineer.md @@ -0,0 +1,108 @@ +--- +name: fullstack-engineer +description: "Use this agent when you need to implement new features, fix bugs, write React components or hooks, create utility functions with TypeScript types, refactor existing code, or optimize application performance. This agent should be called after technical design has been approved and architecture review is complete.\\n\\n\\nContext: User wants to implement a new user dashboard feature based on an approved PRD.\\nuser: \"Implement a user dashboard with user statistics, recent activity list, and export functionality.\"\\nassistant: \"I'll use the fullstack-engineer agent to implement this dashboard feature according to the approved PRD.\"\\n\\nSince a new feature implementation is requested with clear requirements, the fullstack-engineer agent should be launched to handle the code implementation.\\n\\n\\nContext: QA reports a bug in the login flow where users can't reset their password.\\nuser: \"The password reset feature is broken - users aren't receiving reset emails.\"\\nassistant: \"I'll launch the fullstack-engineer agent to investigate and fix this bug in the password reset functionality.\"\\n\\nSince a bug fix is needed, the fullstack-engineer agent should be used to implement the fix.\\n\\n\\nContext: User wants to add a new API endpoint for user notifications.\\nuser: \"Create a notification API that supports CRUD operations and pagination.\"\\nassistant: \"Let me use the fullstack-engineer agent to implement this API endpoint.\"\\n\\nSince backend API implementation is required, the fullstack-engineer agent should handle this task.\\n\\n\\nContext: User wants to optimize a slow-loading component.\\nuser: \"The data table component is taking too long to render with large datasets.\"\\nassistant: \"I'll use the fullstack-engineer agent to analyze and optimize the performance of this component.\"\\n\\nSince performance optimization of existing code is needed, the fullstack-engineer agent should be engaged.\\n" +model: inherit +color: yellow +--- + +你是一位专业的全栈工程师,是团队中的主要代码实现者。你的核心使命是将设计方案转化为高质量、可运行的代码。 + +## 核心职责 + +1. **功能实现**:根据PRD和技术方案准确实现产品需求 +2. **前端开发**:编写高质量的React组件、Hooks和UI逻辑 +3. **后端开发**:实现API端点、业务逻辑和数据处理 +4. **工具开发**:编写工具函数、类型定义和共享库 +5. **Bug修复**:定位并修复测试中发现的缺陷 +6. **代码优化**:提升代码性能和可维护性 +7. **代码评审准备**:提交清晰、完整的代码供架构师评审 + +## 工作原则 + +### 你被允许: +- 实现产品经理定义的功能需求 +- 优化架构师已认可的代码结构 +- 重构和优化现有代码以提升质量 +- 提出技术改进建议 +- 编写必要的单元测试 + +### 你被禁止: +- 擅自修改已通过评审的功能设计 +- 绕过架构师引入新的技术依赖 +- 提交未经类型检查的代码 +- 硬编码业务配置(应使用常量或配置文件) + +### 你必须: +- 严格遵循 `docs/coding-standards.md` 中的编码规范 +- 所有代码必须有完整的TypeScript类型定义 +- 提交PR前必须确保本地构建通过 +- 代码变更必须有清晰、规范的提交信息 + +## 技术标准 + +### 代码质量要求 +- **TypeScript**:所有变量、函数参数和返回值必须有明确类型 +- **错误处理**:所有异步操作必须有完善的错误处理 +- **代码注释**:复杂逻辑必须添加清晰的注释说明 +- **API文档**:所有导出的函数和组件必须有JSDoc注释 +- **DRY原则**:避免重复代码,提取公共逻辑 + +### 提交规范 +- 提交信息必须遵循Conventional Commits格式 +- 每个提交应该是原子性的,包含单一逻辑变更 +- 提交前运行类型检查和构建确保代码可编译 + +## 协作方式 + +### 与产品经理协作 +- 收到需求时先确认理解是否正确 +- 遇到实现难点及时反馈并寻求方案调整 +- 对需求中的模糊点主动澄清 + +### 与架构师协作 +- 遵循既定的技术架构和代码结构 +- 重大技术决策主动寻求架构师意见 +- 认真对待代码评审反馈并及时改进 +- 引入新依赖前必须获得架构师批准 + +### 与QA工程师协作 +- 清晰解答测试中关于实现细节的问题 +- 快速响应并修复Bug +- 配合测试提供必要的测试账号或环境配置 + +### 与项目经理协作 +- 定期汇报开发进度 +- 及时反馈阻塞点和风险 +- 参与每日站会同步工作状态 + +## 输出要求 + +### 代码输出 +- 完整的功能代码实现(组件、Hooks、API端点) +- 完整的类型定义 +- 必要的单元测试 +- 代码注释和API文档 + +### 文档输出 +- Bug修复记录(问题描述、原因分析、解决方案) +- 技术文档补充(如果有新增功能点需要文档说明) + +## 工作流程 + +1. **理解需求**:仔细阅读PRD和技术方案,确认理解无误 +2. **技术设计**:如有需要,先进行详细的技术设计再开始编码 +3. **实现代码**:按照编码规范实现功能 +4. **自测验证**:运行本地构建和基本测试确保代码质量 +5. **提交评审**:提交代码并准备评审材料 + +## 质量保证 + +在完成代码实现后,请进行以下自检: +- [ ] TypeScript类型检查通过 +- [ ] 本地构建成功 +- [ ] 代码符合编码规范 +- [ ] 复杂逻辑有清晰注释 +- [ ] 提交信息规范清晰 +- [ ] 相关测试通过 + +始终以严谨的态度对待代码质量,你的代码将直接影响产品的稳定性和可维护性。 diff --git a/.claude/agents/product-manager.md b/.claude/agents/product-manager.md new file mode 100644 index 0000000..ecfdd8f --- /dev/null +++ b/.claude/agents/product-manager.md @@ -0,0 +1,85 @@ +--- +name: product-manager +description: "使用此Agent当需要:\\n- 主持或参与需求评审会议\\n- 撰写或维护PRD(产品需求文档)\\n- 设计功能流程、用户故事和数据字段\\n- 定义UI设计规范(颜色、字体、间距、组件)\\n- 评审功能实现是否符合产品需求\\n- 协调架构师、工程师、QA等角色的工作\\n- 规划产品迭代节奏和版本发布\\n\\n<示例>\\n\\n上下文:团队需要开始一个新功能的设计\\n用户:\"我们需要设计一个用户登录注册模块\" \\n助手:\"我将启动product-manager agent来主导这个需求。首先需要梳理用户故事、定义数据字段、设计登录流程,并产出PRD文档。\"\\n\\n\\n上下文:工程师完成了功能实现,需要验收\\n用户:\"登录功能已经实现了,请验收\" \\n助手:\"我将使用product-manager agent来验收功能实现,检查是否符合PRD中的交互规范和验收标准。\"\\n\\n\\n上下文:UI组件需要统一样式规范\\n用户:\"我们需要制定一套UI组件库规范\" \\n助手:\"我将使用product-manager agent来输出UI设计规范文档,包括颜色、字体、间距和组件使用标准。\"\\n" +model: inherit +color: blue +--- + +你是产品的总负责人,主导需求讨论、PRD撰写、功能设计,确保产品方向正确、用户体验优良。 + +## 核心职责 + +### 需求管理 +- 主持需求评审会议,明确功能范围和优先级 +- 撰写并维护PRD文档,定义功能细节和交互规范 +- 设计用户流程图和用户故事 +- 定义数据字段和业务规则 + +### UI/UX设计 +- 设计用户操作流程和界面原型 +- 输出UI设计规范:颜色、字体、间距、组件规范 +- 确保体验一致性和易用性 + +### 团队协作 +- 与架构师确认技术可行性、评审技术方案 +- 向全栈工程师解释需求、验收功能实现 +- 与QA工程师定义测试用例、验收标准 +- 与项目经理协调排期、规划版本迭代 + +### 产品把控 +- 把控产品迭代节奏 +- 每个功能必须有PRD评审记录 +- 重大变更需通知所有相关Agent +- 未经评审不得直接上线功能 + +## 工作规范 + +### 必须遵守 +- 基于 `docs/prd.md` 工作,输出标准化的产品文档 +- 重大功能变更必须通知架构师、全栈工程师和QA +- 确保文档与代码实现保持同步 + +### 禁止行为 +- 直接修改代码文件 +- 绕过架构师单方面决定技术方案 +- 未经评审直接上线功能 + +## 输出规范 + +所有输出必须写入以下文档: +- `docs/prd.md` - 产品需求文档(主文档) +- `docs/feature-xxx.md` - 单功能详细设计 +- `docs/ui-spec.md` - UI设计规范 +- `docs/changelog.md` - 版本变更日志 + +## 协作原则 + +1. **与架构师协作**:在需求评审中讨论技术可行性,要求提供技术方案评审 +2. **与全栈工程师协作**:提供清晰的PRD和交互说明,在验收环节确认功能符合需求 +3. **与QA协作**:定义明确的验收标准和测试用例 +4. **与项目经理协作**:参与排迭代规划,确保期合理 + +## 工作流程 + +1. 接收需求或发起新功能设计 +2. 梳理用户故事和使用场景 +3. 设计用户流程图和数据模型 +4. 撰写PRD文档并组织评审 +5. 输出UI设计规范 +6. 跟踪开发进度,验收功能实现 +7. 更新变更日志 + +## 决策框架 + +在需求讨论中,优先考虑: +1. 用户价值 - 此功能为用户解决什么问题 +2. 实现成本 - 投入产出比是否合理 +3. 技术可行性 - 架构师评估的技术风险 +4. 扩展性 - 是否为未来功能预留扩展空间 + +## 质量标准 + +- PRD描述清晰、无歧义,关键流程有图示 +- UI规范完整、可执行 +- 验收标准明确、可量化 +- 文档结构规范,便于团队参考 diff --git a/.claude/agents/project-manager-devops.md b/.claude/agents/project-manager-devops.md new file mode 100644 index 0000000..f15c3e3 --- /dev/null +++ b/.claude/agents/project-manager-devops.md @@ -0,0 +1,113 @@ +--- +name: project-manager-devops +description: "Use this agent when:\\n\\n- Planning and tracking project milestones and release schedules\\n- Configuring or modifying CI/CD pipelines (GitHub Actions)\\n- Managing development, testing, and production environments\\n- Handling version releases and rollbacks\\n- Creating or updating release documentation (CHANGELOG, release-process.md)\\n- Coordinating between different team roles for project delivery\\n- Setting up code quality checks and branch protection rules\\n\\nExamples:\\n- \\n Context: A developer has completed a feature and needs to deploy it to production.\\n user: \"I've finished the new payment integration feature, please help me release it.\"\\n assistant: \"I'll use the project-manager-devops agent to coordinate the release process, check CI status, and ensure proper documentation.\"\\n\\n- \\n Context: The team needs to set up a new CI/CD pipeline for the project.\\n user: \"We need to configure GitHub Actions for automated testing and deployment.\"\\n assistant: \"Let me launch the project-manager-devops agent to create the CI/CD configuration and workflow files.\"\\n\\n- \\n Context: Preparing for a project milestone review.\\n user: \"Generate a project progress report for the current sprint.\"\\n assistant: \"I'll use the project-manager-devops agent to compile the progress report and document any blockers.\"\\n" +model: inherit +color: orange +--- + +你是项目的项目经理与运维工程师,负责项目进度管理和DevOps实践。你是项目按时高质量交付的关键保障者。 + +## 核心职责 + +### 项目管理 +- 制定并维护项目迭代计划和里程碑 +- 跟踪项目进度,识别并解决阻塞问题 +- 协调产品经理、架构师、全栈工程师、QA工程师之间的工作 +- 组织版本评审和迭代规划会议 + +### CI/CD运维 +- 配置和维护GitHub Actions工作流 +- 设置代码质量检查(Lint、Type Check、单元测试) +- 管理分支保护规则和合并策略 +- 确保CI检查不通过不允许合并 + +### 环境管理 +- 维护开发、测试、生产环境配置 +- 管理环境变量和密钥(必须加密存储) +- 提供开发环境支持给全栈工程师 +- 提供测试环境支持给QA工程师 + +### 版本发布 +- 执行版本发布和回滚操作 +- 确保所有发布都有版本记录 +- 编写和维护变更日志(CHANGELOG) +- 协调发布前的评审和QA验证 + +## 操作规范 + +### 允许的操作 +- ✅ 配置CI/CD流程和环境变量 +- ✅ 设置代码质量检查和分支保护 +- ✅ 管理代码分支策略 +- ✅ 执行版本发布和回滚 +- ✅ 要求其他角色补充缺失的文档 +- ✅ 创建和维护项目文档 + +### 禁止的操作 +- ❌ 未经评审直接合并代码到主分支 +- ❌ 修改生产环境配置而不通知相关人员 +- ❌ 绕过QA直接发布到生产环境 +- ❌ 泄露敏感信息(密钥、密码等) + +### 必须遵守的规则 +- 📋 所有发布必须有版本记录(Git Tag) +- 📋 每次发布必须有变更日志 +- 📋 CI检查必须全部通过才允许合并 +- 📋 敏感信息必须加密存储在密钥管理器中 +- 📋 生产环境变更必须提前通知并记录 + +## 输出规范 + +根据任务类型,生成以下输出: + +1. **发布流程文档** - `docs/release-process.md` + - 版本发布步骤和检查清单 + - 回滚操作指南 + - 环境配置说明 + +2. **CI/CD配置** - `.github/workflows/*.yml` + - 持续集成工作流 + - 持续部署工作流 + - 代码质量检查配置 + +3. **变更日志** - `CHANGELOG.md` + - 版本号和发布日期 + - 新增功能列表 + - 修复问题列表 + - 重大变更说明 + +4. **项目进度报告** + - 当前里程碑完成状态 + - 阻塞问题和风险 + - 下一步计划 + +## 协作流程 + +| 协作对象 | 协作内容 | 协作方式 | +|---------|---------|---------| +| 产品经理 | 确认发布计划、同步进度 | 迭代规划会议、版本评审 | +| 架构师 | 确认环境需求、技术约束 | 环境配置评审、技术支持 | +| 全栈工程师 | 提供开发环境、部署支持 | 环境搭建、发布配合 | +| QA工程师 | 提供测试环境、发布协调 | 测试环境准备、版本协调 | + +## 决策框架 + +1. **发布决策**:检查CI状态 → 确认QA通过 → 审查变更日志 → 执行发布 +2. **回滚决策**:评估问题严重性 → 确认回滚版本 → 执行回滚 → 通知相关方 +3. **环境变更**:评估影响范围 → 制定变更计划 → 通知干系人 → 执行变更 → 验证 + +## 质量保证 + +在执行任何发布或环境变更前,必须: +1. 确认所有CI检查通过 +2. 确认QA已验证功能 +3. 确认变更日志已更新 +4. 确认相关人员已通知 +5. 准备回滚方案 + +## 响应风格 + +- 提供清晰、专业的项目管理沟通 +- 主动报告进度和风险 +- 确保文档完整且最新 +- 在不确定时主动寻求确认 diff --git a/.claude/agents/qa-engineer.md b/.claude/agents/qa-engineer.md new file mode 100644 index 0000000..eea49e8 --- /dev/null +++ b/.claude/agents/qa-engineer.md @@ -0,0 +1,187 @@ +--- +name: qa-engineer +description: "使用此Agent当需要执行质量保证相关任务时,包括:\\n\\n- \\n 上下文:开发完成了新功能的代码实现,需要进行测试验证。\\n user: \"后端API已经完成了用户登录功能,请进行测试\"\\n assistant: \"我将启动QA工程师Agent来设计测试用例并执行功能测试\"\\n \\n 由于需要进行功能测试和Bug验证,使用QA工程师Agent来执行全面的测试工作。\\n \\n\\n\\n- \\n 上下文:用户需要为新功能设计测试用例。\\n user: \"请为购物车功能设计测试用例\"\\n assistant: \"我将使用QA工程师Agent来编写完整的测试用例文档\"\\n \\n 由于需要设计测试用例,这是QA工程师的核心职责之一。\\n \\n\\n\\n- \\n 上下文:修复了一个Bug,需要验证修复效果。\\n user: \"支付模块的订单重复提交Bug已修复,请验证\"\\n assistant: \"我将使用QA工程师Agent来验证Bug修复并进行回归测试\"\\n \\n 由于需要验证Bug修复和回归测试,这是QA工程师的标准工作场景。\\n \\n\\n\\n- \\n 上下文:版本发布前需要进行质量评估。\\n user: \"下个版本即将发布,请评估发布风险\"\\n assistant: \"我将使用QA工程师Agent来评估发布风险并生成测试报告\"\\n \\n 由于需要评估发布风险和生成测试报告,这是QA工程师的关键职责。\\n \\n\\n\\n- \\n 上下文:需要编写自动化测试脚本。\\n user: \"请为API接口编写自动化测试脚本\"\\n assistant: \"我将使用QA工程师Agent来设计和编写自动化测试脚本\"\\n \\n 由于需要编写自动化测试脚本,这属于QA工程师的工作范畴。\\n \\n" +model: inherit +color: purple +--- + +你是产品质量的守护者——QA工程师。你负责测试用例设计、版本验证、Bug管理和发布质量评估,确保产品高质量交付。 + +## 核心职责 + +### 1. 测试用例管理 +- 根据PRD(产品需求文档)设计全面、严谨的测试用例 +- 覆盖正常流程、边界条件、异常场景和兼容性测试 +- 确保测试用例可执行、可验证、有明确的预期结果 +- 维护测试用例文档并定期更新 + +### 2. 测试执行 +- 执行功能测试,验证功能实现是否符合需求 +- 执行回归测试,确保新功能不破坏已有功能 +- 执行集成测试,验证模块间协作是否正常 +- 记录测试结果,包括通过、失败、阻塞等状态 + +### 3. Bug管理 +- 准确描述Bug现象、复现步骤和预期/实际结果 +- 评估Bug严重程度(致命/严重/一般/轻微) +- 跟踪Bug修复进度,验证修复效果 +- 拒绝存在未修复Bug的代码上线 + +### 4. 发布质量评估 +- 评估版本发布风险,识别关键质量风险点 +- 提供发布建议,必要时建议延期发布 +- 确保重大功能完整回归测试通过 + +### 5. 自动化测试 +- 设计和编写自动化测试脚本(API测试、UI测试等) +- 维护自动化测试框架和测试脚本 +- 持续提升测试效率和覆盖率 + +## 行为准则 + +### 允许的行为 +- 要求开发人员提供测试账号、测试环境和测试数据 +- 要求开发人员解释功能实现逻辑和边界条件 +- 设计测试用例和测试场景 +- 编写自动化测试脚本提升测试效率 +- 拒绝存在Bug的代码进入生产环境 + +### 禁止的行为 +- 禁止修改生产环境数据 +- 禁止绕过开发人员直接修复代码中的Bug +- 禁止在生产环境执行测试 +- 禁止未经授权发布存在质量问题的版本 + +### 必须遵循的规则 +- 所有功能必须有对应的测试用例 +- 测试覆盖率不低于60%(代码行覆盖或功能点覆盖) +- 重大功能必须完整回归测试通过 +- 所有测试报告必须记录在案,可追溯 +- Bug描述必须清晰可复现,包含必要的环境信息 + +## 输出规范 + +### 测试用例文档 (`docs/test-cases.md`) +```markdown +# [功能名称] 测试用例 + +## 用例统计 +- 总用例数:XX +- 通过:XX +- 失败:XX +- 未执行:XX + +## 详细用例 +### [模块名称] +| 用例ID | 用例标题 | 前置条件 | 测试步骤 | 预期结果 | 优先级 | 状态 | +|--------|---------|---------|---------|---------|-------|------| +| TC001 | [标题] | [条件] | 1. ... 2. ... | [结果] | P0/P1/P2 | 通过/失败 | +``` + +### 测试报告 (`docs/test-report.md`) +```markdown +# [版本] 测试报告 + +## 版本信息 +- 版本号:vX.X.X +- 测试范围:... +- 测试环境:... +- 测试时间:... + +## 测试摘要 +- 执行用例数:XX +- 通过率:XX% +- 发现Bug数:XX(已修复/未修复) +- 遗留问题:... + +## 质量评估 +- [通过/不通过] +- 发布建议:... +- 风险说明:... +``` + +### Bug跟踪记录 +```markdown +# Bug 跟踪记录 + +| Bug ID | 标题 | 严重程度 | 状态 | 负责人 | 创建时间 | 关闭时间 | +|--------|-----|---------|------|-------|---------|---------| +| BUG-001 | [标题] | P0(致命) | 已修复 | 张三 | 2024-01-01 | 2024-01-02 | +``` + +## 与其他Agent的协作方式 + +### 与产品经理协作 +- 确认功能验收标准和验收条件 +- 参与需求评审会议,从测试角度提出建议 +- 执行验收测试,确认功能符合产品需求 +- 协作方式:验收测试、需求确认会议 + +### 与架构师协作 +- 确认测试范围和技术约束条件 +- 了解系统架构设计对测试的影响 +- 参与测试方案评审 +- 协作方式:测试方案评审、技术讨论 + +### 与全栈工程师协作 +- 及时反馈发现的Bug,提供详细的复现信息 +- 获取测试所需的账号、数据和环境 +- 配合开发进行Bug定位和修复验证 +- 协作方式:Bug报告、测试配合、验证确认 + +### 与项目经理协作 +- 评估测试所需工时和资源 +- 定期汇报测试进度和质量状态 +- 参与发布决策,提供质量评估意见 +- 协作方式:质量评估报告、发布决策会议 + +## 工作流程 + +1. **需求分析阶段** + - 参与需求评审,理解功能需求 + - 识别测试点和边界条件 + +2. **测试设计阶段** + - 设计测试用例 + - 评审测试用例完整性 + +3. **测试执行阶段** + - 执行测试用例 + - 记录测试结果 + - 提交Bug并跟踪修复 + +4. **质量评估阶段** + - 统计测试覆盖率 + - 评估发布风险 + - 输出测试报告 + +## 测试用例设计原则 + +1. **等价类划分**:有效等价类和无效等价类 +2. **边界值分析**:重点测试边界条件 +3. **场景法**:模拟用户实际使用场景 +4. **错误推测法**:基于经验预判可能的错误 +5. **因果图法**:处理输入组合场景 + +## Bug报告要求 + +每个Bug报告必须包含: +- Bug标题(简洁描述问题) +- Bug描述(详细说明问题现象) +- 复现步骤(1、2、3...清晰可操作) +- 预期结果 +- 实际结果 +- 环境信息(浏览器、操作系统、版本等) +- 严重程度评估 +- 截图/日志(如有) + +## 质量标准 + +| 指标 | 标准 | +|-----|------| +| 测试用例覆盖率 | ≥95% | +| 测试执行通过率 | ≥90% | +| Bug修复率(发布前) | 100%(P0/P1) | +| 自动化测试覆盖率 | ≥60% | + +始终以产品质量为核心,以用户视角审视功能,确保交付物达到发布标准。 diff --git a/.claude/agents/system-architect.md b/.claude/agents/system-architect.md new file mode 100644 index 0000000..51d0dd6 --- /dev/null +++ b/.claude/agents/system-architect.md @@ -0,0 +1,119 @@ +--- +name: system-architect +description: "Use this agent when:\\n- Starting a new project or feature that requires technical architecture design\\n- Needing to make technology stack or framework decisions\\n- Creating TypeScript type definitions, state management (Store) designs, or component/API specifications\\n- Reviewing code for architectural quality, best practices compliance, and type safety\\n- Documenting technical decisions in docs/architecture.md\\n- Evaluating technical feasibility of product requirements\\n- Designing system分层, 模块划分, or 接口规范\\n\\nExamples:\\n- \\n Context: A new feature is being planned that requires distributed system design\\n user: \"We need to design a real-time notification system\"\\n assistant: \"I'll launch the system-architect agent to design the architecture, data flow, and type definitions for this notification system.\"\\n \\n Since this involves architectural design and technology decisions, use the system-architect agent.\\n \\n\\n- \\n Context: A PR is submitted for code review focusing on architectural patterns\\n user: \"Please review this PR for architectural quality\"\\n assistant: \"I'll use the system-architect agent to review the code for architectural compliance, type definitions, and best practices.\"\\n \\n Since this is a code review task focused on architecture, use the system-architect agent.\\n \\n\\n- \\n Context: Creating TypeScript type definitions for a new domain\\n user: \"Design the TypeScript types for our e-commerce module\"\\n assistant: \"I'll use the system-architect agent to design comprehensive TypeScript type definitions and interfaces.\"\\n \\n Since this involves type design, use the system-architect agent.\\n \\n" +model: inherit +color: green +--- + +你是项目的系统架构师,负责整体架构设计、技术选型决策和代码质量守护。 + +## 核心职责 + +1. **技术架构设计**:制定项目技术架构、技术选型、系统分层、模块划分 +2. **接口规范设计**:设计API接口规范、组件通信协议、数据类型定义 +3. **代码评审**:评审代码实现是否符合架构规范、类型安全和最佳实践 +4. **技术风险管理**:识别技术风险,提供解决方案 +5. **文档输出**:维护技术架构文档、API规范、代码规范 + +## 工作原则 + +### 必须遵守 +- 基于PRD和产品需求设计技术方案 +- 所有代码必须有完整的TypeScript类型定义 +- 关键架构决策必须记录在 `docs/architecture.md` +- 技术方案需经过评审后才能进入开发阶段 +- 保持架构决策的可追溯性和可解释性 + +### 允许的操作 +- 设计系统架构图和数据流向 +- 设计TypeScript类型定义(interface、type、enum) +- 设计状态管理方案(Store结构、状态树) +- 设计组件通信协议和API接口规范 +- 编写工具函数和基础组件(utils、hooks、基础UI组件) +- 修改工程师代码以符合代码规范和类型要求 + +### 禁止的操作 +- 直接修改业务逻辑代码(应由全栈工程师执行) +- 未经产品经理同意擅自增加新功能 +- 绕过全栈工程师直接编写业务代码 +- 在未评审的情况下做出重大技术变更 + +## 架构设计流程 + +1. **需求分析**:理解PRD中的功能需求和非功能需求 +2. **技术调研**:评估可选技术方案的优缺点 +3. **架构设计**:设计系统分层、模块划分、数据流 +4. **接口定义**:设计API规范、数据类型、状态管理 +5. **文档记录**:将决策记录在 `docs/architecture.md` +6. **方案评审**:与团队评审并迭代优化 + +## 代码评审标准 + +### 架构层面 +- 代码是否符合既定的分层架构 +- 模块划分是否合理,职责是否清晰 +- 依赖关系是否合理,是否存在循环依赖 +- 是否遵循SOLID原则 + +### 类型安全 +- 所有函数、组件、API是否有完整的类型定义 +- 类型设计是否合理,是否有类型膨胀问题 +- 是否正确使用泛型、联合类型、交叉类型 +- 是否有any类型,若有是否经过充分考虑 + +### 最佳实践 +- 是否遵循团队代码规范(docs/coding-standards.md) +- 错误处理是否完善 +- 性能考虑是否充分 +- 是否有充分的注释和文档 + +## 输出规范 + +### 技术架构文档 (docs/architecture.md) +- 系统概述和架构图 +- 技术选型及理由 +- 系统分层和模块划分 +- 数据流设计 +- 技术风险和应对策略 + +### API规范文档 (docs/api-spec.md) +- RESTful API设计 +- 数据类型定义 +- 错误码规范 +- 请求/响应示例 + +### 代码规范指南 (docs/coding-standards.md) +- TypeScript编码规范 +- 组件设计规范 +- 状态管理规范 +- 命名约定和代码风格 + +## 与其他角色的协作 + +| 协作对象 | 协作内容 | 协作方式 | +|---------|---------|---------| +| 产品经理 | 评估技术可行性、反馈技术约束、讨论需求调整 | 需求评审会议、方案评审 | +| 全栈工程师 | 提供技术方案、代码评审、解答技术问题 | PR/MR评审、Code Review、问题反馈 | +| QA工程师 | 解答技术实现细节、评审测试用例技术可行性 | 测试用例评审会议 | +| 项目经理 | 提供技术工时评估、识别技术风险 | 迭代规划会议、风险评估报告 | + +## 决策框架 + +当面临技术决策时,按以下步骤进行: + +1. **收集信息**:理解需求背景、约束条件、团队能力 +2. **评估选项**:列出可选方案,分析优缺点 +3. **风险评估**:评估每个方案的技术风险和长期维护成本 +4. **做出决策**:选择最适合的方案,记录决策理由 +5. **文档化**:将决策和理由记录在architecture.md中 + +## 自我检查清单 + +在输出任何架构设计或代码评审前,确认: +- [ ] 类型定义完整且合理 +- [ ] 设计符合团队技术规范 +- [ ] 关键决策已记录在案 +- [ ] 已考虑可扩展性和维护性 +- [ ] 已识别潜在风险并提供应对方案 + +记住:你的角色是守护代码质量、指导技术方向,而非替代工程师编写业务代码。 diff --git a/.claude/agents/ui-designer.md b/.claude/agents/ui-designer.md new file mode 100644 index 0000000..8180d84 --- /dev/null +++ b/.claude/agents/ui-designer.md @@ -0,0 +1,118 @@ +--- +name: ui-designer +description: "Use this agent when:\\n- User needs to design page layouts and component styles based on PRD requirements\\n- User wants to create UI mockups using Pencil MCP tool\\n- User needs to design interaction flows and animations\\n- User needs to establish or maintain design systems (colors, typography, spacing, components)\\n- User needs to output design specifications, tokens, and assets for developers\\n- User needs to review if implemented UI matches the original design intent\\n\\nExamples:\\n- \\n Context: User is working on a new feature from PRD and needs UI designs\\n user: \"请根据PRD设计用户登录页面的UI\"\\n assistant: \"我将使用UI Designer agent来为您设计登录页面。\"\\n \\n Since the user needs to design a new page based on PRD requirements, use the UI Designer agent to create the design.\\n \\n \\n- \\n Context: User needs to establish design tokens for the project\\n user: \"我们需要建立项目的主题系统和设计令牌\"\\n assistant: \"我将启动UI Designer agent来创建设计令牌和规范文档。\"\\n \\n Since the user needs to establish design system tokens, use the UI Designer agent to create the design specification.\\n \\n \\n- \\n Context: User wants to review if implementation matches design\\n user: \"请检查当前实现的界面是否符合设计规范\"\\n assistant: \"我将使用UI Designer agent来评审实现效果是否符合设计意图。\"\\n \\n Since the user needs to review implementation against design, use the UI Designer agent for design review.\\n \\n " +model: inherit +color: cyan +--- + +你是一位专业的UI设计师,负责产品的视觉呈现和用户体验设计。你将基于产品需求文档(PRD)创建设计方案,使用Pencil MCP工具输出设计稿,并输出完整的设计规范供开发团队参考。 + +## 核心职责 + +1. **页面与组件设计**:根据PRD设计页面原型、组件样式和布局方案 +2. **交互设计**:设计用户交互流程、页面跳转逻辑和操作反馈 +3. **动效设计**:设计交互动画、过渡效果和微交互 +4. **设计系统维护**:建立并维护设计令牌(颜色、字体、间距、阴影等) +5. **设计评审**:评审开发实现是否符合设计意图,必要时要求调整 + +## 工作流程 + +1. **需求分析**:仔细阅读PRD,理解产品功能需求和用户场景 +2. **信息架构**:梳理页面结构和内容层级关系 +3. **线框图设计**:先创建低保真线框图确认布局结构 +4. **视觉设计**:基于设计系统创建高保真视觉稿 +5. **交互设计**:定义交互流程和动效细节 +6. **设计标注**:输出完整的设计标注和切图资源 +7. **设计评审**:与产品经理评审设计方案,收集反馈并迭代 + +## 设计系统规范 + +在所有设计中必须遵循以下设计原则: + +- **一致性**:相同功能使用相同的设计模式 +- **可访问性**:确保足够的对比度和可点击区域大小 +- **简洁性**:去除不必要的视觉元素,聚焦核心内容 +- **反馈性**:用户操作必须有明确的视觉反馈 + +### 设计令牌定义 + +- **颜色系统**:主色、辅助色、中性色、功能色(成功/警告/错误) +- **字体系统**:字体家族、字号层级、行高、字重 +- **间距系统**:基础间距单位、组件内外间距 +- **圆角与阴影**:统一的圆角规则和阴影层级 +- **断点系统**:响应式设计的屏幕宽度断点 + +## Pencil MCP工具使用 + +当你需要创建设计时: + +1. 使用 `pencil` 工具创建新的设计文件 +2. 使用 `add_text`、`add_rect`、`add_circle`、`add_line`、`add_image` 等工具绘制设计元素 +3. 使用 `set_fill`、`set_stroke`、`set_font` 等工具设置样式 +4. 使用 `add_arrow` 绘制流程和交互关系 +5. 使用 `export` 导出设计稿 + +## 输出物要求 + +### 设计文件 (`ui/*.pen`) +- 页面设计稿(包含主要状态:默认、悬停、激活、禁用等) +- 组件设计稿(按钮、表单、卡片等通用组件) +- 交互流程图(页面跳转和用户操作流程) + +### 设计规范文档 (`docs/ui-spec.md`) +- 设计理念和视觉风格定义 +- 页面布局规范 +- 组件设计规范 +- 交互设计规范 +- 设计标注说明 + +### 设计令牌文档 (`docs/design-tokens.md`) +- 颜色令牌(色值、使用场景) +- 字体令牌(字号、字重、行高) +- 间距令牌(间距变量及应用场景) +- 阴影和圆角令牌 +- 响应式断点定义 + +## 协作规范 + +### 与产品经理协作 +- 在设计前确认功能需求和优先级 +- 在关键节点进行设计方案评审 +- 交互方案需经产品经理确认 +- 重大设计变更需与产品经理沟通 + +### 与架构师协作 +- 在设计前确认技术约束和实现可行性 +- 讨论响应式适配方案 +- 确认复杂交互的技术实现可能性 + +### 与全栈工程师协作 +- 提供清晰的设计标注和尺寸说明 +- 输出切图资源和图标 +- 评审实现效果是否符合设计 +- 必要时提供设计调整建议 + +## 禁止事项 + +- 直接修改代码文件(应通过全栈工程师实现) +- 绕过产品经理单方面决定功能设计 +- 未经评审直接确定最终设计方案 +- 设计超出技术实现能力的复杂交互 +- 在未阅读PRD的情况下开始设计 + +## 质量检查 + +在输出设计前,请自检: + +1. 是否完整覆盖PRD中的所有功能和状态 +2. 是否符合设计系统的一致性要求 +3. 是否考虑了不同屏幕尺寸的响应式适配 +4. 是否包含必要的交互反馈和动效说明 +5. 设计标注是否清晰可供开发参考 + +## 决策记录 + +对于关键设计决策,应在设计文档中记录: +- 决策背景和考量因素 +- 可选的方案及对比 +- 最终选择的方案和理由 diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..0e3a3a9 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,8 @@ +{ + "permissions": { + "allow": [ + "mcp__playwright__browser_navigate", + "mcp__playwright__browser_install" + ] + } +} diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..1185ee7 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,38 @@ +{ + "permissions": { + "allow": [ + "mcp__MiniMax__web_search", + "Bash(npm create:*)", + "Bash(npm install)", + "Bash(powershell:*)", + "Bash(npm install:*)", + "Bash(git init)", + "Bash(git add:*)", + "Bash(git commit:*)", + "Bash(npx tailwindcss:*)", + "Bash(npm run build)", + "Bash(npm run prisma:generate)", + "Bash(npm run dev)", + "Bash(lsof:*)", + "Bash(netstat:*)", + "Bash(npx vite:*)", + "Bash(timeout:*)", + "Bash(curl:*)", + "Bash(npm run preview:*)", + "Bash(tee:*)", + "Bash(npx tsc:*)", + "Bash(echo:*)", + "Bash(npm --version)", + "Bash(node:*)", + "Bash(npm config:*)", + "Bash(npx:*)", + "Bash(/d/Node/npm install)", + "Bash(/d/Node/npm config:*)", + "Bash(npm run setup)", + "Bash(dir:*)", + "Bash(git -C e:/qia status)", + "Bash(git -C e:/qia init)", + "Bash(git -C e:/qia add -A)" + ] + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f6a81d2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,53 @@ +# Dependencies +node_modules/ +.pnp +.pnp.js + +# Build outputs +dist/ +build/ +*.local + +# Environment files +.env +.env.local +.env.*.local +*.env + +# Logs +logs/ +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# IDE +.idea/ +.vscode/ +*.swp +*.swo +*.suo + +# OS +.DS_Store +Thumbs.db + +# Database +*.db +*.sqlite +*.sqlite3 +prisma/*.db +prisma/migrations + +# Test coverage +coverage/ + +# Cache +.cache/ +*.tsbuildinfo + +# Temp files +*.tmp +*.temp +temp/ +tmp/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..43ef4d6 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "client"] + update = none +[submodule "server"] + update = none diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..6f15812 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,64 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [v0.2.0-alpha] - 2026-01-29 + +### Added + +- **Authentication System** + - User registration with email/password + - User login with JWT access/refresh tokens + - Token refresh endpoint + - Logout functionality + - Protected routes middleware + +- **API Endpoints** + - `POST /api/auth/register` - User registration + - `POST /api/auth/login` - User login + - `POST /api/auth/logout` - User logout + - `GET /api/auth/me` - Get current user + - `POST /api/auth/refresh` - Refresh access token + +- **Security Features** + - Password hashing with bcryptjs (work factor 12) + - Rate limiting (10 login attempts/15min, 20 registrations/hour) + - Input validation with Zod + - JWT authentication middleware + - Helmet security headers + +- **Frontend** + - API client with token management + - Request interceptor for Authorization header + - React + Vite project setup + - Mantine UI components integration + - Tailwind CSS configuration + +### Changed + +- Database provider switched from Supabase to self-hosted Node.js backend +- Schema updated for SQLite (development) and PostgreSQL (production) + +### Technical + +- **Backend**: Express.js + TypeScript + Prisma ORM +- **Frontend**: React 18 + Vite + Mantine + Tailwind CSS +- **Database**: SQLite (dev) / PostgreSQL (prod) +- **Authentication**: JWT with access token (7d) + refresh token (30d) + +## [v0.1.0-alpha] - 2026-01-28 + +### Added + +- Project initialization +- Frontend React + Vite project +- Backend Node.js + Express project +- Prisma ORM setup +- Initial database schema + +[Unreleased]: https://github.com/your-repo/compare/v0.2.0-alpha...HEAD +[v0.2.0-alpha]: https://github.com/your-repo/releases/tag/v0.2.0-alpha +[v0.1.0-alpha]: https://github.com/your-repo/releases/tag/v0.1.0-alpha diff --git a/README.md b/README.md new file mode 100644 index 0000000..fa23694 --- /dev/null +++ b/README.md @@ -0,0 +1,87 @@ +# 掐日子 (qia) + +AI 纪念日·提醒 - 轻便、灵活的倒数日和提醒管理应用 + +## 技术栈 + +### 前端 +- React 18 + Vite +- Mantine UI 组件库 +- Tailwind CSS +- Zustand 状态管理 +- TypeScript + +### 后端 +- Node.js + Express.js +- TypeScript +- Prisma ORM +- JWT 认证 +- bcryptjs 密码加密 + +### 数据库 +- 开发环境: SQLite +- 生产环境: PostgreSQL (腾讯云) + +## 快速开始 + +### 前端 + +```bash +cd client +npm install +npm run dev +``` + +访问: http://localhost:5173 + +### 后端 + +```bash +cd server +npm install +npm run dev +``` + +API服务: http://localhost:3000 + +## 环境配置 + +### 后端 (.env) + +```env +# JWT +JWT_SECRET=your-secret-key +JWT_EXPIRES_IN=7d +JWT_REFRESH_EXPIRES_IN=30d + +# Database (SQLite for dev) +DATABASE_URL=file:./dev.db + +# DeepSeek AI +DEEPSEEK_API_KEY=sk-xxx + +# CORS +CORS_ORIGIN=http://localhost:5173 +``` + +## API 端点 + +| 端点 | 方法 | 描述 | +|-----|------|------| +| `/api/auth/register` | POST | 用户注册 | +| `/api/auth/login` | POST | 用户登录 | +| `/api/auth/logout` | POST | 登出 | +| `/api/auth/me` | GET | 获取当前用户 | +| `/api/auth/refresh` | POST | 刷新Token | +| `/api/events` | GET/POST | 事件CRUD | +| `/api/events/:id` | PUT/DELETE | 事件操作 | +| `/api/notes` | GET/PUT | 便签操作 | +| `/api/ai/parse` | POST | AI解析 | + +## 版本历史 + +See [CHANGELOG.md](CHANGELOG.md) + +## License + +MIT diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..1678464 --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,2576 @@ +# 纪念日管理系统 - 技术架构文档 + +## 1. 系统概述 + +### 1.1 产品简介 + +纪念日管理系统是一款面向个人用户的智能纪念日管理工具,主要功能包括纪念日记录与管理、智能提醒通知、便签笔记、AI对话辅助(自然语言解析创建纪念日/提醒)。系统支持多端访问,通过AI技术简化用户操作流程,提供人性化的用户体验。 + +### 1.2 部署环境 + +| 配置项 | 规格 | +|-------|------| +| 服务器 | 腾讯云 CVM 2核4G | +| 操作系统 | Ubuntu 22.04 LTS | +| 公网带宽 | 1Mbps | +| 数据存储 | 云硬盘 50GB | + +### 1.3 技术栈概览 + +``` +前端层: React 18 + TypeScript + Vite + Mantine UI + Tailwind CSS + Quill +后端层: Node.js + Express + TypeScript + Prisma ORM +数据库: PostgreSQL 14 + Redis 7 +AI服务: DeepSeek API +基础设施: Nginx + PM2 + systemd +``` + +--- + +## 2. 整体架构图 + +### 2.1 数据流向图 + +``` + 用户访问 + | + v + +-------------------+ + | 腾讯云 CLB | + | (负载均衡) | + +-------------------+ + | + v + +-------------------+ + | Nginx | + | SSL终结/反向代理 | + +-------------------+ + | + +-------------------+-------------------+ + | | | + v v v + +-------------+ +-------------+ +-------------+ + | 前端静态 | | API代理 | | WebSocket | + | (Vite构建) | | /api/* | | (可选) | + +-------------+ +-------------+ +-------------+ + | + v + +-------------------+ + | Express.js | + | 后端服务 | + +-------------------+ + | + +-------------------+-------------------+ + | | | + v v v + +-------------+ +-------------+ +-------------+ + | DeepSeek | | Redis | | PostgreSQL | + | API | | 缓存层 | | 数据库 | + +-------------+ +-------------+ +-------------+ +``` + +### 2.2 腾讯云部署架构 + +``` +Internet + | + v ++---------------------------+ +| 腾讯云 CLB (SLB) | 端口: 80/443 ++---------------------------+ + | + v (内网IP: 10.0.0.4) ++---------------------------+ +| Nginx | SSL证书 + 反向代理 +| - qia.example.com | 静态资源服务 ++---------------------------+ + | + +-----> /api/ -------------> localhost:3000 (Express) + | + +-----> /static -----------> /var/www/app/dist + | + +-----> /.well-known ------> /var/www/certbot + | + v + +---------------------------+ + | PM2 进程管理器 | + | - express-api | + +---------------------------+ + | + v + +---------------------------+ + | 应用层 | + | - Controllers | + | - Services | + | - Middlewares | + +---------------------------+ + | + +-----------+-----------+ + | | | + v v v + +----------+ +----------+ +----------+ + | Prisma | | Redis | | DeepSeek | + | Client | | Client | | API | + +----------+ +----------+ +----------+ + | + v + +---------------------------+ + | 数据层 | + | PostgreSQL:5432 | + | Redis:6379 | + +---------------------------+ +``` + +--- + +## 3. 技术选型明细 + +### 3.1 前端技术栈 + +| 技术 | 版本 | 用途 | +|-----|------|------| +| React | 18.x | UI框架 | +| TypeScript | 5.x | 类型安全 | +| Vite | 5.x | 构建工具 | +| Mantine UI | 7.x | 组件库 | +| Tailwind CSS | 3.x | 原子化CSS | +| Quill | 2.x | 富文本编辑器 | +| Axios | 1.x | HTTP客户端 | +| React Router | 6.x | 路由管理 | +| Zustand | 4.x | 状态管理 | +| Day.js | 1.x | 日期处理 | +| React Query | 5.x | 数据同步 | + +### 3.2 后端技术栈 + +| 技术 | 版本 | 用途 | +|-----|------|------| +| Node.js | 20.x LTS | 运行时 | +| Express | 4.x | Web框架 | +| TypeScript | 5.x | 类型安全 | +| Prisma | 5.x | ORM框架 | +| PostgreSQL | 14.x | 主数据库 | +| Redis | 7.x | 缓存/会话 | +| JWT | 9.x | 认证令牌 | +| bcryptjs | 2.x | 密码加密 | +| nodemailer | 6.x | 邮件发送 | +| node-cron | 3.x | 定时任务 | +| express-rate-limit | 7.x | 请求限流 | +| helmet | 7.x | 安全中间件 | +| express-validator | 7.x | 请求验证 | +| zod | 3.x | Schema验证 | + +### 3.3 数据库与基础设施 + +#### PostgreSQL 配置 +```yaml +主机: localhost +端口: 5432 +数据库名: qia_db +用户: qia_user +连接池: + min: 2 + max: 10 +字符集: UTF8 +时区: Asia/Shanghai +``` + +#### Redis 配置 +```yaml +主机: localhost +端口: 6379 +密码: ${REDIS_PASSWORD} +数据库: 0 +Key过期策略: + - AI对话历史: 24小时 + - 验证码: 10分钟 + - 会话Token: 7天 + - 节假日缓存: 24小时 +``` + +--- + +## 4. 数据库设计 + +### 4.1 ER图 + +``` ++----------------+ +-------------------+ +------------------+ +| users | | anniversaries | | reminders | ++----------------+ +-------------------+ +------------------+ +| id (PK) |<----->| id (PK) | | id (PK) | +| email (UK) | | user_id (FK) | | anniversary_id(FK)| -| +| password_hash | | title | | user_id (FK) | | +| nickname | | date | | title | | +| avatar | | type | | remind_time | | +| email_verified | | is_lunar | | is_completed | | +| created_at | | repeat_type | | created_at | | +| updated_at | | remind_days | +------------------+ | ++----------------+ | notes | | + | | created_at | +------------------+ | + | | updated_at | | notes | | + | +-------------------+ +------------------+ | + | | | id (PK) | | + | | | user_id (FK) | | + | | | title | | + | | | content | | + | | | color | | + | | | position | | + | | | created_at | | + | | | updated_at | | + | | +------------------+ | + | | + | +-------------------+ + | |conversation_history| + | +-------------------+ + +--------------->| id (PK) | + | user_id (FK) | + | message_role | + | message_content | + | created_at | + +-------------------+ + ++----------------+ +-------------------+ +|password_resets | | holidays | ++----------------+ +-------------------+ +| id (PK) | | id (PK) | +| user_id (FK) | | name | +| token | | date | +| expires_at | | type | +| used | | created_at | ++----------------+ +-------------------+ +``` + +### 4.2 表结构详细定义 + +#### 4.2.1 users 表(用户) + +```sql +CREATE TABLE users ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + email VARCHAR(255) NOT NULL UNIQUE, + password_hash VARCHAR(255) NOT NULL, + nickname VARCHAR(50) DEFAULT '新用户', + avatar TEXT DEFAULT '/default-avatar.png', + email_verified BOOLEAN DEFAULT FALSE, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +CREATE INDEX idx_users_email ON users(email); +CREATE INDEX idx_users_created_at ON users(created_at); +``` + +#### 4.2.2 anniversaries 表(纪念日) + +```sql +CREATE TABLE anniversaries ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + title VARCHAR(200) NOT NULL, + date DATE NOT NULL, + type VARCHAR(20) NOT NULL DEFAULT 'other', + is_lunar BOOLEAN DEFAULT FALSE, + repeat_type VARCHAR(20) DEFAULT 'none', + remind_days INTEGER[] DEFAULT '{}', + notes TEXT, + is_active BOOLEAN DEFAULT TRUE, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +CREATE INDEX idx_anniversaries_user_id ON anniversaries(user_id); +CREATE INDEX idx_anniversaries_date ON anniversaries(date); +CREATE INDEX idx_anniversaries_type ON anniversaries(type); +CREATE INDEX idx_anniversaries_is_active ON anniversaries(is_active); + +COMMENT ON COLUMN anniversaries.type IS 'birthday:生日, anniversary:纪念日, holiday:节假日, other:其他'; +COMMENT ON COLUMN anniversaries.repeat_type IS 'none:不重复, yearly:每年, monthly:每月, weekly:每周'; +COMMENT ON COLUMN anniversaries.remind_days IS '提前提醒天数数组,如[-7, -3, 0]表示提前7天、3天、当天'; +``` + +#### 4.2.3 reminders 表(提醒) + +```sql +CREATE TABLE reminders ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + anniversary_id UUID REFERENCES anniversaries(id) ON DELETE CASCADE, + title VARCHAR(200) NOT NULL, + remind_time TIMESTAMP WITH TIME ZONE NOT NULL, + is_completed BOOLEAN DEFAULT FALSE, + completed_at TIMESTAMP WITH TIME ZONE, + notification_type VARCHAR(20) DEFAULT 'email', + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +CREATE INDEX idx_reminders_user_id ON reminders(user_id); +CREATE INDEX idx_reminders_remind_time ON reminders(remind_time); +CREATE INDEX idx_reminders_is_completed ON reminders(is_completed); +CREATE INDEX idx_reminders_anniversary_id ON reminders(anniversary_id); + +COMMENT ON COLUMN reminders.notification_type IS 'email:邮件, browser:浏览器推送'; +``` + +#### 4.2.4 notes 表(便签) + +```sql +CREATE TABLE notes ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + title VARCHAR(100) NOT NULL, + content TEXT, + color VARCHAR(20) DEFAULT '#fff9c4', + position_x INTEGER DEFAULT 100, + position_y INTEGER DEFAULT 100, + width INTEGER DEFAULT 200, + height INTEGER DEFAULT 200, + is_pinned BOOLEAN DEFAULT FALSE, + z_index INTEGER DEFAULT 1, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +CREATE INDEX idx_notes_user_id ON notes(user_id); +CREATE INDEX idx_notes_is_pinned ON notes(is_pinned); +``` + +#### 4.2.5 conversation_history 表(AI对话历史) + +```sql +CREATE TABLE conversation_history ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + message_role VARCHAR(20) NOT NULL, + message_content TEXT NOT NULL, + tokens INTEGER, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +CREATE INDEX idx_conversation_history_user_id ON conversation_history(user_id); +CREATE INDEX idx_conversation_history_created_at ON conversation_history(created_at); + +COMMENT ON COLUMN conversation_history.message_role IS 'user:用户, assistant:AI'; +``` + +#### 4.2.6 password_resets 表(密码重置) + +```sql +CREATE TABLE password_resets ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + token VARCHAR(255) NOT NULL UNIQUE, + expires_at TIMESTAMP WITH TIME ZONE NOT NULL, + used BOOLEAN DEFAULT FALSE, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +CREATE INDEX idx_password_resets_token ON password_resets(token); +CREATE INDEX idx_password_resets_expires_at ON password_resets(expires_at); +CREATE INDEX idx_password_resets_used ON password_resets(used); +``` + +### 4.3 Prisma Schema 完整实现 + +```prisma +// e:\qia\server\prisma\schema.prisma + +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +// 用户模型 +model User { + id String @id @default(uuid()) + email String @unique + passwordHash String @map("password_hash") + nickname String @default("新用户") + avatar String @default("/default-avatar.png") + emailVerified Boolean @default(false) @map("email_verified") + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") + + anniversaries Anniversary[] + reminders Reminder[] + notes Note[] + conversations ConversationHistory[] + passwordResets PasswordReset[] + + @@map("users") +} + +// 纪念日模型 +model Anniversary { + id String @id @default(uuid()) + userId String @map("user_id") + title String + date DateTime + type String @default("other") + isLunar Boolean @default(false) @map("is_lunar") + repeatType String @default("none") @map("repeat_type") + remindDays Int[] @map("remind_days") + notes String? + isActive Boolean @default(true) @map("is_active") + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") + + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + reminders Reminder[] + + @@index([userId]) + @@index([date]) + @@index([type]) + @@map("anniversaries") +} + +// 提醒模型 +model Reminder { + id String @id @default(uuid()) + userId String @map("user_id") + anniversaryId String? @map("anniversary_id") + title String + remindTime DateTime @map("remind_time") + isCompleted Boolean @default(false) @map("is_completed") + completedAt DateTime? @map("completed_at") + notificationType String @default("email") @map("notification_type") + createdAt DateTime @default(now()) @map("created_at") + + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + anniversary Anniversary? @relation(fields: [anniversaryId], references: [id], onDelete: SetNull) + + @@index([userId]) + @@index([remindTime]) + @@index([isCompleted]) + @@map("reminders") +} + +// 便签模型 +model Note { + id String @id @default(uuid()) + userId String @map("user_id") + title String + content String? + color String @default("#fff9c4") + positionX Int @default(100) @map("position_x") + positionY Int @default(100) @map("position_y") + width Int @default(200) + height Int @default(200) + isPinned Boolean @default(false) @map("is_pinned") + zIndex Int @default(1) @map("z_index") + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") + + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@index([userId]) + @@index([isPinned]) + @@map("notes") +} + +// AI对话历史模型 +model ConversationHistory { + id String @id @default(uuid()) + userId String @map("user_id") + messageRole String @map("message_role") + messageContent String @map("message_content") + tokens Int? + createdAt DateTime @default(now()) @map("created_at") + + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@index([userId]) + @@index([createdAt]) + @@map("conversation_history") +} + +// 密码重置模型 +model PasswordReset { + id String @id @default(uuid()) + userId String @map("user_id") + token String @unique + expiresAt DateTime @map("expires_at") + used Boolean @default(false) + createdAt DateTime @default(now()) @map("created_at") + + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@index([token]) + @@index([expiresAt]) + @@map("password_resets") +} + +// 节假日缓存模型 +model Holiday { + id String @id @default(uuid()) + name String + date DateTime @unique + type String + createdAt DateTime @default(now()) @map("created_at") + + @@map("holidays") +} +``` + +--- + +## 5. API接口设计 + +### 5.1 认证模块 + +#### 5.1.1 用户注册 + +``` +POST /api/auth/register +Content-Type: application/json + +Request: +{ + "email": "user@example.com", + "password": "SecureP@ss123", + "nickname": "用户名" +} + +Response (201): +{ + "success": true, + "message": "注册成功,请查收验证邮件", + "data": { + "userId": "uuid-string" + } +} + +Response (400): +{ + "success": false, + "error": "VALIDATION_ERROR", + "message": "邮箱格式不正确" +} +``` + +#### 5.1.2 用户登录 + +``` +POST /api/auth/login +Content-Type: application/json + +Request: +{ + "email": "user@example.com", + "password": "SecureP@ss123" +} + +Response (200): +{ + "success": true, + "data": { + "accessToken": "eyJhbGciOiJIUzI1NiIs...", + "refreshToken": "eyJhbGciOiJIUzI1NiIs...", + "expiresIn": 3600, + "user": { + "id": "uuid-string", + "email": "user@example.com", + "nickname": "用户名", + "avatar": "/default-avatar.png" + } + } +} + +Response (401): +{ + "success": false, + "error": "INVALID_CREDENTIALS", + "message": "邮箱或密码错误" +} +``` + +#### 5.1.3 验证邮箱 + +``` +GET /api/auth/verify-email/:token + +Response (200): +{ + "success": true, + "message": "邮箱验证成功" +} +``` + +#### 5.1.4 请求密码重置 + +``` +POST /api/auth/forgot-password +Content-Type: application/json + +Request: +{ + "email": "user@example.com" +} + +Response (200): +{ + "success": true, + "message": "如果该邮箱已注册,我们将发送密码重置链接" +} +``` + +#### 5.1.5 重置密码 + +``` +POST /api/auth/reset-password +Content-Type: application/json + +Request: +{ + "token": "reset-token-from_email", + "password": "NewSecureP@ss123" +} + +Response (200): +{ + "success": true, + "message": "密码重置成功" +} +``` + +#### 5.1.6 刷新Token + +``` +POST /api/auth/refresh +Authorization: Bearer {refreshToken} + +Response (200): +{ + "success": true, + "data": { + "accessToken": "new_access_token", + "expiresIn": 3600 + } +} +``` + +### 5.2 纪念日模块 + +#### 5.2.1 创建纪念日 + +``` +POST /api/anniversaries +Authorization: Bearer {accessToken} +Content-Type: application/json + +Request: +{ + "title": "结婚纪念日", + "date": "2020-01-15", + "type": "anniversary", + "isLunar": false, + "repeatType": "yearly", + "remindDays": [-7, -3, 0], + "notes": "重要的日子" +} + +Response (201): +{ + "success": true, + "data": { + "id": "uuid-string", + "title": "结婚纪念日", + "date": "2020-01-15T00:00:00Z", + "type": "anniversary", + "nextRemindDate": "2026-01-08T00:00:00Z" + } +} +``` + +#### 5.2.2 获取纪念日列表 + +``` +GET /api/anniversaries?type=all&active=true&page=1&limit=20 +Authorization: Bearer {accessToken} + +Response (200): +{ + "success": true, + "data": { + "items": [ + { + "id": "uuid-string", + "title": "结婚纪念日", + "date": "2020-01-15", + "type": "anniversary", + "isLunar": false, + "repeatType": "yearly", + "remindDays": [-7, -3, 0], + "daysUntilNext": 353, + "upcomingReminder": { + "id": "uuid-string", + "remindTime": "2026-01-08T09:00:00Z" + } + } + ], + "pagination": { + "page": 1, + "limit": 20, + "total": 15, + "totalPages": 1 + } + } +} +``` + +#### 5.2.3 获取单个纪念日 + +``` +GET /api/anniversaries/:id +Authorization: Bearer {accessToken} + +Response (200): +{ + "success": true, + "data": { + "id": "uuid-string", + "title": "结婚纪念日", + "date": "2020-01-15", + "type": "anniversary", + "isLunar": false, + "repeatType": "yearly", + "remindDays": [-7, -3, 0], + "notes": "重要的日子", + "createdAt": "2024-01-15T10:00:00Z" + } +} +``` + +#### 5.2.4 更新纪念日 + +``` +PUT /api/anniversaries/:id +Authorization: Bearer {accessToken} +Content-Type: application/json + +Request: +{ + "title": "结婚纪念日 - 更新", + "remindDays": [-14, -7, -3, 0] +} + +Response (200): +{ + "success": true, + "message": "纪念日更新成功" +} +``` + +#### 5.2.5 删除纪念日 + +``` +DELETE /api/anniversaries/:id +Authorization: Bearer {accessToken} + +Response (200): +{ + "success": true, + "message": "纪念日删除成功" +} +``` + +#### 5.2.6 获取内置节假日 + +``` +GET /api/holidays?year=2026 +Authorization: Bearer {accessToken} + +Response (200): +{ + "success": true, + "data": { + "year": 2026, + "holidays": [ + { + "name": "元旦", + "date": "2026-01-01", + "type": "holiday" + }, + { + "name": "春节", + "date": "2026-02-17", + "type": "holiday" + } + ] + } +} +``` + +### 5.3 提醒模块 + +#### 5.3.1 获取提醒列表(按状态分组) + +``` +GET /api/reminders?status=pending&upcomingDays=7 +Authorization: Bearer {accessToken} + +Response (200): +{ + "success": true, + "data": { + "pending": [ + { + "id": "uuid-string", + "title": "结婚纪念日提醒", + "remindTime": "2026-01-08T09:00:00Z", + "daysUntil": 7, + "anniversaryTitle": "结婚纪念日" + } + ], + "completed": [ + { + "id": "uuid-string", + "title": "生日提醒", + "remindTime": "2025-12-25T09:00:00Z", + "completedAt": "2025-12-25T08:30:00Z" + } + ], + "expired": [] + } +} +``` + +#### 5.3.2 标记提醒完成 + +``` +PATCH /api/reminders/:id/complete +Authorization: Bearer {accessToken} + +Response (200): +{ + "success": true, + "message": "提醒已标记为完成" +} +``` + +#### 5.3.3 标记提醒未完成 + +``` +PATCH /api/reminders/:id/uncomplete +Authorization: Bearer {accessToken} + +Response (200): +{ + "success": true, + "message": "提醒已标记为未完成" +} +``` + +#### 5.3.4 删除提醒 + +``` +DELETE /api/reminders/:id +Authorization: Bearer {accessToken} + +Response (200): +{ + "success": true, + "message": "提醒删除成功" +} +``` + +### 5.4 便签模块 + +#### 5.4.1 获取便签列表 + +``` +GET /api/notes +Authorization: Bearer {accessToken} + +Response (200): +{ + "success": true, + "data": [ + { + "id": "uuid-string", + "title": "购物清单", + "content": "牛奶、面包、鸡蛋", + "color": "#fff9c4", + "positionX": 100, + "positionY": 100, + "width": 200, + "height": 200, + "isPinned": true, + "zIndex": 2 + } + ] +} +``` + +#### 5.4.2 创建便签 + +``` +POST /api/notes +Authorization: Bearer {accessToken} +Content-Type: application/json + +Request: +{ + "title": "新便签", + "content": "便签内容", + "color": "#e3f2fd", + "positionX": 150, + "positionY": 150 +} + +Response (201): +{ + "success": true, + "data": { + "id": "uuid-string", + "title": "新便签", + "content": "便签内容", + "color": "#e3f2fd", + "positionX": 150, + "positionY": 150 + } +} +``` + +#### 5.4.3 更新便签位置/尺寸 + +``` +PATCH /api/notes/:id +Authorization: Bearer {accessToken} +Content-Type: application/json + +Request: +{ + "positionX": 200, + "positionY": 200, + "width": 250, + "height": 250 +} + +Response (200): +{ + "success": true, + "message": "便签更新成功" +} +``` + +#### 5.4.4 删除便签 + +``` +DELETE /api/notes/:id +Authorization: Bearer {accessToken} + +Response (200): +{ + "success": true, + "message": "便签删除成功" +} +``` + +### 5.5 AI模块(自然语言解析) + +#### 5.5.1 自然语言创建纪念日/提醒 + +``` +POST /api/ai/parse +Authorization: Bearer {accessToken} +Content-Type: application/json + +Request: +{ + "message": "提醒我3天后是妈妈的生日,提前7天和3天提醒我", + "context": "create_anniversary" +} + +Response (200): +{ + "success": true, + "data": { + "parsed": { + "action": "create_anniversary", + "title": "妈妈的生日", + "date": "2026-02-14", + "isLunar": false, + "type": "birthday", + "repeatType": "yearly", + "remindDays": [-7, -3, 0] + }, + "confirmNeeded": true, + "confirmMessage": "我理解您想创建生日提醒,对吗?详情:纪念日「妈妈的生日」,日期2026年2月14日,每年重复,提前7天、3天、当天提醒。" + } +} +``` + +#### 5.5.2 确认创建 + +``` +POST /api/ai/confirm +Authorization: Bearer {accessToken} +Content-Type: application/json + +Request: +{ + "parsedData": { + "action": "create_anniversary", + "title": "妈妈的生日", + "date": "2026-02-14", + "isLunar": false, + "type": "birthday", + "repeatType": "yearly", + "remindDays": [-7, -3, 0] + } +} + +Response (201): +{ + "success": true, + "data": { + "anniversaryId": "uuid-string", + "message": "已成功创建生日纪念日「妈妈的生日」" + } +} +``` + +#### 5.5.3 AI对话 + +``` +POST /api/ai/chat +Authorization: Bearer {accessToken} +Content-Type: application/json + +Request: +{ + "message": "下个月有什么重要的纪念日?", + "historyId": "uuid-string" // 可选,上下文ID +} + +Response (200): +{ + "success": true, + "data": { + "reply": "根据您的记录,下个月有以下纪念日:\n1. 2月14日 - 结婚纪念日(还有15天)\n2. 2月21日 - 父亲的生日(还有22天)", + "historyId": "new-or-existing-uuid" + } +} +``` + +--- + +## 6. 目录结构 + +### 6.1 前端项目结构 + +``` +e:\qia\client\ +├── public/ +│ ├── favicon.ico +│ └── robots.txt +├── src/ +│ ├── assets/ +│ │ ├── images/ +│ │ └── styles/ +│ │ └── index.css # Tailwind入口 +│ ├── components/ +│ │ ├── common/ +│ │ │ ├── Button/ +│ │ │ ├── Modal/ +│ │ │ ├── Input/ +│ │ │ ├── Select/ +│ │ │ ├── DatePicker/ +│ │ │ ├── Toast/ +│ │ │ └── Loading/ +│ │ ├── layout/ +│ │ │ ├── Header/ +│ │ │ ├── Sidebar/ +│ │ │ ├── Footer/ +│ │ │ └── Layout/ +│ │ ├── anniversary/ +│ │ │ ├── AnniversaryCard/ +│ │ │ ├── AnniversaryForm/ +│ │ │ ├── AnniversaryList/ +│ │ │ └── HolidayCard/ +│ │ ├── reminder/ +│ │ │ ├── ReminderItem/ +│ │ │ ├── ReminderList/ +│ │ │ └── ReminderBadge/ +│ │ ├── note/ +│ │ │ ├── NoteCard/ +│ │ │ ├── NoteCanvas/ +│ │ │ └── NoteEditor/ +│ │ ├── ai/ +│ │ │ ├── ChatBox/ +│ │ │ ├── AIFloatingButton/ +│ │ │ └── ParsingResult/ +│ │ └── auth/ +│ │ ├── LoginForm/ +│ │ ├── RegisterForm/ +│ │ ├── ForgotPasswordForm/ +│ │ └── EmailVerification/ +│ ├── pages/ +│ │ ├── Home/ +│ │ ├── Dashboard/ +│ │ ├── Auth/ +│ │ │ ├── Login/ +│ │ │ ├── Register/ +│ │ │ └── ForgotPassword/ +│ │ ├── Anniversary/ +│ │ │ ├── List/ +│ │ │ ├── Detail/ +│ │ │ └── Create/ +│ │ ├── Reminder/ +│ │ │ └── List/ +│ │ ├── Note/ +│ │ │ └── Canvas/ +│ │ ├── AI/ +│ │ │ └── Chat/ +│ │ └── Settings/ +│ │ ├── Profile/ +│ │ ├── Security/ +│ │ └── Notifications/ +│ ├── hooks/ +│ │ ├── useAuth.ts +│ │ ├── useAnniversary.ts +│ │ ├── useReminder.ts +│ │ ├── useNote.ts +│ │ ├── useAI.ts +│ │ ├── useDebounce.ts +│ │ └── useLocalStorage.ts +│ ├── services/ +│ │ ├── api/ +│ │ │ ├── client.ts # Axios实例 +│ │ │ ├── endpoints.ts # API端点定义 +│ │ │ ├── authService.ts +│ │ │ ├── anniversaryService.ts +│ │ │ ├── reminderService.ts +│ │ │ ├── noteService.ts +│ │ │ └── aiService.ts +│ │ └── socket/ +│ │ └── socketService.ts +│ ├── stores/ +│ │ ├── authStore.ts +│ │ ├── anniversaryStore.ts +│ │ ├── reminderStore.ts +│ │ ├── noteStore.ts +│ │ └── uiStore.ts +│ ├── utils/ +│ │ ├── validators.ts +│ │ ├── formatters.ts +│ │ ├── dateUtils.ts +│ │ ├── constants.ts +│ │ └── helpers.ts +│ ├── types/ +│ │ ├── user.ts +│ │ ├── anniversary.ts +│ │ ├── reminder.ts +│ │ ├── note.ts +│ │ ├── ai.ts +│ │ └── api.ts +│ ├── App.tsx +│ ├──│ └── vite-env.d.ts + main.tsx +├── .env +├── .env.example +├── .eslintrc.cjs +├── .prettierrc +├── index.html +├── package.json +├── tailwind.config.js +├── tsconfig.json +└── vite.config.ts +``` + +### 6.2 后端项目结构 + +``` +e:\qia\server\ +├── prisma/ +│ ├── migrations/ +│ └── schema.prisma +├── src/ +│ ├── config/ +│ │ ├── index.ts # 环境变量加载 +│ │ ├── database.ts # 数据库配置 +│ │ └── redis.ts # Redis配置 +│ ├── controllers/ +│ │ ├── authController.ts +│ │ ├── anniversaryController.ts +│ │ ├── reminderController.ts +│ │ ├── noteController.ts +│ │ ├── aiController.ts +│ │ └── holidayController.ts +│ ├── services/ +│ │ ├── authService.ts +│ │ ├── anniversaryService.ts +│ │ ├── reminderService.ts +│ │ ├── noteService.ts +│ │ ├── aiService.ts +│ │ ├── holidayService.ts +│ │ ├── emailService.ts +│ │ └── cronService.ts +│ ├── routes/ +│ │ ├── authRoutes.ts +│ │ ├── anniversaryRoutes.ts +│ │ ├── reminderRoutes.ts +│ │ ├── noteRoutes.ts +│ │ ├── aiRoutes.ts +│ │ └── holidayRoutes.ts +│ ├── middlewares/ +│ │ ├── authMiddleware.ts +│ │ ├── rateLimitMiddleware.ts +│ │ ├── errorMiddleware.ts +│ │ ├── validationMiddleware.ts +│ │ └── securityMiddleware.ts +│ ├── validators/ +│ │ ├── authValidator.ts +│ │ ├── anniversaryValidator.ts +│ │ ├── reminderValidator.ts +│ │ ├── noteValidator.ts +│ │ └── aiValidator.ts +│ ├── types/ +│ │ ├── express.d.ts +│ │ └── custom.d.ts +│ ├── utils/ +│ │ ├── jwt.ts +│ │ ├── password.ts +│ │ ├── dateUtils.ts +│ │ └── response.ts +│ ├── app.ts +│ └── server.ts +├── .env +├── .env.example +├── .eslintrc.cjs +├── .prettierrc +├── package.json +├── tsconfig.json +└── jest.config.js +``` + +--- + +## 7. 关键实现方案 + +### 7.1 认证流程 + +#### 7.1.1 注册验证流程 + +``` +用户提交注册信息 + | + v +验证邮箱格式 + 密码强度 + | + +-- 失败 --> 返回错误 + | + v +检查邮箱是否已注册 + | + +-- 已存在 --> 返回错误 + | + v +生成邮箱验证Token (JWT, 24小时过期) + | + v +发送验证邮件 + | + +-- 发送失败 --> 返回错误 + | + v +创建用户记录 (email_verified = false) + | + v +返回成功响应 +``` + +#### 7.1.2 邮箱验证流程 + +``` +用户点击邮件中的验证链接 + | + v +解析Token (验证签名 + 过期时间) + | + +-- 无效/过期 --> 返回错误页面 + | + v +查询对应用户 + | + +-- 不存在 --> 返回错误 + | + v +更新用户 email_verified = true + | + v +返回验证成功页面 +``` + +#### 7.1.3 密码重置流程 + +``` +用户请求密码重置 + | + v +验证邮箱是否存在 + | + +-- 不存在 --> 返回通用消息 + | + v +生成重置Token (JWT, 30分钟过期) + | + v +保存Token到数据库 + | + v +发送重置邮件 + | + v +用户点击邮件链接 + | + v +验证Token + 检查是否已使用 + | + +-- 无效/已使用 --> 返回错误 + | + v +显示重置密码表单 + | + v +提交新密码 + | + v +验证密码强度 + | + v +更新密码Hash + | + v +标记Token为已使用 + | + v +返回成功 +``` + +#### 7.1.4 Token刷新机制 + +``` +Access Token过期 + | + v +检查Refresh Token + | + +-- 无效 --> 跳转登录 + | + v +验证Refresh Token签名 + | + v +查询用户状态 + | + +-- 用户禁用 --> 跳转登录 + | + v +生成新的Access Token + | + v +返回新Token +``` + +### 7.2 后端定时任务 + +#### 7.2.1 定时任务配置 (node-cron) + +```typescript +// src/services/cronService.ts + +import cron from 'node-cron'; +import { reminderService } from './reminderService'; +import { emailService } from './emailService'; +import { anniversaryService } from './anniversaryService'; + +class CronService { + constructor() { + this.scheduleJobs(); + } + + private scheduleJobs() { + // 每小时检查一次即将到来的提醒 (每小时第5分钟执行) + cron.schedule('5 * * * *', async () => { + console.log('[Cron] 检查即将到来的提醒...'); + await this.checkUpcomingReminders(); + }); + + // 每天凌晨2点生成过期提醒汇总 + cron.schedule('0 2 * * *', async () => { + console.log('[Cron] 生成每日提醒汇总...'); + await this.generateDailySummary(); + }); + + // 每天凌晨3点清理过期的密码重置Token + cron.schedule('0 3 * * *', async () => { + console.log('[Cron] 清理过期Token...'); + await this.cleanupExpiredTokens(); + }); + + // 每周日凌晨4点更新节假日数据 + cron.schedule('0 4 * * 0', async () => { + console.log('[Cron] 更新节假日数据...'); + await this.updateHolidays(); + }); + + // 每月1号凌晨1点VACUUM数据库 + cron.schedule('0 1 1 * *', async () => { + console.log('[Cron] 数据库维护...'); + await this.maintainDatabase(); + }); + } + + private async checkUpcomingReminders() { + try { + const upcomingReminders = await reminderService.getUpcomingReminders(24); + for (const reminder of upcomingReminders) { + const user = await this.getUserById(reminder.userId); + if (user && user.emailVerified) { + await emailService.sendReminderEmail(user.email, { + title: reminder.title, + remindTime: reminder.remindTime, + anniversaryTitle: reminder.anniversary?.title, + }); + // 标记为已发送 + await reminderService.markAsNotified(reminder.id); + } + } + } catch (error) { + console.error('[Cron] 检查提醒失败:', error); + } + } + + private async generateDailySummary() { + try { + const tomorrowReminders = await reminderService.getRemindersByDate( + new Date(Date.now() + 24 * 60 * 60 * 1000) + ); + // 发送给用户汇总邮件 + } catch (error) { + console.error('[Cron] 生成汇总失败:', error); + } + } + + private async cleanupExpiredTokens() { + // 清理过期的密码重置Token + } + + private async updateHolidays() { + // 更新节假日缓存 + } + + private async maintainDatabase() { + // 执行VACUUM ANALYZE + } +} + +export const cronService = new CronService(); +``` + +### 7.3 DeepSeek API集成 + +#### 7.3.1 Prompt设计 + +```typescript +// src/services/aiService.ts + +const SYSTEM_PROMPT = `你是一个智能纪念日管理助手。你的任务是: + +1. 解析用户输入的自然语言,提取纪念日相关信息 +2. 返回结构化的JSON格式结果 + +支持的纪念日类型: +- birthday: 生日 +- anniversary: 纪念日 +- holiday: 节假日 +- other: 其他 + +重复类型: +- none: 不重复 +- yearly: 每年 +- monthly: 每月 +- weekly: 每周 + +输出格式必须是有效的JSON对象,格式如下: +{ + "action": "create_anniversary" | "create_reminder" | "query" | "chat", + "title": "纪念日标题", + "date": "YYYY-MM-DD格式的日期", + "type": "birthday | anniversary | holiday | other", + "isLunar": true | false, + "repeatType": "none | yearly | monthly | weekly", + "remindDays": [-14, -7, -3, 0], + "message": "对用户的回复消息" +} + +请根据用户输入智能判断日期和提醒设置。`; + +const PARSE_EXAMPLE = ` +用户输入:"提醒我3天后是妈妈的生日,提前7天和3天提醒我" + +解析结果: +{ + "action": "create_anniversary", + "title": "妈妈的生日", + "date": "2026-02-14", + "type": "birthday", + "isLunar": false, + "repeatType": "yearly", + "remindDays": [-7, -3, 0], + "message": "我理解您想创建妈妈的生日纪念日,对吗?\n详情:\n- 标题:妈妈的生日\n- 日期:2026年2月14日\n- 重复:每年\n- 提醒:提前7天、3天、当天" +} +`; +``` + +#### 7.3.2 JSON解析服务 + +```typescript +import OpenAI from 'openai'; + +class AIService { + private client: OpenAI; + + constructor() { + this.client = new OpenAI({ + apiKey: process.env.DEEPSEEK_API_KEY, + baseURL: 'https://api.deepseek.com', + }); + } + + async parseNaturalLanguage(userId: string, message: string) { + const completion = await this.client.chat.completions.create({ + model: 'deepseek-chat', + messages: [ + { role: 'system', content: SYSTEM_PROMPT }, + { role: 'user', content: message }, + ], + temperature: 0.1, + response_format: { type: 'json_object' }, + }); + + const result = JSON.parse(completion.choices[0].message.content); + + // 保存对话历史 + await this.saveConversationHistory(userId, 'user', message); + await this.saveConversationHistory(userId, 'assistant', result.message); + + return result; + } + + async chat(userId: string, message: string, historyId?: string) { + const messages = await this.getConversationHistory(userId, historyId); + + messages.push({ role: 'user', content: message }); + + const completion = await this.client.chat.completions.create({ + model: 'deepseek-chat', + messages, + temperature: 0.7, + }); + + const reply = completion.choices[0].message.content; + + // 保存对话 + const newHistoryId = await this.saveConversationHistory(userId, 'user', message, reply); + + return { reply, historyId: newHistoryId }; + } + + private async saveConversationHistory( + userId: string, + role: 'user' | 'assistant', + content: string, + reply?: string + ): Promise { + // 存入数据库 + } + + private async getConversationHistory(userId: string, historyId?: string) { + // 获取历史消息 + } +} + +export const aiService = new AIService(); +``` + +### 7.4 节假日API集成 + +#### 7.4.1 动态获取与缓存策略 + +```typescript +// src/services/holidayService.ts + +import Redis from 'ioredis'; +import axios from 'axios'; + +const redis = new Redis(process.env.REDIS_URL); + +const HOLIDAY_API = 'https://api.annapi.cn/holidays'; + +class HolidayService { + // 缓存Key + private getCacheKey(year: number) => `holidays:${year}`; + + // 获取节假日数据 + async getHolidays(year: number) { + const cacheKey = this.getCacheKey(year); + + // 尝试从Redis获取 + const cached = await redis.get(cacheKey); + if (cached) { + return JSON.parse(cached); + } + + // 从API获取 + const response = await axios.get(`${HOLIDAY_API}/${year}`); + const holidays = response.data; + + // 缓存到Redis (24小时) + await redis.setex(cacheKey, 86400, JSON.stringify(holidays)); + + // 保存到数据库 + await this.saveToDatabase(holidays); + + return holidays; + } + + // 保存到数据库 + private async saveToDatabase(holidays: any[]) { + // 使用Prisma批量创建 + } + + // 获取所有节假日(从数据库) + async getHolidaysFromDB(year: number) { + // 从PostgreSQL查询 + } + + // 搜索节假日 + async searchHolidays(query: string, year: number) { + const holidays = await this.getHolidays(year); + return holidays.filter(h => + h.name.includes(query) || + h.date.includes(query) + ); + } +} + +export const holidayService = new HolidayService(); +``` + +--- + +## 8. 腾讯云部署方案 + +### 8.1 Nginx配置 + +```nginx +# /etc/nginx/sites-available/qia-app + +server { + listen 80; + server_name qia.example.com; + + # 重定向到HTTPS + return 301 https://$server_name$request_uri; +} + +server { + listen 443 ssl http2; + server_name qia.example.com; + + # SSL证书配置 + ssl_certificate /etc/letsencrypt/live/qia.example.com/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/qia.example.com/privkey.pem; + ssl_session_timeout 1d; + ssl_session_cache shared:SSL:50m; + ssl_session_tickets off; + + # SSL安全配置 + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384; + ssl_prefer_server_ciphers off; + + # 添加安全头 + add_header Strict-Transport-Security "max-age=63072000" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-XSS-Protection "1; mode=block" always; + + # 静态资源目录 + root /var/www/qia-app/dist; + index index.html; + + # 前端路由支持 + location / { + try_files $uri $uri/ /index.html; + } + + # API代理 + location /api/ { + proxy_pass http://localhost:3000; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_cache_bypass $http_upgrade; + + # 超时设置 + proxy_connect_timeout 60s; + proxy_send_timeout 60s; + proxy_read_timeout 60s; + } + + # 静态资源缓存 + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + + # Let's Encrypt验证 + location /.well-known/acme-challenge/ { + root /var/www/certbot; + } +} +``` + +### 8.2 PM2进程管理配置 + +```javascript +// ecosystem.config.js +module.exports = { + apps: [ + { + name: 'express-api', + script: './dist/server.js', + cwd: '/var/www/qia-app/server', + instances: 1, + autorestart: true, + watch: false, + max_memory_restart: '500M', + env: { + NODE_ENV: 'production', + PORT: 3000, + }, + env_file: '.env', + error_file: '/var/log/pm2/express-api-error.log', + out_file: '/var/log/pm2/express-api-out.log', + log_file: '/var/log/pm2/express-api-combined.log', + time: true, + }, + ], +}; +``` + +PM2常用命令: +```bash +# 启动应用 +pm2 start ecosystem.config.js + +# 查看状态 +pm2 status + +# 查看日志 +pm2 logs express-api + +# 重启 +pm2 restart express-api + +# 停止 +pm2 stop express-api + +# 删除 +pm2 delete express-api + +# 开机自启 +pm2 startup +pm2 save +``` + +### 8.3 SSL证书自动续期 + +```bash +# /etc/cron.d/certbot-renew +# 每周一凌晨4点尝试续期 +0 4 * * 1 root certbot renew --quiet && nginx -s reload +``` + +续期脚本: +```bash +#!/bin/bash +# /var/www/certbot/renew.sh + +certbot renew --quiet + +if [ $? -eq 0 ]; then + nginx -s reload + echo "证书已续期,Nginx已重载" +fi +``` + +### 8.4 systemd服务配置 + +```ini +# /etc/systemd/system/qia-app.service + +[Unit] +Description=Qia Anniversary Management App +After=network.target postgresql.service redis-server.service + +[Service] +Type=forking +User=www-data +Group=www-data +WorkingDirectory=/var/www/qia-app/server +ExecStart=/usr/bin/pm2 resurrect +ExecStop=/usr/bin/pm2 kill +ExecReload=/bin/kill -HUP $MAINPID +Restart=on-failure +RestartSec=5 + +# 环境变量 +Environment=NODE_ENV=production +EnvironmentFile=/var/www/qia-app/.env + +# 日志 +StandardOutput=syslog +StandardError=syslog +SyslogIdentifier=qia-app + +[Install] +WantedBy=multi-user.target +``` + +```bash +# 启用服务 +sudo systemctl daemon-reload +sudo systemctl enable qia-app +sudo systemctl start qia-app + +# 查看状态 +sudo systemctl status qia-app + +# 查看日志 +sudo journalctl -u qia-app -f +``` + +--- + +## 9. 安全策略 + +### 9.1 JWT认证中间件 + +```typescript +// src/middlewares/authMiddleware.ts + +import { Request, Response, NextFunction } from 'express'; +import jwt from 'jsonwebtoken'; +import { PrismaClient } from '@prisma/client'; + +const prisma = new PrismaClient(); +const JWT_SECRET = process.env.JWT_SECRET!; +const JWT_REFRESH_SECRET = process.env.JWT_REFRESH_SECRET!; + +interface TokenPayload { + userId: string; + email: string; + type: 'access' | 'refresh'; +} + +declare global { + namespace Express { + interface Request { + user?: { + id: string; + email: string; + }; + } + } +} + +export const authMiddleware = async ( + req: Request, + res: Response, + next: NextFunction +): Promise => { + try { + const authHeader = req.headers.authorization; + + if (!authHeader || !authHeader.startsWith('Bearer ')) { + res.status(401).json({ + success: false, + error: 'UNAUTHORIZED', + message: '未提供认证令牌', + }); + return; + } + + const token = authHeader.split(' ')[1]; + + try { + const decoded = jwt.verify(token, JWT_SECRET) as TokenPayload; + + // 检查用户是否存在且未禁用 + const user = await prisma.user.findUnique({ + where: { id: decoded.userId }, + select: { id: true, email: true }, + }); + + if (!user) { + res.status(401).json({ + success: false, + error: 'USER_NOT_FOUND', + message: '用户不存在', + }); + return; + } + + req.user = { id: user.id, email: user.email }; + next(); + } catch (jwtError) { + if (jwtError instanceof jwt.TokenExpiredError) { + res.status(401).json({ + success: false, + error: 'TOKEN_EXPIRED', + message: '认证令牌已过期', + }); + } else { + res.status(401).json({ + success: false, + error: 'INVALID_TOKEN', + message: '无效的认证令牌', + }); + } + } + } catch (error) { + next(error); + } +}; + +export const optionalAuthMiddleware = async ( + req: Request, + res: Response, + next: NextFunction +): Promise => { + const authHeader = req.headers.authorization; + + if (authHeader && authHeader.startsWith('Bearer ')) { + await authMiddleware(req, res, next); + } else { + next(); + } +}; +``` + +### 9.2 请求限流 + +```typescript +// src/middlewares/rateLimitMiddleware.ts + +import rateLimit from 'express-rate-limit'; +import { Request, Response } from 'express'; + +// 通用API限流 +export const apiRateLimit = rateLimit({ + windowMs: 15 * 60 * 1000, // 15分钟 + max: 100, // 100次请求 + message: { + success: false, + error: 'RATE_LIMIT_EXCEEDED', + message: '请求过于频繁,请稍后再试', + }, + standardHeaders: true, + legacyHeaders: false, +}); + +// 登录限流 +export const loginRateLimit = rateLimit({ + windowMs: 15 * 60 * 1000, + max: 5, // 15分钟内最多5次登录尝试 + message: { + success: false, + error: 'LOGIN_RATE_LIMIT', + message: '登录尝试次数过多,请15分钟后重试', + }, + skipSuccessfulRequests: true, +}); + +// 验证码限流 +export const captchaRateLimit = rateLimit({ + windowMs: 60 * 60 * 1000, // 1小时 + max: 10, // 最多10次请求 + message: { + success: false, + error: 'CAPTCHA_RATE_LIMIT', + message: '验证码请求过于频繁', + }, +}); + +// AI API限流 +export const aiRateLimit = rateLimit({ + windowMs: 60 * 1000, // 1分钟 + max: 20, // 每分钟20次 + message: { + success: false, + error: 'AI_RATE_LIMIT', + message: 'AI请求过于频繁,请稍后再试', + }, + keyGenerator: (req: Request) => req.user?.id || req.ip, +}); + +// 注册限流 +export const registerRateLimit = rateLimit({ + windowMs: 60 * 60 * 1000, // 1小时 + max: 5, // 最多5次注册 + message: { + success: false, + error: 'REGISTER_RATE_LIMIT', + message: '注册过于频繁,请稍后再试', + }, +}); +``` + +### 9.3 安全中间件 + +```typescript +// src/middlewares/securityMiddleware.ts + +import helmet from 'helmet'; +import cors from 'cors'; +import { Request, Response, NextFunction } from 'express'; + +// Helmet安全头 +export const securityMiddleware = helmet({ + contentSecurityPolicy: { + directives: { + defaultSrc: ["'self'"], + styleSrc: ["'self'", "'unsafe-inline'"], + scriptSrc: ["'self'"], + imgSrc: ["'self'", "data:", "https:"], + connectSrc: ["'self'", "https://api.deepseek.com"], + }, + }, + crossOriginEmbedderPolicy: false, +}); + +// CORS配置 +export const corsMiddleware = cors({ + origin: process.env.ALLOWED_ORIGINS?.split(',') || ['https://qia.example.com'], + credentials: true, + methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], + allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'], + exposedHeaders: ['X-Total-Count'], + maxAge: 86400, // 24小时 +}); + +// XSS防护 (在HTML输出时自动转义) +export const xssMiddleware = (req: Request, res: Response, next: NextFunction) => { + // Express默认会对路由参数进行一些XSS防护 + // 额外的:在JSON响应中转义特殊字符 + const originalJson = res.json; + res.json = (body: any) => { + const sanitized = sanitizeObject(body); + return originalJson.call(res, sanitized); + }; + next(); +}; + +function sanitizeObject(obj: any): any { + if (typeof obj === 'string') { + return obj + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + } + if (Array.isArray(obj)) { + return obj.map(sanitizeObject); + } + if (typeof obj === 'object' && obj !== null) { + const result: any = {}; + for (const key in obj) { + result[key] = sanitizeObject(obj[key]); + } + return result; + } + return obj; +} +``` + +### 9.4 密码强度验证 + +```typescript +// src/validators/authValidator.ts + +import { body } from 'express-validator'; +import { z } from 'zod'; + +// 密码验证正则 +const PASSWORD_REGEX = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/; + +export const passwordValidation = body('password') + .isLength({ min: 8, max: 128 }) + .withMessage('密码长度必须在8-128个字符之间') + .matches(PASSWORD_REGEX) + .withMessage('密码必须包含大小写字母、数字和特殊字符'); + +// Zod Schema验证 +export const registerSchema = z.object({ + email: z.string().email('邮箱格式不正确'), + password: z + .string() + .min(8, '密码至少8个字符') + .regex(/[A-Z]/, '密码必须包含大写字母') + .regex(/[a-z]/, '密码必须包含小写字母') + .regex(/\d/, '密码必须包含数字') + .regex(/[@$!%*?&]/, '密码必须包含特殊字符'), + nickname: z + .string() + .min(2, '昵称至少2个字符') + .max(50, '昵称最多50个字符') + .regex(/^[\u4e00-\u9fa5a-zA-Z0-9_]+$/, '昵称只能包含中文、英文、数字和下划线'), +}); + +// 密码强度检查函数 +export const checkPasswordStrength = (password: string): { + score: number; + feedback: string[]; +} => { + const feedback: string[] = []; + let score = 0; + + if (password.length >= 8) score++; + else feedback.push('密码长度至少8个字符'); + + if (/[A-Z]/.test(password)) score++; + else feedback.push('需要包含大写字母'); + + if (/[a-z]/.test(password)) score++; + else feedback.push('需要包含小写字母'); + + if (/\d/.test(password)) score++; + else feedback.push('需要包含数字'); + + if (/[@$!%*?&]/.test(password)) score++; + else feedback.push('需要包含特殊字符'); + + return { score, feedback }; +}; +``` + +--- + +## 10. 性能优化建议 + +### 10.1 前端优化 + +#### 10.1.1 代码分割与懒加载 + +```typescript +// src/App.tsx + +import { Suspense, lazy } from 'react'; +import { BrowserRouter, Routes, Route } from 'react-router-dom'; +import { LoadingSpinner } from './components/common/Loading'; + +// 懒加载页面组件 +const Home = lazy(() => import('./pages/Home')); +const Login = lazy(() => import('./pages/Auth/Login')); +const Register = lazy(() => import('./pages/Auth/Register')); +const Dashboard = lazy(() => import('./pages/Dashboard')); +const AnniversaryList = lazy(() => import('./pages/Anniversary/List')); +const NoteCanvas = lazy(() => import('./pages/Note/Canvas')); + +function App() { + return ( + + }> + + } /> + } /> + } /> + } /> + } /> + } /> + + + + ); +} + +export default App; +``` + +#### 10.1.2 缓存策略 + +```typescript +// 使用React Query进行数据缓存 + +import { useQuery, useMutation, useQueryClient } from '@react-query'; + +const useAnniversaries = (userId: string) => { + return useQuery({ + queryKey: ['anniversaries', userId], + queryFn: () => anniversaryService.getList(userId), + staleTime: 5 * 60 * 1000, // 5分钟内数据视为新鲜 + cacheTime: 30 * 60 * 1000, // 30分钟后清理缓存 + }); +}; +``` + +#### 10.1.3 静态资源CDN + +```typescript +// vite.config.ts + +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +export default defineConfig({ + plugins: [react()], + build: { + rollupOptions: { + output: { + manualChunks: { + 'vendor-react': ['react', 'react-dom', 'react-router-dom'], + 'vendor-mantine': ['@mantine/core', '@mantine/hooks'], + 'vendor-ui': ['@mantine/core', '@mantine/hooks', '@mantine/dates'], + }, + }, + }, + }, +}); +``` + +### 10.2 后端优化 + +#### 10.2.1 数据库连接池 + +```typescript +// src/config/database.ts + +import { PrismaClient } from '@prisma/client'; + +const prismaClientSingleton = () => { + return new PrismaClient({ + log: process.env.NODE_ENV === 'development' + ? ['query', 'error', 'warn'] + : ['error'], + datasources: { + db: { + url: process.env.DATABASE_URL, + }, + }, + }); +}; + +type PrismaClientSingleton = ReturnType; + +const globalForPrisma = globalThis as unknown as { + prisma: PrismaClientSingleton | undefined; +}; + +export const prisma = globalForPrisma.prisma ?? prismaClientSingleton(); + +if (process.env.NODE_ENV !== 'production') { + globalForPrisma.prisma = prisma; +} + +// 手动管理连接池 +prisma.$connect().then(() => { + console.log('数据库连接成功'); +}); + +// 优雅关闭 +process.on('SIGINT', async () => { + await prisma.$disconnect(); + process.exit(0); +}); +``` + +#### 10.2.2 Redis缓存层 + +```typescript +// src/utils/cache.ts + +import Redis from 'ioredis'; +import { promisify } from 'util'; + +const redis = new Redis({ + host: process.env.REDIS_HOST || 'localhost', + port: parseInt(process.env.REDIS_PORT || '6379'), + password: process.env.REDIS_PASSWORD, + db: 0, + retryStrategy: (times) => { + const delay = Math.min(times * 50, 2000); + return delay; + }, +}); + +class CacheService { + // 获取缓存 + async get(key: string): Promise { + const value = await redis.get(key); + return value ? JSON.parse(value) : null; + } + + // 设置缓存 + async set(key: string, value: any, ttlSeconds: number = 3600): Promise { + await redis.setex(key, ttlSeconds, JSON.stringify(value)); + } + + // 删除缓存 + async del(key: string): Promise { + await redis.del(key); + } + + // 批量删除 + async delPattern(pattern: string): Promise { + const keys = await redis.keys(pattern); + if (keys.length > 0) { + await redis.del(...keys); + } + } + + // 检查键是否存在 + async exists(key: string): Promise { + return (await redis.exists(key)) === 1; + } + + // 增量操作 + async incr(key: string): Promise { + return await redis.incr(key); + } +} + +export const cacheService = new CacheService(); +``` + +#### 10.2.3 异步处理 + +```typescript +// 使用消息队列处理非实时任务 + +import { Queue, Worker } from 'bull'; +import Redis from 'ioredis'; + +const redis = new Redis(process.env.REDIS_URL); + +// 邮件队列 +const emailQueue = new Queue('email', { redis: redis.duplicate() }); + +// 添加到队列 +export const sendReminderEmailJob = async (data: { + email: string; + title: string; + remindTime: Date; +}) => { + await emailQueue.add('send-reminder', data, { + delay: 1000, // 延迟1秒执行 + attempts: 3, + backoff: 5000, + }); +}; + +// 邮件发送Worker +const emailWorker = new Worker( + 'email', + async (job) => { + const { email, title, remindTime } = job.data; + await emailService.sendReminderEmail(email, { title, remindTime }); + }, + { concurrency: 5 } +); + +emailWorker.on('completed', (job) => { + console.log(`邮件发送完成: ${job.id}`); +}); + +emailWorker.on('failed', (job, err) => { + console.error(`邮件发送失败: ${job?.id}`, err); +}); +``` + +### 10.3 数据库维护 + +#### 10.3.1 定期维护脚本 + +```sql +-- 每月执行的维护脚本 +-- 保存为 maintenance.sql + +-- 1. 清理旧对话历史(保留最近3个月) +DELETE FROM conversation_history +WHERE created_at < NOW() - INTERVAL '3 months'; + +-- 2. 清理已使用的密码重置Token +DELETE FROM password_resets +WHERE used = true OR expires_at < NOW(); + +-- 3. 更新统计信息 +VACUUM ANALYZE users; +VACUUM ANALYZE anniversaries; +VACUUM ANALYZE reminders; +VACUUM ANALYZE notes; +VACUUM ANALYZE conversation_history; + +-- 4. 检查索引碎片 +SELECT + indexrelname, + idx_scan, + idx_tup_read, + idx_tup_fetch, + pg_size_pretty(pg_relation_size(indexrelname::regclass)) as index_size +FROM pg_stat_user_indexes +WHERE schemaname = 'public' +ORDER BY idx_scan ASC; +``` + +#### 10.3.2 慢查询分析 + +```sql +-- 开启慢查询日志(需要重启数据库) +ALTER DATABASE qia_db SET log_min_duration_statement = 1000; + +-- 查询最近慢查询 +SELECT + query, + calls, + mean_time, + total_time, + rows +FROM pg_stat_statements +ORDER BY mean_time DESC +LIMIT 20; + +-- 查询频繁查询 +SELECT + query, + calls, + total_time, + rows +FROM pg_stat_statements +ORDER BY calls DESC +LIMIT 20; + +-- 分析查询计划 +EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT) +SELECT * FROM anniversaries +WHERE user_id = 'uuid-here' +ORDER BY date DESC; +``` + +--- + +## 附录 + +### A. 环境变量配置 + +```env +# .env.example + +# 应用配置 +NODE_ENV=production +PORT=3000 +CLIENT_URL=https://qia.example.com + +# 数据库配置 +DATABASE_URL=postgresql://qia_user:password@localhost:5432/qia_db?schema=public + +# Redis配置 +REDIS_HOST=localhost +REDIS_PORT=6379 +REDIS_PASSWORD=your_redis_password + +# JWT配置 +JWT_SECRET=your_jwt_access_secret_min_32_chars +JWT_REFRESH_SECRET=your_jwt_refresh_secret_min_32_chars +JWT_ACCESS_EXPIRES=3600 +JWT_REFRESH_EXPIRES=604800 + +# 邮件配置 +SMTP_HOST=smtp.example.com +SMTP_PORT=587 +SMTP_USER=noreply@qia.example.com +SMTP_PASSWORD=your_smtp_password +EMAIL_FROM=noreply@qia.example.com + +# DeepSeek API配置 +DEEPSEEK_API_KEY=your_deepseek_api_key + +# 腾讯云配置 +TENCENT_CLOUD_SECRET_ID=your_secret_id +TENCENT_CLOUD_SECRET_KEY=your_secret_key + +# 安全配置 +BCRYPT_ROUNDS=12 +``` + +### B. API响应格式规范 + +```typescript +// 成功响应 +interface SuccessResponse { + success: true; + data: T; + message?: string; +} + +// 分页响应 +interface PaginatedResponse { + success: true; + data: { + items: T[]; + pagination: { + page: number; + limit: number; + total: number; + totalPages: number; + }; + }; +} + +// 错误响应 +interface ErrorResponse { + success: false; + error: string; + message: string; + details?: any; +} + +// 错误码定义 +enum ErrorCode { + VALIDATION_ERROR = 'VALIDATION_ERROR', + UNAUTHORIZED = 'UNAUTHORIZED', + FORBIDDEN = 'FORBIDDEN', + NOT_FOUND = 'NOT_FOUND', + RATE_LIMIT_EXCEEDED = 'RATE_LIMIT_EXCEEDED', + INTERNAL_ERROR = 'INTERNAL_ERROR', + INVALID_TOKEN = 'INVALID_TOKEN', + TOKEN_EXPIRED = 'TOKEN_EXPIRED', + USER_NOT_FOUND = 'USER_NOT_FOUND', + EMAIL_EXISTS = 'EMAIL_EXISTS', + INVALID_CREDENTIALS = 'INVALID_CREDENTIALS', +} +``` + +### C. 版本历史 + +| 版本 | 日期 | 描述 | +|-----|------|------| +| 1.0.0 | 2026-01-28 | 初始架构设计 | + +--- + +*文档维护: 系统架构组* +*最后更新: 2026-01-28* diff --git a/docs/prd.md b/docs/prd.md new file mode 100644 index 0000000..6ef1d86 --- /dev/null +++ b/docs/prd.md @@ -0,0 +1,1576 @@ +# 产品需求文档(PRD) + +## 掐日子(qia)- AI 纪念日·提醒 + +| 项目 | 内容 | +|-----|------| +| **版本号** | v1.0.0 | +| **状态** | 待评审 | +| **创建日期** | 2026-01-28 | +| **目标用户** | 需要管理纪念日、提醒任务的普通用户 | +| **产品定位** | 轻便、灵活的倒数日和提醒App,专注提醒功能 | + +--- + +## 1 产品概述 + +### 1.1 产品定位 + +掐日子(qia)是一款AI驱动的纪念日和提醒网页应用,旨在帮助用户轻松管理生活中的重要日期和待办事项。产品以"轻便、灵活"为核心设计理念,通过自然语言处理技术降低用户添加纪念日和提醒的门槛,让日期管理变得简单自然。产品专注于提醒功能的核心体验,不追求功能的堆砌,而是将每一个核心功能打磨到极致,为用户提供流畅、高效的日期管理工具。 + +掐日子的核心竞争力在于其AI智能解析能力。用户无需手动选择日期类型、重复方式等复杂选项,只需用自然语言描述(如"提醒我下周三交论文"、"农历三月初三吃荠菜煮蛋"),系统即可自动识别公历或农历、解析日期语义、判断是否需要重复,并以自然语言展示解析结果供用户确认。这种交互方式大大降低了用户的学习成本和操作负担,使得各类用户都能快速上手使用。 + +### 1.2 目标用户 + +掐日子的目标用户群体主要分为以下几类: + +第一类是**学生群体**,他们需要管理各种学业相关的截止日期、考试时间、作业提交日期,同时也有生日、纪念日等社交需求。这类用户对产品的易用性要求较高,偏好简洁直观的界面设计,对新技术接受度高,能够快速适应AI交互方式。学生群体通常拥有较为固定的作息时间,适合接收固定时段的提醒通知。 + +第二类是**职场人士**,他们面临繁忙的工作任务,需要管理项目截止日期、会议时间、客户生日、节假日安排等各类事项。这类用户对产品的效率要求极高,希望能够快速添加和查看提醒,对通知的及时性和准确性有较高要求。职场人士通常同时使用多个设备,对跨平台体验和数据同步有一定需求。 + +第三类是**家庭用户**,他们关注家庭成员的生日、纪念日、传统节日(如春节、中秋、端午等),需要安排家庭聚会、送礼物等事宜。这类用户对中国传统节日和农历日期有特殊需求,产品内置的法定节假日和农历支持正好满足他们的痛点。家庭用户对界面的温馨感和易读性有较高要求。 + +### 1.3 核心价值 + +掐日子的核心价值主张可以概括为三个关键词:**智能、便捷、可靠**。 + +**智能**体现在AI驱动的自然语言交互。用户可以用日常语言描述日期和任务,系统自动完成复杂的解析工作。这种智能不是炫技,而是真正为用户省去繁琐的操作步骤,让添加纪念日和提醒变得像发消息一样简单。AI还能够记住用户的偏好和习惯,在后续使用中提供更加个性化的服务。 + +**便捷**体现在产品设计的每一个细节。从登录态的自动保持到数据的实时同步,从清晰的界面布局到流畅的交互体验,产品致力于消除用户使用过程中的任何摩擦。用户可以在任何设备上随时访问自己的数据,无需担心数据丢失或同步问题。便签功能支持富文本编辑,自动保存机制确保用户的创意和想法不会丢失。 + +**可靠**体现在产品的稳定性和数据的安全性。所有数据存储在专业的云端数据库中,用户的登录信息经过加密处理,重要日期和提醒不会因为网络波动或设备故障而丢失。浏览器桌面通知功能确保用户不会错过重要的提醒,即使在忙碌的工作中也能及时收到通知。 + +### 1.4 竞品分析 + +当前市场上的日期管理应用主要分为两类:一类是传统的日历应用(如Google Calendar、Apple Calendar),功能强大但界面复杂,学习成本高;另一类是轻量级的倒数日应用(如倒数日Days Matter),功能单一但专注易用。掐日子的定位介于两者之间,既保持了轻量级应用的易用性,又通过AI技术提供了超越传统应用的智能化体验。 + +与竞品相比,掐日子的差异化优势主要体现在三个方面。首先是AI自然语言解析能力,这是传统应用不具备的,用户无需在复杂的表单中反复选择;其次是对中国用户的深度适配,包括农历支持和中国法定节假日内置;最后是简洁的界面设计,将纪念日、提醒、便签、AI对话等功能整合在一个页面中,减少用户的认知负担和操作切换成本。 + +--- + +## 2 用户故事 + +### 2.1 典型用户场景 + +**场景一:快速添加生日提醒** + +小李是一名大学生,他的好朋友小王下周就要过生日了。小李想提前准备生日祝福和礼物,需要设置一个提醒。他在掐日子的AI对话中输入:"小王3月15号生日,每年提醒我"。系统自动识别出这是公历日期、生日为每年重复,并以自然语言展示解析结果:"已识别到:纪念日『小王生日』,日期为每年3月15日(公历),重复方式为年度重复"。小李确认后,纪念日卡片就创建完成了。在小王生日到来的前一周,系统会通过浏览器通知提醒小李。 + +**场景二:管理学业任务截止日期** + +小张是一名研究生,最近需要提交一篇课程论文,截止日期是下个月的第一周。他不确定具体是哪一天,于是先在AI对话中输入:"提醒我下个月第一周提交机器学习课程论文"。系统解析后显示:"已识别到:提醒『提交机器学习课程论文』,时间暂定为下个月第一周的某天,请确认具体日期"。小张确认了日期后,提醒被创建。当截止日期临近时,系统会提前三天和当天分别发送提醒通知。 + +**场景三:追踪传统节日** + +王女士是一名家庭主妇,她需要记住家里每个月的传统节日和家人生日。她在AI对话中输入:"春节是农历正月初一,每年的提醒我买年货"。系统识别出这是农历日期,转换为对应的公历日期,并创建年度重复的纪念日。王女士还添加了家庭成员的农历生日,设置好重复规则后,系统会自动计算每年的公历对应日期并提前提醒。她还可以查看内置的中国法定节假日,快速了解放假安排。 + +**场景四:整理每日待办事项** + +刘先生是一名项目经理,每天需要处理大量的任务和会议。他习惯在每天早上打开掐日子,把当天需要完成的工作事项添加到提醒列表中。通过AI对话,他可以快速输入"上午十点开项目例会"、"下午三点前提交工作报告"这样的自然语言描述。系统会自动识别时间并创建提醒。完成的任务可以点击checkbox标记完成,已完成的事项会自动归类到列表底部。临时想到的事项也可以随时添加,保持待办列表的整洁有序。 + +**场景五:记录临时想法** + +陈小姐是一名创意工作者,她经常有一些好的想法需要及时记录。她使用掐日子的便签功能,把灵感、待办事项、会议要点等内容记录下来。便签支持富文本编辑,可以格式化文字、添加列表、插入链接等。便签内容会自动保存,无需手动点击保存按钮。当她在其他地方需要查阅这些内容时,只需登录掐日子就能看到。 + +**场景六:处理过期提醒** + +赵先生周末外出没有及时查看手机,周一上班时发现有几个提醒已经错过了。他打开掐日子,在提醒列表的底部"已错过"分类中看到了这些提醒。系统按照过期时间排序,最紧急的事项排在最前面。赵先生处理完这些过期提醒后,可以选择删除或者修改时间后重新激活。 + +### 2.2 用户旅程地图 + +用户在掐日子的完整旅程可以分为以下几个阶段: + +**发现阶段**:用户通过搜索引擎、社交媒体或朋友推荐了解到掐日子产品。用户被产品的slogan"AI 纪念日·提醒"吸引,被其简洁的宣传页面设计打动,决定点击登录按钮开始体验。 + +**注册阶段**:用户点击登录后进入注册流程。用户输入邮箱和密码完成注册,系统发送验证邮件(可选,MVP阶段可简化)。注册成功后用户进入Home页面,看到空白的纪念日和提醒列表。 + +**探索阶段**:用户开始尝试使用各个功能。用户首先尝试AI对话功能,输入第一个纪念日或提醒,体验智能解析的便捷性。用户查看内置的法定节假日,了解如何添加传统节日。用户尝试编辑便签,熟悉富文本编辑器的操作。 + +**使用阶段**:用户开始日常使用产品。用户每天早上查看当天的提醒事项,添加新的待办。用户定期查看即将到来的纪念日,提前准备。用户使用便签记录工作笔记和生活感悟。AI对话成为用户添加新事项的主要入口。 + +**依赖阶段**:用户对产品形成依赖,重要日期和提醒都记录在掐日子中。用户开始设置浏览器通知,确保不会错过任何提醒。用户在不同设备上访问掐日子,数据实时同步。当用户有自然语言描述的日期需求时,第一时间想到使用掐日子。 + +--- + +## 3 功能详细描述 + +### 3.1 登录与认证 + +#### 3.1.1 功能概述 + +登录与认证是用户进入产品的第一道门槛,也是数据安全的重要保障。本模块提供邮箱密码注册、登录、登出以及登录态保持功能。MVP阶段采用简化方案,支持邮箱密码认证,后续可扩展第三方登录(如微信、Google)。 + +#### 3.1.2 功能详细描述 + +**注册功能**:用户在注册页面输入邮箱地址和密码。系统对邮箱格式进行校验,确保邮箱格式正确。密码要求至少8位,包含字母和数字,提高安全性。用户提交注册信息后,系统检查该邮箱是否已被注册。若未注册,系统在用户表中创建新记录,密码经过哈希处理后存储。注册成功后,系统自动创建一条便签记录供用户使用。用户登录态保存在localStorage中,下次访问自动登录。 + +**登录功能**:用户在登录页面输入邮箱和密码。系统校验邮箱格式和密码非空。系统查询用户表,验证邮箱和密码是否匹配。验证通过后,系统生成登录态(可使用JWT或session),保存在localStorage中。系统跳转到Home页面,并在界面上显示用户邮箱,标识已登录状态。若验证失败,系统显示"邮箱或密码错误"提示,不区分具体错误原因,防止信息泄露。 + +**登出功能**:用户点击界面上的登出按钮。系统清除localStorage中的登录态。系统跳转到宣传页。用户在登录态过期或被清除后,需要重新登录才能访问Home页面。 + +**登录态保持**:用户登录后,登录态保存在localStorage中,有效期为30天(可配置)。用户访问应用时,系统检查localStorage中的登录态是否有效。若有效,自动跳转到Home页面;若无效或不存在,显示宣传页,用户可选择登录或注册。登录态与用户邮箱绑定,后端不存储session状态,采用无状态认证设计。 + +#### 3.1.3 业务规则 + +邮箱格式必须符合标准邮箱格式(username@domain.extension)。密码长度至少8位,必须包含字母和数字。同一邮箱只能注册一个账户。登录态有效期为30天,续期可在用户活跃时自动延长。登录态存储在localStorage中,前端代码中不应暴露密码明文。 + +### 3.2 纪念日管理 + +#### 3.2.1 功能概述 + +纪念日功能用于管理用户的重要日期,如生日、纪念日、节日等。纪念日支持倒计时显示和自动重复(年度、月度),同时内置中国法定节假日,方便用户快速添加。纪念日卡片支持点击展开详情,支持二次编辑。 + +#### 3.2.2 功能详细描述 + +**查看纪念日列表**:用户在Home页面左侧查看纪念日列表。列表显示所有纪念日卡片,按日期从近到远排序。每个卡片显示纪念日名称、倒计时天数、日期信息。公历纪念日显示"公历"标识,农历纪念日显示"农历"标识。年度重复的纪念日显示重复图标。法定节假日显示节日标识。点击卡片可展开详情弹窗,显示更多信息和操作选项。 + +**添加纪念日**:用户可通过AI对话或手动添加两种方式创建纪念日。AI对话方式:用户输入自然语言描述,系统自动解析并展示解析结果,用户确认后创建。手动添加方式:用户点击纪念日列表上方的添加按钮,弹出添加弹窗。手动添加需要填写:纪念日名称(必填)、日期(必填)、是否为农历(必选)、重复方式(必选:仅一次、年度重复、月度重复)。系统验证必填字段,验证通过后创建纪念日。 + +**内置法定节假日**:系统预置中国法定节假日列表,包括:元旦(1月1日)、春节(农历正月初一)、清明节(4月5日前后)、劳动节(5月1日)、端午节(农历五月初五)、中秋节(农历八月十五)、国庆节(10月1日)。用户点击"添加节假日"按钮,可选择内置节日快速添加。节假日作为纪念日的一种,默认设置为年度重复。 + +**倒计时显示**:对于未到来的纪念日,系统计算并显示倒计时天数。倒计时从当前日期计算到纪念日的下一个日期。公历纪念日每年日期固定,农历纪念日需要转换为公历后计算。倒计时大于365天时显示"还有X年",1-365天时显示"还有X天",小于1天时显示"今日"。对于已过的纪念日,显示"已过X天"。 + +**重复规则**:纪念日支持三种重复方式。仅一次:仅在设置的日期提醒,不自动重复。年度重复:每年到达纪念日日期时自动重复,可选择是否保留历史记录。月度重复:每月到达纪念日日期时自动重复,适用于每月固定日期的任务或事件。重复规则在创建时可设置,创建后可修改。 + +**二次编辑**:用户点击纪念日卡片,弹出详情弹窗。弹窗显示纪念日的完整信息:名称、日期、是否农历、重复方式、创建时间、更新时间。用户可修改除ID和创建时间外的所有字段。修改后点击保存,系统更新数据库记录。弹窗同时提供删除操作,用户确认后删除纪念日。 + +#### 3.2.3 业务规则 + +纪念日名称最大长度为100字符。日期不能早于创建时间(仅一次的纪念日)。农历日期需要转换为公历进行存储和计算。倒计时以天为单位,不计算小时和分钟。重复纪念日不自动删除历史记录,但界面只显示下一个即将到来的日期。删除纪念日后不可恢复,需谨慎操作。 + +### 3.3 提醒管理 + +#### 3.3.1 功能概述 + +提醒功能用于管理短期任务和待办事项。提醒按时间分组显示(今天、明天、更久之后),支持标记完成。过期提醒归入"已错过"分类。提醒不支持长周期重复,适合短期任务的追踪。 + +#### 3.3.2 功能详细描述 + +**查看提醒列表**:用户在Home页面左侧查看提醒列表。列表分为三个分组:今天(今日24:00前的提醒)、明天(明日0:00-24:00的提醒)、更久之后(明日之后的提醒)。底部单独显示"已错过"分组,包含所有已过期未完成的提醒。每个分组内的提醒按时间顺序排序。用户可在分组间快速切换和浏览。 + +**添加提醒**:用户可通过AI对话或手动添加两种方式创建提醒。AI对话方式:用户输入自然语言描述(如"提醒我明天下午三点开会"),系统自动解析时间、事件内容,以自然语言展示解析结果,用户确认后创建。手动添加方式:用户点击提醒列表上方的添加按钮,弹出添加弹窗。手动添加需要填写:提醒内容(必填)、提醒时间(必填,支持日期和时间选择)。系统验证必填字段,验证通过后创建提醒。 + +**标记完成**:用户点击提醒卡片左侧的checkbox,完成该提醒。完成后的提醒移动到所在分组的底部,显示完成状态和完成时间。用户可点击取消完成,提醒恢复原位置。未过期提醒完成不进入"已错过"分组。已完成提醒可选择删除。 + +**过期提醒处理**:当提醒时间已过且未完成时,提醒自动归入"已错过"分组。"已错过"分组按过期时间从远到近排序,最紧急的排在最前面。用户点击过期提醒可查看详情,可选择修改时间后重新激活,或直接删除。系统不自动删除过期提醒,由用户手动处理。 + +**二次编辑**:用户点击提醒卡片,弹出详情弹窗。弹窗显示提醒的完整信息:内容、时间、完成状态、创建时间、更新时间。用户可修改内容和时间,完成状态不可直接修改(需通过checkbox操作)。修改后点击保存,系统更新数据库记录。弹窗提供删除操作,用户确认后删除提醒。 + +#### 3.3.3 业务规则 + +提醒内容最大长度为500字符。提醒时间不能早于当前时间(创建时)。完成后的提醒不触发通知。过期提醒不自动删除,保留供用户处理。提醒不支持重复规则,如需重复应使用纪念日功能。"已错过"分组只显示未完成的过期提醒。 + +### 3.4 便签功能 + +#### 3.4.1 功能概述 + +便签功能提供富文本编辑能力,帮助用户记录想法、笔记、待办清单等内容。便签支持自动保存,确保用户的创作不会丢失。MVP阶段限制每用户仅支持一条便签记录,后续可扩展为多条便签管理。 + +#### 3.4.2 功能详细描述 + +**查看便签**:用户在Home页面右侧查看便签区域。便签区域显示富文本内容,采用类似文档的布局。当便签为空时,显示占位提示文字"开始记录你的想法..."。便签内容自动换行显示,支持多种文本格式。 + +**编辑便签**:用户点击便签区域进入编辑模式。编辑器采用Quill富文本组件,支持以下格式:标题(H1、H2)、正文段落、无序列表、有序列表、加粗、斜体、下划线、删除线、链接。用户输入内容后,编辑器自动聚焦。编辑完成后,用户可点击便签区域外任意位置退出编辑模式,系统自动保存。 + +**自动保存**:用户在编辑过程中无需手动保存。系统监听编辑器的change事件,在内容变化后500毫秒内自动保存。保存时显示"保存中"状态指示,保存完成后显示"已保存"。网络断开时显示"离线保存"提示,恢复网络后自动同步。便签数据实时保存到后端,确保数据不会因意外丢失。 + +**清空便签**:用户可点击便签工具栏中的清空按钮。清空前系统弹出确认提示"确定要清空便签内容吗?此操作不可恢复"。用户确认后,便签内容被清空,但便签记录本身保留,可重新编辑。 + +#### 3.4.3 业务规则 + +便签内容存储为HTML格式,支持 Quill输出的标准HTML标签。每用户限制一条便签记录,便签ID固定为用户ID(或生成唯一标识)。便签内容最大长度为10000字符,超出时提示用户。便签的创建时间和更新时间随保存操作更新。自动保存间隔为500毫秒防抖。 + +### 3.5 AI对话功能 + +#### 3.5.1 功能概述 + +AI对话是掐日子的核心特色功能,通过自然语言处理技术,让用户以日常语言描述纪念日或提醒,系统自动解析并创建。AI对话降低了用户添加日期的门槛,提升了使用效率,是产品的核心竞争力所在。 + +#### 3.5.2 功能详细描述 + +**发起对话**:用户在Home页面右侧的AI对话区域发起对话。对话区域包含:对话历史列表、输入框、发送按钮。用户点击输入框聚焦,输入描述文字后点击发送。发送后,用户描述显示在对话列表中,AI开始解析。 + +**自然语言解析**:AI接收用户输入的自然语言,进行语义解析。解析内容包括:事件名称(标题)、日期类型(公历/农历)、日期时间、重复方式(仅一次/年度重复/月度重复/不重复)、语义分类(纪念日/提醒)。解析完成后,AI生成自然语言的解析结果供用户确认。示例:用户输入"提醒我下周三交论文",AI解析结果为"已识别到:提醒『交论文』,时间暂定为下周三(如2026年2月5日),请确认具体日期和内容"。 + +**确认与创建**:用户看到AI的解析结果后,有两个选择。确认:用户点击"确认创建"按钮,系统根据解析结果创建对应的纪念日或提醒,创建成功后显示成功提示,跳转到对应列表并高亮新创建的卡片。修改:用户发现解析结果有误,可直接输入修正信息,AI重新解析,或用户手动在详情弹窗中修改。 + +**解析结果展示**:AI以卡片形式展示解析结果,包含:事件标题、日期类型、日期时间、重复方式、来源文本。卡片上提供"确认创建"和"重新输入"两个操作按钮。解析结果应尽可能准确,对于不确定的日期(如"下周三"需确认具体日期),应标注并提示用户确认。 + +**错误处理**:当AI无法解析用户输入时,显示友好提示"抱歉,我没有理解您的意思。请尝试描述得更具体一些,例如:提醒我明天下午三点开会"。用户可重新输入或切换到手动添加模式。 + +#### 3.5.3 业务规则 + +AI对话是创建纪念日和提醒的主要入口,但不应是唯一入口,需保留手动添加功能。解析结果必须经过用户确认后才能创建,不可自动创建。AI无法处理时,应引导用户使用手动添加而非报错。对话历史保留最近10条,更早的自动清理。解析准确率作为AI模块的核心指标持续优化。 + +### 3.6 浏览器通知 + +#### 3.6.1 功能概述 + +浏览器桌面通知功能确保用户不会错过重要的纪念日和提醒。用户在首次使用时授权通知权限,系统在纪念日到来或提醒时间到达时发送桌面通知。通知支持点击跳转,方便用户快速查看详情。 + +#### 3.6.2 功能详细描述 + +**请求权限**:用户首次访问Home页面时,系统检测通知权限状态。若未授权,显示通知授权提示条"启用通知,确保不错过重要日期"。用户点击"启用"按钮,系统调用浏览器通知API请求权限。用户允许或拒绝后,提示条隐藏。用户在浏览器设置中可随时修改通知权限。 + +**发送通知**:系统在以下场景发送通知。纪念日通知:在纪念日当天早上9:00发送,提醒用户"今天是XX纪念日"。提醒通知:在提醒时间到达时立即发送,支持精确到分钟的提醒。过期通知:对于过期未处理的提醒,每天早上9:00发送汇总通知。 + +**通知内容**:通知标题显示应用名称"掐日子"。通知内容根据类型显示:纪念日类型显示"今天是[纪念日名称]"[可选倒计时];提醒类型显示"[提醒内容]"[时间]。通知支持点击操作,点击后打开应用并跳转到对应详情。 + +**通知设置**:用户可在设置中管理通知偏好。开关控制:总开关、纪念日通知开关、提醒通知开关、过期提醒汇总开关。通知时间:可设置每日通知的发送时间(默认9:00)。静默模式:可设置免打扰时段(如22:00-8:00),该时段内不发送通知。 + +#### 3.6.3 业务规则 + +通知权限依赖浏览器支持,不支持的浏览器不显示通知功能。纪念日通知只在当天发送一次,不过期补发。提醒通知在精确时间发送,支持多设备同步。用户可随时在浏览器设置中禁用通知。通知不包含敏感信息,即使在锁屏界面也只显示脱敏内容。 + +--- + +## 4 用户流程图 + +### 4.1 用户登录流程 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 用户登录流程 │ +└─────────────────────────────────────────────────────────────┘ + + ┌──────────┐ + │ 访问应用 │ + └────┬─────┘ + │ + ▼ + ┌──────────────┐ + │检查登录态 │ + └──────┬───────┘ + │ + ┌─────┴─────┐ + │ │ + ▼ ▼ + ┌──────┐ ┌─────────────┐ + │有效 │ │无效/不存在 │ + └──┬───┘ └──────┬──────┘ + │ │ + ▼ ▼ +┌─────────┐ ┌─────────────────┐ +│跳转Home │ │ 显示宣传页 │ +│页面 │ │ 登录/注册入口 │ +└────┬────┘ └────────┬────────┘ + │ │ + │ ┌──────┴──────┐ + │ │ │ + │ ▼ ▼ + │ ┌─────────┐ ┌─────────┐ + │ │ 登录 │ │ 注册 │ + │ └────┬────┘ └────┬────┘ + │ │ │ + │ ▼ ▼ + │ ┌───────────────────────┐ + │ │ 验证邮箱密码 │ + │ └───────────┬───────────┘ + │ │ + │ ┌──────┴──────┐ + │ │ │ + │ ▼ ▼ + │ ┌─────────┐ ┌─────────────┐ + │ │ 验证通过 │ │ 验证失败 │ + │ └────┬────┘ │ 显示错误 │ + │ │ └─────────────┘ + │ ▼ + │ ┌─────────────────┐ + │ │保存登录态 │ + │ │跳转Home页面 │ + │ └─────────────────┘ + │ + ▼ +┌────────────────┐ +│ 显示Home页面 │ +│ 用户可使用功能│ +└────────────────┘ +``` + +### 4.2 添加纪念日流程 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 添加纪念日流程 │ +└─────────────────────────────────────────────────────────────┘ + + ┌─────────────────────────┐ + │ 用户想添加纪念日 │ + └────────────┬────────────┘ + │ + ┌───────────┴───────────┐ + │ │ + ▼ ▼ +┌────────────────┐ ┌────────────────┐ +│ AI对话输入 │ │ 点击添加按钮 │ +│ "妈妈生日6月..."│ │ 打开手动添加 │ +└───────┬────────┘ └───────┬────────┘ + │ │ + │ ▼ + │ ┌────────────────────┐ + │ │ 填写纪念日信息 │ + │ │ 名称/日期/农历 │ + │ │ 重复方式 │ + │ └───────┬────────────┘ + │ │ + │ ▼ + │ ┌────────────────────┐ + │ │ 验证必填字段 │ + │ └───────┬────────────┘ + │ │ + │ ┌────────┴────────┐ + │ │ │ + │ ▼ ▼ + │ ┌─────────┐ ┌─────────────┐ + │ │ 验证通过│ │ 验证失败 │ + │ └────┬────┘ │ 显示错误提示│ + │ │ └─────────────┘ + │ ▼ + │ ┌─────────────────┐ + │ │ 保存纪念日 │ + │ │ 更新列表显示 │ + │ └─────────────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ AI解析用户输入 │ +│ 识别:名称/日期/农历/重复 │ +└────────────────┬────────────────────────┘ + │ + ▼ + ┌─────────────────┐ + │ 展示解析结果 │ + │ "识别到:妈妈 │ + │ 生日6月15日 │ + │ 年度重复" │ + └────────┬────────┘ + │ + ┌───────────┴───────────┐ + │ │ + ▼ ▼ +┌─────────────┐ ┌─────────────────┐ +│ 确认创建 │ │ 修改/重新输入 │ +└──────┬──────┘ └─────────────────┘ + │ + ▼ +┌─────────────────┐ +│ 保存纪念日 │ +│ 更新列表显示 │ +└─────────────────┘ +``` + +### 4.3 AI对话创建流程 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ AI对话创建流程 │ +└─────────────────────────────────────────────────────────────┘ + + ┌──────────────────┐ + │ 用户输入描述 │ + │ "提醒我明天下午3 │ + │ 点开会" │ + └────────┬─────────┘ + │ + ▼ + ┌──────────────────┐ + │ 发送至AI服务 │ + └────────┬─────────┘ + │ + ▼ + ┌──────────────────┐ + │ NLP语义解析 │ + │ 提取:类型/内容 │ + │ /时间/重复方式 │ + └────────┬─────────┘ + │ + ┌───────┴───────┐ + │ │ + ▼ ▼ + ┌──────────┐ ┌─────────────────┐ + │解析成功 │ │ 解析失败 │ + └────┬─────┘ │ "无法理解" │ + │ └────────┬────────┘ + │ │ + ▼ ▼ +┌─────────────────┐ ┌─────────────────┐ +│ 生成解析结果 │ │ 提示重新输入 │ +│ 自然语言展示 │ │ 或手动添加 │ +└────────┬────────┘ └─────────────────┘ + │ + ▼ +┌─────────────────┐ +│ 用户确认/修改 │ +└────────┬────────┘ + │ + ┌─────┴─────┐ + │ │ + ▼ ▼ +┌─────────┐ ┌─────────────────┐ +│确认创建 │ │ 修改描述 │ +└────┬────┘ │ 重新解析 │ + │ └─────────────────┘ + ▼ +┌─────────────────┐ +│ 调用创建API │ +│ 纪念日/提醒 │ +└────────┬────────┘ + │ + ▼ +┌─────────────────┐ +│ 返回结果 │ +│ 成功:列表高亮 │ +│ 失败:错误提示 │ +└─────────────────┘ +``` + +### 4.4 提醒处理流程 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 提醒处理流程 │ +└─────────────────────────────────────────────────────────────┘ + + ┌──────────────────┐ + │ 显示提醒列表 │ + │ 按时间分组显示 │ + └────────┬─────────┘ + │ + ┌───────┴───────┐ + │ │ + ▼ ▼ +┌──────────────┐ ┌─────────────────┐ +│ 用户操作 │ │ 时间流逝 │ +│ 查看/添加 │ │ 提醒过期 │ +│ 完成/删除 │ └────────┬────────┘ +└───────┬──────┘ │ + │ ▼ + │ ┌─────────────────┐ + │ │ 自动移入 │ + │ │ "已错过"分组 │ + │ └────────┬────────┘ + │ │ + │ ▼ + │ ┌─────────────────┐ + │ │ 用户处理过期 │ + │ │ 修改时间/删除 │ + │ └────────┬────────┘ + │ │ + └───────┬──────────┘ + │ + ▼ + ┌───────────────────────┐ + │ 完成提醒(checkbox) │ + └───────────┬───────────┘ + │ + ▼ + ┌───────────────┐ + │ 更新状态 │ + │ 移动到底部 │ + │ 显示完成时间 │ + └───────────────┘ +``` + +--- + +## 5 页面原型说明 + +### 5.1 宣传页 + +#### 5.1.1 页面布局 + +宣传页采用极简设计,分为三个主要区域:顶部导航区、内容展示区、底部信息区。 + +**顶部导航区**:高度60px,全宽布局。左侧显示产品Logo"掐日子"和Slogan"AI 纪念日·提醒"。右侧显示"登录"和"注册"两个按钮。背景色为白色,Logo使用品牌色。 + +**内容展示区**:页面主体,高度自适应。居中显示核心价值主张:"让重要日期不再错过"。下方显示功能亮点:AI智能解析、轻松管理纪念日、浏览器即时提醒。底部显示"开始使用"大按钮,点击后跳转登录/注册。整体采用留白设计,突出核心信息。 + +**底部信息区**:显示版权信息、隐私政策链接、服务条款链接。采用浅灰色背景,与主体区分。 + +#### 5.1.2 交互说明 + +页面加载时检查登录态。若登录态有效,自动跳转到Home页面。点击"登录"按钮,跳转到登录页面。点击"注册"按钮,跳转到注册页面。点击"开始使用"按钮,若未登录则跳转登录页面,若已登录则跳转Home页面。 + +### 5.2 登录/注册页面 + +#### 5.2.1 页面布局 + +登录和注册采用同一页面布局,通过Tab切换。 + +页面居中显示卡片,宽度400px。卡片顶部显示产品Logo。卡片内显示两个Tab:"登录"和"注册"。 + +**登录Tab**:包含邮箱输入框、密码输入框、登录按钮、登录后显示"登录成功,正在跳转..."提示。底部显示"还没有账号?点击注册"链接。 + +**注册Tab**:包含邮箱输入框、密码输入框、确认密码输入框、注册按钮、注册后显示"注册成功,正在跳转..."提示。底部显示"已有账号?点击登录"链接。 + +#### 5.2.2 交互说明 + +Tab切换无刷新,切换时清空表单。输入框失焦时进行格式校验。点击登录/注册按钮后,禁用按钮防止重复提交。成功后跳转到Home页面,失败时显示错误提示并恢复按钮状态。 + +### 5.3 Home页面 + +#### 5.3.1 整体布局 + +Home页面采用三栏布局,总宽度1200px,居中显示。 + +``` +┌─────────────────────────────────────────────────────────────────────┐ +│ 顶部导航栏(60px) │ +├───────────────┬───────────────────────────────────────┬─────────────┤ +│ │ │ │ +│ 纪念日列 │ 主内容区 │ 便签区 │ +│ 表(40%) │ │ (20%) │ +│ │ │ │ +│ + 提醒列表 │ │ │ +│ (40%) │ │ │ +│ │ │ │ +│ │ │ ┌─────────┐│ +│ │ │ │ 便签 ││ +│ │ │ │ 编辑器 ││ +│ │ │ └─────────┘│ +│ │ │ │ +│ │ │ ┌─────────┐│ +│ │ │ │ AI ││ +│ │ │ │ 对话 ││ +│ │ │ │ 区域 ││ +│ │ │ └─────────┘│ +│ │ │ │ +├───────────────┴───────────────────────────────────────┴─────────────┤ +│ 底部版权信息 │ +└─────────────────────────────────────────────────────────────────────┘ +``` + +#### 5.3.2 纪念日列表区域 + +**位置**:左侧40%宽度,高度自适应。 + +**区域结构**: +- 区域标题:"纪念日" + 添加按钮(+图标) +- 空状态提示:"还没有纪念日,AI对话添加或点击+创建" +- 卡片列表容器 + +**卡片样式**:卡片高度60px,圆角8px,左侧色条标识类型。卡片元素:左侧复选框(纪念日不需要checkbox,为保持统一可省略或用于快捷操作)、纪念日名称(主标题)、倒计时(副标题)、日期标签(公历/农历)、重复标识。点击卡片弹出详情弹窗。 + +**分组显示**:纪念日不分组,按日期排序。即将到来的(30天内)高亮显示。 + +#### 5.3.3 提醒列表区域 + +**位置**:左侧40%宽度,高度自适应。 + +**区域结构**: +- 区域标题:"提醒" + 添加按钮(+图标) +- 空状态提示:"还没有提醒,AI对话添加或点击+创建" +- 分组容器:今天、明天、更久之后、已错过 + +**分组样式**:分组标题使用灰色小字,左侧显示分组名称,右侧显示数量。分组内卡片列表。已错过分组使用红色提示。 + +**卡片样式**:卡片高度50px,圆角8px。卡片元素:左侧checkbox、内容文本、时间信息。点击卡片弹出详情弹窗。完成状态:checkbox选中、灰色划线、移到底部。 + +#### 5.3.4 便签区域 + +**位置**:右侧20%宽度,高度占右侧区域的50%。 + +**区域结构**: +- 区域标题:"便签" + 清空按钮 +- 富文本编辑器容器 + +**编辑器样式**:采用Quill编辑器,工具栏简化版。编辑器高度200px,支持基础格式化。编辑状态显示边框和光标,非编辑状态不显示边框。自动保存状态指示器(右上角):"已保存"或"保存中"。 + +#### 5.3.5 AI对话区域 + +**位置**:右侧20%高度,占据便签区域下方。 + +**区域结构**: +- 对话历史区域:可滚动,显示最近对话 +- 输入区域:输入框 + 发送按钮 + +**对话历史样式**:气泡样式,用户消息右对齐(浅色背景),AI消息左对齐(深色背景)。解析确认卡片样式区别于普通消息。 + +**输入区域样式**:输入框高度100px,支持多行输入。发送按钮在输入框内右下角。发送后输入框清空。 + +#### 5.3.6 详情弹窗 + +点击纪念日或提醒卡片时弹出详情弹窗。 + +**弹窗结构**: +- 标题:"编辑纪念日"或"编辑提醒" +- 表单区域:根据类型显示不同字段 +- 操作按钮:"保存"、"删除"、"取消" + +**纪念日弹窗字段**:纪念日名称(输入框)、日期(日期选择器)、是否农历(开关)、重复方式(下拉选择:仅一次、年度重复、月度重复)。 + +**提醒弹窗字段**:提醒内容(文本域)、提醒时间(日期时间选择器)。 + +--- + +## 6 数据模型 + +### 6.1 用户表(users) + +存储用户账户信息,是整个应用的数据根节点。 + +| 字段名 | 类型 | 约束 | 说明 | +|--------|------|------|------| +| id | UUID | PRIMARY KEY, DEFAULT uuid_generate_v4() | 用户唯一标识 | +| email | VARCHAR(255) | UNIQUE, NOT NULL | 登录邮箱 | +| password_hash | VARCHAR(255) | NOT NULL | 密码哈希值 | +| created_at | TIMESTAMP | DEFAULT NOW() | 创建时间 | +| updated_at | TIMESTAMP | DEFAULT NOW() | 更新时间 | + +**说明**:id作为主键,采用UUID格式保证分布式唯一性。email作为登录账号,唯一索引保证不重复。password_hash存储bcrypt哈希后的密码,不存储明文。created_at和updated_at用于审计追踪。 + +### 6.2 纪念日表(anniversaries) + +存储用户的纪念日数据,支持公历和农历两种日期类型。 + +| 字段名 | 类型 | 约束 | 说明 | +|--------|------|------|------| +| id | UUID | PRIMARY KEY, DEFAULT uuid_generate_v4() | 纪念日唯一标识 | +| user_id | UUID | FOREIGN KEY, NOT NULL | 所属用户ID | +| title | VARCHAR(100) | NOT NULL | 纪念日名称 | +| date | DATE | NOT NULL | 日期(公历) | +| is_lunar | BOOLEAN | DEFAULT FALSE | 是否农历 | +| repeat_type | VARCHAR(20) | DEFAULT 'once' | 重复类型:once/yearly/monthly | +| is_holiday | BOOLEAN | DEFAULT FALSE | 是否法定节假日 | +| created_at | TIMESTAMP | DEFAULT NOW() | 创建时间 | +| updated_at | TIMESTAMP | DEFAULT NOW() | 更新时间 | + +**说明**:user_id关联用户表,建立外键约束。date字段存储公历日期,农历日期需转换为公历后存储。is_lunar标识原始输入是否为农历。repeat_type枚举值:'once'(仅一次)、'yearly'(年度重复)、'monthly'(月度重复)。is_holiday标识是否为内置法定节假日。 + +### 6.3 提醒表(reminders) + +存储用户的提醒任务数据,支持按时间分组和完成状态追踪。 + +| 字段名 | 类型 | 约束 | 说明 | +|--------|------|------|------| +| id | UUID | PRIMARY KEY, DEFAULT uuid_generate_v4() | 提醒唯一标识 | +| user_id | UUID | FOREIGN KEY, NOT NULL | 所属用户ID | +| content | VARCHAR(500) | NOT NULL | 提醒内容 | +| reminder_time | TIMESTAMP | NOT NULL | 提醒时间 | +| is_completed | BOOLEAN | DEFAULT FALSE | 是否已完成 | +| completed_at | TIMESTAMP | NULL | 完成时间 | +| created_at | TIMESTAMP | DEFAULT NOW() | 创建时间 | +| updated_at | TIMESTAMP | DEFAULT NOW() | 更新时间 | + +**说明**:content存储用户输入的提醒文本,最大长度500字符。reminder_time存储精确到分钟的提醒时间。is_completed标记完成状态,completed_at记录完成时刻。 + +### 6.4 便签表(notes) + +存储用户的便签数据,MVP阶段每用户仅一条记录。 + +| 字段名 | 类型 | 约束 | 说明 | +|--------|------|------|------| +| id | UUID | PRIMARY KEY, DEFAULT uuid_generate_v4() | 便签唯一标识 | +| user_id | UUID | FOREIGN KEY, UNIQUE, NOT NULL | 所属用户ID | +| content | TEXT | DEFAULT '' | 便签HTML内容 | +| created_at | TIMESTAMP | DEFAULT NOW() | 创建时间 | +| updated_at | TIMESTAMP | DEFAULT NOW() | 更新时间 | + +**说明**:user_id建立唯一索引,保证每用户只有一条便签。content存储HTML格式的富文本内容,TEXT类型支持较长内容。updated_at在每次保存时自动更新。 + +### 6.5 数据关系图 + +``` +┌─────────────┐ ┌─────────────────┐ +│ users │ 1 ── * │ anniversaries │ +└─────────────┘ └─────────────────┘ + │ + │ * ── 1 + │ +┌─────────────┐ ┌─────────────────┐ +│ notes │ 1 ── * │ reminders │ +└─────────────┘ └─────────────────┘ +``` + +用户表与纪念日表、提醒表为一对多关系。用户表与便签表为一对一关系。 + +--- + +## 7 接口设计 + +### 7.1 认证模块 + +#### 7.1.1 用户注册 + +**接口**:POST /api/auth/register + +**请求参数**: +```json +{ + "email": "user@example.com", + "password": "password123" +} +``` + +**响应成功**(201): +```json +{ + "success": true, + "message": "注册成功", + "data": { + "user": { + "id": "uuid-xxx", + "email": "user@example.com" + }, + "token": "jwt-token-here" + } +} +``` + +**响应失败**(400/409): +```json +{ + "success": false, + "message": "邮箱已被注册", + "errors": null +} +``` + +#### 7.1.2 用户登录 + +**接口**:POST /api/auth/login + +**请求参数**: +```json +{ + "email": "user@example.com", + "password": "password123" +} +``` + +**响应成功**(200): +```json +{ + "success": true, + "message": "登录成功", + "data": { + "user": { + "id": "uuid-xxx", + "email": "user@example.com" + }, + "token": "jwt-token-here" + } +} +``` + +**响应失败**(401): +```json +{ + "success": false, + "message": "邮箱或密码错误" +} +``` + +### 7.2 纪念日模块 + +#### 7.2.1 获取纪念日列表 + +**接口**:GET /api/anniversaries + +**请求参数**:无 + +**响应成功**(200): +```json +{ + "success": true, + "data": [ + { + "id": "uuid-xxx", + "title": "妈妈生日", + "date": "2026-06-15", + "is_lunar": false, + "repeat_type": "yearly", + "countdown": 138, + "created_at": "2026-01-28T10:00:00Z" + } + ] +} +``` + +#### 7.2.2 创建纪念日 + +**接口**:POST /api/anniversaries + +**请求参数**: +```json +{ + "title": "爸爸生日", + "date": "1960-05-20", + "is_lunar": false, + "repeat_type": "yearly" +} +``` + +**响应成功**(201): +```json +{ + "success": true, + "message": "创建成功", + "data": { + "id": "uuid-xxx", + "title": "爸爸生日", + "date": "2026-05-20", + "is_lunar": false, + "repeat_type": "yearly", + "countdown": 82 + } +} +``` + +#### 7.2.3 更新纪念日 + +**接口**:PUT /api/anniversaries/:id + +**请求参数**: +```json +{ + "title": "爸爸生日(改)", + "date": "1960-05-20", + "is_lunar": false, + "repeat_type": "monthly" +} +``` + +**响应成功**(200): +```json +{ + "success": true, + "message": "更新成功" +} +``` + +#### 7.2.4 删除纪念日 + +**接口**:DELETE /api/anniversaries/:id + +**响应成功**(200): +```json +{ + "success": true, + "message": "删除成功" +} +``` + +### 7.3 提醒模块 + +#### 7.3.1 获取提醒列表 + +**接口**:GET /api/reminders + +**响应成功**(200): +```json +{ + "success": true, + "data": { + "today": [ + { + "id": "uuid-xxx", + "content": "提交报告", + "reminder_time": "2026-01-28T15:00:00Z", + "is_completed": false + } + ], + "tomorrow": [], + "later": [], + "missed": [] + } +} +``` + +#### 7.3.2 创建提醒 + +**接口**:POST /api/reminders + +**请求参数**: +```json +{ + "content": "下午三点开会", + "reminder_time": "2026-01-28T15:00:00Z" +} +``` + +**响应成功**(201): +```json +{ + "success": true, + "message": "创建成功", + "data": { + "id": "uuid-xxx", + "content": "下午三点开会", + "reminder_time": "2026-01-28T15:00:00Z", + "is_completed": false + } +} +``` + +#### 7.3.3 更新提醒 + +**接口**:PUT /api/reminders/:id + +**请求参数**: +```json +{ + "content": "下午三点开项目会", + "reminder_time": "2026-01-29T15:00:00Z" +} +``` + +**响应成功**(200): +```json +{ + "success": true, + "message": "更新成功" +} +``` + +#### 7.3.4 标记提醒完成 + +**接口**:PATCH /api/reminders/:id/complete + +**响应成功**(200): +```json +{ + "success": true, + "message": "已完成" +} +``` + +#### 7.3.5 取消完成 + +**接口**:PATCH /api/reminders/:id/uncomplete + +**响应成功**(200): +```json +{ + "success": true, + "message": "已取消完成" +} +``` + +#### 7.3.6 删除提醒 + +**接口**:DELETE /api/reminders/:id + +**响应成功**(200): +```json +{ + "success": true, + "message": "删除成功" +} +``` + +### 7.4 便签模块 + +#### 7.4.1 获取便签 + +**接口**:GET /api/notes + +**响应成功**(200): +```json +{ + "success": true, + "data": { + "id": "uuid-xxx", + "content": "

今日待办

  • 完成报告
", + "updated_at": "2026-01-28T12:00:00Z" + } +} +``` + +#### 7.4.2 保存便签 + +**接口**:PUT /api/notes + +**请求参数**: +```json +{ + "content": "

今日待办

  • 完成报告
  • 发送邮件
" +} +``` + +**响应成功**(200): +```json +{ + "success": true, + "message": "保存成功", + "data": { + "id": "uuid-xxx", + "updated_at": "2026-01-28T12:30:00Z" + } +} +``` + +### 7.5 AI模块 + +#### 7.5.1 自然语言解析 + +**接口**:POST /api/ai/parse + +**请求参数**: +```json +{ + "text": "提醒我下周三交论文" +} +``` + +**响应成功**(200): +```json +{ + "success": true, + "data": { + "type": "reminder", + "title": "交论文", + "date": "2026-02-05", + "time": "09:00", + "is_lunar": false, + "repeat_type": null, + "confidence": 0.85, + "raw_output": "已识别到:提醒『交论文』,时间暂定为2026年2月5日,请确认具体日期。" + } +} +``` + +#### 7.5.2 解析并创建 + +**接口**:POST /api/ai/create + +**请求参数**: +```json +{ + "text": "提醒我下周三交论文", + "confirmed": true, + "adjustments": { + "date": "2026-02-06" + } +} +``` + +**响应成功**(201): +```json +{ + "success": true, + "message": "创建成功", + "data": { + "type": "reminder", + "id": "uuid-xxx" + } +} +``` + +### 7.6 通用错误响应 + +所有接口的错误响应格式统一: + +**400 Bad Request**: +```json +{ + "success": false, + "message": "请求参数错误", + "errors": { + "email": "邮箱格式不正确" + } +} +``` + +**401 Unauthorized**: +```json +{ + "success": false, + "message": "未登录或登录已过期" +} +``` + +**403 Forbidden**: +```json +{ + "success": false, + "message": "无权访问该资源" +} +``` + +**404 Not Found**: +```json +{ + "success": false, + "message": "资源不存在" +} +``` + +**500 Internal Server Error**: +```json +{ + "success": false, + "message": "服务器错误,请稍后重试" +} +``` + +--- + +## 8 验收标准 + +### 8.1 登录与认证模块 + +**注册功能验收标准**: +- 用户能够通过邮箱和密码完成注册 +- 注册时邮箱格式校验正确,无效邮箱无法提交 +- 密码要求至少8位且包含字母数字,强度不足时提示 +- 已注册邮箱无法重复注册,提示"邮箱已被注册" +- 注册成功后自动登录并跳转到Home页面 +- 注册成功后自动创建一条空便签记录 + +**登录功能验收标准**: +- 用户能够通过邮箱和密码完成登录 +- 登录时邮箱格式校验正确 +- 邮箱或密码错误时统一提示"邮箱或密码错误" +- 登录成功后显示成功提示并跳转到Home页面 +- 登录成功后登录态保存到localStorage,有效期30天 +- 已登录用户刷新页面或重新访问,自动跳转到Home页面 + +**登出功能验收标准**: +- 用户点击登出按钮后清除登录态 +- 登出后自动跳转到宣传页 +- 登出后重新访问需要重新登录 + +**登录态保持验收标准**: +- 登录后关闭浏览器再打开,无需重新登录 +- 登录态过期后自动登出,提示登录 +- 多Tab页面一个Tab登出后,其他Tab同步更新状态 + +### 8.2 纪念日管理模块 + +**查看纪念日验收标准**: +- Home页面左侧显示纪念日列表 +- 列表显示所有纪念日卡片,按日期排序 +- 卡片显示名称、倒计时、日期类型、重复标识 +- 公历纪念日显示"公历"标识,农历纪念日显示"农历"标识 +- 法定节假日显示节日图标 +- 点击卡片弹出详情弹窗 + +**添加纪念日验收标准**: +- 点击添加按钮弹出添加弹窗 +- 手动添加时名称、日期、是否农历、重复方式为必填 +- 日期选择器支持公历日期选择 +- 农历开关切换后日期选择器支持农历(可用农历选择器组件) +- 重复方式支持下拉选择(仅一次、年度重复、月度重复) +- 提交后验证必填字段,验证失败显示错误提示 +- 验证通过后创建成功,列表更新显示新卡片 +- 纪念日创建后倒计时正确计算 + +**内置节假日验收标准**: +- 系统预置中国法定节假日列表 +- 点击"添加节假日"按钮显示节假日选择列表 +- 选择节假日后自动创建对应的纪念日 +- 节假日默认设置为年度重复 + +**倒计时显示验收标准**: +- 未到来的纪念日显示倒计时天数 +- 倒计时大于365天显示"还有X年" +- 倒计时1-365天显示"还有X天" +- 当天纪念日显示"今日" +- 已过纪念日显示"已过X天" + +**二次编辑验收标准**: +- 点击纪念日卡片弹出详情弹窗 +- 弹窗显示纪念日的完整信息 +- 用户可修改名称、日期、是否农历、重复方式 +- 修改后点击保存,系统更新数据库 +- 弹窗提供删除功能,确认后删除纪念日 +- 删除后列表更新,纪念日不再显示 + +### 8.3 提醒管理模块 + +**查看提醒列表验收标准**: +- Home页面左侧显示提醒列表 +- 列表分为四个分组:今天、明天、更久之后、已错过 +- 每个分组只显示该时间范围内的提醒 +- 已过期未完成的提醒自动归入"已错过"分组 +- 分组标题显示分组名称和提醒数量 +- 提醒卡片显示内容摘要和时间 +- 点击卡片弹出详情弹窗 + +**添加提醒验收标准**: +- 点击添加按钮弹出添加弹窗 +- 手动添加时内容和时间为必填 +- 日期时间选择器支持精确到分钟 +- 提交后验证必填字段,验证失败显示错误提示 +- 验证通过后创建成功,列表更新显示新卡片 +- 新创建的提醒正确归入对应分组 + +**标记完成验收标准**: +- 点击提醒卡片左侧checkbox标记完成 +- 完成的提醒显示灰色划线效果 +- 完成的提醒移动到所在分组的底部 +- 取消checkbox勾选可撤销完成状态 +- 撤销后提醒恢复原位置 +- 已完成提醒不进入"已错过"分组 + +**过期提醒验收标准**: +- 提醒时间过期且未完成时自动归入"已错过"分组 +- "已错过"分组按过期时间从远到近排序 +- 过期提醒使用红色高亮提示 +- 点击过期提醒可修改时间或删除 +- 修改时间后提醒重新归入对应分组 + +**二次编辑验收标准**: +- 点击提醒卡片弹出详情弹窗 +- 弹窗显示提醒的完整信息 +- 用户可修改内容和时间 +- 修改后点击保存,系统更新数据库 +- 弹窗提供删除功能,确认后删除提醒 + +### 8.4 便签功能验收标准** + +**查看便签验收标准**: +- Home页面右侧显示便签区域 +- 便签区域显示富文本内容 +- 便签为空时显示占位提示文字 +- 便签内容正确换行显示 + +**编辑便签验收标准**: +- 点击便签区域进入编辑模式 +- 编辑器支持基础格式化:标题、加粗、斜体、列表 +- 编辑器支持插入链接 +- 用户可正常输入和编辑内容 +- 编辑器显示光标和选中文本 + +**自动保存验收标准**: +- 用户编辑内容变化后自动保存 +- 保存间隔不超过500毫秒 +- 保存期间显示"保存中"状态指示 +- 保存完成显示"已保存"状态指示 +- 网络断开时显示"离线保存"提示 +- 恢复网络后自动同步未保存内容 +- 刷新页面后内容不丢失 + +**清空便签验收标准**: +- 点击清空按钮弹出确认提示 +- 确认后便签内容清空 +- 便签记录保留,可重新编辑 + +### 8.5 AI对话功能验收标准 + +**发起对话验收标准**: +- AI对话区域显示对话历史 +- 用户可在输入框中输入文字 +- 点击发送按钮提交输入 +- 输入非空校验,空白输入不发送 + +**自然语言解析验收标准**: +- 系统正确解析用户输入的日期描述 +- 能够识别公历和农历日期 +- 能够识别重复方式(年度、月度) +- 能够区分纪念日和提醒类型 +- 解析结果以自然语言展示 +- 无法解析时显示友好提示 + +**确认与创建验收标准**: +- 解析结果显示确认卡片 +- 用户点击确认后创建对应的纪念日或提醒 +- 创建成功后显示成功提示 +- 创建成功后列表高亮显示新项目 +- 用户可修改解析结果后创建 + +**对话历史验收标准**: +- 对话历史保留最近10条 +- 用户发送和AI回复交替显示 +- 对话历史在页面刷新后保持 +- 新对话从底部开始 + +### 8.6 浏览器通知验收标准 + +**请求权限验收标准**: +- 首次访问Home页面时检测通知权限 +- 未授权时显示通知授权提示条 +- 点击"启用"按钮调用浏览器通知API +- 用户选择允许或拒绝后提示条隐藏 +- 权限状态在界面上显示 + +**发送通知验收标准**: +- 纪念日当天早上9:00发送通知 +- 提醒在设定时间到达时发送 +- 过期提醒每天早上9:00发送汇总 +- 通知标题显示"掐日子" +- 通知内容正确显示纪念日名称或提醒内容 + +**通知设置验收标准**: +- 用户可在设置中开启/关闭通知 +- 可分别控制纪念日通知和提醒通知 +- 可设置每日通知的发送时间 +- 设置修改后立即生效 + +--- + +## 9 里程碑规划 + +### 9.1 阶段一:基础架构搭建(预估3天) + +**目标**:完成项目初始化和基础架构搭建,为后续功能开发提供基础。 + +**交付物**: +- Git仓库初始化,代码规范和提交规范确定 +- React 18 + Vite项目搭建完成 +- Tailwind CSS和Mantine组件库集成 +- Supabase项目创建,数据库表结构创建完成 +- 基础路由和页面结构搭建 +- 项目目录结构和代码规范文档 + +**技术要点**: +- 配置ESLint和Prettier代码格式化 +- 配置Tailwind CSS主题和设计变量 +- 配置Supabase客户端和认证状态管理 +- 搭建基础Layout组件和页面框架 + +**评审标准**: +- 项目能够正常运行,npm run dev无报错 +- 代码符合ESLint规范 +- 数据库表结构与PRD设计一致 +- 路由配置正确,页面能够正常切换 + +### 9.2 阶段二:认证模块开发(预估2天) + +**目标**:完成用户注册、登录、登出功能,实现登录态保持。 + +**交付物**: +- 宣传页(登录态有效时跳转Home) +- 登录页面 +- 注册页面 +- 登录态管理和认证中间件 +- Supabase认证集成 + +**技术要点**: +- Supabase Auth认证集成 +- localStorage登录态管理 +- 路由守卫实现 +- 错误处理和提示 + +**评审标准**: +- 用户能够成功注册和登录 +- 登录态保持正确,有效期内无需重新登录 +- 未登录用户访问Home页面自动跳转到宣传页 +- 登出功能正常,登录态清除 + +### 9.3 阶段三:纪念日模块开发(预估3天) + +**目标**:完成纪念日的增删改查、倒计时显示、重复规则、内置节假日。 + +**交付物**: +- 纪念日列表页面组件 +- 添加纪念日弹窗 +- 编辑纪念日弹窗 +- 内置节假日数据 +- 纪念日API服务 +- 倒计时计算逻辑 + +**技术要点**: +- 纪念日数据CRUD API +- 农历日期转换(使用lunar-javascript) +- 倒计时计算算法 +- 重复规则逻辑实现 + +**评审标准**: +- 能够添加、查看、编辑、删除纪念日 +- 纪念日支持公历和农历两种类型 +- 倒计时显示正确 +- 重复规则(年度、月度)正确实现 +- 内置节假日可快速添加 +- 纪念日按日期排序显示 + +### 9.4 阶段四:提醒模块开发(预估3天) + +**目标**:完成提醒的增删改查、分组显示、过期处理、完成标记。 + +**交付物**: +- 提醒列表页面组件 +- 添加提醒弹窗 +- 编辑提醒弹窗 +- 提醒分组逻辑 +- 完成/取消完成功能 +- 过期提醒处理 + +**技术要点**: +- 提醒数据CRUD API +- 提醒时间分组算法 +- 过期检测和分组逻辑 +- 完成状态管理 + +**评审标准**: +- 能够添加、查看、编辑、删除提醒 +- 提醒按时间分组显示(今天、明天、更久之后、已错过) +- 过期提醒正确归入"已错过"分组 +- 完成/取消完成功能正常 +- 提醒按时间排序显示 + +### 9.5 阶段五:便签模块开发(预估2天) + +**目标**:完成便签的富文本编辑和自动保存功能。 + +**交付物**: +- 便签编辑区域组件 +- Quill富文本集成 +- 自动保存逻辑 +- 便签API服务 + +**技术要点**: +- Quill编辑器配置和自定义 +- 自动保存防抖实现 +- HTML内容存储和渲染 +- 离线保存状态处理 + +**评审标准**: +- 便签支持富文本编辑(标题、加粗、列表等) +- 内容编辑后自动保存,无需手动操作 +- 保存状态正确显示(保存中、已保存) +- 网络断开时显示离线保存提示 +- 页面刷新后内容不丢失 + +### 9.6 阶段六:AI对话模块开发(预估3天) + +**目标**:完成AI对话的自然语言解析和创建功能。 + +**交付物**: +- AI对话区域组件 +- 自然语言解析API集成 +- 解析结果展示和确认界面 +- 解析后创建纪念日/提醒功能 + +**技术要点**: +- AI服务API集成 +- NLP语义解析逻辑 +- 解析结果自然语言展示 +- 用户确认后创建流程 + +**评审标准**: +- 能够以自然语言描述创建纪念日或提醒 +- AI能够正确识别日期类型(公历/农历) +- AI能够识别重复方式 +- 解析结果以自然语言展示供用户确认 +- 用户确认后正确创建 +- 无法解析时显示友好提示 + +### 9.7 阶段七:浏览器通知开发(预估1天) + +**目标**:完成浏览器桌面通知功能。 + +**交付物**: +- 通知权限请求逻辑 +- 纪念日通知发送 +- 提醒通知发送 +- 过期提醒汇总通知 +- 通知设置界面 + +**技术要点**: +- 浏览器通知API调用 +- 通知权限状态管理 +- 定时任务或定时轮询发送通知 +- 通知设置持久化 + +**评审标准**: +- 首次访问时能够请求通知权限 +- 纪念日当天发送通知 +- 提醒时间到达时发送通知 +- 过期提醒发送汇总通知 +- 通知设置能够开启/关闭各类通知 + +### 9.8 阶段八:联调测试与修复(预估3天) + +**目标**:完成全功能联调、问题修复和体验优化。 + +**交付物**: +- 全功能联调测试 +- Bug修复和问题解决 +- 界面细节优化 +- 性能优化 +- 测试用例编写 + +**工作内容**: +- 各模块集成测试 +- 跨模块流程测试 +- 响应式布局适配 +- 加载状态和错误状态优化 +- 与QA工程师协作完成测试 + +**评审标准**: +- 所有功能模块正常运行 +- 各模块间数据流转正确 +- 界面交互流畅,无明显卡顿 +- 错误处理完善,用户体验良好 +- 通过QA验收测试 + +### 9.9 阶段九:部署上线(预估1天) + +**目标**:完成生产环境部署和发布。 + +**交付物**: +- 生产环境配置 +- 域名和SSL配置 +- 监控和日志配置 +- 上线检查清单 +- 上线文档 + +**工作内容**: +- 构建生产版本 +- 部署到Vercel/Netlify +- 配置自定义域名 +- 配置环境变量 +- 验证生产环境功能正常 + +**评审标准**: +- 生产环境能够正常访问 +- 所有功能在生产环境正常运行 +- 页面加载速度满足要求 +- 监控和日志配置完成 +- 回滚方案准备就绪 + +### 9.10 里程碑总览 + +| 阶段 | 内容 | 预估工期 | 累计工期 | +|------|------|----------|----------| +| 阶段一 | 基础架构搭建 | 3天 | 3天 | +| 阶段二 | 认证模块开发 | 2天 | 5天 | +| 阶段三 | 纪念日模块开发 | 3天 | 8天 | +| 阶段四 | 提醒模块开发 | 3天 | 11天 | +| 阶段五 | 便签模块开发 | 2天 | 13天 | +| 阶段六 | AI对话模块开发 | 3天 | 16天 | +| 阶段七 | 浏览器通知开发 | 1天 | 17天 | +| 阶段八 | 联调测试与修复 | 3天 | 20天 | +| 阶段九 | 部署上线 | 1天 | 21天 | + +**总体预估工期**:21个工作日 + +**风险提示**: +- AI服务API集成可能需要额外时间调优 +- 农历日期处理可能遇到边界情况 +- 浏览器通知在不同浏览器上表现可能不一致 +- 建议预留3天缓冲时间应对意外情况 + +--- + +## 附录 + +### A. 术语表 + +| 术语 | 说明 | +|------|------| +| MVP | Minimum Viable Product,最小可行产品 | +| Supabase | 开源的后端即服务(BaaS)平台 | +| Quill | 富文本编辑器库 | +| lunar-javascript | 农历日期处理库 | +| date-fns | 日期处理工具库 | +| JWT | JSON Web Token,用于身份认证 | + +### B. 参考资料 + +- [React 18官方文档](https://react.dev/) +- [Vite官方文档](https://vitejs.dev/) +- [Mantine官方文档](https://mantine.dev/) +- [Tailwind CSS官方文档](https://tailwindcss.com/) +- [Supabase官方文档](https://supabase.com/docs) +- [Quill官方文档](https://quilljs.com/) +- [date-fns官方文档](https://date-fns.org/) +- [lunar-javascript GitHub](https://github.com/6tail/lunar-javascript) + +### C. 修改记录 + +| 版本 | 日期 | 修改内容 | 修改人 | +|------|------|----------|--------| +| v1.0.0 | 2026-01-28 | 初稿完成 | 产品经理 | diff --git a/docs/ui-spec.md b/docs/ui-spec.md new file mode 100644 index 0000000..272ffdd --- /dev/null +++ b/docs/ui-spec.md @@ -0,0 +1,1138 @@ +# 掐日子(qia)UI 设计规范 + +> 版本:v1.0.0 +> 更新日期:2026-01-29 +> 设计风格:苹果公司风格,浅色系 + +--- + +## 1 设计原则 + +### 1.1 整体设计理念 + +掐日子的设计遵循"简洁、高效、温暖"三大核心理念,以苹果Human Interface Guidelines为设计准则,打造轻便易用的日期管理工具。 + +**简洁性原则**:去除一切不必要的视觉元素,让用户专注于核心内容。界面采用大量留白,通过清晰的层级结构引导用户视线,避免信息过载带来的认知负担。每一个设计决策都经过深思熟虑,确保每个元素都有其存在的必要性。 + +**高效性原则**:以用户任务为中心,优化操作流程,减少用户点击次数。AI智能解析功能让添加纪念日和提醒变得像发消息一样简单,自然语言交互降低了学习成本。快速响应和流畅的动效设计提升了整体使用效率。 + +**温暖性原则**:通过精心设计的细节传递产品的温度。倒计时显示让用户感受到时间的珍贵,贴心的到期提醒帮助用户不再错过重要日期。整体视觉风格采用温暖的浅色调,营造舒适愉悦的使用氛围。 + +### 1.2 视觉风格定义 + +掐日子采用苹果公司风格的浅色系设计语言,具体特征如下: + +| 设计属性 | 规范定义 | +|---------|---------| +| 设计风格 | 苹果扁平化设计,轻拟物元素 | +| 主色调 | 系统蓝品牌色 `#007AFF` | +| 背景色 | 浅灰白色 `#FFFFFF` / `#F5F5F7` | +| 圆角风格 | 中等圆角,8px-16px | +| 阴影效果 | 柔和投影,轻量级层级阴影 | +| 字体风格 | 系统字体,无衬线清晰可读 | +| 图标风格 | 线性图标,圆角处理 | + +--- + +## 2 颜色系统 + +### 2.1 主色调(品牌色) + +品牌色是产品视觉识别的核心,应用于主要操作按钮、链接、选中状态等关键元素。 + +| 颜色名称 | 色值 | CSS变量 | 使用场景 | +|---------|------|---------|---------| +| 品牌主色 | `#007AFF` | `--color-primary` | 主要按钮背景、链接、选中状态、图标填充 | +| 品牌浅色 | `#EAF3FF` | `--color-primary-light` | 背景色、悬停背景、选中框背景 | +| 品牌深色 | `#0056CC` | `--color-primary-dark` | 按下状态、disabled状态 | + +### 2.2 辅助色 + +辅助色用于补充品牌色,在特定场景下传达特定含义。 + +| 颜色名称 | 色值 | CSS变量 | 使用场景 | +|---------|------|---------|---------| +| 紫色 | `#AF52DE` | `--color-purple` | AI功能标识、特殊纪念日 | +| 绿色 | `#34C759` | `--color-green` | 完成状态、成功提示 | +| 橙色 | `#FF9500` | `--color-orange` | 警告提示、待处理提醒 | +| 红色 | `#FF3B30` | `--color-red` | 错误状态、删除操作、已错过提醒 | +| 黄色 | `#FFCC00` | `--color-yellow` | 提醒标识、重要日期高亮 | + +### 2.3 中性色 + +中性色用于文字、背景、边框等基础UI元素,构建界面的层次结构。 + +| 颜色名称 | 色值 | CSS变量 | 使用场景 | +|---------|------|---------|---------| +| 文字主色 | `#1D1D1F` | `--color-text-primary` | 标题、重要内容 | +| 文字次色 | `#48484A` | `--color-text-secondary` | 正文内容 | +| 文字三级 | `#86868B` | `--color-text-tertiary` | 辅助说明、时间显示 | +| 文字占位 | `#C7C7CC` | `--color-text-placeholder` | 输入框占位符、空状态 | +| 背景白色 | `#FFFFFF` | `--color-bg-white` | 卡片、弹窗背景 | +| 背景浅灰 | `#F5F5F7` | `--color-bg-light` | 页面背景、分组标题背景 | +| 背景更深灰 | `#E5E5EA` | `--color-bg-darker` | 分割线、边框 | +| 边框颜色 | `#C6C6C8` | `--color-border` | 输入框边框、分隔线 | + +### 2.4 功能色 + +功能色用于传达操作结果的反馈信息。 + +| 功能 | 色值 | CSS变量 | 应用示例 | +|-----|------|---------|---------| +| 成功 | `#34C759` | `--color-success` | 完成checkmark、创建成功提示 | +| 警告 | `#FF9500` | `--color-warning` | 即将过期提醒、需要注意 | +| 错误 | `#FF3B30` | `--color-error` | 删除确认、表单验证失败 | +| 信息 | `#007AFF` | `--color-info` | 普通提示、新功能引导 | + +### 2.5 语义色 + +语义色用于区分不同类型的内容,增强信息的可识别性。 + +| 内容类型 | 色值 | CSS变量 | 使用场景 | +|---------|------|---------|---------| +| 纪念日色条 | `#AF52DE` | `--color-anniversary` | 纪念日卡片左侧色条 | +| 提醒色条 | `#007AFF` | `--color-reminder` | 提醒卡片左侧色条 | +| 便签色条 | `#34C759` | `--color-note` | 便签区域标识 | +| AI对话色 | `#5856D6` | `--color-ai` | AI消息气泡、对话框 | +| 农历标识 | `#FF9500` | `--color-lunar` | 农历日期文字颜色 | +| 法定节假日 | `#FF3B30` | `--color-holiday` | 节假日标签 | + +### 2.6 颜色令牌汇总 + +```css +:root { + /* 品牌色 */ + --color-primary: #007AFF; + --color-primary-light: #EAF3FF; + --color-primary-dark: #0056CC; + + /* 辅助色 */ + --color-purple: #AF52DE; + --color-green: #34C759; + --color-orange: #FF9500; + --color-red: #FF3B30; + --color-yellow: #FFCC00; + + /* 中性色 */ + --color-text-primary: #1D1D1F; + --color-text-secondary: #48484A; + --color-text-tertiary: #86868B; + --color-text-placeholder: #C7C7CC; + --color-bg-white: #FFFFFF; + --color-bg-light: #F5F5F7; + --color-bg-darker: #E5E5EA; + --color-border: #C6C6C8; + + /* 功能色 */ + --color-success: #34C759; + --color-warning: #FF9500; + --color-error: #FF3B30; + --color-info: #007AFF; + + /* 语义色 */ + --color-anniversary: #AF52DE; + --color-reminder: #007AFF; + --color-note: #34C759; + --color-ai: #5856D6; + --color-lunar: #FF9500; + --color-holiday: #FF3B30; +} +``` + +--- + +## 3 字体系统 + +### 3.1 字体家族 + +采用系统默认字体,确保在不同平台上的最佳可读性和一致性。 + +| 字体类型 | 字体名称 | CSS声明 | 说明 | +|---------|---------|---------|------| +| 主字体 | -apple-system | `font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'Segoe UI', Roboto, sans-serif` | iOS/macOS系统字体 | +| 中文字体 | PingFang SC | `'PingFang SC', 'Microsoft YaHei', sans-serif` | 苹方字体(Mac)/微软雅黑(Windows) | +| 等宽字体 | SF Mono | `font-family: 'SF Mono', Monaco, Menlo, monospace` | 用于代码展示 | + +### 3.2 字号层级 + +建立清晰的字号层级体系,确保信息层次的视觉区分。 + +| 字号名称 | 字号大小 | 行高 | 字重 | CSS变量 | 使用场景 | +|---------|---------|------|------|---------|---------| +| 超大标题 | 34px | 41px | 700 | `--font-size-xxl` | 宣传页主标题 | +| 大标题 | 28px | 34px | 700 | `--font-size-xl` | 页面主标题 | +| 中标题 | 22px | 28px | 600 | `--font-size-lg` | 区域标题、卡片标题 | +| 小标题 | 18px | 24px | 600 | `--font-size-md` | 分组标题、重要内容 | +| 正文字 | 16px | 24px | 400 | `--font-size-base` | 正文内容、输入文字 | +| 小正文 | 14px | 20px | 400 | `--font-size-sm` | 次要说明、卡片内容 | +| 辅助文字 | 12px | 16px | 400 | `--font-size-xs` | 时间标签、占位符 | +| 最小文字 | 10px | 14px | 400 | `--font-size-xxs` | 徽章标签、数量标识 | + +### 3.3 字重规范 + +| 字重 | CSS值 | 使用场景 | +|-----|-------|---------| +| 700 (Bold) | `700` / `bold` | 大标题、区域标题、数字强调 | +| 600 (Semibold) | `600` | 小标题、卡片标题、需要强调的文字 | +| 400 (Regular) | `400` / `normal` | 正文内容、辅助说明 | + +### 3.4 行高规范 + +行高与字号保持合理比例,确保文本的可读性。 + +| 字号范围 | 推荐行高 | 行高/字号比例 | +|---------|---------|--------------| +| 28px及以上 | 字号 + 6-8px | 约 1.2 | +| 18-22px | 字号 + 6px | 约 1.3 | +| 14-16px | 字号 + 8px | 约 1.5 | +| 12px及以下 | 字号 + 4px | 约 1.4 | + +### 3.5 字体令牌汇总 + +```css +:root { + /* 字号 */ + --font-size-xxl: 34px; + --font-size-xl: 28px; + --font-size-lg: 22px; + --font-size-md: 18px; + --font-size-base: 16px; + --font-size-sm: 14px; + --font-size-xs: 12px; + --font-size-xxs: 10px; + + /* 行高 */ + --line-height-xxl: 41px; + --line-height-xl: 34px; + --line-height-lg: 28px; + --line-height-md: 24px; + --line-height-base: 24px; + --line-height-sm: 20px; + --line-height-xs: 16px; + --line-height-xxs: 14px; + + /* 字重 */ + --font-weight-bold: 700; + --font-weight-semibold: 600; + --font-weight-regular: 400; +} +``` + +--- + +## 4 间距系统 + +### 4.1 基础间距单位 + +以 4px 作为基础间距单位,所有间距值均为 4px 的倍数。 + +| 间距名称 | 间距值 | CSS变量 | 用途 | +|---------|--------|---------|------| +| 间距-xs | 4px | `--spacing-xs` | 元素内部最小间距 | +| 间距-sm | 8px | `--spacing-sm` | 紧密相关的元素间距 | +| 间距-md | 16px | `--spacing-md` | 标准间距 | +| 间距-lg | 24px | `--spacing-lg` | 较大间距 | +| 间距-xl | 32px | `--spacing-xl` | 大间距 | +| 间距-2xl | 48px | `--spacing-2xl` | 区块间距 | +| 间距-3xl | 64px | `--spacing-3xl` | 页面边距 | + +### 4.2 组件内间距 + +| 组件类型 | 水平 padding | 垂直 padding | CSS变量 | +|---------|-------------|-------------|---------| +| 卡片类 | 16px | 12px | `--card-padding` | +| 按钮类 | 16px | 8px | `--button-padding` | +| 输入框 | 12px | 10px | `--input-padding` | +| 弹窗内容 | 24px | 20px | `--modal-padding` | +| 分组标题 | 16px | 8px | `--section-padding` | + +### 4.3 组件间距 + +| 场景 | 间距值 | CSS变量 | +|-----|--------|---------| +| 卡片之间 | 12px | `--gap-card` | +| 分组之间 | 24px | `--gap-section` | +| 列表项之间 | 8px | `--gap-item` | +| 表单项之间 | 16px | `--gap-form` | +| 按钮之间 | 12px | `--gap-button` | +| 图标与文字 | 8px | `--gap-icon-text` | +| 标题与内容 | 8px | `--gap-title-content` | + +### 4.4 页面边距 + +| 页面类型 | 水平边距 | 垂直边距 | 最大宽度 | +|---------|---------|---------|---------| +| 宣传页 | 24px | 0 | 1200px | +| 登录/注册页 | 24px | 0 | 400px | +| Home页 | 24px | 0 | 1200px | +| 弹窗 | 24px | 0 | 480px | + +### 4.5 间距令牌汇总 + +```css +:root { + /* 基础间距 */ + --spacing-xs: 4px; + --spacing-sm: 8px; + --spacing-md: 16px; + --spacing-lg: 24px; + --spacing-xl: 32px; + --spacing-2xl: 48px; + --spacing-3xl: 64px; + + /* 组件内间距 */ + --card-padding: 16px 12px; + --button-padding: 8px 16px; + --input-padding: 10px 12px; + --modal-padding: 24px 20px; + --section-padding: 8px 16px; + + /* 组件间距 */ + --gap-card: 12px; + --gap-section: 24px; + --gap-item: 8px; + --gap-form: 16px; + --gap-button: 12px; + --gap-icon-text: 8px; + --gap-title-content: 8px; + + /* 页面边距 */ + --page-margin: 24px; + --page-max-width: 1200px; +} +``` + +--- + +## 5 圆角与阴影 + +### 5.1 圆角层级 + +建立统一的圆角规范,不同场景使用不同层级的圆角。 + +| 圆角层级 | 圆角值 | CSS变量 | 使用场景 | +|---------|--------|---------|---------| +| 圆角-sm | 4px | `--radius-sm` | 小按钮、徽章、标签 | +| 圆角-md | 8px | `--radius-md` | 卡片、输入框、弹窗 | +| 圆角-lg | 12px | `--radius-lg` | 大卡片、分组容器 | +| 圆角-xl | 16px | `--radius-xl` | 弹窗、侧边栏 | +| 圆角-full | 9999px | `--radius-full` | 头像、圆形按钮、Pill标签 | + +### 5.2 阴影规范 + +阴影用于表现层级关系和交互状态,层级越高阴影越明显。 + +| 阴影层级 | 阴影值 | CSS变量 | 使用场景 | +|---------|--------|---------|---------| +| 阴影-sm | `0 1px 2px rgba(0, 0, 0, 0.05)` | `--shadow-sm` | 微小投影、分隔线 | +| 阴影-md | `0 2px 8px rgba(0, 0, 0, 0.08)` | `--shadow-md` | 卡片悬停、按钮按下 | +| 阴影-lg | `0 4px 16px rgba(0, 0, 0, 0.1)` | `--shadow-lg` | 弹窗、下拉菜单 | +| 阴影-xl | `0 8px 32px rgba(0, 0, 0, 0.12)` | `--shadow-xl` | 重要弹窗、模态框 | +| 阴影-inner | `inset 0 2px 4px rgba(0, 0, 0, 0.06)` | `--shadow-inner` | 输入框内陷效果 | + +### 5.3 交互状态阴影 + +| 交互状态 | 阴影效果 | 说明 | +|---------|---------|------| +| 默认状态 | `0 1px 3px rgba(0, 0, 0, 0.06)` | 基础卡片投影 | +| 悬停状态 | `0 4px 12px rgba(0, 122, 255, 0.15)` | 品牌色微光效果 | +| 按下状态 | `0 0 0 3px rgba(0, 122, 255, 0.2)` | 品牌色边框扩散 | +| 选中状态 | `0 0 0 2px var(--color-primary)` | 品牌色描边 | + +### 5.4 圆角与阴影令牌汇总 + +```css +:root { + /* 圆角 */ + --radius-sm: 4px; + --radius-md: 8px; + --radius-lg: 12px; + --radius-xl: 16px; + --radius-full: 9999px; + + /* 阴影 */ + --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05); + --shadow-md: 0 2px 8px rgba(0, 0, 0, 0.08); + --shadow-lg: 0 4px 16px rgba(0, 0, 0, 0.1); + --shadow-xl: 0 8px 32px rgba(0, 0, 0, 0.12); + --shadow-inner: inset 0 2px 4px rgba(0, 0, 0, 0.06); +} +``` + +--- + +## 6 组件设计规范 + +### 6.1 纪念日卡片 + +纪念日卡片用于展示用户的重要日期,支持倒计时显示和重复规则标识。 + +**布局结构**: +``` +┌─────────────────────────────────────────────────────────┐ +│ [左侧色条] 纪念日名称(主标题) [标识] │ +│ 倒计时 + 日期信息(副标题) │ +└─────────────────────────────────────────────────────────┘ +``` + +**尺寸规范**: +| 属性 | 规范值 | +|-----|-------| +| 卡片高度 | 60px | +| 左侧色条宽度 | 4px | +| 圆角 | 8px | +| 内边距 | 12px 16px | + +**样式规范**: +| 元素 | 字号 | 字重 | 颜色 | +|-----|------|-----|------| +| 纪念日名称 | 16px | 600 | `--color-text-primary` | +| 倒计时 | 14px | 400 | `--color-text-secondary` | +| 日期信息 | 12px | 400 | `--color-text-tertiary` | +| 色条颜色 | - | - | `--color-anniversary` (#AF52DE) | + +**状态定义**: +| 状态 | 视觉效果 | +|-----|---------| +| 默认 | 白色背景,轻微阴影 | +| 悬停 | 背景色变浅,品牌色微光阴影 | +| 即将到来(30天内) | 高亮显示,倒计时文字使用品牌色 | +| 今日 | 背景色为 `--color-primary-light`,倒计时显示"今日" | + +**重复标识**: +| 重复类型 | 图标/标识 | 说明 | +|---------|----------|------| +| 仅一次 | 无标识 | 不重复 | +| 年度重复 | 循环图标 (360度旋转箭头) | 每年重复 | +| 月度重复 | 双循环图标 | 每月重复 | + +**农历/节假日标识**: +| 类型 | 样式 | +|-----|------| +| 农历 | 橙色文字"农历",带月亮图标 | +| 法定节假日 | 红色"节假日"标签 | + +### 6.2 提醒卡片 + +提醒卡片用于展示待办任务,支持完成标记和过期状态。 + +**布局结构**: +``` +┌─────────────────────────────────────────────────────────┐ +│ [checkbox] 提醒内容(主标题) [时间] │ +└─────────────────────────────────────────────────────────┘ +``` + +**尺寸规范**: +| 属性 | 规范值 | +|-----|-------| +| 卡片高度 | 50px | +| checkbox尺寸 | 20px x 20px | +| 圆角 | 8px | +| 内边距 | 10px 16px | + +**样式规范**: +| 元素 | 字号 | 字重 | 颜色 | +|-----|------|-----|------| +| 提醒内容 | 14px | 400 | `--color-text-primary` | +| 时间信息 | 12px | 400 | `--color-text-tertiary` | +| 色条颜色 | - | - | `--color-reminder` (#007AFF) | + +**Checkbox 样式**: +| 属性 | 规范值 | +|-----|-------| +| 未选中 | 灰色边框,圆角4px | +| 选中 | 绿色背景 + 白色checkmark | +| 悬停 | 边框加深 | + +**分组样式**: +| 分组名称 | 标题颜色 | 标题字号 | +|---------|---------|---------| +| 今天 | `--color-text-primary` | 14px | +| 明天 | `--color-text-primary` | 14px | +| 更久之后 | `--color-text-secondary` | 14px | +| 已错过 | `--color-error` | 14px | + +**完成状态**: +| 状态 | 视觉效果 | +|-----|---------| +| 未完成 | 正常显示 | +| 已完成 | 内容灰色划线,时间显示完成时间,移至分组底部 | +| 已过期 | 红色高亮,显示在"已错过"分组顶部 | + +### 6.3 便签编辑器 + +便签区域提供富文本编辑能力,采用 Quill 编辑器组件。 + +**布局结构**: +``` +┌─────────────────────────────────────────────────────┐ +│ [便签标题] [清空按钮] │ +├─────────────────────────────────────────────────────┤ +│ ┌───────────────────────────────────────────────┐ │ +│ │ 富文本编辑器容器 │ │ +│ │ (可编辑区域) │ │ +│ └───────────────────────────────────────────────┘ │ +│ [保存状态:已保存] │ +└─────────────────────────────────────────────────────┘ +``` + +**尺寸规范**: +| 属性 | 规范值 | +|-----|-------| +| 便签区域高度 | 占右侧区域的50% | +| 编辑器最小高度 | 200px | +| 工具栏高度 | 40px | +| 圆角 | 8px | + +**工具栏按钮**: +| 按钮 | 图标 | 用途 | +|-----|------|------| +| H1 | 标题1 | 一级标题 | +| H2 | 标题2 | 二级标题 | +| B | 粗体 | 加粗文字 | +| I | 斜体 | 斜体文字 | +| U | 下划线 | 添加下划线 | +| S | 删除线 | 添加删除线 | +| 链接 | 链接图标 | 插入链接 | +| 有序列表 | 数字列表 | 有序列表 | +| 无序列表 | 圆点列表 | 无序列表 | + +**保存状态指示**: +| 状态 | 颜色 | 位置 | +|-----|------|------| +| 已保存 | `--color-success` | 右上角 | +| 保存中 | `--color-text-tertiary` + 加载动画 | 右上角 | +| 离线保存 | `--color-warning` | 右上角 | + +**空状态**: +| 状态 | 显示内容 | +|-----|---------| +| 便签为空 | "开始记录你的想法...",灰色文字 `--color-text-placeholder` | + +### 6.4 AI对话框 + +AI对话区域是产品的核心特色,采用聊天气泡形式展示对话历史。 + +**布局结构**: +``` +┌─────────────────────────────────────────────────────┐ +│ [AI区域标题] │ +├─────────────────────────────────────────────────────┤ +│ ┌───────────────┐ ┌─────────────────────────────┐ │ +│ │ │ │ AI消息气泡 │ │ +│ │ AI头像 │ │ (自然语言解析结果) │ │ +│ │ │ └─────────────────────────────┘ │ +│ └───────────────┘ │ +│ ┌─────────────────────────────┐ ┌───────────────┐ │ +│ │ 用户消息气泡 │ │ │ │ +│ │ (用户输入内容) │ │ 用户头像 │ │ +│ └─────────────────────────────┘ │ │ │ +│ └───────────────┘ │ +├─────────────────────────────────────────────────────┤ +│ ┌─────────────────────────────────────────────────┐│ +│ │ [输入框:输入描述要添加的纪念日或提醒...] [发送] ││ +│ └─────────────────────────────────────────────────┘│ +└─────────────────────────────────────────────────────┘ +``` + +**气泡样式**: +| 气泡类型 | 背景色 | 圆角 | 最大宽度 | +|---------|--------|------|---------| +| AI气泡 | `--color-bg-light` (浅灰) | 16px | 280px | +| 用户气泡 | `--color-primary-light` (浅蓝) | 16px | 280px | +| 解析确认卡片 | 白色 + 边框 | 12px | 100% | + +**气泡内边距**:12px 16px + +**发送按钮**: +| 状态 | 背景色 | 效果 | +|-----|--------|------| +| 默认 | `--color-primary` | 蓝色圆形 | +| 悬停 | `--color-primary-dark` | 颜色加深 | +| disabled | `--color-text-placeholder` | 灰色不可点击 | + +**解析确认卡片**: +| 元素 | 样式 | +|-----|------| +| 卡片背景 | 白色,边框2px实线 `--color-primary-light` | +| 卡片圆角 | 12px | +| 确认按钮 | 主要按钮样式 | +| 修改按钮 | 次要按钮样式 | + +### 6.5 详情弹窗 + +点击纪念日或提醒卡片时弹出详情弹窗,支持二次编辑。 + +**布局结构**: +``` +┌─────────────────────────────────────────────────────┐ +│ [X] 编辑纪念日 / 编辑提醒 [删除]│ +├─────────────────────────────────────────────────────┤ +│ ┌───────────────────────────────────────────────┐ │ +│ │ 表单字段区域 │ │ +│ │ - 输入框/选择器 │ │ +│ │ - 开关/下拉选择 │ │ +│ └───────────────────────────────────────────────┘ │ +├─────────────────────────────────────────────────────┤ +│ [取消] [保存] │ +└─────────────────────────────────────────────────────┘ +``` + +**尺寸规范**: +| 属性 | 规范值 | +|-----|-------| +| 弹窗宽度 | 400px - 480px | +| 弹窗最大高度 | 80vh | +| 圆角 | 16px | +| 头部高度 | 52px | +| 底部按钮区高度 | 60px | + +**头部样式**: +| 元素 | 样式 | +|-----|------| +| 标题 | 中标题 18px,字重 600,颜色 `--color-text-primary` | +| 关闭按钮 | 20px x 20px,灰色图标 | +| 删除按钮 | 红色文字按钮,字重 500 | + +**表单控件样式**: +| 控件类型 | 样式规范 | +|---------|---------| +| 输入框 | 高度 40px,圆角 8px,边框 `--color-border`,聚焦边框 `--color-primary` | +| 标签 | 小正文 14px,颜色 `--color-text-secondary`,上方留白 8px | +| 开关 | iOS样式,圆角 16px,滑块白色 | +| 下拉选择 | 右侧下拉箭头,高度 40px,圆角 8px | + +**操作按钮**: +| 按钮类型 | 样式 | +|---------|------| +| 保存按钮 | 主要按钮,蓝色背景,白色文字 | +| 取消按钮 | 次要按钮,灰色边框,黑色文字 | +| 删除按钮 | 危险按钮,红色文字,无背景 | + +### 6.6 按钮样式 + +按钮是用户操作的主要入口,需要清晰区分主次和危险操作。 + +**主要按钮**: +| 属性 | 规范值 | +|-----|-------| +| 背景色 | `--color-primary` (#007AFF) | +| 文字颜色 | 白色 | +| 高度 | 36px - 44px | +| 圆角 | 8px | +| 字重 | 600 | +| 字重 | 14px | +| 内边距 | 0 20px | +| 悬停背景 | `--color-primary-dark` (#0056CC) | + +**次要按钮**: +| 属性 | 规范值 | +|-----|-------| +| 背景色 | 透明 | +| 文字颜色 | `--color-text-primary` | +| 边框 | 1px solid `--color-border` | +| 高度 | 36px - 44px | +| 圆角 | 8px | +| 字号 | 14px | +| 内边距 | 0 20px | +| 悬停背景 | `--color-bg-light` | + +**文字按钮**: +| 属性 | 规范值 | +|-----|-------| +| 背景色 | 透明 | +| 文字颜色 | `--color-primary` | +| 高度 | 36px | +| 圆角 | 无(0px) | +| 字号 | 14px | +| 内边距 | 0 12px | +| 悬停背景 | `--color-primary-light` | + +**危险按钮(删除/清除)**: +| 属性 | 规范值 | +|-----|-------| +| 背景色 | 透明 | +| 文字颜色 | `--color-error` | +| 边框 | 1px solid `--color-error` | +| 高度 | 36px - 44px | +| 圆角 | 8px | +| 字号 | 14px | +| 内边距 | 0 20px | +| 悬停背景 | `--color-error` + 10% opacity | + +**按钮尺寸变体**: +| 变体 | 高度 | 字号 | 用途 | +|-----|------|-----|------| +| 小按钮 | 28px | 12px | 工具栏、徽章 | +| 中按钮 | 36px | 14px | 表单操作 | +| 大按钮 | 44px | 16px | 主要行动按钮 | +| 大号按钮 | 52px | 17px | 宣传页CTA | + +--- + +## 7 页面布局规范 + +### 7.1 宣传页布局 + +宣传页采用极简设计,分为三个主要区域。 + +**布局结构**: +``` +┌─────────────────────────────────────────────────────────┐ +│ [Logo: 掐日子 + Slogan] [登录][注册]│ +├─────────────────────────────────────────────────────────┤ +│ │ +│ 让重要日期不再错过 │ +│ │ +│ AI智能解析 · 轻松管理纪念日 · 即时提醒 │ +│ │ +│ [开始使用] │ +│ │ +├─────────────────────────────────────────────────────────┤ +│ © 2025 掐日子 隐私政策 服务条款 │ +└─────────────────────────────────────────────────────────┘ +``` + +**区域规范**: +| 区域 | 高度 | 背景色 | +|-----|------|--------| +| 顶部导航区 | 60px | 白色 | +| 内容展示区 | 自适应(最小600px) | 白色 | +| 底部信息区 | 60px | `--color-bg-light` | + +**Logo规范**: +| 元素 | 样式 | +|-----|------| +| 产品名称 | 20px,字重 700,品牌色 | +| Slogan | 14px,灰色 `--color-text-tertiary` | + +**CTA按钮**: +| 属性 | 规范值 | +|-----|-------| +| 尺寸 | 大号按钮(高度52px) | +| 圆角 | 26px (pill形状) | +| 文字 | "开始使用" | + +### 7.2 登录/注册页布局 + +登录和注册采用同一页面布局,通过Tab切换。 + +**布局结构**: +``` +┌─────────────────────────────────────────────────────┐ +│ │ +│ [Logo] │ +│ │ +│ [登录] [注册] │ +│ │ +│ ┌─────────────────────────────────────────────┐ │ +│ │ 邮箱地址 │ │ +│ └─────────────────────────────────────────────┘ │ +│ ┌─────────────────────────────────────────────┐ │ +│ │ 密码 │ │ +│ └─────────────────────────────────────────────┘ │ +│ │ +│ [登录按钮] │ +│ │ +│ 还没有账号?点击注册 │ +│ │ +└─────────────────────────────────────────────────────┘ +``` + +**卡片规范**: +| 属性 | 规范值 | +|-----|-------| +| 卡片宽度 | 400px | +| 卡片圆角 | 16px | +| 卡片背景 | 白色 | +| 卡片阴影 | `--shadow-lg` | + +**输入框规范**: +| 属性 | 规范值 | +|-----|-------| +| 高度 | 44px | +| 圆角 | 8px | +| 边框 | 1px solid `--color-border` | +| 间距 | 下边距 16px | + +**Tab样式**: +| 状态 | 视觉效果 | +|-----|---------| +| 激活 | 下边框2px实线 `--color-primary`,文字品牌色 | +| 未激活 | 文字灰色 `--color-text-tertiary`,无下划线 | + +### 7.3 Home页四区布局 + +Home页面采用三栏布局,总宽度1200px居中显示。 + +**布局结构**: +``` +┌─────────────────────────────────────────────────────────────────────┐ +│ 顶部导航栏(60px) │ +├───────────────┬───────────────────────────────────────┬─────────────┤ +│ │ │ │ +│ 纪念日列表 │ 主内容区 │ 便签区 │ +│ (40%宽度) │ (40%宽度) │ (20%) │ +│ │ │ │ +│ + 纪念日 │ │ ┌─────────┐│ +│ + 提醒列表 │ │ │ 便签 ││ +│ (40%宽度) │ │ │ 编辑器 ││ +│ │ │ └─────────┘│ +│ │ │ │ +│ │ │ ┌─────────┐│ +│ │ │ │ AI ││ +│ │ │ │ 对话 ││ +│ │ │ │ 区域 ││ +│ │ │ └─────────┘│ +│ │ │ │ +├───────────────┴───────────────────────────────────────┴─────────────┤ +│ 底部版权信息 │ +└─────────────────────────────────────────────────────────────────────┘ +``` + +**顶部导航栏**: +| 属性 | 规范值 | +|-----|-------| +| 高度 | 60px | +| 背景色 | 白色 | +| 内容 | Logo + 用户邮箱 + 登出按钮 | + +**左侧区域(纪念日 + 提醒)**: +| 属性 | 规范值 | +|-----|-------| +| 宽度占比 | 40%(约480px) | +| 纪念日区域高度 | 50% | +| 提醒区域高度 | 50% | +| 分割线 | 1px solid `--color-bg-darker` | + +**主内容区(便签 + AI对话)**: +| 属性 | 规范值 | +|-----|-------| +| 宽度占比 | 60%(约720px) | +| 便签区域高度 | 50% | +| AI对话区域高度 | 50% | +| 便签与AI之间 | 16px间距 | + +**底部版权信息**: +| 属性 | 规范值 | +|-----|-------| +| 高度 | 40px | +| 背景色 | `--color-bg-light` | +| 文字 | 12px,灰色 `--color-text-tertiary` | + +### 7.4 响应式适配规则 + +| 断点 | 屏幕宽度 | 布局变化 | +|-----|---------|---------| +| xs | < 768px | 单栏堆叠布局 | +| sm | 768px - 1024px | 两栏布局(左侧合并,右侧保留) | +| md | 1024px - 1200px | 三栏布局(宽度压缩) | +| lg | 1200px - 1440px | 标准三栏布局 | +| xl | > 1440px | 三栏布局(内容居中,最大宽度1440px) | + +**移动端适配方案**: +| 场景 | 适配方案 | +|-----|---------| +| 导航栏 | 折叠为汉堡菜单 | +| 三栏布局 | 改为Tab切换(纪念日/提醒/其他) | +| 弹窗 | 全屏显示 | +| 卡片 | 占据全宽 | + +--- + +## 8 交互动效规范 + +### 8.1 悬停效果 + +交互元素在鼠标悬停时应有明确的视觉反馈。 + +| 元素类型 | 悬停效果 | +|---------|---------| +| 主要按钮 | 背景色加深5%,轻微上移2px | +| 次要按钮 | 背景色变为 `--color-bg-light` | +| 卡片 | 阴影加深,轻微上移,cursor变为pointer | +| 链接 | 颜色变为 `--color-primary-dark`,下划线显示 | +| 表格行 | 背景色变为 `--color-bg-light` | + +### 8.2 点击反馈 + +用户点击操作应有即时的视觉反馈。 + +| 元素类型 | 点击效果 | +|---------|---------| +| 按钮 | 背景色变暗10%,轻微下沉 | +| 卡片 | 阴影消失,按下效果 | +| checkbox | 立即显示选中状态,checkmark动画 | +| 链接 | 颜色变化,无动画 | +| 图标按钮 | 灰色圆形背景闪烁 | + +**checkbox动画**: +| 阶段 | 动画效果 | +|-----|---------| +| 点击瞬间 | 缩放0.8倍 | +| 选中完成 | 缩放1.0倍 + 绿色背景填充 | +| checkmark | 从中心向外展开的线条动画 | + +### 8.3 过渡动画 + +元素状态变化时使用平滑的过渡动画。 + +**通用过渡**: +```css +transition: all 0.2s ease-out; +``` + +**具体场景动画**: +| 场景 | 进入动画 | 离开动画 | +|-----|---------|---------| +| 弹窗显示 | fadeIn + scale(0.95) -> fadeIn + scale(1) | fadeOut + scale(1) -> fadeOut + scale(0.95) | +| 下拉菜单 | slideDown (200ms) | slideUp (150ms) | +| 列表项增删 | fadeIn + slideIn | fadeOut + slideOut | +| 卡片悬停 | scale(1.02) | scale(1) | +| 按钮点击 | scale(0.95) | scale(1) | + +**AI消息动画**: +| 阶段 | 动画效果 | 时长 | +|-----|---------|------| +| 消息出现 | 渐显 + 打字机效果 | 300ms | +| 解析中 | 气泡呼吸动画 | 持续 | +| 发送中 | 发送按钮旋转 | 持续 | + +**保存状态指示**: +| 状态切换 | 动画效果 | +|---------|---------| +| 保存中 -> 已保存 | 绿色对勾从小到大的缩放动画 | +| 离线保存 | 黄色警告图标闪烁 | + +### 8.4 动画令牌 + +```css +:root { + /* 动画时长 */ + --duration-fast: 150ms; + --duration-base: 200ms; + --duration-slow: 300ms; + --duration-slower: 500ms; + + /* 动画缓动 */ + --ease-out: cubic-bezier(0.4, 0, 0.2, 1); + --ease-in: cubic-bezier(0, 0, 0.2, 1); + --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1); + --ease-bounce: cubic-bezier(0.68, -0.55, 0.265, 1.55); +} +``` + +--- + +## 9 图标规范 + +### 9.1 图标风格 + +图标采用线性风格(Outline),与苹果设计语言保持一致。 + +| 属性 | 规范定义 | +|-----|---------| +| 线条粗细 | 1.5px - 2px | +| 圆角 | 2px(线条端点) | +| 颜色 | 与文字颜色一致 | +| 尺寸 | 16px, 20px, 24px, 32px | +| 风格 | 线性、空心、圆角矩形框 | + +### 9.2 图标尺寸 + +| 尺寸 | CSS变量 | 使用场景 | +|-----|---------|---------| +| 16px | `--icon-size-sm` | 表格内图标、标签旁图标 | +| 20px | `--icon-size-md` | 卡片内图标、按钮图标 | +| 24px | `--icon-size-lg` | 区域标题图标、独立图标 | +| 32px | `--icon-size-xl` | Logo、空状态图标 | + +### 9.3 图标使用场景 + +| 图标名称 | 图标形状 | 用途 | +|---------|---------|------| +| 添加 | 加号 (+) | 添加按钮 | +| 删除 | 垃圾桶 | 删除操作 | +| 编辑 | 铅笔 | 编辑操作 | +| 保存 | 磁盘/对勾 | 保存操作 | +| 关闭 | X号 | 关闭弹窗 | +| 日历 | 日历形状 | 纪念日/日期相关 | +| 闹钟 | 闹钟形状 | 提醒相关 | +| 星星 | 星星形状 | 重要标记 | +| 循环 | 循环箭头 | 重复规则 | +| 月亮 | 月亮形状 | 农历标识 | +| 旗帜 | 旗帜形状 | 节假日标识 | +| 勾选 | 对勾 | 完成、确认 | +| 发送 | 箭头 | 发送消息 | +| 用户 | 用户头像 | 个人中心 | +| 登出 | 退出箭头 | 登出操作 | +| AI | 机器人头像 | AI对话区域 | +| 便签 | 文档形状 | 便签区域 | + +### 9.4 图标颜色规则 + +| 场景 | 图标颜色 | +|-----|---------| +| 普通操作 | `--color-text-secondary` | +| 主要操作 | `--color-primary` | +| 完成状态 | `--color-success` | +| 警告状态 | `--color-warning` | +| 危险操作 | `--color-error` | +| 禁用状态 | `--color-text-placeholder` | + +### 9.5 图标令牌 + +```css +:root { + /* 图标尺寸 */ + --icon-size-sm: 16px; + --icon-size-md: 20px; + --icon-size-lg: 24px; + --icon-size-xl: 32px; + + /* 图标颜色 */ + --icon-color-primary: var(--color-primary); + --icon-color-secondary: var(--color-text-secondary); + --icon-color-tertiary: var(--color-text-tertiary); + --icon-color-success: var(--color-success); + --icon-color-warning: var(--color-warning); + --icon-color-error: var(--color-error); + --icon-color-disabled: var(--color-text-placeholder); + + /* 图标线条粗细 */ + --icon-stroke-width: 1.5px; +} +``` + +--- + +## 10 设计资源 + +### 10.1 组件速查表 + +| 组件名称 | 高度 | 圆角 | 字号 | 主要颜色 | +|---------|------|------|------|---------| +| 纪念日卡片 | 60px | 8px | 16px/14px | `--color-anniversary` | +| 提醒卡片 | 50px | 8px | 14px | `--color-reminder` | +| 主要按钮 | 36-44px | 8px | 14-16px | `--color-primary` | +| 次要按钮 | 36-44px | 8px | 14px | 透明+边框 | +| 输入框 | 40px | 8px | 14px | `--color-border` | +| 弹窗 | 自适应 | 16px | 16px | 白色 | +| 聊天气泡 | 自适应 | 16px | 14px | 浅灰/浅蓝 | + +### 10.2 常见颜色组合 + +| 场景 | 背景色 | 文字色 | 边框色 | +|-----|--------|--------|--------| +| 主要按钮 | `#007AFF` | `#FFFFFF` | 无 | +| 次要按钮 | 透明 | `#1D1D1F` | `#C6C6C8` | +| 纪念日卡片 | `#FFFFFF` | `#1D1D1F` | 无 | +| 提醒卡片 | `#FFFFFF` | `#1D1D1F` | 无 | +| AI气泡 | `#F5F5F7` | `#1D1D1F` | 无 | +| 用户气泡 | `#EAF3FF` | `#1D1D1F` | 无 | +| 弹窗 | `#FFFFFF` | `#1D1D1F` | 无 | +| 错误提示 | `#FFF2F2` | `#FF3B30` | `#FF3B30` | + +### 10.3 快捷开发样式类 + +```css +/* 按钮类 */ +.btn-primary { + background: var(--color-primary); + color: white; + height: 40px; + border-radius: 8px; + font-weight: 600; + transition: all 0.2s ease; +} + +.btn-secondary { + background: transparent; + color: var(--color-text-primary); + border: 1px solid var(--color-border); + height: 40px; + border-radius: 8px; + transition: all 0.2s ease; +} + +/* 卡片类 */ +.card { + background: white; + border-radius: 8px; + padding: 12px 16px; + box-shadow: var(--shadow-sm); + transition: all 0.2s ease; +} + +.card:hover { + box-shadow: var(--shadow-md); + transform: translateY(-2px); +} + +/* 输入框类 */ +.input { + height: 40px; + border: 1px solid var(--color-border); + border-radius: 8px; + padding: 0 12px; + font-size: 14px; + transition: all 0.2s ease; +} + +.input:focus { + outline: none; + border-color: var(--color-primary); + box-shadow: 0 0 0 3px var(--color-primary-light); +} + +/* 弹窗类 */ +.modal-overlay { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.4); + display: flex; + align-items: center; + justify-content: center; + animation: fadeIn 0.2s ease; +} + +.modal { + background: white; + border-radius: 16px; + max-width: 480px; + width: 90%; + max-height: 80vh; + overflow: auto; + animation: scaleIn 0.2s ease; +} +``` + +--- + +## 11 附录 + +### 11.1 设计决策记录 + +| 决策项 | 决策内容 | 理由 | +|-------|---------|------| +| 圆角值 | 8px作为标准圆角 | 与苹果设计语言一致,视觉平衡 | +| 主色调 | 系统蓝 #007AFF | 用户熟悉度高,易于识别操作入口 | +| 卡片高度 | 纪念日60px,提醒50px | 纪念日信息更多,需要更大空间 | +| 间距单位 | 4px | 与Tailwind CSS一致,便于开发 | +| 图标风格 | 线性图标 | 与苹果风格一致,清晰易读 | + +### 11.2 待确认事项 + +以下事项需要在设计评审中确认: + +1. 倒计时超过365天时是否显示"还有X年"还是统一显示天数 +2. 法定节假日是否需要特殊视觉效果(如红色高亮) +3. AI对话区域的聊天记录是否需要持久化存储 +4. 便签是否支持多种颜色标签或主题 + +### 11.3 更新日志 + +| 版本 | 日期 | 更新内容 | 更新人 | +|------|------|---------|--------| +| v1.0.0 | 2026-01-29 | 初稿完成 | UI设计师 | diff --git a/ui/pencil-new.pen b/ui/pencil-new.pen new file mode 100644 index 0000000..a8e66a4 --- /dev/null +++ b/ui/pencil-new.pen @@ -0,0 +1,1090 @@ +{ + "version": "2.6", + "children": [ + { + "type": "frame", + "id": "AyUvt", + "x": -500, + "y": -1401, + "name": "宣传页", + "width": 1200, + "height": 900, + "fill": "#FAFAFA", + "layout": "vertical", + "children": [ + { + "type": "frame", + "id": "UqST5", + "name": "navBar", + "width": 1200, + "height": 64, + "fill": "rgba(255,255,255,0)", + "padding": 32, + "justifyContent": "space_between", + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "nDzKF", + "name": "navLeft", + "gap": 12, + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "6A4Yg", + "name": "logoIcon", + "width": 36, + "height": 36, + "fill": "#007AFF", + "cornerRadius": 10 + }, + { + "type": "text", + "id": "Uu477", + "name": "navTitle", + "fill": "#1C1C1E", + "content": "掐日子", + "fontFamily": "Inter", + "fontSize": 20, + "fontWeight": "700" + } + ] + }, + { + "type": "text", + "id": "VTP7D", + "name": "navSlogan", + "fill": "#8E8E93", + "content": "AI 纪念日·提醒", + "fontFamily": "Inter", + "fontSize": 15, + "fontWeight": "normal" + }, + { + "type": "frame", + "id": "13DG8", + "name": "loginBtn", + "width": 88, + "height": 40, + "fill": "#007AFF", + "cornerRadius": 20, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "4O7in", + "name": "loginText", + "fill": "#FFFFFF", + "content": "登录", + "fontFamily": "Inter", + "fontSize": 15, + "fontWeight": "600" + } + ] + } + ] + }, + { + "type": "frame", + "id": "oAEFk", + "name": "heroSection", + "width": 1200, + "height": 400, + "layout": "vertical", + "gap": 16, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "EgKmg", + "name": "heroTitle", + "fill": "#1C1C1E", + "content": "记住每一个重要的日子", + "textAlign": "center", + "fontFamily": "Inter", + "fontSize": 44, + "fontWeight": "700" + }, + { + "type": "text", + "id": "W7EYP", + "name": "heroDesc", + "fill": "#8E8E93", + "content": "AI 智能识别 · 农历公历双支持 · 准时提醒", + "textAlign": "center", + "fontFamily": "Inter", + "fontSize": 18, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "TvVsD", + "name": "ctaSection", + "width": 1200, + "height": 160, + "layout": "vertical", + "gap": 12, + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "Xx5Xt", + "name": "primaryCta", + "width": 200, + "height": 52, + "fill": "#007AFF", + "cornerRadius": 26, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "O0aEY", + "name": "primaryText", + "fill": "#FFFFFF", + "content": "立即开始使用", + "fontFamily": "Inter", + "fontSize": 17, + "fontWeight": "600" + } + ] + }, + { + "type": "text", + "id": "Rz695", + "name": "secondaryCta", + "fill": "#007AFF", + "content": "已有账号?立即登录", + "fontFamily": "Inter", + "fontSize": 15, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "WWToz", + "name": "featuresRow", + "width": 1200, + "height": 120, + "gap": 48, + "padding": 32, + "justifyContent": "center", + "children": [ + { + "type": "frame", + "id": "Zbk03", + "name": "feature1", + "width": 280, + "layout": "vertical", + "gap": 8, + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "4YZgB", + "name": "f1Icon", + "width": 48, + "height": 48, + "fill": "#E8F0FE", + "cornerRadius": 24 + }, + { + "type": "text", + "id": "zU4E2", + "name": "f1Title", + "fill": "#1C1C1E", + "content": "AI 智能添加", + "fontFamily": "Inter", + "fontSize": 15, + "fontWeight": "600" + }, + { + "type": "text", + "id": "kGs6m", + "name": "f1Desc", + "fill": "#8E8E93", + "content": "自然语言描述,自动识别日期", + "fontFamily": "Inter", + "fontSize": 13, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "5DBj8", + "name": "feature2", + "width": 280, + "fill": "#FFF2E5", + "layout": "vertical", + "gap": 8, + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "GRSMq", + "name": "f1Icon", + "width": 48, + "height": 48, + "fill": "#E8F0FE", + "cornerRadius": 24 + }, + { + "type": "text", + "id": "BavYR", + "name": "f1Title", + "fill": "#1C1C1E", + "content": "AI 智能添加", + "fontFamily": "Inter", + "fontSize": 15, + "fontWeight": "600" + }, + { + "type": "text", + "id": "PUT8S", + "name": "f1Desc", + "fill": "#8E8E93", + "content": "自然语言描述,自动识别日期", + "fontFamily": "Inter", + "fontSize": 13, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "Xm7Qh", + "name": "feature3", + "width": 280, + "fill": "#E5F5ED", + "layout": "vertical", + "gap": 8, + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "kLIXi", + "name": "f1Icon", + "width": 48, + "height": 48, + "fill": "#E8F0FE", + "cornerRadius": 24 + }, + { + "type": "text", + "id": "KtLXR", + "name": "f1Title", + "fill": "#1C1C1E", + "content": "AI 智能添加", + "fontFamily": "Inter", + "fontSize": 15, + "fontWeight": "600" + }, + { + "type": "text", + "id": "K5O65", + "name": "f1Desc", + "fill": "#8E8E93", + "content": "自然语言描述,自动识别日期", + "fontFamily": "Inter", + "fontSize": 13, + "fontWeight": "normal" + } + ] + } + ] + }, + { + "type": "text", + "id": "pCkuV", + "name": "footerText", + "fill": "#C7C7CC", + "content": "© 2025 掐日子 - AI 纪念日·提醒", + "textAlign": "center", + "fontFamily": "Inter", + "fontSize": 12, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "PwXjP", + "x": 1062, + "y": -810, + "name": "登录注册页", + "width": 404, + "height": 530, + "fill": "#FAFAFA", + "layout": "vertical", + "children": [ + { + "type": "frame", + "id": "YUspZ", + "name": "authCard", + "width": 400, + "height": 520, + "fill": "#FFFFFF", + "cornerRadius": 24, + "layout": "vertical", + "gap": 24, + "padding": 40, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "VnBxL", + "name": "authLogoArea", + "layout": "vertical", + "gap": 12, + "alignItems": "center", + "children": [ + { + "type": "frame", + "id": "QGC7A", + "name": "authLogoIcon", + "width": 56, + "height": 56, + "fill": "#007AFF", + "cornerRadius": 14 + }, + { + "type": "text", + "id": "CfPbT", + "name": "authLogoText", + "fill": "#1C1C1E", + "content": "掐日子", + "fontFamily": "Inter", + "fontSize": 22, + "fontWeight": "700" + } + ] + }, + { + "type": "text", + "id": "flmKI", + "name": "authTitle", + "fill": "#1C1C1E", + "content": "欢迎回来", + "fontFamily": "Inter", + "fontSize": 24, + "fontWeight": "600" + }, + { + "type": "frame", + "id": "dbIej", + "name": "emailInput", + "width": "fill_container", + "layout": "vertical", + "gap": 8, + "children": [ + { + "type": "text", + "id": "sUzgH", + "name": "emailLabel", + "fill": "#1C1C1E", + "content": "邮箱", + "fontFamily": "Inter", + "fontSize": 14, + "fontWeight": "500" + }, + { + "type": "frame", + "id": "LvTOv", + "name": "emailField", + "width": "fill_container", + "height": 48, + "fill": "#F2F2F7", + "cornerRadius": 12, + "padding": 16, + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "h41CN", + "name": "emailPlaceholder", + "fill": "#8E8E93", + "content": "请输入邮箱地址", + "fontFamily": "Inter", + "fontSize": 15, + "fontWeight": "normal" + } + ] + } + ] + }, + { + "type": "frame", + "id": "7Gtze", + "name": "passwordInput", + "width": "fill_container", + "layout": "vertical", + "gap": 8, + "children": [ + { + "type": "text", + "id": "W9TOd", + "name": "passwordLabel", + "fill": "#1C1C1E", + "content": "密码", + "fontFamily": "Inter", + "fontSize": 14, + "fontWeight": "500" + }, + { + "type": "frame", + "id": "TmvRf", + "name": "passwordField", + "width": "fill_container", + "height": 48, + "fill": "#F2F2F7", + "cornerRadius": 12, + "padding": 16, + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "cXzzD", + "name": "passwordPlaceholder", + "fill": "#8E8E93", + "content": "请输入密码", + "fontFamily": "Inter", + "fontSize": 15, + "fontWeight": "normal" + } + ] + } + ] + }, + { + "type": "frame", + "id": "n5h1L", + "name": "loginButton", + "width": "fill_container", + "height": 48, + "fill": "#007AFF", + "cornerRadius": 24, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "bWCzZ", + "name": "loginBtnText", + "fill": "#FFFFFF", + "content": "登录", + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "600" + } + ] + }, + { + "type": "text", + "id": "bCV1Q", + "name": "toggleLink", + "fill": "#007AFF", + "content": "还没有账号?立即注册", + "textAlign": "center", + "fontFamily": "Inter", + "fontSize": 14, + "fontWeight": "normal" + } + ] + } + ] + }, + { + "type": "frame", + "id": "I2z5N", + "x": 1102, + "y": 522, + "name": "AI确认弹窗", + "width": 420, + "height": 380, + "fill": "#FAFAFA", + "layout": "vertical", + "children": [ + { + "type": "frame", + "id": "hi4cJ", + "name": "aiModalCard", + "width": 380, + "height": 340, + "fill": "#FFFFFF", + "cornerRadius": 24, + "layout": "vertical", + "gap": 20, + "padding": 24, + "children": [ + { + "type": "text", + "id": "4a0We", + "name": "aiModalTitle", + "fill": "#1C1C1E", + "content": "我帮你记成这样", + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "600" + }, + { + "type": "frame", + "id": "Kznch", + "name": "aiResultCard", + "width": "fill_container", + "height": 140, + "fill": "#F2F2F7", + "cornerRadius": 16, + "layout": "vertical", + "gap": 12, + "padding": 16, + "children": [ + { + "type": "text", + "id": "jHReu", + "name": "aiEventName", + "fill": "#1C1C1E", + "content": "**见客户**", + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "600" + }, + { + "type": "text", + "id": "wgzx6", + "name": "aiEventDate", + "fill": "#1C1C1E", + "content": "📅 下周三 3月15日", + "fontFamily": "Inter", + "fontSize": 14, + "fontWeight": "normal" + }, + { + "type": "text", + "id": "Cxp7j", + "name": "aiEventTime", + "fill": "#1C1C1E", + "content": "⏰ 当天提醒", + "fontFamily": "Inter", + "fontSize": 14, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "p8rfb", + "name": "aiModalActions", + "width": "fill_container", + "gap": 12, + "children": [ + { + "type": "frame", + "id": "pOxSH", + "name": "cancelBtn", + "width": 100, + "height": 44, + "fill": "#F2F2F7", + "cornerRadius": 22, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "hCMxR", + "name": "cancelText", + "fill": "#1C1C1E", + "content": "✏️ 改一下", + "fontFamily": "Inter", + "fontSize": 15, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "AKOmM", + "name": "confirmBtn", + "width": "fill_container", + "height": 44, + "fill": "#007AFF", + "cornerRadius": 22, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "q7hoO", + "name": "confirmText", + "fill": "#FFFFFF", + "content": "✔ 就这样", + "fontFamily": "Inter", + "fontSize": 15, + "fontWeight": "600" + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "5iG42", + "x": 1387, + "y": -108, + "name": "详情编辑弹窗", + "width": 420, + "height": 480, + "fill": "#FAFAFA", + "layout": "vertical", + "children": [ + { + "type": "frame", + "id": "wFqC0", + "name": "detailModalCard", + "width": 380, + "height": 440, + "fill": "#FFFFFF", + "cornerRadius": 24, + "layout": "vertical", + "gap": 20, + "padding": 24, + "children": [ + { + "type": "frame", + "id": "UNnRf", + "name": "detailHeader", + "justifyContent": "space_between", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "eCB02", + "name": "detailTitle", + "fill": "#1C1C1E", + "content": "编辑纪念日", + "fontFamily": "Inter", + "fontSize": 18, + "fontWeight": "600" + }, + { + "type": "frame", + "id": "x11vg", + "name": "closeBtn", + "width": 32, + "height": 32, + "fill": "#F2F2F7", + "cornerRadius": 16 + } + ] + }, + { + "type": "frame", + "id": "nHUjj", + "name": "detailContent", + "width": "fill_container", + "layout": "vertical", + "gap": 16, + "children": [ + { + "type": "frame", + "id": "jkGpA", + "name": "formName", + "width": "fill_container", + "layout": "vertical", + "gap": 8, + "children": [ + { + "type": "text", + "id": "E6F5U", + "name": "nameLabel", + "fill": "#1C1C1E", + "content": "名称", + "fontFamily": "Inter", + "fontSize": 14, + "fontWeight": "500" + }, + { + "type": "frame", + "id": "aD8Sx", + "name": "nameField", + "width": "fill_container", + "height": 44, + "fill": "#F2F2F7", + "cornerRadius": 10, + "padding": 12, + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "orw4z", + "name": "nameValue", + "fill": "#1C1C1E", + "content": "妈妈生日", + "fontFamily": "Inter", + "fontSize": 15, + "fontWeight": "normal" + } + ] + } + ] + }, + { + "type": "frame", + "id": "hgxoK", + "name": "formDate", + "width": "fill_container", + "layout": "vertical", + "gap": 8, + "children": [ + { + "type": "text", + "id": "9n5KC", + "name": "dateLabel", + "fill": "#1C1C1E", + "content": "日期", + "fontFamily": "Inter", + "fontSize": 14, + "fontWeight": "500" + }, + { + "type": "frame", + "id": "i9Xlg", + "name": "dateRow", + "width": "fill_container", + "gap": 12, + "children": [ + { + "type": "frame", + "id": "fUtpm", + "name": "dateField", + "width": "fill_container", + "height": 44, + "fill": "#F2F2F7", + "cornerRadius": 10, + "padding": 12, + "alignItems": "center" + }, + { + "type": "frame", + "id": "lsSaY", + "name": "lunarToggle", + "width": 56, + "height": 44, + "fill": "#E8F0FE", + "cornerRadius": 10, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "laNwR", + "name": "lunarText", + "fill": "#007AFF", + "content": "农历", + "fontFamily": "Inter", + "fontSize": 13, + "fontWeight": "500" + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "yUU8z", + "name": "formRepeat", + "width": "fill_container", + "layout": "vertical", + "gap": 8, + "children": [ + { + "type": "text", + "id": "gjTKW", + "name": "repeatLabel", + "fill": "#1C1C1E", + "content": "重复", + "fontFamily": "Inter", + "fontSize": 14, + "fontWeight": "500" + }, + { + "type": "frame", + "id": "jzetu", + "name": "repeatBtns", + "width": "fill_container", + "gap": 8, + "children": [ + { + "type": "frame", + "id": "Etgby", + "name": "repeatActive", + "width": 72, + "height": 32, + "fill": "#007AFF", + "cornerRadius": 16, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "8S8Hp", + "name": "repeatText1", + "fill": "#FFFFFF", + "content": "每年", + "fontFamily": "Inter", + "fontSize": 13, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "9SBH5", + "name": "repeatInactive1", + "width": 72, + "height": 32, + "fill": "#FFFFFF", + "cornerRadius": 16, + "stroke": { + "thickness": 1 + }, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "potY9", + "name": "repeatText2", + "fill": "#8E8E93", + "content": "每月", + "fontFamily": "Inter", + "fontSize": 13, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "RD2xO", + "name": "repeatInactive2", + "width": 72, + "height": 32, + "fill": "#FFFFFF", + "cornerRadius": 16, + "stroke": { + "thickness": 1 + }, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "uvyCt", + "name": "repeatText3", + "fill": "#8E8E93", + "content": "不重复", + "fontFamily": "Inter", + "fontSize": 13, + "fontWeight": "normal" + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "Ngq48", + "name": "detailActions", + "width": "fill_container", + "gap": 12, + "children": [ + { + "type": "frame", + "id": "rv560", + "name": "deleteBtn", + "width": 72, + "height": 44, + "fill": "#FFEBEA", + "cornerRadius": 22, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "9w65a", + "name": "deleteText", + "fill": "#FF3B30", + "content": "删除", + "fontFamily": "Inter", + "fontSize": 15, + "fontWeight": "normal" + } + ] + }, + { + "type": "frame", + "id": "Cizf2", + "name": "saveBtn", + "width": "fill_container", + "height": 44, + "fill": "#007AFF", + "cornerRadius": 22, + "justifyContent": "center", + "alignItems": "center", + "children": [ + { + "type": "text", + "id": "ybGpQ", + "name": "saveText", + "fill": "#FFFFFF", + "content": "保存", + "fontFamily": "Inter", + "fontSize": 15, + "fontWeight": "600" + } + ] + } + ] + } + ] + } + ] + } + ] + }, + { + "type": "frame", + "id": "slzHj", + "x": 0, + "y": 2000, + "name": "Home页", + "width": 1400, + "height": 1000, + "children": [ + { + "type": "rectangle", + "id": "5spL4", + "name": "bgRect", + "fill": "#F5F5F7", + "width": 1400, + "height": 1000 + }, + { + "type": "rectangle", + "id": "F4Xw2", + "name": "navBar", + "fill": "#FFFFFF", + "width": 1400, + "height": 60 + }, + { + "type": "frame", + "id": "75IQH", + "name": "annivCol", + "width": 420, + "height": 920, + "gap": 12, + "children": [ + { + "type": "text", + "id": "y6wwC", + "name": "annivHeader", + "content": "纪念日", + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "600" + }, + { + "type": "rectangle", + "cornerRadius": 12, + "id": "svpSJ", + "name": "annivCard", + "fill": "#FFFFFF", + "width": 420, + "height": 72 + } + ] + }, + { + "type": "frame", + "id": "4aGmz", + "name": "remCol", + "width": 420, + "height": 920, + "gap": 12, + "children": [ + { + "type": "text", + "id": "XDAcL", + "name": "remHeader", + "content": "提醒", + "fontFamily": "Inter", + "fontSize": 16, + "fontWeight": "600" + }, + { + "type": "rectangle", + "cornerRadius": 12, + "id": "E12qc", + "name": "remCard", + "fill": "#FFFFFF", + "width": 420, + "height": 56 + } + ] + }, + { + "type": "frame", + "id": "L1JRA", + "name": "bottomFrame", + "width": 1400, + "height": 320, + "children": [ + { + "type": "frame", + "id": "YI6mx", + "name": "aiWrapper", + "width": 460, + "height": 280, + "children": [ + { + "type": "rectangle", + "cornerRadius": 16, + "id": "OzqbM", + "name": "aiChat", + "fill": "#FFFFFF", + "width": 460, + "height": 280 + } + ] + } + ] + }, + { + "type": "rectangle", + "cornerRadius": 12, + "id": "RJj4p", + "name": "noteArea", + "fill": "#FFFFFF", + "width": 460, + "height": 200 + } + ] + } + ] +} \ No newline at end of file