Compare commits

..

No commits in common. "3aefd1e707c3ad24da7c1f217bdaf19251a8b7a1" and "a69b49ed341cba5899432566cf0817d28ea5c5d6" have entirely different histories.

4 changed files with 60 additions and 449 deletions

2
client

@ -1 +1 @@
Subproject commit 1559e603b0ac6fdae8e82ec163248c33923e1965
Subproject commit a8b4f17043328f8a5e23f9b4fcfca5bd5e2d3b4f

View File

@ -149,7 +149,7 @@ Internet
| React Router | 6.x | 路由管理 |
| Zustand | 4.x | 状态管理 |
| Day.js | 1.x | 日期处理 |
| Zustand | 4.x | 状态管理 |
| React Query | 5.x | 数据同步 |
### 3.2 后端技术栈
@ -303,31 +303,23 @@ COMMENT ON COLUMN anniversaries.remind_days IS '提前提醒天数数组,如[-
```sql
CREATE TABLE reminders (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
type VARCHAR(20) DEFAULT 'reminder' NOT NULL,
title VARCHAR(200) NOT NULL,
content VARCHAR(500),
date TIMESTAMP WITH TIME ZONE NOT NULL,
is_lunar INTEGER DEFAULT 0,
repeat_type VARCHAR(20) DEFAULT 'none',
repeat_interval INTEGER,
next_reminder_date TIMESTAMP WITH TIME ZONE,
is_completed INTEGER DEFAULT 0,
completed_at TIMESTAMP WITH TIME ZONE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
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_date ON reminders(date);
CREATE INDEX idx_reminders_remind_time ON reminders(remind_time);
CREATE INDEX idx_reminders_is_completed ON reminders(is_completed);
CREATE INDEX idx_reminders_type ON reminders(type);
CREATE INDEX idx_reminders_repeat_type ON reminders(repeat_type);
CREATE INDEX idx_reminders_anniversary_id ON reminders(anniversary_id);
COMMENT ON COLUMN reminders.type IS 'reminder:提醒, anniversary:纪念日';
COMMENT ON COLUMN reminders.repeat_type IS 'none:不重复, daily:每天, weekly:每周, monthly:每月, yearly:每年';
COMMENT ON COLUMN reminders.next_reminder_date IS '下一次提醒日期(基于周期计算)';
COMMENT ON COLUMN reminders.notification_type IS 'email:邮件, browser:浏览器推送';
```
#### 4.2.4 notes 表(便签)
@ -448,28 +440,22 @@ model Anniversary {
// 提醒模型
model Reminder {
id String @id @default(uuid())
userId String @map("user_id")
type String @default("reminder") @map("type")
title String
content String? @map("content")
date DateTime @map("date")
isLunar Int @default(0) @map("is_lunar")
repeatType String @default("none") @map("repeat_type")
repeatInterval Int? @map("repeat_interval")
nextReminderDate DateTime? @map("next_reminder_date")
isCompleted Int @default(0) @map("is_completed")
completedAt DateTime? @map("completed_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
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)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
anniversary Anniversary? @relation(fields: [anniversaryId], references: [id], onDelete: SetNull)
@@index([userId])
@@index([date])
@@index([remindTime])
@@index([isCompleted])
@@index([type])
@@index([repeatType])
@@map("reminders")
}
@ -1099,27 +1085,41 @@ e:\qia\client\
│ │ └── styles/
│ │ └── index.css # Tailwind入口
│ ├── components/
│ │ ├── common/
│ │ │ ├── Button/
│ │ │ ├── Modal/
│ │ │ ├── Input/
│ │ │ ├── Select/
│ │ │ ├── DatePicker/
│ │ │ ├── Toast/
│ │ │ └── Loading/
│ │ ├── layout/
│ │ │ ├── Header/
│ │ │ ├── Sidebar/
│ │ │ ├── Footer/
│ │ │ └── Layout/
│ │ ├── anniversary/
│ │ │ ├── AnniversaryCard/
│ │ │ ├── AnniversaryForm/
│ │ │ ├── AnniversaryList/
│ │ │ └── HolidayCard/
│ │ ├── reminder/
│ │ │ ├── ReminderCard/
│ │ │ ├── ReminderItem/
│ │ │ ├── ReminderList/
│ │ │ ├── ReminderForm/
│ │ │ ├── ArchiveReminderModal/
│ │ │ └── ReminderDetailModal/
│ │ │ └── ReminderBadge/
│ │ ├── note/
│ │ │ ├── NoteCard/
│ │ │ ├── NoteCanvas/
│ │ │ └── NoteEditor/
│ │ ├── ai/
│ │ │ └── ChatBox/
│ │ ├── layout/
│ │ │ └── Layout/
│ │ └── common/
│ │ ├── Modal/
│ │ └── Toast/
│ │ │ ├── ChatBox/
│ │ │ ├── AIFloatingButton/
│ │ │ └── ParsingResult/
│ │ └── auth/
│ │ ├── LoginForm/
│ │ ├── RegisterForm/
│ │ ├── ForgotPasswordForm/
│ │ └── EmailVerification/
│ ├── pages/
│ │ ├── Home/
│ │ ├── Dashboard/
@ -1161,7 +1161,11 @@ e:\qia\client\
│ │ └── socket/
│ │ └── socketService.ts
│ ├── stores/
│ │ └── index.ts # Zustand主状态管理包含所有store
│ │ ├── authStore.ts
│ │ ├── anniversaryStore.ts
│ │ ├── reminderStore.ts
│ │ ├── noteStore.ts
│ │ └── uiStore.ts
│ ├── utils/
│ │ ├── validators.ts
│ │ ├── formatters.ts
@ -2574,11 +2578,10 @@ enum ErrorCode {
| 版本 | 日期 | 描述 |
|-----|------|------|
| 1.3.0 | 2026-02-04 | 更新提醒模块数据模型、重复提醒完成移除设置、逾期列表展开收起、无时间显示优化 |
| 1.1.0 | 2026-01-29 | 添加SQLite本地开发支持 |
| 1.0.0 | 2026-01-28 | 初始架构设计PostgreSQL |
---
*文档维护: 系统架构组*
*最后更新: 2026-02-04*
*最后更新: 2026-01-29*

View File

@ -4,10 +4,10 @@
| 项目 | 内容 |
|-----|------|
| **版本号** | v1.0.2 |
| **版本号** | v1.0.1 |
| **状态** | 开发中 |
| **创建日期** | 2026-01-28 |
| **最近更新** | 2026-02-04 |
| **最近更新** | 2026-02-03 |
| **目标用户** | 需要管理纪念日、提醒任务的普通用户 |
| **产品定位** | 轻便、灵活的倒数日和提醒App专注提醒功能 |
@ -1612,9 +1612,6 @@ Home页面采用三栏布局总宽度1200px居中显示。
| **设置** | 显示设置 | ✅ 已完成 | 节假日显示开关 |
| **其他** | 归档页 | ✅ 已完成 | 恢复/删除功能 |
| | 设置页 | ✅ 已完成 | 基础框架 |
| | 重复提醒完成移除设置 | ✅ 已完成 | 勾选完成后移除repeat_type等字段 |
| | 逾期列表展开收起 | ✅ 已完成 | 默认收起最多3条点击展开 |
| | 无时间提醒显示优化 | ✅ 已完成 | 只显示日期不显示00:00 |
### 10.2 提醒模块当前实现详情
@ -1708,404 +1705,15 @@ Home页面采用三栏布局总宽度1200px居中显示。
|------|------|------|----------|
| v1.0.0 | MVP基础功能 | ✅ 已完成 | 2026-01-28 |
| v1.0.1 | 提醒模块完善、归档页 | ✅ 已完成 | 2026-02-03 |
| v1.0.2 | 核心体验优化、重复提醒完成移除设置、逾期列表展开收起 | ✅ 已完成 | 2026-02-04 |
| v1.0.2 | 核心体验优化 | 进行中 | 待定 |
| v1.1.0 | 功能增强 | 计划中 | 待定 |
| v1.2.0 | 生态扩展 | 计划中 | 待定 |
---
## 12 重复提醒功能设计
### 12.1 功能概述
重复提醒功能允许用户创建周期性的提醒任务,系统自动按照设定的周期计算下一次提醒时间。当前支持三种重复模式:每天、每周、每年。重复提醒从用户设置的首次提醒时间开始,按固定周期循环显示。
### 12.2 重复模式定义
| 重复类型 | 描述 | 示例 |
|----------|------|------|
| **每天(daily)** | 每天同一时间提醒 | 每天早上8点吃药 |
| **每周(weekly)** | 每周同一星期同一时间提醒 | 每周五下午3点开会 |
| **每年(yearly)** | 每年同一日期同一时间提醒 | 每年3月15日交论文 |
### 12.3 数据模型扩展
#### 12.3.1 提醒表扩展reminders
在现有提醒表基础上增加重复相关字段:
| 字段名 | 类型 | 约束 | 说明 |
|--------|------|------|------|
| id | UUID | PRIMARY KEY | 提醒唯一标识 |
| user_id | UUID | FOREIGN KEY | 所属用户ID |
| type | VARCHAR(20) | DEFAULT 'reminder' | 类型reminder/anniversary |
| title | VARCHAR(200) | NOT NULL | 提醒标题 |
| content | VARCHAR(500) | | 提醒内容 |
| date | TIMESTAMP | NOT NULL | 当前提醒日期(展示用) |
| is_lunar | INTEGER | DEFAULT 0 | 是否为农历日期 |
| repeat_type | VARCHAR(20) | DEFAULT 'none' | 重复类型none/daily/weekly/monthly/yearly |
| repeat_interval | INTEGER | | 周数间隔(仅 weekly 类型使用) |
| next_reminder_date | TIMESTAMP | | 下一次提醒日期(计算用,基于周期推算) |
| is_holiday | INTEGER | DEFAULT 0 | 是否为节假日(仅纪念日) |
| is_completed | INTEGER | DEFAULT 0 | 是否已完成 |
| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | 创建时间 |
| updated_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | 更新时间 |
**字段说明**
- `date`:当前提醒日期(展示用),用于分组和展示
- `next_reminder_date`:下一次提醒日期,基于周期推算得出。创建时自动计算,勾选完成时用于计算下一条记录
- `repeat_type`'none'表示不重复,'daily'每天,'weekly'每周,'monthly'每月,'yearly'每年
- `repeat_interval`:周数间隔,仅 weekly 类型使用
#### 12.3.2 重复周期计算规则
**每天重复**
```
next_date = current_date + 1天
时间保持不变
```
**每周重复**
```
next_date = current_date + 7天
时间保持不变
星期几根据repeat_base_date确定
```
**每年重复**
```
next_date = current_date + 1年
如果下一年该日期不存在如2月29日则调整为2月28日
时间保持不变
```
### 12.4 功能详细描述
#### 12.4.1 添加重复提醒
**手动添加**
用户点击提醒列表上方的添加按钮,弹出添加弹窗。表单字段:
- 提醒内容(必填)
- 提醒时间(必填,日期+时间)
- 重复方式(下拉选择):不重复 / 每天 / 每周 / 每月 / 每年
**AI对话添加**
用户输入自然语言描述,系统自动识别重复规则:
- "每天早上8点吃药" → 自动识别为每天重复
- "每周五下午3点开会" → 自动识别为每周重复
- "每年3月15日交论文" → 自动识别为每年重复
- "提醒我明天下午3点开会" → 不重复
#### 12.4.2 卡片显示规则
**正常提醒卡片**(未逾期、未完成):
```
┌─────────────────────────────────────────────────────────┐
│ [checkbox] 标题 [循环icon] 2月3日 14:00 │
│ 内容... │
└─────────────────────────────────────────────────────────<EFBFBD>循环icon位置在日期时间左侧
```
**逾期提醒卡片**(已过期、未完成):
```
┌─────────────────────────────────────────────────────────┐
│ [checkbox] 标题 [循环icon] │
│ 内容... 2月1日 14:00 (已错过) │
└─────────────────────────────────────────────────────────┘
循环icon位置在标题右侧
```
**循环icon设计**
- 样式:循环箭头图标(类似⟳)
- 颜色:与重复类型相关
- 每天:蓝色
- 每周:绿色
- 每年:紫色
- 悬停提示:显示重复类型("每天重复"、"每周重复"、"每年重复"
#### 12.4.3 列表显示规则
**常规分组显示**(今天、明天、更久之后):
- 每个重复提醒在常规列表中**只显示最近1条未逾期、未完成的记录**
- 当用户勾选完成当前提醒后:
1. 当前记录标记完成,移入归档
2. 自动计算下一周期日期
3. 下一周期记录出现在对应分组中
- 始终保持每个重复提醒只有1条可见记录
**示例**
用户创建"每天早上8点吃药"首次日期为2月3日
- 2月3日显示在"今天"分组
- 用户勾选完成后2月3日记录归档2月4日自动出现在"明天"分组
- 依此类推
**已错过分组显示**
- **所有逾期未勾选的重复提醒都显示**不只显示1条
- 按过期时间从远到近排序
- 勾选完成后,自动计算下一周期并显示
#### 12.4.4 重复提醒完成流程
```
用户勾选完成当前重复提醒
┌─────────────────────────────────────────────────────────┐
│ 1. 当前记录标记完成 │
│ - is_completed = true │
│ - 移除重复设置repeat_type = 'none' │
│ repeat_interval = null │
│ next_reminder_date = null │
│ - 移入归档页 │
└───────────┬─────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ 2. 从 原始next_reminder_date 开始循环计算 │
│ 直到找到 >= 当前日期 的日期作为新提醒日期 │
│ (注意:使用计算后的日期,不使用已清空的字段) │
│ │
│ 示例: │
│ - 原始next_reminder_date = 02-04 08:00 │
│ - 当前日期 = 02-05 │
│ - 02-04 < 02-05 不满足
│ - 02-05 >= 02-05 → 满足 │
│ - 新提醒日期 = 02-05 08:00 │
└───────────┬─────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ 3. 检查是否已存在相同提醒(去重) │
│ 条件title + date + repeat_type 完全相同 │
│ - 存在:跳过,不创建 │
│ - 不存在:创建新记录 │
└───────────┬─────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ 4. 创建新记录 │
│ - date = 新提醒日期 │
│ - repeat_type = 原始重复类型 │
│ - repeat_interval = 原始间隔 │
│ - next_reminder_date = 新提醒日期 + 周期 │
└───────────┬─────────────────────────────────────────────┘
新记录出现在对应时间分组中(带循环图标)
```
**去重规则**
创建新提醒时检查是否存在完全相同的记录:
- 条件title + date + repeat_type 完全相同
- 存在:跳过,不创建
- 不存在:创建新记录
#### 12.4.5 二次编辑
用户点击重复提醒卡片,弹出详情弹窗:
- 可修改:标题、内容、日期时间、重复方式
- 修改重复方式后,下次周期按新规则计算
- 修改日期时间后:
- **不自动更新 next_reminder_date**
- 下次勾选完成时,会从原来的 next_reminder_date 继续计算
- 这样可以保持原始周期不被打破
#### 12.4.6 顺延逻辑(仅逾期提醒)
**场景**:用户错过了提醒日期,想要将提醒顺延到今天
- 只更改 `date`(当前提醒日期)
- **不更改** `next_reminder_date`(下一次提醒日期)
- 这样下次勾选完成时,会从原始的 next_reminder_date 继续计算周期
**示例**
原始记录2月1日创建
```
date = 02-01 08:00
next_reminder_date = 02-02 08:00
```
顺延到今天2月3日
```
date = 02-03 08:00 ← 改变
next_reminder_date = 02-02 08:00 ← 不变!
```
2月3日勾选完成后
```
从 next_reminder_date (02-02 08:00) 开始计算
02-02 < 当前日期(02-03) 不满足
02-03 >= 当前日期 → 满足
新记录date = 02-03 08:00, next_reminder_date = 02-04 08:00
```
### 12.5 交互流程图
```
┌─────────────────────────────────────────────────────────────┐
│ 重复提醒创建流程 │
└─────────────────────────────────────────────────────────────┘
┌──────────────────┐
│ 用户添加提醒 │
└────────┬─────────┘
┌───────┴───────┐
│ │
▼ ▼
┌─────────┐ ┌─────────────────┐
│ 手动添加 │ │ AI对话添加 │
└────┬────┘ └────────┬────────┘
│ │
▼ ▼
┌─────────────────────────────┐
│ 填写/识别重复方式 │
│ none / daily / weekly / │
│ yearly │
└─────────────┬───────────────┘
┌─────────────────────────────┐
│ 设置重复基准日期 │
│ repeat_base_date │
│ (首次提醒时间) │
└─────────────┬───────────────┘
┌─────────────────────────────┐
│ 创建提醒记录 │
│ date = repeat_base_date │
│ repeat_type = 设置值 │
└─────────────┬───────────────┘
┌─────────────────────────────────────────┐
│ 显示在对应分组中 │
│ 并显示循环icon │
└─────────────────────────────────────────┘
```
### 12.6 业务规则
1. **重复基准**:所有重复从用户设置的首次时间开始,不支持"从下周一才开始"等偏移
2. **时间不变**:重复时只改变日期,时间分量保持不变
3. **闰年处理**2月29日出生的用户每年2月28日提醒
4. **批量操作限制**:重复提醒不支持批量删除,需要逐条确认
5. **归档保留**:已完成的历史记录保留在归档页,供用户追溯
6. **数量限制**单用户最多100个有效重复提醒防止滥用
### 12.7 验收标准
| 场景 | 验收标准 |
|------|----------|
| 创建每天重复 | 创建后显示在对应分组,日期时间正确 |
| 创建每周重复 | 创建后显示在对应分组,日期+7天计算正确 |
| 创建每年重复 | 创建后显示在对应分组,跨年日期正确 |
| 循环icon显示 | 正常卡片在日期时间左侧,逾期卡片在标题右侧 |
| 勾选完成 | 当前记录归档,下一周期记录出现 |
| 逾期列表 | 所有逾期未完成记录都显示不只1条 |
| 编辑重复方式 | 修改后按新规则计算后续周期 |
| 闰年日期 | 2月29日创建的每年重复2月28日提醒 |
---
## 13 交互优化设计
### 13.1 退出后跳转优化
**当前行为**:退出后跳转到登录页
**优化行为**:退出后跳转到宣传页
**修改位置**`HomePage.tsx``handleLogout` 函数
```typescript
const handleLogout = async () => {
await logout();
navigate('/landing'); // 改为跳转到宣传页
};
```
### 13.2 提醒列表归档入口优化
**当前状态**
- Home页面右上角有归档入口
- 提醒列表Header有归档入口
**优化方案**
- 移除Home页面右上角的归档入口
- 保留提醒列表Header中的归档入口与归档页功能一致
### 13.3 归档图标抖动动画
**触发时机**:用户勾选完成逾期提醒后
**动画效果**
1. 卡片播放淡出动画300ms
2. 动画完成后归档图标触发缩放抖动动画400ms
3. 示意"该提醒已收入归档"
**实现方式**CSS animation `@keyframes archive-shake`
### 13.4 分类折叠功能
**位置**:提醒列表的"今天"、"明天"、"更久之后"分类
**功能描述**
- 每个分类标题右侧显示展开/折叠图标
- 默认全部展开
- 点击分类标题或图标切换展开/折叠状态
- 折叠后隐藏该分类下所有卡片
**图标设计**
- 展开状态:下箭头 `IconChevronDown`
- 折叠状态:右箭头 `IconChevronRight`
### 13.5 逾期列表展开/收起功能
**位置**:提醒列表的"已错过"分组
**功能描述**
- 默认收起状态最多显示3条逾期提醒
- 超过3条时显示"还有 X 个逾期提醒..."链接,点击展开
- 展开后显示所有逾期提醒,底部显示"收起"按钮
- 收起后恢复显示前3条
**交互效果**
- 点击"还有 X 个逾期提醒..."展开全部逾期列表
- 展开后底部显示收起按钮,点击可收起
- 收起/展开切换时有平滑过渡效果
**业务规则**
- 无论逾期数量多少默认只显示前3条
- 展开后显示所有逾期未完成提醒
- 已过期且已完成的提醒自动移入归档页,不显示在逾期列表
### 13.6 无时间提醒显示优化
**问题场景**
- 用户创建的提醒未设置具体时间00:00
- 自动创建的重复提醒时间显示异常
**优化方案**
- 判断逻辑检查小时和分钟是否全为0
- 如果 hours === 0 && minutes === 0视为"未设置时间"
- 显示格式:只显示日期(如"2月5日"),不显示时间
- 归档列表同样适用此规则
---
### C. 修改记录
| 版本 | 日期 | 修改内容 | 修改人 |
|------|------|----------|--------|
| v1.0.0 | 2026-01-28 | 初稿完成 | 产品经理 |
| v1.0.1 | 2026-02-03 | 完善提醒模块,实现归档页、设置页、优化样式 | 产品经理 |
| v1.0.2 | 2026-02-04 | 退出跳转宣传页、移除重复归档入口、归档抖动动画、分类折叠、重复提醒完成移除设置、逾期列表展开收起、无时间提醒显示优化 | 产品经理 |
| v1.1.0 | 待定 | 重复提醒功能:每天/每周/每年重复、循环图标、列表显示优化 | 产品经理 |

2
server

@ -1 +1 @@
Subproject commit 60fdd4ec2be26590f8f96ddfe6c4468d7cf16b5a
Subproject commit e3014a9d4c3c59735726b262ccda3c1e3b007456