From 13bf23a48c8911853ac36c3decff3c68afe1926b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AD=99=E8=AF=9A?= Date: Mon, 29 Dec 2025 14:18:09 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E6=B6=88=E6=81=AF=E8=AE=B0?= =?UTF-8?q?=E5=BD=95=E5=8A=9F=E8=83=BD=EF=BC=8C=E5=8C=85=E6=8B=AC=E5=A2=9E?= =?UTF-8?q?=E5=88=A0=E6=94=B9=E6=9F=A5=E5=92=8C=E6=A0=87=E8=AE=B0=E5=B7=B2?= =?UTF-8?q?=E8=AF=BB=EF=BC=8C=E4=BC=98=E5=8C=96=E6=B6=88=E6=81=AF=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E5=B1=95=E7=A4=BA=E5=92=8C=E6=9C=AA=E8=AF=BB=E6=B6=88?= =?UTF-8?q?=E6=81=AF=E8=AE=A1=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Entity/MessageRecord.cs | 22 ++ Repository/MessageRecordRepository.cs | 31 ++ Service/EmailHandleService.cs | 11 +- Service/MessageRecordService.cs | 68 +++++ Web/src/App.vue | 33 +- Web/src/api/message.js | 81 +++++ Web/src/stores/message.js | 20 ++ Web/src/views/MessageView.vue | 284 +++++++++++++++++- Web/src/views/TransactionsRecord.vue | 3 - WebApi/Controllers/Dto/BaseResponse.cs | 17 ++ WebApi/Controllers/Dto/PagedResponse.cs | 12 + WebApi/Controllers/MessageRecordController.cs | 117 ++++++++ 12 files changed, 664 insertions(+), 35 deletions(-) create mode 100644 Entity/MessageRecord.cs create mode 100644 Repository/MessageRecordRepository.cs create mode 100644 Service/MessageRecordService.cs create mode 100644 Web/src/api/message.js create mode 100644 Web/src/stores/message.js create mode 100644 WebApi/Controllers/MessageRecordController.cs diff --git a/Entity/MessageRecord.cs b/Entity/MessageRecord.cs new file mode 100644 index 0000000..4e72739 --- /dev/null +++ b/Entity/MessageRecord.cs @@ -0,0 +1,22 @@ +namespace Entity; + +/// +/// 消息实体 +/// +public class MessageRecord : BaseEntity +{ + /// + /// 消息标题 + /// + public string Title { get; set; } = string.Empty; + + /// + /// 消息内容 + /// + public string Content { get; set; } = string.Empty; + + /// + /// 是否已读 + /// + public bool IsRead { get; set; } = false; +} diff --git a/Repository/MessageRecordRepository.cs b/Repository/MessageRecordRepository.cs new file mode 100644 index 0000000..a673d3e --- /dev/null +++ b/Repository/MessageRecordRepository.cs @@ -0,0 +1,31 @@ +namespace Repository; + +public interface IMessageRecordRepository : IBaseRepository +{ + Task<(IEnumerable List, long Total)> GetPagedListAsync(int pageIndex, int pageSize); + Task MarkAllAsReadAsync(); +} + +public class MessageRecordRepository(IFreeSql freeSql) : BaseRepository(freeSql), IMessageRecordRepository +{ + public async Task<(IEnumerable List, long Total)> GetPagedListAsync(int pageIndex, int pageSize) + { + var list = await FreeSql.Select() + .OrderByDescending(x => x.CreateTime) + .Count(out var total) + .Page(pageIndex, pageSize) + .ToListAsync(); + + return (list, total); + } + + public async Task MarkAllAsReadAsync() + { + var affected = await FreeSql.Update() + .Set(a => a.IsRead, true) + .Set(a => a.UpdateTime, DateTime.Now) + .Where(a => !a.IsRead) + .ExecuteAffrowsAsync(); + return affected >= 0; + } +} diff --git a/Service/EmailHandleService.cs b/Service/EmailHandleService.cs index 1129001..4351e50 100644 --- a/Service/EmailHandleService.cs +++ b/Service/EmailHandleService.cs @@ -20,7 +20,8 @@ public class EmailHandleService( ILogger logger, IEmailMessageRepository emailRepo, ITransactionRecordRepository trxRepo, - IEnumerable emailParsers + IEnumerable emailParsers, + IMessageRecordService messageRecordService ) : IEmailHandleService { public async Task HandleEmailAsync( @@ -60,10 +61,18 @@ public class EmailHandleService( ); if (parsed == null || parsed.Length == 0) { + await messageRecordService.AddAsync( + "邮件解析失败", + $"来自 {from} 发送给 {to} 的邮件(主题:{subject})未能成功解析内容,可能格式已变更或不受支持。" + ); logger.LogWarning("未能成功解析邮件内容,跳过账单处理"); return true; } + await messageRecordService.AddAsync( + "邮件解析成功", + $"来自 {from} 发送给 {to} 的邮件(主题:{subject})已成功解析出 {parsed.Length} 条交易记录。" + ); logger.LogInformation("成功解析邮件,共 {Count} 条交易记录", parsed.Length); bool allSuccess = true; diff --git a/Service/MessageRecordService.cs b/Service/MessageRecordService.cs new file mode 100644 index 0000000..b637276 --- /dev/null +++ b/Service/MessageRecordService.cs @@ -0,0 +1,68 @@ +namespace Service; + +public interface IMessageRecordService +{ + Task<(IEnumerable List, long Total)> GetPagedListAsync(int pageIndex, int pageSize); + Task GetByIdAsync(long id); + Task AddAsync(MessageRecord message); + Task AddAsync(string title, string content); + Task MarkAsReadAsync(long id); + Task MarkAllAsReadAsync(); + Task DeleteAsync(long id); + Task GetUnreadCountAsync(); +} + +public class MessageRecordService(IMessageRecordRepository messageRepo) : IMessageRecordService +{ + public async Task<(IEnumerable List, long Total)> GetPagedListAsync(int pageIndex, int pageSize) + { + return await messageRepo.GetPagedListAsync(pageIndex, pageSize); + } + + public async Task GetByIdAsync(long id) + { + return await messageRepo.GetByIdAsync(id); + } + + public async Task AddAsync(MessageRecord message) + { + return await messageRepo.AddAsync(message); + } + + public async Task AddAsync(string title, string content) + { + var message = new MessageRecord + { + Title = title, + Content = content, + IsRead = false + }; + return await messageRepo.AddAsync(message); + } + + public async Task MarkAsReadAsync(long id) + { + var message = await messageRepo.GetByIdAsync(id); + if (message == null) return false; + + message.IsRead = true; + message.UpdateTime = DateTime.Now; + return await messageRepo.UpdateAsync(message); + } + + public async Task MarkAllAsReadAsync() + { + return await messageRepo.MarkAllAsReadAsync(); + } + + public async Task DeleteAsync(long id) + { + return await messageRepo.DeleteAsync(id); + } + + public async Task GetUnreadCountAsync() + { + var list = await messageRepo.GetAllAsync(); + return list.Count(x => !x.IsRead); + } +} diff --git a/Web/src/App.vue b/Web/src/App.vue index b5c752a..c624a12 100644 --- a/Web/src/App.vue +++ b/Web/src/App.vue @@ -12,54 +12,30 @@ 账单 - + 消息 设置 - - -
-
VH: {{ debugInfo.vh }}
-
InnerHeight: {{ debugInfo.innerHeight }}
-
ClientHeight: {{ debugInfo.clientHeight }}
-
BodyHeight: {{ debugInfo.bodyHeight }}
-
SafeAreaBottom: {{ debugInfo.safeAreaBottom }}
-
\ No newline at end of file + if (!item.isRead) { + try { + await markAsRead(item.id); + item.isRead = true; + messageStore.updateUnreadCount(); + } catch (error) { + console.error('标记已读失败', error); + } + } +}; + +const handleDelete = (item) => { + showDialog({ + title: '提示', + message: '确定要删除这条消息吗?', + showCancelButton: true, + }).then(async (action) => { + if (action === 'confirm') { + try { + const res = await deleteMessage(item.id); + if (res.success) { + showToast('删除成功'); + const wasUnread = !item.isRead; + list.value = list.value.filter(i => i.id !== item.id); + if (wasUnread) { + messageStore.updateUnreadCount(); + } + } else { + showToast(res.message || '删除失败'); + } + } catch (error) { + showToast('删除失败'); + } + } + }); +}; + +const handleMarkAllRead = () => { + showDialog({ + title: '提示', + message: '确定要将所有消息标记为已读吗?', + showCancelButton: true, + }).then(async (action) => { + if (action === 'confirm') { + try { + const res = await markAllAsRead(); + if (res.success) { + showToast('操作成功'); + // 刷新列表 + onRefresh(); + // 更新未读计数 + messageStore.updateUnreadCount(); + } else { + showToast(res.message || '操作失败'); + } + } catch (error) { + showToast('操作失败'); + } + } + }); +}; + +onMounted(() => { + // onLoad 会由 van-list 自动触发 +}); + + + \ No newline at end of file diff --git a/Web/src/views/TransactionsRecord.vue b/Web/src/views/TransactionsRecord.vue index f47ba8a..4e0bf24 100644 --- a/Web/src/views/TransactionsRecord.vue +++ b/Web/src/views/TransactionsRecord.vue @@ -297,7 +297,6 @@ const loadData = async (isRefresh = false) => { // 下拉刷新 const onRefresh = () => { finished.value = false - lastId.value = null transactionList.value = [] loadData(false) } @@ -315,8 +314,6 @@ const onSearchChange = () => { const onSearch = () => { // 重置分页状态并刷新数据 - lastId.value = null - lastTime.value = null transactionList.value = [] finished.value = false loadData(true) diff --git a/WebApi/Controllers/Dto/BaseResponse.cs b/WebApi/Controllers/Dto/BaseResponse.cs index c1acf60..ab47912 100644 --- a/WebApi/Controllers/Dto/BaseResponse.cs +++ b/WebApi/Controllers/Dto/BaseResponse.cs @@ -20,6 +20,14 @@ public class BaseResponse Message = message }; } + + public static BaseResponse Done() + { + return new BaseResponse + { + Success = true + }; + } } public class BaseResponse : BaseResponse @@ -37,5 +45,14 @@ public class BaseResponse : BaseResponse Message = message }; } + + public static BaseResponse Done(T data) + { + return new BaseResponse + { + Success = true, + Data = data + }; + } } diff --git a/WebApi/Controllers/Dto/PagedResponse.cs b/WebApi/Controllers/Dto/PagedResponse.cs index 274e187..5441be8 100644 --- a/WebApi/Controllers/Dto/PagedResponse.cs +++ b/WebApi/Controllers/Dto/PagedResponse.cs @@ -22,4 +22,16 @@ public class PagedResponse : BaseResponse Message = message }; } + + public static PagedResponse Done(T[] data, int total, long lastId = 0, DateTime? lastTime = null) + { + return new PagedResponse + { + Success = true, + Data = data, + LastId = lastId, + LastTime = lastTime, + Total = total + }; + } } \ No newline at end of file diff --git a/WebApi/Controllers/MessageRecordController.cs b/WebApi/Controllers/MessageRecordController.cs new file mode 100644 index 0000000..75c16cc --- /dev/null +++ b/WebApi/Controllers/MessageRecordController.cs @@ -0,0 +1,117 @@ +using Microsoft.AspNetCore.Authorization; + +namespace WebApi.Controllers; + +[Authorize] +[ApiController] +[Route("api/[controller]/[action]")] +public class MessageRecordController(IMessageRecordService messageService, ILogger logger) : ControllerBase +{ + /// + /// 获取消息列表 + /// + [HttpGet] + public async Task> GetList([FromQuery] int pageIndex = 1, [FromQuery] int pageSize = 20) + { + try + { + var (list, total) = await messageService.GetPagedListAsync(pageIndex, pageSize); + return PagedResponse.Done(list.ToArray(), (int)total); + } + catch (Exception ex) + { + logger.LogError(ex, "获取消息列表失败"); + return PagedResponse.Fail($"获取消息列表失败: {ex.Message}"); + } + } + + /// + /// 获取未读消息数量 + /// + [HttpGet] + public async Task> GetUnreadCount() + { + try + { + var count = await messageService.GetUnreadCountAsync(); + return BaseResponse.Done(count); + } + catch (Exception ex) + { + logger.LogError(ex, "获取未读消息数量失败"); + return BaseResponse.Fail($"获取未读消息数量失败: {ex.Message}"); + } + } + + /// + /// 标记已读 + /// + [HttpPost] + public async Task> MarkAsRead([FromQuery] long id) + { + try + { + var result = await messageService.MarkAsReadAsync(id); + return BaseResponse.Done(result); + } + catch (Exception ex) + { + logger.LogError(ex, "标记消息已读失败,ID: {Id}", id); + return BaseResponse.Fail($"标记消息已读失败: {ex.Message}"); + } + } + + /// + /// 全部标记已读 + /// + [HttpPost] + public async Task> MarkAllAsRead() + { + try + { + var result = await messageService.MarkAllAsReadAsync(); + return BaseResponse.Done(result); + } + catch (Exception ex) + { + logger.LogError(ex, "全部标记已读失败"); + return BaseResponse.Fail($"全部标记已读失败: {ex.Message}"); + } + } + + /// + /// 删除消息 + /// + [HttpPost] + public async Task> Delete([FromQuery] long id) + { + try + { + var result = await messageService.DeleteAsync(id); + return BaseResponse.Done(result); + } + catch (Exception ex) + { + logger.LogError(ex, "删除消息失败,ID: {Id}", id); + return BaseResponse.Fail($"删除消息失败: {ex.Message}"); + } + } + + /// + /// 新增消息 (测试用) + /// + [HttpPost] + public async Task> Add([FromBody] MessageRecord message) + { + try + { + var result = await messageService.AddAsync(message); + return BaseResponse.Done(result); + } + catch (Exception ex) + { + logger.LogError(ex, "新增消息失败"); + return BaseResponse.Fail($"新增消息失败: {ex.Message}"); + } + } +}