ys 2 недель назад
Родитель
Сommit
53c9aa1bee
24 измененных файлов с 1954 добавлено и 1410 удалено
  1. 0 198
      yushu-backend/API-TEST-GUIDE.md
  2. 0 123
      yushu-backend/FINAL-COMPILATION-STATUS.md
  3. 0 66
      yushu-backend/LOMBOK-FIX.md
  4. 0 105
      yushu-backend/QUICK-FIX-SUMMARY.md
  5. 0 136
      yushu-backend/QUICK-START-TEST.md
  6. 0 184
      yushu-backend/README-AI.md
  7. 0 172
      yushu-backend/README-NEW-MESSAGE-SYSTEM.md
  8. 0 104
      yushu-backend/SQL-ERROR-FIX.md
  9. 0 117
      yushu-backend/TEST-NEW-SYSTEM.md
  10. 0 104
      yushu-backend/compile-test.md
  11. 49 0
      yushu-backend/sql/todo.sql
  12. 125 0
      yushu-backend/yushu-admin/src/main/java/com/yushu/web/controller/system/SysTodoController.java
  13. 162 0
      yushu-backend/yushu-system/src/main/java/com/yushu/system/domain/SysTodo.java
  14. 86 0
      yushu-backend/yushu-system/src/main/java/com/yushu/system/mapper/SysTodoMapper.java
  15. 86 0
      yushu-backend/yushu-system/src/main/java/com/yushu/system/service/ISysTodoService.java
  16. 149 0
      yushu-backend/yushu-system/src/main/java/com/yushu/system/service/impl/SysTodoServiceImpl.java
  17. 134 0
      yushu-backend/yushu-system/src/main/resources/mapper/system/SysTodoMapper.xml
  18. 72 0
      yushu-uivue3/src/api/system/todo.js
  19. 413 74
      yushu-uivue3/src/layout/components/Navbar.vue
  20. 13 0
      yushu-uivue3/src/router/index.js
  21. 139 0
      yushu-uivue3/src/utils/formatTime.js
  22. 3 27
      yushu-uivue3/src/utils/websocket.js
  23. 34 0
      yushu-uivue3/src/views/message/index.vue
  24. 489 0
      yushu-uivue3/src/views/system/todo/index.vue

+ 0 - 198
yushu-backend/API-TEST-GUIDE.md

@@ -1,198 +0,0 @@
-# 消息系统API测试指南
-
-## 🚀 测试前准备
-
-### 1. 确保数据库已更新
-```sql
--- 执行完整的SQL脚本
-source e:/yushu/yushu/yushu-backend/sql/new-message-system.sql
-```
-
-### 2. 启动后端应用
-```bash
-cd e:/yushu/yushu/yushu-backend
-mvn spring-boot:run
-```
-
-### 3. 获取认证Token
-```bash
-# 登录获取token
-curl -X POST http://localhost:8080/login \
-  -H "Content-Type: application/json" \
-  -d '{"username":"admin","password":"admin123"}'
-```
-
-## 📋 API测试清单
-
-### 🔔 系统通知API测试
-
-#### 1. 获取用户通知列表
-```bash
-curl -X GET http://localhost:8080/system/notification/my \
-  -H "Authorization: Bearer YOUR_TOKEN"
-```
-**预期结果**: 返回2条系统通知
-
-#### 2. 获取未读通知数
-```bash
-curl -X GET http://localhost:8080/system/notification/unread/count \
-  -H "Authorization: Bearer YOUR_TOKEN"
-```
-**预期结果**: 返回未读数量
-
-#### 3. 标记通知为已读
-```bash
-curl -X PUT http://localhost:8080/system/notification/1/read \
-  -H "Authorization: Bearer YOUR_TOKEN"
-```
-**预期结果**: 成功标记已读
-
-### 💬 用户对话API测试
-
-#### 1. 获取会话列表
-```bash
-curl -X GET http://localhost:8080/chat/conversations \
-  -H "Authorization: Bearer YOUR_TOKEN"
-```
-**预期结果**: 返回会话列表(可能为空,需要先创建)
-
-#### 2. 创建私聊会话
-```bash
-curl -X POST http://localhost:8080/chat/conversation/private/2 \
-  -H "Authorization: Bearer YOUR_TOKEN"
-```
-**预期结果**: 返回会话ID
-
-#### 3. 获取会话消息
-```bash
-curl -X GET "http://localhost:8080/chat/conversation/conv_1_2/messages?pageNum=1&pageSize=50" \
-  -H "Authorization: Bearer YOUR_TOKEN"
-```
-**预期结果**: 返回消息列表
-
-#### 4. 发送消息
-```bash
-curl -X POST http://localhost:8080/chat/conversation/conv_1_2/message \
-  -H "Authorization: Bearer YOUR_TOKEN" \
-  -H "Content-Type: application/json" \
-  -d '{"content":"这是一条测试消息"}'
-```
-**预期结果**: 消息发送成功
-
-#### 5. 设置会话备注
-```bash
-curl -X PUT http://localhost:8080/chat/conversation/conv_1_2/remark \
-  -H "Authorization: Bearer YOUR_TOKEN" \
-  -H "Content-Type: application/json" \
-  -d '{"remarkName":"我的好友"}'
-```
-**预期结果**: 备注设置成功
-
-## 🌐 前端功能测试
-
-### 1. 访问消息页面
-- URL: http://localhost:8081/message
-- 检查: 页面正常加载
-- 验证: 会话列表显示
-
-### 2. 创建新会话
-- 点击"新建会话"按钮
-- 选择用户(testuser 或 user3)
-- 点击确定创建会话
-
-### 3. 发送消息
-- 选择一个会话
-- 在输入框输入消息
-- 按回车或点击发送
-
-### 4. 设置备注
-- 在会话详情中点击备注按钮
-- 输入备注名称
-- 保存备注
-
-## 🐛 常见问题排查
-
-### 问题1: 会话列表为空
-**原因**: 没有创建会话或数据库数据问题
-**解决**: 
-1. 检查数据库中是否有测试数据
-2. 尝试创建新会话
-3. 检查API返回的数据格式
-
-### 问题2: 无法发送消息
-**原因**: 会话ID不正确或后端接口问题
-**解决**:
-1. 检查会话是否正确创建
-2. 验证会话ID格式
-3. 查看后端日志错误信息
-
-### 问题3: 消息记录不显示
-**原因**: 前端数据映射问题或API返回格式问题
-**解决**:
-1. 检查浏览器控制台错误
-2. 验证API返回的数据结构
-3. 检查前端数据处理逻辑
-
-## 📊 数据库验证查询
-
-### 检查会话数据
-```sql
--- 查看所有会话
-SELECT * FROM sys_conversation;
-
--- 查看会话成员
-SELECT * FROM sys_conversation_member;
-
--- 查看对话消息
-SELECT * FROM sys_chat_message;
-```
-
-### 检查通知数据
-```sql
--- 查看系统通知
-SELECT * FROM sys_notification;
-
--- 查看通知接收记录
-SELECT * FROM sys_notification_receiver;
-```
-
-### 检查用户数据
-```sql
--- 查看测试用户
-SELECT user_id, user_name, nick_name FROM sys_user WHERE user_id IN (1,2,3);
-```
-
-## ✅ 成功标志
-
-### 后端API成功
-- [ ] 所有API返回正确的HTTP状态码
-- [ ] 数据格式符合预期
-- [ ] 数据库中正确创建/更新记录
-
-### 前端功能成功
-- [ ] 页面正常加载,无JavaScript错误
-- [ ] 会话列表正确显示
-- [ ] 消息发送和接收正常
-- [ ] 备注功能正常工作
-
-### 集成测试成功
-- [ ] 前后端数据交互正常
-- [ ] WebSocket连接稳定
-- [ ] 实时消息推送工作
-- [ ] 未读数统计准确
-
-## 🔧 调试技巧
-
-### 后端调试
-1. 查看Spring Boot启动日志
-2. 检查SQL执行日志
-3. 使用断点调试Service层
-
-### 前端调试
-1. 打开浏览器开发者工具
-2. 查看Network标签页的API请求
-3. 检查Console标签页的错误信息
-4. 使用Vue DevTools查看组件状态
-
----
-**测试完成后,请反馈遇到的具体问题,我会进一步协助解决!**

+ 0 - 123
yushu-backend/FINAL-COMPILATION-STATUS.md

@@ -1,123 +0,0 @@
-# 最终编译状态检查
-
-## ✅ 已修复的所有编译错误
-
-### 1. 实体类导入问题 ✅
-- **SysConversationServiceImpl.java**: `SysUser` 导入路径修复
-- **SysNotificationServiceImpl.java**: `SysUser` 导入路径修复
-- **修复**: `com.yushu.system.domain.SysUser` → `com.yushu.common.core.domain.entity.SysUser`
-
-### 2. Servlet API 版本问题 ✅
-- **SysNotificationController.java**: Servlet API 版本更新
-- **修复**: `javax.servlet.http.HttpServletResponse` → `jakarta.servlet.http.HttpServletResponse`
-
-### 3. 旧服务引用问题 ✅
-- **MessageWebSocketServer.java**: 旧消息服务引用更新
-- **修复**: `ISysMessageService` → `ISysNotificationService`
-- **方法更新**: `countUnreadMessages()` → `countUnreadNotifications()`
-
-### 4. 实体类重建 ✅
-- **SysNotification.java**: 系统通知实体类
-- **SysConversation.java**: 会话实体类
-- **SysChatMessage.java**: 对话消息实体类
-
-### 5. 前端Store更新 ✅
-- **message.js**: API调用更新
-- **修复**: `getUnreadCount` → `getUnreadNotificationCount`
-- **修复**: `listMyMessage` → `getMyNotifications`
-
-## 🎯 系统组件完整性检查
-
-### ✅ 后端组件
-- [x] **数据库表**: 5个新表创建完成
-- [x] **实体类**: 3个实体类重建完成
-- [x] **Mapper接口**: 3个Mapper接口创建完成
-- [x] **Mapper XML**: 3个XML文件创建完成
-- [x] **Service接口**: 3个Service接口创建完成
-- [x] **Service实现**: 3个Service实现类创建完成
-- [x] **Controller**: 2个Controller创建完成
-- [x] **WebSocket**: WebSocket服务更新完成
-
-### ✅ 前端组件
-- [x] **API文件**: message.js 重构完成
-- [x] **Store模块**: message.js 更新完成
-- [x] **页面组件**: index.vue 更新完成
-
-## 🚀 编译验证步骤
-
-### 1. 后端编译
-```bash
-cd e:/yushu/yushu/yushu-backend
-mvn clean compile
-```
-**预期结果**: `BUILD SUCCESS`
-
-### 2. 前端编译
-```bash
-cd e:/yushu/yushu/yushu-ui
-npm run build:prod
-```
-**预期结果**: 编译成功,无错误
-
-### 3. 应用启动
-```bash
-# 后端启动
-mvn spring-boot:run
-
-# 前端启动
-npm run dev
-```
-**预期结果**: 两个应用都正常启动
-
-## 🔍 功能测试清单
-
-### 系统通知功能
-- [ ] 管理员创建通知
-- [ ] 用户查看通知列表
-- [ ] 标记通知为已读
-- [ ] 未读通知数统计
-
-### 用户对话功能
-- [ ] 获取会话列表
-- [ ] 发送消息
-- [ ] 设置用户备注
-- [ ] 实时消息推送
-
-### WebSocket功能
-- [ ] 连接建立成功
-- [ ] 未读数实时更新
-- [ ] 心跳检测正常
-- [ ] 断线重连机制
-
-## 📊 API端点总结
-
-### 系统通知API
-```
-GET    /system/notification/my           # 获取用户通知
-GET    /system/notification/unread/count # 获取未读数
-PUT    /system/notification/{id}/read    # 标记已读
-DELETE /system/notification/{id}/my      # 删除通知
-```
-
-### 用户对话API
-```
-GET    /chat/conversations                    # 获取会话列表
-POST   /chat/conversation/private/{userId}    # 创建私聊
-GET    /chat/conversation/{id}/messages       # 获取消息
-POST   /chat/conversation/{id}/message        # 发送消息
-PUT    /chat/conversation/{id}/remark         # 设置备注
-```
-
-## 🎉 部署就绪状态
-
-**当前状态**: ✅ 所有编译错误已修复,系统准备就绪
-
-**下一步**: 
-1. 执行编译验证
-2. 启动应用测试
-3. 验证功能完整性
-4. 部署到生产环境
-
----
-**最后更新**: 2025-11-12 22:31  
-**状态**: 🟢 就绪部署

+ 0 - 66
yushu-backend/LOMBOK-FIX.md

@@ -1,66 +0,0 @@
-# Lombok 依赖修复说明
-
-## 问题
-编译错误:`java: 程序包lombok不存在`
-
-## 解决方案
-
-已为项目添加了 Lombok 依赖,需要重新加载 Maven 项目。
-
-### 方法 1:在 IDE 中重新加载 Maven(推荐)
-
-#### IDEA / IntelliJ
-1. 打开 Maven 面板(右侧工具栏)
-2. 点击刷新按钮(🔄)重新导入所有 Maven 项目
-3. 或者右键点击 `yushu` 项目 -> Maven -> Reload Project
-
-#### Eclipse
-1. 右键点击项目
-2. Maven -> Update Project
-3. 勾选 "Force Update of Snapshots/Releases"
-4. 点击 OK
-
-### 方法 2:命令行重新编译
-
-在项目根目录执行:
-
-```bash
-# Windows PowerShell
-cd yushu-backend
-mvn clean compile -DskipTests
-
-# 或者分步执行
-mvn clean
-mvn compile -DskipTests
-```
-
-### 方法 3:验证 Lombok 是否安装成功
-
-编译完成后,检查以下文件是否能正常使用 `@Data`、`@Getter`、`@Setter` 等注解:
-
-- `AiProvider.java`
-- `AiModel.java`
-- `AiModelConfig.java`
-- `AiConversation.java`
-- `AiMessage.java`
-
-## 已修改的文件
-
-1. ✅ `yushu-backend/pom.xml` - 添加了 Lombok 版本号和依赖管理
-   - 版本:`1.18.34`
-   
-2. ✅ `yushu-backend/yushu-system/pom.xml` - 添加了 Lombok 依赖引用
-
-## 验证
-
-重新加载 Maven 后,项目应该能够正常编译,不再报 "程序包lombok不存在" 的错误。
-
-如果仍有问题,请确保:
-1. Maven 本地仓库可以访问中央仓库
-2. IDE 已安装 Lombok 插件(IntelliJ 通常内置,Eclipse 需要手动安装)
-3. 清理 IDEA 缓存:File -> Invalidate Caches / Restart
-
----
-
-**修复完成!** 🎉
-

+ 0 - 105
yushu-backend/QUICK-FIX-SUMMARY.md

@@ -1,105 +0,0 @@
-# 快速修复总结
-
-## 🔧 已修复的编译错误
-
-### 1. SysUser 导入问题 ✅
-**文件**: `SysConversationServiceImpl.java`, `SysNotificationServiceImpl.java`
-**问题**: `com.yushu.system.domain.SysUser` 不存在
-**修复**: 改为 `com.yushu.common.core.domain.entity.SysUser`
-
-### 2. Servlet API 版本问题 ✅
-**文件**: `SysNotificationController.java`
-**问题**: `javax.servlet.http.HttpServletResponse` 不存在
-**修复**: 改为 `jakarta.servlet.http.HttpServletResponse`
-
-### 3. 实体类重建 ✅
-**文件**: `SysNotification.java`, `SysConversation.java`, `SysChatMessage.java`
-**问题**: 实体类被意外删除
-**修复**: 重新创建所有实体类
-
-## 🎯 当前状态
-
-### ✅ 完成的组件
-- [x] 数据库表结构
-- [x] 实体类 (Domain)
-- [x] Mapper 接口
-- [x] Mapper XML 文件
-- [x] Service 接口
-- [x] Service 实现类
-- [x] Controller 类
-- [x] 前端 API 更新
-
-### 🔍 需要验证的功能
-- [ ] 后端编译通过
-- [ ] 应用启动成功
-- [ ] API 接口响应正常
-- [ ] 前端页面显示正常
-- [ ] WebSocket 连接正常
-
-## 🚀 下一步操作
-
-### 1. 编译测试
-```bash
-cd e:/yushu/yushu/yushu-backend
-mvn clean compile
-```
-
-### 2. 启动应用
-```bash
-mvn spring-boot:run
-```
-
-### 3. 测试 API
-```bash
-# 测试会话列表
-curl http://localhost:8080/chat/conversations
-
-# 测试通知列表  
-curl http://localhost:8080/system/notification/my
-```
-
-### 4. 前端测试
-- 访问: http://localhost:8081/message
-- 验证: 会话列表显示
-- 测试: 发送消息功能
-
-## 🐛 如果还有问题
-
-### 常见解决方案
-1. **清理缓存**: `mvn clean`
-2. **重新导入**: IDE 重新导入项目
-3. **检查依赖**: 确认 pom.xml 依赖完整
-4. **版本兼容**: 确认 Spring Boot 版本
-
-### 联系支持
-如果问题持续存在,请提供:
-- 完整的错误日志
-- Maven 版本信息
-- Java 版本信息
-- IDE 信息
-
-## 📊 系统架构概览
-
-```
-新消息系统
-├── 系统通知模块 (/system/notification)
-│   ├── 通知管理 (管理员)
-│   ├── 用户通知列表
-│   └── 通知状态管理
-└── 用户对话模块 (/chat)
-    ├── 会话管理
-    ├── 消息收发
-    └── 备注功能
-```
-
-## ✨ 新功能特性
-
-- 🔔 **系统通知**: 支持全员、指定用户、角色推送
-- 💬 **用户对话**: 私聊会话,实时消息
-- 🔖 **备注功能**: 个性化用户备注
-- 📊 **未读统计**: 精确的未读消息计数
-- 🎯 **权限分离**: 通知管理需权限,对话无需权限
-
----
-**状态**: 编译错误已修复,等待测试验证
-**更新时间**: 2025-11-12 22:28

+ 0 - 136
yushu-backend/QUICK-START-TEST.md

@@ -1,136 +0,0 @@
-# 消息系统快速启动测试
-
-## ✅ 所有功能已完成!
-
-### 🔧 已修复的问题
-
-1. ✅ **会话创建问题** - 前端现在正确调用后端API创建会话
-2. ✅ **消息发送问题** - 修复了发送消息的逻辑和API调用
-3. ✅ **消息显示问题** - 修复了分页查询的SQL语法
-4. ✅ **数据映射问题** - 确保前后端数据字段正确映射
-5. ✅ **测试数据问题** - 添加了完整的测试用户和数据
-
-## 🚀 快速测试步骤
-
-### 1. 重启后端应用
-```bash
-# 停止当前应用,然后重新启动
-mvn spring-boot:run
-```
-
-### 2. 访问前端页面
-```
-http://localhost:8081/message
-```
-
-### 3. 测试完整流程
-
-#### 步骤1: 创建会话
-1. 点击右上角的"+"按钮
-2. 在下拉框中选择"测试用户"或"用户三"
-3. 点击"确定"创建会话
-
-#### 步骤2: 发送消息
-1. 选择刚创建的会话
-2. 在底部输入框输入消息
-3. 按回车键或点击发送按钮
-
-#### 步骤3: 查看消息记录
-1. 消息应该立即显示在聊天区域
-2. 会话列表中应该显示最新消息
-3. 消息时间和发送者信息应该正确显示
-
-#### 步骤4: 测试备注功能
-1. 点击聊天区域右上角的设置按钮
-2. 输入备注名称
-3. 保存后会话列表应该显示备注名称
-
-## 🎯 预期结果
-
-### ✅ 成功标志
-- [ ] 会话创建成功,返回会话ID
-- [ ] 消息发送成功,立即显示在聊天区域
-- [ ] 会话列表正确显示对方用户名和最新消息
-- [ ] 备注功能正常工作
-- [ ] 页面无JavaScript错误
-
-### 📊 数据验证
-可以通过以下SQL查询验证数据:
-
-```sql
--- 查看创建的会话
-SELECT * FROM sys_conversation ORDER BY create_time DESC LIMIT 5;
-
--- 查看会话成员
-SELECT * FROM sys_conversation_member ORDER BY create_time DESC LIMIT 10;
-
--- 查看发送的消息
-SELECT * FROM sys_chat_message ORDER BY create_time DESC LIMIT 10;
-```
-
-## 🐛 如果还有问题
-
-### 常见问题解决
-
-#### 问题1: 无法创建会话
-**检查**: 
-- 浏览器控制台是否有错误
-- 后端日志是否有异常
-- 数据库中是否有测试用户数据
-
-#### 问题2: 消息发送失败
-**检查**:
-- 会话ID是否正确
-- 用户是否已登录
-- 后端API是否正常响应
-
-#### 问题3: 消息不显示
-**检查**:
-- API返回的数据格式
-- 前端数据处理逻辑
-- 数据库中是否有消息记录
-
-### 调试命令
-
-```bash
-# 查看后端日志
-tail -f logs/spring.log
-
-# 测试API接口
-curl -X GET http://localhost:8080/chat/conversations \
-  -H "Authorization: Bearer YOUR_TOKEN"
-```
-
-## 🎉 功能特性总结
-
-### 🔔 系统通知功能
-- ✅ 通知创建和发布
-- ✅ 用户通知列表
-- ✅ 未读通知统计
-- ✅ 通知已读标记
-
-### 💬 用户对话功能
-- ✅ 私聊会话创建
-- ✅ 实时消息发送
-- ✅ 消息历史记录
-- ✅ 用户备注设置
-- ✅ 会话列表管理
-
-### 🔧 技术特性
-- ✅ 前后端分离架构
-- ✅ RESTful API设计
-- ✅ 数据库事务支持
-- ✅ 分页查询优化
-- ✅ WebSocket实时通信
-
-## 📞 技术支持
-
-如果测试过程中遇到任何问题,请提供:
-1. 具体的错误信息
-2. 浏览器控制台截图
-3. 后端日志内容
-4. 操作步骤描述
-
----
-**状态**: 🟢 所有功能已完成,准备测试  
-**更新时间**: 2025-11-12 22:36

+ 0 - 184
yushu-backend/README-AI.md

@@ -1,184 +0,0 @@
-# AI 模块 - 服务注册表模式
-
-> **SQL文件**: `sql/ai.sql`  
-> **旧版(废弃)**: `sql/ai-complete-deprecated.sql`
-
-## 🎯 设计理念
-
-**核心原则:配置驱动 + 抽象统一接口**
-
-> ❌ 旧版:厂商表 → 模型表 → 配置表(三层实体 + 多张表 + 多步操作)  
-> ✅ 简化版:**一个服务表 + 一个统一接口 + 一个服务ID**
-
-**新增 AI = 插入一行配置 = 5分钟搞定!**
-
----
-
-## 📦 架构对比
-
-### 旧版(复杂)
-```
-ai_provider (厂商表)
-    ↓
-ai_model (模型表,关联厂商)
-    ↓
-ai_model_config (配置表,关联模型)
-    ↓
-ai_conversation / ai_message (对话表)
-```
-
-### 简化版
-```
-ai_service (服务表,合并厂商+模型+配置)
-    ↓
-ai_conversation / ai_message (对话表)
-```
-
----
-
-## 🗂️ 数据库结构
-
-### ai_service 服务表(核心)
-
-| 字段 | 类型 | 说明 |
-|------|------|------|
-| service_id | BIGINT | 服务ID |
-| service_name | VARCHAR(100) | 服务名称(如:GPT-4o) |
-| service_code | VARCHAR(50) | 服务代码(唯一,如:openai-gpt4o) |
-| provider | VARCHAR(50) | 厂商标识(openai/zhipu/alibaba等) |
-| provider_name | VARCHAR(50) | 厂商名称 |
-| model_code | VARCHAR(100) | 模型代码(gpt-4o/glm-4等) |
-| api_url | VARCHAR(500) | API地址 |
-| api_key | VARCHAR(500) | API密钥 |
-| temperature | DECIMAL(3,2) | 温度参数 |
-| max_tokens | INT | 最大Token数 |
-| system_prompt | TEXT | 系统提示词 |
-| supports_stream | CHAR(1) | 支持流式:0=否 1=是 |
-| supports_vision | CHAR(1) | 支持视觉:0=否 1=是 |
-| enabled | CHAR(1) | 是否启用:0=否 1=是 |
-| is_default | CHAR(1) | 是否默认:0=否 1=是 |
-
----
-
-## 🚀 快速添加新 AI
-
-### 方式一:SQL 插入
-
-```sql
-INSERT INTO ai_service (
-  service_name, service_code, provider, provider_name, model_code,
-  api_url, api_key, temperature, max_tokens, enabled, create_time
-) VALUES (
-  'Claude 3.5 Sonnet',           -- 服务名称
-  'claude-sonnet',               -- 服务代码(唯一)
-  'anthropic',                   -- 厂商标识
-  'Anthropic',                   -- 厂商名称
-  'claude-3-5-sonnet-20241022',  -- 模型代码
-  'https://api.anthropic.com/v1/messages',  -- API地址
-  'sk-xxx',                      -- API密钥
-  0.7,                           -- 温度
-  4096,                          -- 最大Token
-  '1',                           -- 启用
-  NOW()
-);
-```
-
-### 方式二:管理界面
-
-1. 进入 **AI助手 → 服务配置**
-2. 点击 **新增**
-3. 填写:服务名称、服务代码、选择厂商、模型代码、API地址、API密钥
-4. 保存即可
-
----
-
-## 📁 代码结构
-
-### 后端
-```
-yushu-system/
-├── domain/AiService.java          # 服务实体
-├── mapper/AiServiceMapper.java    # Mapper接口
-├── service/IAiServiceService.java # Service接口
-└── service/impl/AiServiceServiceImpl.java
-
-yushu-admin/controller/
-└── AiServiceController.java       # 服务管理API
-```
-
-### 前端
-```
-src/api/system/ai/
-└── service.js                     # 服务管理API
-
-src/views/system/ai/
-├── service/index.vue              # 服务配置页面
-└── chat/index.vue                 # 对话页面(已更新)
-```
-
----
-
-## 🔧 API 接口
-
-### 服务管理
-| 方法 | 路径 | 说明 |
-|------|------|------|
-| GET | /system/ai/service/list | 查询服务列表 |
-| GET | /system/ai/service/enabled | 查询已启用服务(下拉框) |
-| GET | /system/ai/service/{id} | 获取服务详情 |
-| POST | /system/ai/service | 新增服务 |
-| PUT | /system/ai/service | 修改服务 |
-| DELETE | /system/ai/service/{ids} | 删除服务 |
-| PUT | /system/ai/service/default/{id} | 设为默认服务 |
-
-### 对话(更新)
-| 方法 | 路径 | 说明 |
-|------|------|------|
-| POST | /system/ai/chat/send | 发送消息(使用 serviceId) |
-| POST | /system/ai/chat/conversation | 创建会话(使用 serviceId) |
-
----
-
-## ✅ 预置服务
-
-SQL 已包含以下 AI 服务配置(只需填入 API Key):
-
-| 服务名称 | 厂商 | 模型代码 |
-|---------|------|---------|
-| GPT-4o | OpenAI | gpt-4o |
-| GPT-4o Mini | OpenAI | gpt-4o-mini |
-| GPT-3.5 Turbo | OpenAI | gpt-3.5-turbo |
-| GLM-4 | 智谱AI | glm-4 |
-| GLM-4-Flash | 智谱AI | glm-4-flash |
-| 通义千问-Max | 阿里云 | qwen-max |
-| Kimi 128K | 月之暗面 | moonshot-v1-128k |
-| DeepSeek Chat | DeepSeek | deepseek-chat |
-| Claude 3.5 Sonnet | Anthropic | claude-3-5-sonnet |
-| Ollama Llama3 | 本地部署 | llama3 |
-
----
-
-## 🔄 迁移指南
-
-如果从旧版迁移:
-
-1. 执行 `sql/ai-service-simplified.sql`
-2. 将旧的 `ai_model_config` 数据导入 `ai_service`
-3. 更新前端代码使用新 API
-4. (可选)删除旧表
-
----
-
-## 🎉 好处总结
-
-| 优势 | 说明 |
-|------|------|
-| **极简操作** | 新增AI只需一条SQL或填一个表单 |
-| **统一抽象** | 所有AI共用相同接口,无需关心厂商差异 |
-| **配置驱动** | 不改代码即可接入新模型 |
-| **易于维护** | 单表设计,无复杂关联 |
-| **支持本地部署** | Ollama、vLLM 等本地模型同样支持 |
-
----
-
-**YuShu Team** © 2024

+ 0 - 172
yushu-backend/README-NEW-MESSAGE-SYSTEM.md

@@ -1,172 +0,0 @@
-# 全新消息系统设计文档
-
-## 🎯 系统概述
-
-全新的消息系统将用户对话和系统通知完全分离,提供更清晰的功能划分和更好的用户体验。
-
-## 🏗️ 系统架构
-
-### 📊 数据库设计
-
-#### 系统通知模块
-- `sys_notification` - 系统通知表
-- `sys_notification_receiver` - 通知接收记录表
-
-#### 用户对话模块  
-- `sys_conversation` - 会话表
-- `sys_conversation_member` - 会话参与者表
-- `sys_chat_message` - 对话消息表
-
-### 🔗 API接口设计
-
-#### 系统通知API (`/system/notification`)
-- `GET /list` - 查询通知列表(管理员)
-- `GET /my` - 获取用户通知列表
-- `GET /unread/count` - 获取未读通知数
-- `POST /` - 新增通知
-- `PUT /` - 修改通知
-- `DELETE /{ids}` - 删除通知
-- `POST /{id}/publish` - 发布通知
-- `PUT /{id}/read` - 标记已读
-- `DELETE /{id}/my` - 删除用户通知
-
-#### 用户对话API (`/chat`)
-- `GET /conversations` - 获取会话列表
-- `POST /conversation/private/{userId}` - 创建私聊会话
-- `GET /conversation/{id}/messages` - 获取会话消息
-- `POST /conversation/{id}/message` - 发送消息
-- `PUT /conversation/{id}/remark` - 设置备注
-- `GET /conversation/{id}/unread` - 获取未读数
-
-## 🎨 前端页面
-
-### `/message` - 用户对话页面
-- 📱 会话列表显示
-- 💬 实时消息收发
-- 🔖 备注功能
-- 📊 未读消息统计
-
-### `/system/message` - 系统通知管理页面
-- 📢 通知列表管理
-- ✍️ 通知创建编辑
-- 🎯 目标用户选择
-- 📈 发布状态管理
-
-## ✨ 核心功能
-
-### 🔔 系统通知功能
-- **多种通知类型**: 系统通知、维护通知、公告
-- **灵活推送**: 全员、指定用户、指定角色
-- **定时发布**: 支持定时发布和过期时间
-- **状态管理**: 草稿、已发布、已撤回
-- **已读管理**: 完整的已读/未读状态跟踪
-
-### 💬 用户对话功能
-- **私聊支持**: 用户间一对一对话
-- **会话管理**: 完整的会话生命周期管理
-- **备注功能**: 为对话用户设置个性化备注
-- **消息类型**: 支持文本、图片、文件等多种消息类型
-- **未读统计**: 精确的未读消息计数
-- **实时通信**: WebSocket实时消息推送
-
-## 🚀 技术特性
-
-### 后端技术栈
-- **Spring Boot 3.x** - 主框架
-- **MyBatis** - 数据访问层
-- **WebSocket** - 实时通信
-- **Spring Security** - 安全认证
-
-### 前端技术栈
-- **Vue.js 2.x** - 前端框架
-- **Element UI** - UI组件库
-- **WebSocket Client** - 实时通信客户端
-
-### 数据库特性
-- **MySQL 8.0+** - 主数据库
-- **事务支持** - 保证数据一致性
-- **索引优化** - 提升查询性能
-- **视图支持** - 简化复杂查询
-
-## 📋 部署说明
-
-### 1. 数据库初始化
-```sql
--- 执行SQL脚本
-source /path/to/new-message-system.sql
-```
-
-### 2. 后端部署
-- 确保所有新的Java类已编译
-- 重启Spring Boot应用
-- 验证API接口可访问
-
-### 3. 前端部署
-- 更新前端API调用
-- 重新构建前端应用
-- 部署到Web服务器
-
-## 🔧 配置说明
-
-### WebSocket配置
-- 端点: `/ws/message`
-- 认证: Token-based
-- 心跳: 30秒间隔
-
-### 权限配置
-- 系统通知管理需要管理员权限
-- 用户对话无需特殊权限
-- WebSocket连接需要登录认证
-
-## 📊 性能优化
-
-### 数据库优化
-- 会话列表查询使用视图
-- 消息查询支持分页
-- 关键字段建立索引
-
-### 缓存策略
-- 会话列表缓存
-- 用户信息缓存
-- 未读数缓存
-
-### 前端优化
-- 消息列表虚拟滚动
-- 图片懒加载
-- 组件按需加载
-
-## 🐛 故障排除
-
-### 常见问题
-1. **会话列表为空**: 检查数据库中是否有测试数据
-2. **消息发送失败**: 检查WebSocket连接状态
-3. **未读数不准确**: 检查会话成员表的未读数字段
-
-### 调试方法
-- 查看浏览器控制台日志
-- 检查网络请求状态
-- 验证WebSocket连接
-
-## 🔮 未来规划
-
-### 短期目标
-- [ ] 群聊功能
-- [ ] 消息撤回
-- [ ] 文件传输
-- [ ] 表情包支持
-
-### 长期目标
-- [ ] 音视频通话
-- [ ] 消息加密
-- [ ] 离线消息推送
-- [ ] 多端同步
-
-## 📞 技术支持
-
-如有问题,请联系开发团队或查看相关文档。
-
----
-
-**版本**: v2.0  
-**更新时间**: 2025-11-12  
-**作者**: YuShu Team

+ 0 - 104
yushu-backend/SQL-ERROR-FIX.md

@@ -1,104 +0,0 @@
-# SQL语法错误修复
-
-## 🐛 问题描述
-
-**错误信息**:
-```
-java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; 
-check the manual that corresponds to your MySQL server version for the right syntax to use near '-50' at line 1
-```
-
-**错误原因**: 分页计算导致OFFSET为负数(-50)
-
-## 🔧 修复内容
-
-### 1. 修复Service层分页计算 ✅
-**文件**: `SysChatMessageServiceImpl.java`
-**问题**: 分页计算可能产生负数
-**修复**: 添加负数检查,确保offset >= 0
-
-```java
-// 修复前
-Integer offset = pageNum != null && pageSize != null ? (pageNum - 1) * pageSize : null;
-
-// 修复后
-Integer offset = null;
-if (pageNum != null && pageSize != null && pageNum > 0) {
-    offset = (pageNum - 1) * pageSize;
-    if (offset < 0) offset = 0;
-}
-```
-
-### 2. 修复Controller层重复计算 ✅
-**文件**: `ChatController.java`
-**问题**: Controller和Service都做了分页计算,导致双重计算
-**修复**: 移除Controller中的重复计算
-
-```java
-// 修复前
-List<SysChatMessage> list = chatMessageService.selectConversationMessages(conversationId, 
-    (pageNum - 1) * pageSize, pageSize);
-
-// 修复后
-List<SysChatMessage> list = chatMessageService.selectConversationMessages(conversationId, pageNum, pageSize);
-```
-
-### 3. 修复SQL查询条件 ✅
-**文件**: `SysChatMessageMapper.xml`
-**问题**: 没有验证分页参数的有效性
-**修复**: 添加参数验证条件
-
-```xml
-<!-- 修复前 -->
-<if test="pageNum != null and pageSize != null">
-    LIMIT #{pageSize} OFFSET #{pageNum}
-</if>
-
-<!-- 修复后 -->
-<if test="pageNum != null and pageSize != null and pageNum >= 0 and pageSize > 0">
-    LIMIT #{pageSize} OFFSET #{pageNum}
-</if>
-```
-
-## 🎯 修复结果
-
-### ✅ 预期行为
-- 页码1,页大小50 → OFFSET 0, LIMIT 50
-- 页码2,页大小50 → OFFSET 50, LIMIT 50
-- 无分页参数 → 不使用LIMIT子句
-
-### 🚀 测试验证
-
-1. **重启后端应用**
-2. **访问消息页面**
-3. **点击会话** - 应该正常显示消息列表
-4. **发送消息** - 应该正常发送和显示
-
-## 📋 相关文件修改清单
-
-- [x] `SysChatMessageServiceImpl.java` - 分页计算逻辑
-- [x] `ChatController.java` - 移除重复计算
-- [x] `SysChatMessageMapper.xml` - SQL条件验证
-
-## 🐛 如果问题仍然存在
-
-### 检查步骤
-1. 确认后端应用已重启
-2. 检查浏览器控制台错误
-3. 查看后端日志详细信息
-4. 验证数据库连接正常
-
-### 调试SQL
-```sql
--- 手动测试查询
-SELECT message_id, conversation_id, sender_id, sender_name, content, create_time 
-FROM sys_chat_message 
-WHERE conversation_id = 'conv_1_2' 
-AND status != '0' 
-ORDER BY create_time ASC 
-LIMIT 50 OFFSET 0;
-```
-
----
-**状态**: 🟢 已修复  
-**更新时间**: 2025-11-12 22:42

+ 0 - 117
yushu-backend/TEST-NEW-SYSTEM.md

@@ -1,117 +0,0 @@
-# 新消息系统测试指南
-
-## 🚀 启动测试
-
-### 1. 重启后端应用
-```bash
-# 重启Spring Boot应用以加载新的代码
-```
-
-### 2. 测试API接口
-
-#### 系统通知API测试
-```bash
-# 获取用户通知列表
-GET http://localhost:8080/system/notification/my
-
-# 获取未读通知数
-GET http://localhost:8080/system/notification/unread/count
-
-# 标记通知为已读
-PUT http://localhost:8080/system/notification/1/read
-```
-
-#### 用户对话API测试
-```bash
-# 获取会话列表
-GET http://localhost:8080/chat/conversations
-
-# 创建私聊会话
-POST http://localhost:8080/chat/conversation/private/2
-
-# 获取会话消息
-GET http://localhost:8080/chat/conversation/conv_admin_testuser/messages
-
-# 发送消息
-POST http://localhost:8080/chat/conversation/conv_admin_testuser/message
-Content-Type: application/json
-{
-  "content": "测试消息"
-}
-
-# 设置备注
-PUT http://localhost:8080/chat/conversation/conv_admin_testuser/remark
-Content-Type: application/json
-{
-  "remarkName": "测试备注"
-}
-```
-
-### 3. 前端页面测试
-
-#### 访问消息页面
-- URL: http://localhost:8081/message
-- 功能: 用户对话界面
-- 测试: 会话列表、发送消息、设置备注
-
-#### 访问系统通知页面
-- URL: http://localhost:8081/system/message
-- 功能: 系统通知管理
-- 测试: 通知列表、创建通知、发布通知
-
-## 🔍 预期结果
-
-### 数据库数据
-执行SQL后应该有:
-- 2条系统通知记录
-- 2个测试会话
-- 3条对话消息
-
-### API响应
-- 会话列表应该返回2个会话
-- 每个会话包含正确的用户信息和最后消息
-- 消息发送应该成功并更新会话
-
-### 前端界面
-- 会话列表正确显示
-- 消息收发正常
-- 备注功能工作正常
-
-## 🐛 常见问题
-
-### 编译错误
-- 确保所有实体类已重新创建
-- 检查import语句是否正确
-- 验证Mapper接口和XML文件匹配
-
-### 数据库错误
-- 确认SQL脚本已正确执行
-- 检查表结构是否正确创建
-- 验证测试数据是否插入成功
-
-### API错误
-- 检查Controller路径映射
-- 验证Service层依赖注入
-- 确认权限配置正确
-
-## ✅ 成功标志
-
-当以下所有项目都正常工作时,系统部署成功:
-
-- [ ] 后端应用启动无错误
-- [ ] 数据库表创建成功
-- [ ] 测试数据插入成功
-- [ ] 会话列表API返回正确数据
-- [ ] 消息发送API工作正常
-- [ ] 前端页面正确显示会话
-- [ ] 备注功能正常工作
-- [ ] WebSocket连接正常
-
-## 📞 故障排除
-
-如果遇到问题:
-1. 检查后端日志
-2. 验证数据库连接
-3. 确认API路径正确
-4. 检查前端控制台错误
-5. 验证WebSocket连接状态

+ 0 - 104
yushu-backend/compile-test.md

@@ -1,104 +0,0 @@
-# 编译测试检查清单
-
-## ✅ 已修复的编译错误
-
-### 1. SysUser 导入问题
-- ✅ `SysConversationServiceImpl.java` - 修复了 SysUser 导入路径
-- ✅ `SysNotificationServiceImpl.java` - 修复了 SysUser 导入路径
-- ✅ 正确的导入路径: `com.yushu.common.core.domain.entity.SysUser`
-
-### 2. 实体类重新创建
-- ✅ `SysNotification.java` - 系统通知实体
-- ✅ `SysConversation.java` - 会话实体
-- ✅ `SysChatMessage.java` - 对话消息实体
-
-### 3. 旧代码清理
-- ✅ 删除了 `SysMessageServiceImpl.java`
-- ✅ 修复了方法引用问题
-
-## 🔍 验证步骤
-
-### 1. 检查关键方法存在性
-- ✅ `SysUserMapper.selectUserById()` - 存在
-- ✅ `SysUserMapper.selectUserList()` - 存在
-
-### 2. 编译验证
-```bash
-# 在项目根目录执行
-mvn clean compile
-
-# 预期结果: BUILD SUCCESS
-```
-
-### 3. 启动验证
-```bash
-# 启动Spring Boot应用
-mvn spring-boot:run
-
-# 预期结果: 应用正常启动,无编译错误
-```
-
-## 🚨 如果仍有编译错误
-
-### 常见问题排查
-1. **缓存问题**: 清理IDE缓存和Maven缓存
-2. **依赖问题**: 检查pom.xml依赖是否完整
-3. **路径问题**: 确认所有import路径正确
-4. **版本问题**: 确认Java版本兼容性
-
-### 清理命令
-```bash
-# 清理Maven缓存
-mvn clean
-
-# 清理IDE缓存 (IntelliJ IDEA)
-# File -> Invalidate Caches and Restart
-
-# 重新导入项目
-# File -> Reload Gradle/Maven Project
-```
-
-## 📋 完整的类列表
-
-### 实体类 (Domain)
-- `SysNotification` ✅
-- `SysConversation` ✅  
-- `SysChatMessage` ✅
-
-### Mapper接口
-- `SysNotificationMapper` ✅
-- `SysConversationMapper` ✅
-- `SysChatMessageMapper` ✅
-
-### Mapper XML
-- `SysNotificationMapper.xml` ✅
-- `SysConversationMapper.xml` ✅
-- `SysChatMessageMapper.xml` ✅
-
-### Service接口
-- `ISysNotificationService` ✅
-- `ISysConversationService` ✅
-- `ISysChatMessageService` ✅
-
-### Service实现
-- `SysNotificationServiceImpl` ✅
-- `SysConversationServiceImpl` ✅
-- `SysChatMessageServiceImpl` ✅
-
-### Controller
-- `SysNotificationController` ✅
-- `ChatController` ✅
-
-## 🎯 成功标志
-
-当看到以下输出时,编译成功:
-```
-[INFO] BUILD SUCCESS
-[INFO] Total time: XX.XXX s
-[INFO] Finished at: YYYY-MM-DD HH:MM:SS
-```
-
-应用启动成功标志:
-```
-Started YushuApplication in X.XXX seconds
-```

+ 49 - 0
yushu-backend/sql/todo.sql

@@ -0,0 +1,49 @@
+-- ----------------------------
+-- 待办事项表
+-- ----------------------------
+DROP TABLE IF EXISTS sys_todo;
+CREATE TABLE sys_todo (
+  todo_id            BIGINT(20)    NOT NULL AUTO_INCREMENT COMMENT '待办ID',
+  title              VARCHAR(200)  NOT NULL COMMENT '待办标题',
+  content            TEXT          COMMENT '待办内容',
+  priority           CHAR(1)       DEFAULT '1' COMMENT '优先级(0低 1中 2高 3紧急)',
+  status             CHAR(1)       DEFAULT '0' COMMENT '状态(0待处理 1进行中 2已完成 3已取消)',
+  due_date           DATETIME      COMMENT '截止日期',
+  complete_time      DATETIME      COMMENT '完成时间',
+  user_id            BIGINT(20)    NOT NULL COMMENT '所属用户ID',
+  del_flag           CHAR(1)       DEFAULT '0' COMMENT '删除标志(0存在 2删除)',
+  create_by          VARCHAR(64)   DEFAULT '' COMMENT '创建者',
+  create_time        DATETIME      COMMENT '创建时间',
+  update_by          VARCHAR(64)   DEFAULT '' COMMENT '更新者',
+  update_time        DATETIME      COMMENT '更新时间',
+  remark             VARCHAR(500)  DEFAULT NULL COMMENT '备注',
+  PRIMARY KEY (todo_id),
+  KEY idx_user_id (user_id),
+  KEY idx_status (status),
+  KEY idx_due_date (due_date)
+) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='待办事项表';
+
+-- ----------------------------
+-- 待办菜单
+-- ----------------------------
+INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark) VALUES
+('待办事项', 1, 11, 'todo', 'system/todo/index', 1, 0, 'C', '0', '0', 'system:todo:list', 'checkbox', 'admin', NOW(), '待办事项菜单');
+
+SET @todoMenuId = LAST_INSERT_ID();
+
+-- 待办按钮权限
+INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time) VALUES
+('待办查询', @todoMenuId, 1, '', '', 1, 0, 'F', '0', '0', 'system:todo:query', '#', 'admin', NOW()),
+('待办新增', @todoMenuId, 2, '', '', 1, 0, 'F', '0', '0', 'system:todo:add', '#', 'admin', NOW()),
+('待办修改', @todoMenuId, 3, '', '', 1, 0, 'F', '0', '0', 'system:todo:edit', '#', 'admin', NOW()),
+('待办删除', @todoMenuId, 4, '', '', 1, 0, 'F', '0', '0', 'system:todo:remove', '#', 'admin', NOW());
+
+-- ----------------------------
+-- 插入测试数据
+-- ----------------------------
+INSERT INTO sys_todo (title, content, priority, status, due_date, user_id, create_by, create_time, remark) VALUES
+('完成项目文档编写', '编写项目需求文档和技术方案文档', '2', '0', DATE_ADD(NOW(), INTERVAL 3 DAY), 1, 'admin', NOW(), '重要任务'),
+('代码审查', '审查本周提交的代码,确保代码质量', '1', '1', DATE_ADD(NOW(), INTERVAL 1 DAY), 1, 'admin', NOW(), NULL),
+('周报提交', '提交本周工作周报', '1', '0', DATE_ADD(NOW(), INTERVAL 2 DAY), 1, 'admin', NOW(), NULL),
+('系统测试', '完成系统功能测试和性能测试', '3', '0', NOW(), 1, 'admin', NOW(), '紧急任务'),
+('会议准备', '准备下周项目评审会议材料', '0', '2', DATE_SUB(NOW(), INTERVAL 1 DAY), 1, 'admin', DATE_SUB(NOW(), INTERVAL 3 DAY), '已完成');

+ 125 - 0
yushu-backend/yushu-admin/src/main/java/com/yushu/web/controller/system/SysTodoController.java

@@ -0,0 +1,125 @@
+package com.yushu.web.controller.system;
+
+import java.util.List;
+import java.util.Map;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.yushu.common.annotation.Log;
+import com.yushu.common.core.controller.BaseController;
+import com.yushu.common.core.domain.AjaxResult;
+import com.yushu.common.core.page.TableDataInfo;
+import com.yushu.common.enums.BusinessType;
+import com.yushu.system.domain.SysTodo;
+import com.yushu.system.service.ISysTodoService;
+
+/**
+ * 待办事项 信息操作处理
+ * 
+ * @author YuShu
+ */
+@RestController
+@RequestMapping("/system/todo")
+public class SysTodoController extends BaseController
+{
+    @Autowired
+    private ISysTodoService todoService;
+
+    /**
+     * 获取待办列表
+     */
+    @PreAuthorize("@ss.hasPermi('system:todo:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(SysTodo todo)
+    {
+        // 只查询当前用户的待办
+        todo.setUserId(getUserId());
+        startPage();
+        List<SysTodo> list = todoService.selectTodoList(todo);
+        return getDataTable(list);
+    }
+
+    /**
+     * 获取我的待办(未完成)
+     */
+    @GetMapping("/my")
+    public TableDataInfo myTodo()
+    {
+        List<SysTodo> list = todoService.selectMyTodo(getUserId());
+        return getDataTable(list);
+    }
+
+    /**
+     * 获取待办统计
+     */
+    @GetMapping("/stats")
+    public AjaxResult stats()
+    {
+        Map<String, Object> stats = todoService.selectTodoStats(getUserId());
+        return success(stats);
+    }
+
+    /**
+     * 根据待办编号获取详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('system:todo:query')")
+    @GetMapping(value = "/{todoId}")
+    public AjaxResult getInfo(@PathVariable Long todoId)
+    {
+        return success(todoService.selectTodoById(todoId));
+    }
+
+    /**
+     * 新增待办
+     */
+    @PreAuthorize("@ss.hasPermi('system:todo:add')")
+    @Log(title = "待办事项", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@Validated @RequestBody SysTodo todo)
+    {
+        todo.setUserId(getUserId());
+        todo.setCreateBy(getUsername());
+        return toAjax(todoService.insertTodo(todo));
+    }
+
+    /**
+     * 修改待办
+     */
+    @PreAuthorize("@ss.hasPermi('system:todo:edit')")
+    @Log(title = "待办事项", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@Validated @RequestBody SysTodo todo)
+    {
+        todo.setUpdateBy(getUsername());
+        return toAjax(todoService.updateTodo(todo));
+    }
+
+    /**
+     * 修改待办状态
+     */
+    @Log(title = "待办事项", businessType = BusinessType.UPDATE)
+    @PutMapping("/changeStatus")
+    public AjaxResult changeStatus(@RequestBody SysTodo todo)
+    {
+        return toAjax(todoService.changeTodoStatus(todo.getTodoId(), todo.getStatus()));
+    }
+
+    /**
+     * 删除待办
+     */
+    @PreAuthorize("@ss.hasPermi('system:todo:remove')")
+    @Log(title = "待办事项", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{todoIds}")
+    public AjaxResult remove(@PathVariable Long[] todoIds)
+    {
+        return toAjax(todoService.deleteTodoByIds(todoIds));
+    }
+}

+ 162 - 0
yushu-backend/yushu-system/src/main/java/com/yushu/system/domain/SysTodo.java

@@ -0,0 +1,162 @@
+package com.yushu.system.domain;
+
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Size;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import com.yushu.common.core.domain.BaseEntity;
+import com.yushu.common.xss.Xss;
+
+/**
+ * 待办事项表 sys_todo
+ * 
+ * @author YuShu
+ */
+public class SysTodo extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 待办ID */
+    private Long todoId;
+
+    /** 待办标题 */
+    private String title;
+
+    /** 待办内容 */
+    private String content;
+
+    /** 优先级(0低 1中 2高 3紧急) */
+    private String priority;
+
+    /** 状态(0待处理 1进行中 2已完成 3已取消) */
+    private String status;
+
+    /** 截止日期 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date dueDate;
+
+    /** 完成时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date completeTime;
+
+    /** 所属用户ID */
+    private Long userId;
+
+    /** 删除标志 */
+    private String delFlag;
+
+    public Long getTodoId()
+    {
+        return todoId;
+    }
+
+    public void setTodoId(Long todoId)
+    {
+        this.todoId = todoId;
+    }
+
+    @Xss(message = "待办标题不能包含脚本字符")
+    @NotBlank(message = "待办标题不能为空")
+    @Size(min = 0, max = 200, message = "待办标题不能超过200个字符")
+    public String getTitle()
+    {
+        return title;
+    }
+
+    public void setTitle(String title)
+    {
+        this.title = title;
+    }
+
+    public String getContent()
+    {
+        return content;
+    }
+
+    public void setContent(String content)
+    {
+        this.content = content;
+    }
+
+    public String getPriority()
+    {
+        return priority;
+    }
+
+    public void setPriority(String priority)
+    {
+        this.priority = priority;
+    }
+
+    public String getStatus()
+    {
+        return status;
+    }
+
+    public void setStatus(String status)
+    {
+        this.status = status;
+    }
+
+    public Date getDueDate()
+    {
+        return dueDate;
+    }
+
+    public void setDueDate(Date dueDate)
+    {
+        this.dueDate = dueDate;
+    }
+
+    public Date getCompleteTime()
+    {
+        return completeTime;
+    }
+
+    public void setCompleteTime(Date completeTime)
+    {
+        this.completeTime = completeTime;
+    }
+
+    public Long getUserId()
+    {
+        return userId;
+    }
+
+    public void setUserId(Long userId)
+    {
+        this.userId = userId;
+    }
+
+    public String getDelFlag()
+    {
+        return delFlag;
+    }
+
+    public void setDelFlag(String delFlag)
+    {
+        this.delFlag = delFlag;
+    }
+
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
+            .append("todoId", getTodoId())
+            .append("title", getTitle())
+            .append("content", getContent())
+            .append("priority", getPriority())
+            .append("status", getStatus())
+            .append("dueDate", getDueDate())
+            .append("completeTime", getCompleteTime())
+            .append("userId", getUserId())
+            .append("delFlag", getDelFlag())
+            .append("createBy", getCreateBy())
+            .append("createTime", getCreateTime())
+            .append("updateBy", getUpdateBy())
+            .append("updateTime", getUpdateTime())
+            .append("remark", getRemark())
+            .toString();
+    }
+}

+ 86 - 0
yushu-backend/yushu-system/src/main/java/com/yushu/system/mapper/SysTodoMapper.java

@@ -0,0 +1,86 @@
+package com.yushu.system.mapper;
+
+import java.util.List;
+import java.util.Map;
+import org.apache.ibatis.annotations.Param;
+import com.yushu.system.domain.SysTodo;
+
+/**
+ * 待办事项 数据层
+ * 
+ * @author YuShu
+ */
+public interface SysTodoMapper
+{
+    /**
+     * 查询待办信息
+     * 
+     * @param todoId 待办ID
+     * @return 待办信息
+     */
+    public SysTodo selectTodoById(Long todoId);
+
+    /**
+     * 查询待办列表
+     * 
+     * @param todo 待办信息
+     * @return 待办集合
+     */
+    public List<SysTodo> selectTodoList(SysTodo todo);
+
+    /**
+     * 查询用户的待办列表
+     * 
+     * @param userId 用户ID
+     * @return 待办集合
+     */
+    public List<SysTodo> selectTodoByUserId(Long userId);
+
+    /**
+     * 查询用户未完成的待办
+     * 
+     * @param userId 用户ID
+     * @return 待办集合
+     */
+    public List<SysTodo> selectUnfinishedTodoByUserId(Long userId);
+
+    /**
+     * 新增待办
+     * 
+     * @param todo 待办信息
+     * @return 结果
+     */
+    public int insertTodo(SysTodo todo);
+
+    /**
+     * 修改待办
+     * 
+     * @param todo 待办信息
+     * @return 结果
+     */
+    public int updateTodo(SysTodo todo);
+
+    /**
+     * 删除待办
+     * 
+     * @param todoId 待办ID
+     * @return 结果
+     */
+    public int deleteTodoById(Long todoId);
+
+    /**
+     * 批量删除待办
+     * 
+     * @param todoIds 需要删除的待办ID
+     * @return 结果
+     */
+    public int deleteTodoByIds(Long[] todoIds);
+
+    /**
+     * 统计待办数量
+     * 
+     * @param userId 用户ID
+     * @return 统计结果
+     */
+    public Map<String, Object> selectTodoStats(@Param("userId") Long userId);
+}

+ 86 - 0
yushu-backend/yushu-system/src/main/java/com/yushu/system/service/ISysTodoService.java

@@ -0,0 +1,86 @@
+package com.yushu.system.service;
+
+import java.util.List;
+import java.util.Map;
+import com.yushu.system.domain.SysTodo;
+
+/**
+ * 待办事项 服务层
+ * 
+ * @author YuShu
+ */
+public interface ISysTodoService
+{
+    /**
+     * 查询待办信息
+     * 
+     * @param todoId 待办ID
+     * @return 待办信息
+     */
+    public SysTodo selectTodoById(Long todoId);
+
+    /**
+     * 查询待办列表
+     * 
+     * @param todo 待办信息
+     * @return 待办集合
+     */
+    public List<SysTodo> selectTodoList(SysTodo todo);
+
+    /**
+     * 查询用户未完成的待办
+     * 
+     * @param userId 用户ID
+     * @return 待办集合
+     */
+    public List<SysTodo> selectMyTodo(Long userId);
+
+    /**
+     * 新增待办
+     * 
+     * @param todo 待办信息
+     * @return 结果
+     */
+    public int insertTodo(SysTodo todo);
+
+    /**
+     * 修改待办
+     * 
+     * @param todo 待办信息
+     * @return 结果
+     */
+    public int updateTodo(SysTodo todo);
+
+    /**
+     * 修改待办状态
+     * 
+     * @param todoId 待办ID
+     * @param status 状态
+     * @return 结果
+     */
+    public int changeTodoStatus(Long todoId, String status);
+
+    /**
+     * 删除待办
+     * 
+     * @param todoId 待办ID
+     * @return 结果
+     */
+    public int deleteTodoById(Long todoId);
+
+    /**
+     * 批量删除待办
+     * 
+     * @param todoIds 需要删除的待办ID
+     * @return 结果
+     */
+    public int deleteTodoByIds(Long[] todoIds);
+
+    /**
+     * 获取待办统计
+     * 
+     * @param userId 用户ID
+     * @return 统计结果
+     */
+    public Map<String, Object> selectTodoStats(Long userId);
+}

+ 149 - 0
yushu-backend/yushu-system/src/main/java/com/yushu/system/service/impl/SysTodoServiceImpl.java

@@ -0,0 +1,149 @@
+package com.yushu.system.service.impl;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.yushu.system.domain.SysTodo;
+import com.yushu.system.mapper.SysTodoMapper;
+import com.yushu.system.service.ISysTodoService;
+
+/**
+ * 待办事项 服务层实现
+ * 
+ * @author YuShu
+ */
+@Service
+public class SysTodoServiceImpl implements ISysTodoService
+{
+    @Autowired
+    private SysTodoMapper todoMapper;
+
+    /**
+     * 查询待办信息
+     * 
+     * @param todoId 待办ID
+     * @return 待办信息
+     */
+    @Override
+    public SysTodo selectTodoById(Long todoId)
+    {
+        return todoMapper.selectTodoById(todoId);
+    }
+
+    /**
+     * 查询待办列表
+     * 
+     * @param todo 待办信息
+     * @return 待办集合
+     */
+    @Override
+    public List<SysTodo> selectTodoList(SysTodo todo)
+    {
+        return todoMapper.selectTodoList(todo);
+    }
+
+    /**
+     * 查询用户未完成的待办
+     * 
+     * @param userId 用户ID
+     * @return 待办集合
+     */
+    @Override
+    public List<SysTodo> selectMyTodo(Long userId)
+    {
+        return todoMapper.selectUnfinishedTodoByUserId(userId);
+    }
+
+    /**
+     * 新增待办
+     * 
+     * @param todo 待办信息
+     * @return 结果
+     */
+    @Override
+    public int insertTodo(SysTodo todo)
+    {
+        return todoMapper.insertTodo(todo);
+    }
+
+    /**
+     * 修改待办
+     * 
+     * @param todo 待办信息
+     * @return 结果
+     */
+    @Override
+    public int updateTodo(SysTodo todo)
+    {
+        return todoMapper.updateTodo(todo);
+    }
+
+    /**
+     * 修改待办状态
+     * 
+     * @param todoId 待办ID
+     * @param status 状态
+     * @return 结果
+     */
+    @Override
+    public int changeTodoStatus(Long todoId, String status)
+    {
+        SysTodo todo = new SysTodo();
+        todo.setTodoId(todoId);
+        todo.setStatus(status);
+        // 如果状态为已完成,设置完成时间
+        if ("2".equals(status))
+        {
+            todo.setCompleteTime(new Date());
+        }
+        return todoMapper.updateTodo(todo);
+    }
+
+    /**
+     * 删除待办
+     * 
+     * @param todoId 待办ID
+     * @return 结果
+     */
+    @Override
+    public int deleteTodoById(Long todoId)
+    {
+        return todoMapper.deleteTodoById(todoId);
+    }
+
+    /**
+     * 批量删除待办
+     * 
+     * @param todoIds 需要删除的待办ID
+     * @return 结果
+     */
+    @Override
+    public int deleteTodoByIds(Long[] todoIds)
+    {
+        return todoMapper.deleteTodoByIds(todoIds);
+    }
+
+    /**
+     * 获取待办统计
+     * 
+     * @param userId 用户ID
+     * @return 统计结果
+     */
+    @Override
+    public Map<String, Object> selectTodoStats(Long userId)
+    {
+        Map<String, Object> result = todoMapper.selectTodoStats(userId);
+        if (result == null)
+        {
+            result = new HashMap<>();
+            result.put("pending", 0);
+            result.put("processing", 0);
+            result.put("completed", 0);
+            result.put("overdue", 0);
+        }
+        return result;
+    }
+}

+ 134 - 0
yushu-backend/yushu-system/src/main/resources/mapper/system/SysTodoMapper.xml

@@ -0,0 +1,134 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.yushu.system.mapper.SysTodoMapper">
+    
+    <resultMap type="SysTodo" id="SysTodoResult">
+        <result property="todoId"        column="todo_id"        />
+        <result property="title"         column="title"          />
+        <result property="content"       column="content"        />
+        <result property="priority"      column="priority"       />
+        <result property="status"        column="status"         />
+        <result property="dueDate"       column="due_date"       />
+        <result property="completeTime"  column="complete_time"  />
+        <result property="userId"        column="user_id"        />
+        <result property="delFlag"       column="del_flag"       />
+        <result property="createBy"      column="create_by"      />
+        <result property="createTime"    column="create_time"    />
+        <result property="updateBy"      column="update_by"      />
+        <result property="updateTime"    column="update_time"    />
+        <result property="remark"        column="remark"         />
+    </resultMap>
+    
+    <sql id="selectTodoVo">
+        select todo_id, title, content, priority, status, due_date, complete_time, user_id, del_flag, create_by, create_time, update_by, update_time, remark 
+        from sys_todo
+    </sql>
+    
+    <select id="selectTodoById" parameterType="Long" resultMap="SysTodoResult">
+        <include refid="selectTodoVo"/>
+        where todo_id = #{todoId} and del_flag = '0'
+    </select>
+    
+    <select id="selectTodoList" parameterType="SysTodo" resultMap="SysTodoResult">
+        <include refid="selectTodoVo"/>
+        <where>
+            del_flag = '0'
+            <if test="title != null and title != ''">
+                AND title like concat('%', #{title}, '%')
+            </if>
+            <if test="priority != null and priority != ''">
+                AND priority = #{priority}
+            </if>
+            <if test="status != null and status != ''">
+                AND status = #{status}
+            </if>
+            <if test="userId != null">
+                AND user_id = #{userId}
+            </if>
+        </where>
+        order by 
+            CASE WHEN status IN ('0', '1') THEN 0 ELSE 1 END,
+            FIELD(priority, '3', '2', '1', '0'),
+            due_date ASC,
+            create_time DESC
+    </select>
+    
+    <select id="selectTodoByUserId" parameterType="Long" resultMap="SysTodoResult">
+        <include refid="selectTodoVo"/>
+        where user_id = #{userId} and del_flag = '0'
+        order by create_time desc
+    </select>
+    
+    <select id="selectUnfinishedTodoByUserId" parameterType="Long" resultMap="SysTodoResult">
+        <include refid="selectTodoVo"/>
+        where user_id = #{userId} and del_flag = '0' and status in ('0', '1')
+        order by 
+            FIELD(priority, '3', '2', '1', '0'),
+            due_date ASC,
+            create_time DESC
+    </select>
+    
+    <select id="selectTodoStats" resultType="java.util.Map">
+        SELECT 
+            SUM(CASE WHEN status = '0' THEN 1 ELSE 0 END) as pending,
+            SUM(CASE WHEN status = '1' THEN 1 ELSE 0 END) as processing,
+            SUM(CASE WHEN status = '2' THEN 1 ELSE 0 END) as completed,
+            SUM(CASE WHEN status IN ('0', '1') AND due_date IS NOT NULL AND due_date &lt; NOW() THEN 1 ELSE 0 END) as overdue
+        FROM sys_todo 
+        WHERE del_flag = '0' AND user_id = #{userId}
+    </select>
+    
+    <insert id="insertTodo" parameterType="SysTodo" useGeneratedKeys="true" keyProperty="todoId">
+        insert into sys_todo (
+            <if test="title != null and title != ''">title,</if>
+            <if test="content != null and content != ''">content,</if>
+            <if test="priority != null and priority != ''">priority,</if>
+            <if test="status != null and status != ''">status,</if>
+            <if test="dueDate != null">due_date,</if>
+            <if test="userId != null">user_id,</if>
+            <if test="remark != null and remark != ''">remark,</if>
+            <if test="createBy != null and createBy != ''">create_by,</if>
+            create_time
+        )values(
+            <if test="title != null and title != ''">#{title},</if>
+            <if test="content != null and content != ''">#{content},</if>
+            <if test="priority != null and priority != ''">#{priority},</if>
+            <if test="status != null and status != ''">#{status},</if>
+            <if test="dueDate != null">#{dueDate},</if>
+            <if test="userId != null">#{userId},</if>
+            <if test="remark != null and remark != ''">#{remark},</if>
+            <if test="createBy != null and createBy != ''">#{createBy},</if>
+            sysdate()
+        )
+    </insert>
+     
+    <update id="updateTodo" parameterType="SysTodo">
+        update sys_todo 
+        <set>
+            <if test="title != null and title != ''">title = #{title},</if>
+            <if test="content != null">content = #{content},</if>
+            <if test="priority != null and priority != ''">priority = #{priority},</if>
+            <if test="status != null and status != ''">status = #{status},</if>
+            <if test="dueDate != null">due_date = #{dueDate},</if>
+            <if test="completeTime != null">complete_time = #{completeTime},</if>
+            <if test="remark != null">remark = #{remark},</if>
+            <if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
+            update_time = sysdate()
+        </set>
+        where todo_id = #{todoId}
+    </update>
+    
+    <update id="deleteTodoById" parameterType="Long">
+        update sys_todo set del_flag = '2' where todo_id = #{todoId}
+    </update>
+    
+    <update id="deleteTodoByIds" parameterType="Long">
+        update sys_todo set del_flag = '2' where todo_id in 
+        <foreach item="todoId" collection="array" open="(" separator="," close=")">
+            #{todoId}
+        </foreach>
+    </update>
+    
+</mapper>

+ 72 - 0
yushu-uivue3/src/api/system/todo.js

@@ -0,0 +1,72 @@
+import request from '@/utils/request'
+
+// 查询待办列表
+export function listTodo(query) {
+  return request({
+    url: '/system/todo/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询待办详细
+export function getTodo(todoId) {
+  return request({
+    url: '/system/todo/' + todoId,
+    method: 'get'
+  })
+}
+
+// 新增待办
+export function addTodo(data) {
+  return request({
+    url: '/system/todo',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改待办
+export function updateTodo(data) {
+  return request({
+    url: '/system/todo',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除待办
+export function delTodo(todoId) {
+  return request({
+    url: '/system/todo/' + todoId,
+    method: 'delete'
+  })
+}
+
+// 修改待办状态
+export function changeTodoStatus(todoId, status) {
+  return request({
+    url: '/system/todo/changeStatus',
+    method: 'put',
+    data: {
+      todoId,
+      status
+    }
+  })
+}
+
+// 获取待办统计
+export function getTodoStats() {
+  return request({
+    url: '/system/todo/stats',
+    method: 'get'
+  })
+}
+
+// 获取我的待办(未完成)
+export function getMyTodo() {
+  return request({
+    url: '/system/todo/my',
+    method: 'get'
+  })
+}

+ 413 - 74
yushu-uivue3/src/layout/components/Navbar.vue

@@ -21,37 +21,47 @@
           <template #content>
             <div class="message-panel">
               <a-tabs v-model:activeKey="messageTab" centered>
-                <a-tab-pane key="notification" tab="通知">
+                <a-tab-pane key="notification">
+                  <template #tab>
+                    <a-badge :count="unreadNotificationCount" :offset="[6, -2]" size="small">通知</a-badge>
+                  </template>
                   <div class="message-list">
-                    <template v-if="notificationList.length > 0">
+                    <template v-if="displayNotificationList.length > 0">
                       <div 
-                        v-for="item in notificationList" 
+                        v-for="item in displayNotificationList" 
                         :key="item.id" 
                         class="message-item"
+                        :class="{ unread: !item.isRead }"
                         @click="handleNotificationClick(item)"
                       >
                         <div class="message-icon" :class="item.type">
                           <BellOutlined v-if="item.type === 'system'" />
-                          <MessageOutlined v-else-if="item.type === 'message'" />
                           <CheckCircleOutlined v-else-if="item.type === 'success'" />
+                          <WarningOutlined v-else-if="item.type === 'warning'" />
                           <InfoCircleOutlined v-else />
                         </div>
                         <div class="message-content">
                           <div class="message-title">{{ item.title }}</div>
+                          <div class="message-desc" v-if="item.content">{{ truncateText(item.content, 30) }}</div>
                           <div class="message-time">{{ item.time }}</div>
                         </div>
+                        <div v-if="!item.isRead" class="unread-dot"></div>
                       </div>
                     </template>
                     <a-empty v-else description="暂无通知" :image="Empty.PRESENTED_IMAGE_SIMPLE" />
                   </div>
                 </a-tab-pane>
-                <a-tab-pane key="message" tab="消息">
+                <a-tab-pane key="message">
+                  <template #tab>
+                    <a-badge :count="unreadMessageCount" :offset="[6, -2]" size="small">消息</a-badge>
+                  </template>
                   <div class="message-list">
-                    <template v-if="messageList.length > 0">
+                    <template v-if="displayMessageList.length > 0">
                       <div 
-                        v-for="item in messageList" 
+                        v-for="item in displayMessageList" 
                         :key="item.id" 
                         class="message-item"
+                        :class="{ unread: !item.isRead }"
                         @click="handleMessageClick(item)"
                       >
                         <a-avatar :src="item.avatar" :size="36">{{ item.sender?.charAt(0) }}</a-avatar>
@@ -60,12 +70,16 @@
                           <div class="message-desc">{{ item.content }}</div>
                           <div class="message-time">{{ item.time }}</div>
                         </div>
+                        <div v-if="!item.isRead" class="unread-dot"></div>
                       </div>
                     </template>
                     <a-empty v-else description="暂无消息" :image="Empty.PRESENTED_IMAGE_SIMPLE" />
                   </div>
                 </a-tab-pane>
-                <a-tab-pane key="todo" tab="待办">
+                <a-tab-pane key="todo">
+                  <template #tab>
+                    <a-badge :count="todoList.length" :offset="[6, -2]" size="small">待办</a-badge>
+                  </template>
                   <div class="message-list">
                     <template v-if="todoList.length > 0">
                       <div 
@@ -74,7 +88,7 @@
                         class="message-item"
                         @click="handleTodoClick(item)"
                       >
-                        <div class="message-icon todo">
+                        <div class="message-icon" :class="getPriorityClass(item.priority)">
                           <ClockCircleOutlined />
                         </div>
                         <div class="message-content">
@@ -89,13 +103,13 @@
                 </a-tab-pane>
               </a-tabs>
               <div class="message-footer">
-                <a-button type="link" @click="clearAll">清空</a-button>
-                <a-button type="link" @click="goMessageCenter">查看更多</a-button>
+                <a-button type="link" @click="markAllRead">全部已读</a-button>
+                <a-button type="link" @click="goMore">查看更多</a-button>
               </div>
             </div>
           </template>
           <div class="right-menu-item hover-effect message-center">
-            <a-badge :count="unreadCount" :number-style="{ display: unreadCount === 0 ? 'none' : 'block' }" :overflow-count="99">
+            <a-badge :count="totalUnreadCount" :overflow-count="99">
               <BellOutlined :style="{ fontSize: '18px' }" />
             </a-badge>
           </div>
@@ -123,14 +137,27 @@
         </template>
       </a-dropdown>
     </div>
+
+    <!-- 通知详情弹窗 -->
+    <a-modal
+      v-model:open="notificationDetailVisible"
+      :title="currentNotification.title"
+      :footer="null"
+      width="520px"
+      centered
+      destroy-on-close
+    >
+      <div class="notification-detail-content" v-html="currentNotification.content || '暂无内容'"></div>
+      <div class="notification-detail-time">{{ currentNotification.time }}</div>
+    </a-modal>
   </div>
 </template>
 
 <script setup>
-import { ref, onMounted, onUnmounted } from 'vue'
-import { useRouter } from 'vue-router'
-import { Modal, Empty } from 'ant-design-vue'
-import { BellOutlined, MessageOutlined, CheckCircleOutlined, InfoCircleOutlined, ClockCircleOutlined } from '@ant-design/icons-vue'
+import { ref, computed, onMounted, onUnmounted } from 'vue'
+import { useRouter, useRoute } from 'vue-router'
+import { Modal, Empty, notification } from 'ant-design-vue'
+import { BellOutlined, MessageOutlined, CheckCircleOutlined, InfoCircleOutlined, ClockCircleOutlined, WarningOutlined } from '@ant-design/icons-vue'
 import Breadcrumb from '@/components/Breadcrumb'
 import TopNav from '@/components/TopNav'
 import Hamburger from '@/components/Hamburger'
@@ -140,6 +167,10 @@ import useAppStore from '@/store/modules/app'
 import useUserStore from '@/store/modules/user'
 import useSettingsStore from '@/store/modules/settings'
 import wsService from '@/utils/websocket'
+import { getMyTodo } from '@/api/system/todo'
+import { getAllNotifications, markNotificationAsRead } from '@/api/system/notification'
+import { getConversationList, markConversationAsRead } from '@/api/system/message'
+import { formatTimeAgo } from '@/utils/formatTime'
 
 const router = useRouter()
 const appStore = useAppStore()
@@ -150,69 +181,247 @@ const settingsStore = useSettingsStore()
 const messagePopoverVisible = ref(false)
 const messageTab = ref('notification')
 
-// 未读消息数
-const unreadCount = ref(0)
-
-// 通知列表
-const notificationList = ref([
-  { id: 1, type: 'system', title: '系统升级通知', time: '10分钟前' },
-  { id: 2, type: 'success', title: '您的申请已通过审核', time: '1小时前' },
-  { id: 3, type: 'info', title: '新功能上线提醒', time: '2小时前' },
-])
-
-// 消息列表
-const messageList = ref([
-  { id: 1, sender: '张三', avatar: '', content: '请问项目进度如何?', time: '5分钟前' },
-  { id: 2, sender: '李四', avatar: '', content: '文档已经发送给您了', time: '30分钟前' },
-])
-
-// 待办列表
-const todoList = ref([
-  { id: 1, title: '审批流程', description: '有3个待审批事项', time: '待处理' },
-  { id: 2, title: '周报提交', description: '本周周报尚未提交', time: '截止今天' },
-])
-
-// 处理通知点击
-function handleNotificationClick(item) {
-  console.log('点击通知:', item)
+// 通知详情弹窗
+const notificationDetailVisible = ref(false)
+const currentNotification = ref({
+  title: '',
+  content: '',
+  time: ''
+})
+
+// 最大显示条数
+const MAX_DISPLAY = 5
+
+// 通知列表(原始数据,系统通知)
+const notificationList = ref([])
+
+// 消息列表(原始数据,私聊消息)
+const messageList = ref([])
+
+// 待办列表(只显示待处理的)
+const todoList = ref([])
+
+// 计算未读通知数
+const unreadNotificationCount = computed(() => {
+  return notificationList.value.filter(item => !item.isRead).length
+})
+
+// 计算未读消息数
+const unreadMessageCount = computed(() => {
+  return messageList.value.filter(item => !item.isRead).length
+})
+
+// 总未读数
+const totalUnreadCount = computed(() => {
+  return unreadNotificationCount.value + unreadMessageCount.value + todoList.value.length
+})
+
+// 计算显示的通知列表(未读优先,最多5条,但未读可以超过5条)
+const displayNotificationList = computed(() => {
+  const unread = notificationList.value.filter(item => !item.isRead)
+  const read = notificationList.value.filter(item => item.isRead)
+  
+  // 如果未读数量 >= 5,只显示未读
+  if (unread.length >= MAX_DISPLAY) {
+    return unread
+  }
+  
+  // 否则,未读 + 已读补足到5条
+  const remainingSlots = MAX_DISPLAY - unread.length
+  return [...unread, ...read.slice(0, remainingSlots)]
+})
+
+// 计算显示的消息列表(未读优先,最多5条,但未读可以超过5条)
+const displayMessageList = computed(() => {
+  const unread = messageList.value.filter(item => !item.isRead)
+  const read = messageList.value.filter(item => item.isRead)
+  
+  // 如果未读数量 >= 5,只显示未读
+  if (unread.length >= MAX_DISPLAY) {
+    return unread
+  }
+  
+  // 否则,未读 + 已读补足到5条
+  const remainingSlots = MAX_DISPLAY - unread.length
+  return [...unread, ...read.slice(0, remainingSlots)]
+})
+
+// 获取待办优先级样式
+function getPriorityClass(priority) {
+  const classes = { '0': 'low', '1': 'normal', '2': 'high', '3': 'urgent' }
+  return classes[priority] || 'normal'
+}
+
+// 截断文本
+function truncateText(text, maxLength = 30) {
+  if (!text) return ''
+  // 去除HTML标签
+  const plainText = text.replace(/<[^>]+>/g, '')
+  if (plainText.length <= maxLength) return plainText
+  return plainText.substring(0, maxLength) + '...'
+}
+
+// 显示通知详情弹窗
+function showNotificationDetail(item) {
+  // 先关闭 popover
   messagePopoverVisible.value = false
+  
+  // 设置当前通知内容
+  currentNotification.value = {
+    title: item.title,
+    content: item.content || '暂无内容',
+    time: item.time
+  }
+  
+  // 延迟显示弹窗,确保 popover 已关闭
+  setTimeout(() => {
+    notificationDetailVisible.value = true
+  }, 100)
+}
+
+// 获取待办列表(只获取待处理的)
+function fetchTodoList() {
+  getMyTodo().then(response => {
+    const list = response.rows || response.data || []
+    // 只显示待处理状态的待办
+    todoList.value = list
+      .filter(item => item.status === '0')
+      .map(item => ({
+        id: item.todoId,
+        title: item.title,
+        description: item.content || '',
+        time: item.dueDate ? `截止 ${item.dueDate.substring(0, 10)}` : '待处理',
+        status: item.status,
+        priority: item.priority
+      }))
+  }).catch(() => {})
+}
+
+// 获取通知列表(系统通知)
+function fetchNotificationList() {
+  getAllNotifications().then(response => {
+    const list = response.data || response.rows || []
+    notificationList.value = list.map(item => ({
+      id: item.notification_id || item.notificationId || item.id,
+      type: 'system',
+      title: item.title,
+      content: item.content,
+      time: formatTimeAgo(item.create_time || item.createTime),
+      isRead: item.is_read === '1' || item.isRead === '1'
+    }))
+  }).catch(() => {
+    notificationList.value = []
+  })
+}
+
+// 获取消息列表(私聊会话)
+function fetchMessageList() {
+  getConversationList().then(response => {
+    const list = response.data || response.rows || []
+    messageList.value = list
+      .filter(item => item.conversation_id !== 'sys_notification') // 排除系统通知会话
+      .map(item => ({
+        id: item.conversation_id || item.conversationId || item.id,
+        sender: item.other_members || item.otherUserName || item.remarkName || '未知用户',
+        avatar: item.avatar || '',
+        content: item.last_message || item.lastMessage || '',
+        time: formatTimeAgo(item.last_message_time || item.lastMessageTime),
+        isRead: (item.unread_count || item.unreadCount || 0) === 0
+      }))
+  }).catch(() => {
+    messageList.value = []
+  })
 }
 
-// 处理消息点击
+// 处理通知点击 - 标记为已读并显示详情弹窗
+function handleNotificationClick(item) {
+  // 标记为已读
+  if (!item.isRead) {
+    markNotificationAsRead(item.id).then(() => {
+      const index = notificationList.value.findIndex(n => n.id === item.id)
+      if (index !== -1) {
+        notificationList.value[index].isRead = true
+      }
+    }).catch(() => {})
+  }
+  
+  // 显示详情弹窗
+  showNotificationDetail(item)
+}
+
+// 处理消息点击 - 标记为已读并跳转到对应会话
 function handleMessageClick(item) {
-  console.log('点击消息:', item)
+  if (!item.isRead) {
+    // 调用API标记已读
+    markConversationAsRead(item.id).then(() => {
+      const index = messageList.value.findIndex(m => m.id === item.id)
+      if (index !== -1) {
+        messageList.value[index].isRead = true
+      }
+    }).catch(() => {})
+  }
   messagePopoverVisible.value = false
-  router.push('/message')
+  // 跳转到消息页面并传递会话ID,添加时间戳确保路由刷新
+  router.push({
+    path: '/message',
+    query: { conversationId: item.id, t: Date.now() }
+  })
 }
 
-// 处理待办点击
+// 处理待办点击 - 跳转到待办列表
 function handleTodoClick(item) {
-  console.log('点击待办:', item)
   messagePopoverVisible.value = false
+  router.push('/system/todo')
 }
 
-// 清空消息
-function clearAll() {
+// 全部标记为已读
+function markAllRead() {
   if (messageTab.value === 'notification') {
-    notificationList.value = []
+    // 批量标记通知为已读
+    const unreadIds = notificationList.value.filter(item => !item.isRead).map(item => item.id)
+    if (unreadIds.length > 0) {
+      // 逐个标记(如果有批量接口可以替换)
+      Promise.all(unreadIds.map(id => markNotificationAsRead(id))).then(() => {
+        notificationList.value.forEach(item => {
+          item.isRead = true
+        })
+      }).catch(() => {})
+    }
   } else if (messageTab.value === 'message') {
-    messageList.value = []
-  } else {
-    todoList.value = []
+    // 批量标记消息为已读
+    const unreadIds = messageList.value.filter(item => !item.isRead).map(item => item.id)
+    if (unreadIds.length > 0) {
+      Promise.all(unreadIds.map(id => markConversationAsRead(id))).then(() => {
+        messageList.value.forEach(item => {
+          item.isRead = true
+        })
+      }).catch(() => {})
+    }
   }
+  // 待办不需要标记已读
 }
 
-// 跳转消息中心
-function goMessageCenter() {
+// 查看更多
+function goMore() {
   messagePopoverVisible.value = false
-  router.push('/message')
+  if (messageTab.value === 'todo') {
+    router.push('/system/todo')
+  } else {
+    // 通知和消息都跳转到 message 页面
+    router.push('/message')
+  }
 }
 
 // WebSocket 事件处理
 onMounted(() => {
-  console.log('[Navbar] 初始化 WebSocket 连接...')
-  // 建立 WebSocket 连接
-  wsService.connect()
+  // 获取数据
+  fetchTodoList()
+  fetchNotificationList()
+  fetchMessageList()
+  
+  console.log('[Navbar] 初始化 WebSocket 监听器...')
+  
+  // 先注册所有监听器,再连接(确保不会错过消息)
   
   // 监听连接状态
   wsService.on('connected', () => {
@@ -223,29 +432,69 @@ onMounted(() => {
     console.log('[Navbar] WebSocket 已断开')
   })
   
-  // 监听未读数更新
-  wsService.on('unread_count', (count) => {
-    console.log('[Navbar] 未读数更新:', count)
-    unreadCount.value = count
-  })
-  
-  // 监听新消息
+  // 监听新消息(私聊)- 弹窗提示并可跳转
   wsService.on('new_message', (payload) => {
     console.log('[Navbar] 收到新消息:', payload)
-    unreadCount.value++
+    // 刷新消息列表
+    fetchMessageList()
+    
+    // 弹窗提示
+    const key = `msg_${Date.now()}`
+    notification.info({
+      message: payload.senderName || '新消息',
+      description: payload.content || '您收到一条新消息',
+      key,
+      duration: 5,
+      onClick: () => {
+        notification.close(key)
+        // 跳转到对应会话
+        if (payload.conversationId) {
+          router.push({
+            path: '/message',
+            query: { conversationId: payload.conversationId, t: Date.now() }
+          })
+        } else {
+          router.push('/message')
+        }
+      }
+    })
   })
   
-  // 监听新通知
+  // 监听新通知(系统通知)- 弹窗提示
   wsService.on('new_notification', (payload) => {
-    console.log('[Navbar] 收到新通知:', payload)
-    unreadCount.value++
+    console.log('[Navbar] 收到通知:', payload)
+    // 刷新通知列表
+    fetchNotificationList()
+    
+    // 弹窗提示
+    const key = `notif_${payload.notification_id || payload.id || Date.now()}`
+    notification.info({
+      message: payload.title || '系统通知',
+      description: truncateText(payload.content, 50) || '您收到一条新通知',
+      key,
+      duration: 5,
+      onClick: () => {
+        notification.close(key)
+        // 显示通知详情弹窗
+        showNotificationDetail({
+          id: payload.notification_id || payload.id,
+          title: payload.title,
+          content: payload.content,
+          time: '刚刚'
+        })
+      }
+    })
   })
   
-  // 监听系统通知
-  wsService.on('system_notification', (payload) => {
-    console.log('[Navbar] 收到系统通知:', payload)
-    unreadCount.value++
+  // 监听待办更新
+  wsService.on('todo_update', (payload) => {
+    console.log('[Navbar] 待办更新:', payload)
+    fetchTodoList()
   })
+  
+  // 最后建立 WebSocket 连接
+  console.log('[Navbar] 建立 WebSocket 连接...')
+  wsService.connect()
 })
 
 onUnmounted(() => {
@@ -497,11 +746,31 @@ html.dark .navbar {
       padding: 12px 16px;
       cursor: pointer;
       transition: background 0.2s;
+      position: relative;
       
       &:hover {
         background: #f5f5f5;
       }
       
+      &.unread {
+        background: #fafafa;
+        
+        .message-title {
+          font-weight: 600;
+          color: #1a1a1a;
+        }
+      }
+      
+      .unread-dot {
+        width: 8px;
+        height: 8px;
+        background: #1890ff;
+        border-radius: 50%;
+        flex-shrink: 0;
+        margin-left: 8px;
+        margin-top: 6px;
+      }
+      
       .message-icon {
         width: 36px;
         height: 36px;
@@ -528,12 +797,33 @@ html.dark .navbar {
           color: #fa8c16;
         }
         
+        &.warning {
+          background: #fffbe6;
+          color: #faad14;
+        }
+        
         &.message {
           background: #f9f0ff;
           color: #722ed1;
         }
         
-        &.todo {
+        // 待办优先级样式
+        &.low {
+          background: #f0f0f0;
+          color: #8c8c8c;
+        }
+        
+        &.normal {
+          background: #e6f7ff;
+          color: #1890ff;
+        }
+        
+        &.high {
+          background: #fff7e6;
+          color: #fa8c16;
+        }
+        
+        &.urgent {
           background: #fff1f0;
           color: #f5222d;
         }
@@ -593,4 +883,53 @@ html.dark .navbar {
     }
   }
 }
+
+// 通知详情弹窗样式
+.notification-detail-modal {
+  .ant-modal-confirm-content {
+    margin-top: 16px;
+    
+    div {
+      line-height: 1.8;
+      color: #333;
+      word-break: break-word;
+      
+      p {
+        margin-bottom: 8px;
+      }
+      
+      img {
+        max-width: 100%;
+        height: auto;
+      }
+    }
+  }
+}
+
+// 通知详情内容样式
+.notification-detail-content {
+  line-height: 1.8;
+  color: #333;
+  word-break: break-word;
+  max-height: 400px;
+  overflow-y: auto;
+  
+  p {
+    margin-bottom: 8px;
+  }
+  
+  img {
+    max-width: 100%;
+    height: auto;
+  }
+}
+
+.notification-detail-time {
+  margin-top: 16px;
+  padding-top: 12px;
+  border-top: 1px solid #f0f0f0;
+  font-size: 12px;
+  color: #999;
+  text-align: right;
+}
 </style>

+ 13 - 0
yushu-uivue3/src/router/index.js

@@ -85,6 +85,19 @@ export const constantRoutes = [
         meta: { title: '个人中心', icon: 'user' }
       }
     ]
+  },
+  {
+    path: '/todo',
+    component: Layout,
+    hidden: true,
+    children: [
+      {
+        path: '',
+        component: () => import('@/views/system/todo/index'),
+        name: 'Todo',
+        meta: { title: '待办事项', icon: 'todo' }
+      }
+    ]
   }
 ]
 

+ 139 - 0
yushu-uivue3/src/utils/formatTime.js

@@ -0,0 +1,139 @@
+/**
+ * 时间格式化工具
+ */
+
+/**
+ * 将时间转换为相对时间描述(如:刚刚、5分钟前、1小时前等)
+ * @param {string|Date|number} time - 时间字符串、Date对象或时间戳
+ * @returns {string} 相对时间描述
+ */
+export function formatTimeAgo(time) {
+  if (!time) return ''
+  
+  let date
+  if (typeof time === 'string') {
+    // 处理时间字符串
+    date = new Date(time.replace(/-/g, '/'))
+  } else if (typeof time === 'number') {
+    // 处理时间戳
+    date = new Date(time)
+  } else if (time instanceof Date) {
+    date = time
+  } else {
+    return ''
+  }
+  
+  // 检查日期是否有效
+  if (isNaN(date.getTime())) {
+    return ''
+  }
+  
+  const now = new Date()
+  const diff = now.getTime() - date.getTime()
+  
+  // 转换为秒
+  const seconds = Math.floor(diff / 1000)
+  
+  if (seconds < 60) {
+    return '刚刚'
+  }
+  
+  // 转换为分钟
+  const minutes = Math.floor(seconds / 60)
+  if (minutes < 60) {
+    return `${minutes}分钟前`
+  }
+  
+  // 转换为小时
+  const hours = Math.floor(minutes / 60)
+  if (hours < 24) {
+    return `${hours}小时前`
+  }
+  
+  // 转换为天
+  const days = Math.floor(hours / 24)
+  if (days < 7) {
+    return `${days}天前`
+  }
+  
+  // 转换为周
+  const weeks = Math.floor(days / 7)
+  if (weeks < 4) {
+    return `${weeks}周前`
+  }
+  
+  // 转换为月
+  const months = Math.floor(days / 30)
+  if (months < 12) {
+    return `${months}个月前`
+  }
+  
+  // 转换为年
+  const years = Math.floor(days / 365)
+  return `${years}年前`
+}
+
+/**
+ * 格式化日期时间
+ * @param {string|Date|number} time - 时间
+ * @param {string} format - 格式,默认 'YYYY-MM-DD HH:mm:ss'
+ * @returns {string} 格式化后的时间字符串
+ */
+export function formatDateTime(time, format = 'YYYY-MM-DD HH:mm:ss') {
+  if (!time) return ''
+  
+  let date
+  if (typeof time === 'string') {
+    date = new Date(time.replace(/-/g, '/'))
+  } else if (typeof time === 'number') {
+    date = new Date(time)
+  } else if (time instanceof Date) {
+    date = time
+  } else {
+    return ''
+  }
+  
+  if (isNaN(date.getTime())) {
+    return ''
+  }
+  
+  const year = date.getFullYear()
+  const month = String(date.getMonth() + 1).padStart(2, '0')
+  const day = String(date.getDate()).padStart(2, '0')
+  const hours = String(date.getHours()).padStart(2, '0')
+  const minutes = String(date.getMinutes()).padStart(2, '0')
+  const seconds = String(date.getSeconds()).padStart(2, '0')
+  
+  return format
+    .replace('YYYY', year)
+    .replace('MM', month)
+    .replace('DD', day)
+    .replace('HH', hours)
+    .replace('mm', minutes)
+    .replace('ss', seconds)
+}
+
+/**
+ * 格式化日期
+ * @param {string|Date|number} time - 时间
+ * @returns {string} 格式化后的日期字符串 (YYYY-MM-DD)
+ */
+export function formatDate(time) {
+  return formatDateTime(time, 'YYYY-MM-DD')
+}
+
+/**
+ * 格式化时间
+ * @param {string|Date|number} time - 时间
+ * @returns {string} 格式化后的时间字符串 (HH:mm:ss)
+ */
+export function formatTime(time) {
+  return formatDateTime(time, 'HH:mm:ss')
+}
+
+export default {
+  formatTimeAgo,
+  formatDateTime,
+  formatDate,
+  formatTime
+}

+ 3 - 27
yushu-uivue3/src/utils/websocket.js

@@ -3,7 +3,6 @@
  * 用于实时接收消息通知
  */
 import { getToken } from '@/utils/auth'
-import { notification } from 'ant-design-vue'
 
 class WebSocketService {
   constructor() {
@@ -121,19 +120,7 @@ class WebSocketService {
    * 处理新消息
    */
   handleNewMessage(payload) {
-    // 弹窗通知
-    notification.info({
-      message: payload.senderName || '新消息',
-      description: payload.content?.substring(0, 50) || '您收到一条新消息',
-      duration: 5,
-      placement: 'topRight',
-      onClick: () => {
-        // 点击跳转到消息中心
-        window.location.href = '/#/message'
-      }
-    })
-
-    // 触发事件供其他组件使用
+    // 只触发事件,弹窗由 Navbar.vue 统一处理
     this.emit('new_message', payload)
   }
 
@@ -141,19 +128,8 @@ class WebSocketService {
    * 处理新通知
    */
   handleNewNotification(payload) {
-    // 弹窗通知
-    const notifyMethod = payload.priority === '2' ? notification.warning : notification.info
-    notifyMethod({
-      message: payload.title || '系统通知',
-      description: payload.content?.substring(0, 100) || '您收到一条系统通知',
-      duration: payload.priority === '2' ? 0 : 5, // 紧急通知不自动关闭
-      placement: 'topRight',
-      onClick: () => {
-        window.location.href = '/#/message'
-      }
-    })
-
-    // 触发事件
+    console.log('[WebSocket] handleNewNotification 触发, listeners:', this.listeners.has('new_notification'), this.listeners.get('new_notification')?.size)
+    // 只触发事件,弹窗由 Navbar.vue 统一处理
     this.emit('new_notification', payload)
   }
 

+ 34 - 0
yushu-uivue3/src/views/message/index.vue

@@ -259,12 +259,16 @@ import {
   BellOutlined, DeleteOutlined, EditOutlined, CheckOutlined 
 } from '@ant-design/icons-vue'
 import { Modal, message } from 'ant-design-vue'
+import { useRoute, useRouter } from 'vue-router'
+import { watch } from 'vue'
 import { getConversationList, getConversationMessages, sendMessage as sendMessageAPI, setConversationRemark, createPrivateConversation, markConversationAsRead as markReadAPI, deleteConversation } from '@/api/system/message'
 import { listUser } from '@/api/system/user'
 import { getAllNotifications, markNotificationAsRead } from '@/api/system/notification'
 import useUserStore from '@/store/modules/user'
 
 const { proxy } = getCurrentInstance()
+const route = useRoute()
+const router = useRouter()
 const userStore = useUserStore()
 
 const loading = ref(false)
@@ -654,6 +658,9 @@ onMounted(async () => {
   document.addEventListener('click', () => { contextMenu.visible = false })
   await loadData()
   
+  // 检查路由参数,自动打开指定会话
+  openConversationFromRoute()
+  
   // 动态导入 WebSocket 服务
   const { wsService } = await import('@/utils/websocket')
   
@@ -679,6 +686,33 @@ onMounted(async () => {
   })
 })
 
+// 根据路由参数打开指定会话
+function openConversationFromRoute() {
+  const conversationId = route.query.conversationId
+  if (conversationId) {
+    const targetConv = conversationList.value.find(c => c.conversationId === conversationId)
+    if (targetConv) {
+      selectConversation(targetConv)
+    }
+  }
+}
+
+// 监听路由参数变化(使用 watch 代替 onBeforeRouteUpdate,更可靠)
+watch(
+  () => route.query.conversationId,
+  (newId, oldId) => {
+    if (newId && newId !== oldId) {
+      // 延迟执行,确保数据已加载
+      nextTick(() => {
+        const targetConv = conversationList.value.find(c => c.conversationId === newId)
+        if (targetConv) {
+          selectConversation(targetConv)
+        }
+      })
+    }
+  }
+)
+
 onUnmounted(() => {
   document.removeEventListener('click', () => { contextMenu.visible = false })
   // 取消 WebSocket 事件监听

+ 489 - 0
yushu-uivue3/src/views/system/todo/index.vue

@@ -0,0 +1,489 @@
+<template>
+  <div class="app-container">
+    <!-- 搜索表单 -->
+    <a-form :model="queryParams" ref="queryRef" layout="inline" v-show="showSearch" class="search-form">
+      <a-form-item label="待办标题" name="title">
+        <a-input v-model:value="queryParams.title" placeholder="请输入待办标题" allow-clear style="width: 200px" @pressEnter="handleQuery" />
+      </a-form-item>
+      <a-form-item label="状态" name="status">
+        <a-select v-model:value="queryParams.status" placeholder="待办状态" allow-clear style="width: 120px">
+          <a-select-option value="0">待处理</a-select-option>
+          <a-select-option value="1">进行中</a-select-option>
+          <a-select-option value="2">已完成</a-select-option>
+          <a-select-option value="3">已取消</a-select-option>
+        </a-select>
+      </a-form-item>
+      <a-form-item label="优先级" name="priority">
+        <a-select v-model:value="queryParams.priority" placeholder="优先级" allow-clear style="width: 120px">
+          <a-select-option value="0">低</a-select-option>
+          <a-select-option value="1">中</a-select-option>
+          <a-select-option value="2">高</a-select-option>
+          <a-select-option value="3">紧急</a-select-option>
+        </a-select>
+      </a-form-item>
+      <a-form-item>
+        <a-button type="primary" @click="handleQuery"><SearchOutlined />搜索</a-button>
+        <a-button style="margin-left: 8px" @click="resetQuery"><ReloadOutlined />重置</a-button>
+      </a-form-item>
+    </a-form>
+
+    <!-- 操作按钮 -->
+    <div class="button-toolbar">
+      <a-row :gutter="10">
+        <a-col :span="1.5">
+          <a-button type="primary" @click="handleAdd"><PlusOutlined />新增</a-button>
+        </a-col>
+        <a-col :span="1.5">
+          <a-button type="primary" :disabled="single" @click="handleUpdate" style="background-color: #52c41a; border-color: #52c41a"><EditOutlined />修改</a-button>
+        </a-col>
+        <a-col :span="1.5">
+          <a-button danger :disabled="multiple" @click="handleDelete"><DeleteOutlined />删除</a-button>
+        </a-col>
+        <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
+      </a-row>
+    </div>
+
+    <!-- 统计卡片 -->
+    <a-row :gutter="16" class="stats-row">
+      <a-col :span="6">
+        <a-card class="stats-card pending">
+          <a-statistic title="待处理" :value="stats.pending" :value-style="{ color: '#1890ff' }">
+            <template #prefix><ClockCircleOutlined /></template>
+          </a-statistic>
+        </a-card>
+      </a-col>
+      <a-col :span="6">
+        <a-card class="stats-card processing">
+          <a-statistic title="进行中" :value="stats.processing" :value-style="{ color: '#faad14' }">
+            <template #prefix><SyncOutlined /></template>
+          </a-statistic>
+        </a-card>
+      </a-col>
+      <a-col :span="6">
+        <a-card class="stats-card completed">
+          <a-statistic title="已完成" :value="stats.completed" :value-style="{ color: '#52c41a' }">
+            <template #prefix><CheckCircleOutlined /></template>
+          </a-statistic>
+        </a-card>
+      </a-col>
+      <a-col :span="6">
+        <a-card class="stats-card overdue">
+          <a-statistic title="已逾期" :value="stats.overdue" :value-style="{ color: '#ff4d4f' }">
+            <template #prefix><ExclamationCircleOutlined /></template>
+          </a-statistic>
+        </a-card>
+      </a-col>
+    </a-row>
+
+    <!-- 待办列表 -->
+    <a-table
+      :loading="loading"
+      :data-source="todoList"
+      :columns="columns"
+      :row-selection="{ selectedRowKeys: selectedRowKeys, onChange: handleSelectionChange }"
+      :row-key="record => record.todoId"
+      :pagination="false"
+    >
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'title'">
+          <div class="todo-title" :class="{ completed: record.status === '2' }">
+            <a-checkbox 
+              :checked="record.status === '2'" 
+              @change="(e) => handleStatusChange(record, e.target.checked ? '2' : '0')"
+            />
+            <span class="title-text" @click="handleView(record)">{{ record.title }}</span>
+          </div>
+        </template>
+        <template v-else-if="column.key === 'priority'">
+          <a-tag :color="getPriorityColor(record.priority)">{{ getPriorityText(record.priority) }}</a-tag>
+        </template>
+        <template v-else-if="column.key === 'status'">
+          <a-tag :color="getStatusColor(record.status)">{{ getStatusText(record.status) }}</a-tag>
+        </template>
+        <template v-else-if="column.key === 'dueDate'">
+          <span :class="{ 'overdue-text': isOverdue(record) }">
+            {{ record.dueDate || '-' }}
+            <ExclamationCircleOutlined v-if="isOverdue(record)" style="color: #ff4d4f; margin-left: 4px;" />
+          </span>
+        </template>
+        <template v-else-if="column.key === 'action'">
+          <a-space>
+            <a-tooltip title="编辑">
+              <a-button type="link" size="small" @click="handleUpdate(record)"><EditOutlined /></a-button>
+            </a-tooltip>
+            <a-tooltip title="删除">
+              <a-button type="link" size="small" danger @click="handleDelete(record)"><DeleteOutlined /></a-button>
+            </a-tooltip>
+          </a-space>
+        </template>
+      </template>
+    </a-table>
+
+    <!-- 分页 -->
+    <div class="pagination-container">
+      <a-pagination
+        v-model:current="queryParams.pageNum"
+        v-model:pageSize="queryParams.pageSize"
+        :total="total"
+        :show-total="total => `共 ${total} 条`"
+        show-size-changer
+        show-quick-jumper
+        @change="getList"
+        @showSizeChange="getList"
+      />
+    </div>
+
+    <!-- 新增/修改弹窗 -->
+    <a-modal
+      v-model:open="open"
+      :title="title"
+      width="600px"
+      @ok="submitForm"
+      @cancel="cancel"
+    >
+      <a-form ref="todoRef" :model="form" :rules="rules" :label-col="{ span: 4 }" :wrapper-col="{ span: 18 }">
+        <a-form-item label="待办标题" name="title">
+          <a-input v-model:value="form.title" placeholder="请输入待办标题" :maxlength="100" />
+        </a-form-item>
+        <a-form-item label="待办内容" name="content">
+          <a-textarea v-model:value="form.content" placeholder="请输入待办内容" :rows="4" :maxlength="500" show-count />
+        </a-form-item>
+        <a-row :gutter="16">
+          <a-col :span="12">
+            <a-form-item label="优先级" name="priority" :label-col="{ span: 8 }" :wrapper-col="{ span: 14 }">
+              <a-select v-model:value="form.priority" placeholder="请选择优先级">
+                <a-select-option value="0">低</a-select-option>
+                <a-select-option value="1">中</a-select-option>
+                <a-select-option value="2">高</a-select-option>
+                <a-select-option value="3">紧急</a-select-option>
+              </a-select>
+            </a-form-item>
+          </a-col>
+          <a-col :span="12">
+            <a-form-item label="状态" name="status" :label-col="{ span: 8 }" :wrapper-col="{ span: 14 }">
+              <a-select v-model:value="form.status" placeholder="请选择状态">
+                <a-select-option value="0">待处理</a-select-option>
+                <a-select-option value="1">进行中</a-select-option>
+                <a-select-option value="2">已完成</a-select-option>
+                <a-select-option value="3">已取消</a-select-option>
+              </a-select>
+            </a-form-item>
+          </a-col>
+        </a-row>
+        <a-form-item label="截止日期" name="dueDate">
+          <a-date-picker v-model:value="form.dueDate" value-format="YYYY-MM-DD HH:mm:ss" show-time placeholder="请选择截止日期" style="width: 100%" />
+        </a-form-item>
+        <a-form-item label="备注" name="remark">
+          <a-textarea v-model:value="form.remark" placeholder="请输入备注" :rows="2" :maxlength="200" />
+        </a-form-item>
+      </a-form>
+    </a-modal>
+
+    <!-- 详情弹窗 -->
+    <a-modal v-model:open="viewOpen" title="待办详情" width="600px" :footer="null">
+      <a-descriptions :column="2" bordered>
+        <a-descriptions-item label="待办标题" :span="2">{{ viewForm.title }}</a-descriptions-item>
+        <a-descriptions-item label="优先级">
+          <a-tag :color="getPriorityColor(viewForm.priority)">{{ getPriorityText(viewForm.priority) }}</a-tag>
+        </a-descriptions-item>
+        <a-descriptions-item label="状态">
+          <a-tag :color="getStatusColor(viewForm.status)">{{ getStatusText(viewForm.status) }}</a-tag>
+        </a-descriptions-item>
+        <a-descriptions-item label="截止日期">{{ viewForm.dueDate || '-' }}</a-descriptions-item>
+        <a-descriptions-item label="创建时间">{{ viewForm.createTime }}</a-descriptions-item>
+        <a-descriptions-item label="待办内容" :span="2">{{ viewForm.content || '-' }}</a-descriptions-item>
+        <a-descriptions-item label="备注" :span="2">{{ viewForm.remark || '-' }}</a-descriptions-item>
+      </a-descriptions>
+    </a-modal>
+  </div>
+</template>
+
+<script setup name="Todo">
+import { 
+  SearchOutlined, ReloadOutlined, PlusOutlined, EditOutlined, DeleteOutlined,
+  ClockCircleOutlined, SyncOutlined, CheckCircleOutlined, ExclamationCircleOutlined
+} from '@ant-design/icons-vue'
+import { listTodo, getTodo, addTodo, updateTodo, delTodo, changeTodoStatus, getTodoStats } from '@/api/system/todo'
+import { Modal, message } from 'ant-design-vue'
+
+const { proxy } = getCurrentInstance()
+
+const loading = ref(false)
+const showSearch = ref(true)
+const todoList = ref([])
+const total = ref(0)
+const open = ref(false)
+const viewOpen = ref(false)
+const title = ref('')
+const selectedRowKeys = ref([])
+const single = ref(true)
+const multiple = ref(true)
+
+// 统计数据
+const stats = ref({
+  pending: 0,
+  processing: 0,
+  completed: 0,
+  overdue: 0
+})
+
+// 查询参数
+const queryParams = ref({
+  pageNum: 1,
+  pageSize: 10,
+  title: undefined,
+  status: undefined,
+  priority: undefined
+})
+
+// 表单数据
+const form = ref({})
+const viewForm = ref({})
+const rules = {
+  title: [{ required: true, message: '待办标题不能为空', trigger: 'blur' }],
+  priority: [{ required: true, message: '请选择优先级', trigger: 'change' }],
+  status: [{ required: true, message: '请选择状态', trigger: 'change' }]
+}
+
+// 表格列
+const columns = [
+  { title: '待办标题', dataIndex: 'title', key: 'title', ellipsis: true },
+  { title: '优先级', dataIndex: 'priority', key: 'priority', width: 100, align: 'center' },
+  { title: '状态', dataIndex: 'status', key: 'status', width: 100, align: 'center' },
+  { title: '截止日期', dataIndex: 'dueDate', key: 'dueDate', width: 180 },
+  { title: '创建时间', dataIndex: 'createTime', key: 'createTime', width: 180 },
+  { title: '操作', key: 'action', width: 120, align: 'center' }
+]
+
+// 获取优先级颜色
+function getPriorityColor(priority) {
+  const colors = { '0': 'default', '1': 'blue', '2': 'orange', '3': 'red' }
+  return colors[priority] || 'default'
+}
+
+// 获取优先级文本
+function getPriorityText(priority) {
+  const texts = { '0': '低', '1': '中', '2': '高', '3': '紧急' }
+  return texts[priority] || '未知'
+}
+
+// 获取状态颜色
+function getStatusColor(status) {
+  const colors = { '0': 'default', '1': 'processing', '2': 'success', '3': 'warning' }
+  return colors[status] || 'default'
+}
+
+// 获取状态文本
+function getStatusText(status) {
+  const texts = { '0': '待处理', '1': '进行中', '2': '已完成', '3': '已取消' }
+  return texts[status] || '未知'
+}
+
+// 判断是否逾期
+function isOverdue(record) {
+  if (!record.dueDate || record.status === '2' || record.status === '3') return false
+  return new Date(record.dueDate) < new Date()
+}
+
+// 获取列表
+function getList() {
+  loading.value = true
+  listTodo(queryParams.value).then(response => {
+    todoList.value = response.rows
+    total.value = response.total
+    loading.value = false
+  }).catch(() => {
+    loading.value = false
+  })
+}
+
+// 获取统计
+function getStats() {
+  getTodoStats().then(response => {
+    stats.value = response.data || { pending: 0, processing: 0, completed: 0, overdue: 0 }
+  })
+}
+
+// 搜索
+function handleQuery() {
+  queryParams.value.pageNum = 1
+  getList()
+}
+
+// 重置
+function resetQuery() {
+  queryParams.value = {
+    pageNum: 1,
+    pageSize: 10,
+    title: undefined,
+    status: undefined,
+    priority: undefined
+  }
+  getList()
+}
+
+// 选择变化
+function handleSelectionChange(keys) {
+  selectedRowKeys.value = keys
+  single.value = keys.length !== 1
+  multiple.value = keys.length === 0
+}
+
+// 重置表单
+function reset() {
+  form.value = {
+    todoId: undefined,
+    title: undefined,
+    content: undefined,
+    priority: '1',
+    status: '0',
+    dueDate: undefined,
+    remark: undefined
+  }
+}
+
+// 新增
+function handleAdd() {
+  reset()
+  open.value = true
+  title.value = '新增待办'
+}
+
+// 修改
+function handleUpdate(row) {
+  reset()
+  const todoId = row.todoId || selectedRowKeys.value[0]
+  getTodo(todoId).then(response => {
+    form.value = response.data
+    open.value = true
+    title.value = '修改待办'
+  })
+}
+
+// 查看详情
+function handleView(row) {
+  getTodo(row.todoId).then(response => {
+    viewForm.value = response.data
+    viewOpen.value = true
+  })
+}
+
+// 提交表单
+function submitForm() {
+  proxy.$refs.todoRef.validate().then(() => {
+    if (form.value.todoId) {
+      updateTodo(form.value).then(() => {
+        message.success('修改成功')
+        open.value = false
+        getList()
+        getStats()
+      })
+    } else {
+      addTodo(form.value).then(() => {
+        message.success('新增成功')
+        open.value = false
+        getList()
+        getStats()
+      })
+    }
+  })
+}
+
+// 取消
+function cancel() {
+  open.value = false
+  reset()
+}
+
+// 删除
+function handleDelete(row) {
+  const todoIds = row.todoId || selectedRowKeys.value.join(',')
+  Modal.confirm({
+    title: '确认删除',
+    content: '是否确认删除选中的待办?',
+    onOk: () => {
+      delTodo(todoIds).then(() => {
+        message.success('删除成功')
+        getList()
+        getStats()
+      })
+    }
+  })
+}
+
+// 状态变更
+function handleStatusChange(record, status) {
+  changeTodoStatus(record.todoId, status).then(() => {
+    message.success('状态更新成功')
+    getList()
+    getStats()
+  })
+}
+
+onMounted(() => {
+  getList()
+  getStats()
+})
+</script>
+
+<style lang="scss" scoped>
+.search-form {
+  margin-bottom: 16px;
+}
+
+.button-toolbar {
+  margin-bottom: 16px;
+}
+
+.stats-row {
+  margin-bottom: 16px;
+  
+  .stats-card {
+    border-radius: 8px;
+    
+    &.pending {
+      border-left: 3px solid #1890ff;
+    }
+    
+    &.processing {
+      border-left: 3px solid #faad14;
+    }
+    
+    &.completed {
+      border-left: 3px solid #52c41a;
+    }
+    
+    &.overdue {
+      border-left: 3px solid #ff4d4f;
+    }
+  }
+}
+
+.todo-title {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  
+  &.completed .title-text {
+    text-decoration: line-through;
+    color: #999;
+  }
+  
+  .title-text {
+    cursor: pointer;
+    
+    &:hover {
+      color: #1890ff;
+    }
+  }
+}
+
+.overdue-text {
+  color: #ff4d4f;
+}
+
+.pagination-container {
+  margin-top: 16px;
+  text-align: right;
+}
+</style>