From 0d94276a0d887d6755884cce2661ae641c9fd7e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AD=99=E8=AF=9A?= Date: Mon, 29 Dec 2025 20:30:15 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Directory.Packages.props | 3 + Repository/BaseRepository.cs | 18 + Repository/TransactionRecordRepository.cs | 113 +- Service/Service.csproj | 2 + Service/TextSegmentService.cs | 152 + Web/.eslintcache | 1 + Web/src/components/SmartClassifyButton.vue | 287 + Web/src/registerServiceWorker.js | 2 +- Web/src/views/CalendarView.vue | 79 +- Web/src/views/ClassificationBatch.vue | 9 + Web/src/views/ClassificationSmart.vue | 262 +- WebApi/Controllers/LogController.cs | 2 +- .../TransactionRecordController.cs | 207 +- WebApi/Resources.bak/char_state_tab.json | 79460 ++++ WebApi/Resources.bak/dict.txt | 349046 +++++++++++++++ WebApi/Resources.bak/pos_prob_emit.json | 89711 ++++ WebApi/Resources.bak/pos_prob_start.json | 258 + WebApi/Resources.bak/pos_prob_trans.json | 5639 + WebApi/Resources.bak/prob_emit.json | 35234 ++ WebApi/Resources.bak/prob_trans.json | 18 + WebApi/Resources.bak/stopwords.txt | 330 + WebApi/WebApi.csproj | 11 + 22 files changed, 560706 insertions(+), 138 deletions(-) create mode 100644 Service/TextSegmentService.cs create mode 100644 Web/.eslintcache create mode 100644 Web/src/components/SmartClassifyButton.vue create mode 100644 WebApi/Resources.bak/char_state_tab.json create mode 100644 WebApi/Resources.bak/dict.txt create mode 100644 WebApi/Resources.bak/pos_prob_emit.json create mode 100644 WebApi/Resources.bak/pos_prob_start.json create mode 100644 WebApi/Resources.bak/pos_prob_trans.json create mode 100644 WebApi/Resources.bak/prob_emit.json create mode 100644 WebApi/Resources.bak/prob_trans.json create mode 100644 WebApi/Resources.bak/stopwords.txt diff --git a/Directory.Packages.props b/Directory.Packages.props index 899235d..5a1cee4 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -29,5 +29,8 @@ + + + \ No newline at end of file diff --git a/Repository/BaseRepository.cs b/Repository/BaseRepository.cs index ee80096..b41d2bc 100644 --- a/Repository/BaseRepository.cs +++ b/Repository/BaseRepository.cs @@ -16,6 +16,11 @@ public interface IBaseRepository where T : BaseEntity /// Task GetByIdAsync(long id); + /// + /// 根据ID获取单条数据 + /// + Task GetByIdsAsync(long[] ids); + /// /// 添加数据 /// @@ -76,6 +81,19 @@ public abstract class BaseRepository(IFreeSql freeSql) : IBaseRepository w } } + public virtual async Task GetByIdsAsync(long[] ids) + { + try + { + var result = await FreeSql.Select().Where(x => ids.Contains(x.Id)).ToListAsync(); + return result.ToArray(); + } + catch + { + return []; + } + } + public virtual async Task AddAsync(T entity) { try diff --git a/Repository/TransactionRecordRepository.cs b/Repository/TransactionRecordRepository.cs index 85defe7..ca5634a 100644 --- a/Repository/TransactionRecordRepository.cs +++ b/Repository/TransactionRecordRepository.cs @@ -149,6 +149,23 @@ public interface ITransactionRecordRepository : IBaseRepository完整的SELECT SQL语句 /// 动态查询结果列表 Task> ExecuteDynamicSqlAsync(string completeSql); + + /// + /// 根据关键词查询已分类的账单(用于智能分类参考) + /// + /// 关键词列表 + /// 返回结果数量限制 + /// 已分类的账单列表 + Task> GetClassifiedByKeywordsAsync(List keywords, int limit = 10); + + /// + /// 根据关键词查询已分类的账单,并计算相关度分数 + /// + /// 关键词列表 + /// 最小匹配率(0.0-1.0),默认0.3表示至少匹配30%的关键词 + /// 返回结果数量限制 + /// 带相关度分数的已分类账单列表 + Task> GetClassifiedByKeywordsWithScoreAsync(List keywords, double minMatchRate = 0.3, int limit = 10); } public class TransactionRecordRepository(IFreeSql freeSql) : BaseRepository(freeSql), ITransactionRecordRepository @@ -344,7 +361,7 @@ public class TransactionRecordRepository(IFreeSql freeSql) : BaseRepository list, int total)> GetReasonGroupsAsync(int pageIndex = 1, int pageSize = 20) { - // 先按照Reason分组,统计每个Reason的数量 + // 先按照Reason分组,统计每个Reason的数量和总金额 var groups = await FreeSql.Select() .Where(t => !string.IsNullOrEmpty(t.Reason)) .Where(t => string.IsNullOrEmpty(t.Classify)) // 只统计未分类的 @@ -352,11 +369,12 @@ public class TransactionRecordRepository(IFreeSql freeSql) : BaseRepository new { Reason = g.Key, - Count = g.Count() + Count = g.Count(), + TotalAmount = g.Sum(g.Value.Amount) }); - // 按数量降序排序 - var sortedGroups = groups.OrderByDescending(g => g.Count).ToList(); + // 按总金额绝对值降序排序 + var sortedGroups = groups.OrderByDescending(g => Math.Abs(g.TotalAmount)).ToList(); var total = sortedGroups.Count; // 分页 @@ -365,22 +383,27 @@ public class TransactionRecordRepository(IFreeSql freeSql) : BaseRepository(); foreach (var group in pagedGroups) { - var sample = await FreeSql.Select() + // 获取该分组的所有记录 + var records = await FreeSql.Select() .Where(t => t.Reason == group.Reason) - .FirstAsync(); + .Where(t => string.IsNullOrEmpty(t.Classify)) + .ToListAsync(); - if (sample != null) + if (records.Count > 0) { + var sample = records.First(); result.Add(new ReasonGroupDto { Reason = group.Reason, Count = (int)group.Count, SampleType = sample.Type, - SampleClassify = sample.Classify ?? string.Empty + SampleClassify = sample.Classify ?? string.Empty, + TransactionIds = records.Select(r => r.Id).ToList(), + TotalAmount = Math.Abs(group.TotalAmount) }); } } @@ -542,6 +565,68 @@ public class TransactionRecordRepository(IFreeSql freeSql) : BaseRepository> GetClassifiedByKeywordsAsync(List keywords, int limit = 10) + { + if (keywords == null || keywords.Count == 0) + { + return new List(); + } + + var query = FreeSql.Select() + .Where(t => t.Classify != ""); // 只查询已分类的账单 + + // 构建OR条件:Reason包含任意一个关键词 + if (keywords.Count > 0) + { + query = query.Where(t => keywords.Any(keyword => t.Reason.Contains(keyword))); + } + + return await query + .OrderByDescending(t => t.OccurredAt) + .Limit(limit) + .ToListAsync(); + } + + public async Task> GetClassifiedByKeywordsWithScoreAsync(List keywords, double minMatchRate = 0.3, int limit = 10) + { + if (keywords == null || keywords.Count == 0) + { + return new List<(TransactionRecord, double)>(); + } + + // 查询所有已分类且包含任意关键词的账单 + var candidates = await FreeSql.Select() + .Where(t => t.Classify != "") + .Where(t => keywords.Any(keyword => t.Reason.Contains(keyword))) + .ToListAsync(); + + // 计算每个候选账单的相关度分数 + var scoredResults = candidates + .Select(record => + { + var matchedCount = keywords.Count(keyword => record.Reason.Contains(keyword, StringComparison.OrdinalIgnoreCase)); + var matchRate = (double)matchedCount / keywords.Count; + + // 额外加分:完全匹配整个摘要(相似度更高) + var exactMatchBonus = keywords.Any(k => record.Reason.Equals(k, StringComparison.OrdinalIgnoreCase)) ? 0.2 : 0.0; + + // 长度相似度加分:长度越接近,相关度越高 + var avgKeywordLength = keywords.Average(k => k.Length); + var lengthSimilarity = 1.0 - Math.Min(1.0, Math.Abs(record.Reason.Length - avgKeywordLength) / Math.Max(record.Reason.Length, avgKeywordLength)); + var lengthBonus = lengthSimilarity * 0.1; + + var score = matchRate + exactMatchBonus + lengthBonus; + return (record, score); + }) + .Where(x => x.score >= minMatchRate) // 过滤低相关度结果 + .OrderByDescending(x => x.score) // 按相关度降序 + .ThenByDescending(x => x.record.OccurredAt) // 相同分数时,按时间降序 + .Take(limit) + .ToList(); + + return scoredResults; + } } /// @@ -568,6 +653,16 @@ public class ReasonGroupDto /// 示例分类(该分组中第一条记录的分类) /// public string SampleClassify { get; set; } = string.Empty; + + /// + /// 该分组的所有账单ID列表 + /// + public List TransactionIds { get; set; } = new(); + + /// + /// 该分组的总金额(绝对值) + /// + public decimal TotalAmount { get; set; } } /// diff --git a/Service/Service.csproj b/Service/Service.csproj index bbfa04e..a2bf00f 100644 --- a/Service/Service.csproj +++ b/Service/Service.csproj @@ -19,6 +19,8 @@ + + diff --git a/Service/TextSegmentService.cs b/Service/TextSegmentService.cs new file mode 100644 index 0000000..0453156 --- /dev/null +++ b/Service/TextSegmentService.cs @@ -0,0 +1,152 @@ +namespace Service; + +using JiebaNet.Segmenter; +using JiebaNet.Analyser; +using Microsoft.Extensions.Logging; + +/// +/// 文本分词服务接口 +/// +public interface ITextSegmentService +{ + /// + /// 从文本中提取关键词 + /// + /// 待分析的文本 + /// 返回前N个关键词,默认5个 + /// 关键词列表 + List ExtractKeywords(string text, int topN = 5); + + /// + /// 对文本进行分词 + /// + /// 待分词的文本 + /// 分词结果列表 + List Segment(string text); +} + +/// +/// 基于 JiebaNet 的文本分词服务实现 +/// +public class TextSegmentService : ITextSegmentService +{ + private readonly JiebaSegmenter _segmenter; + private readonly TfidfExtractor _extractor; + private readonly ILogger _logger; + + public TextSegmentService(ILogger logger) + { + _logger = logger; + _segmenter = new JiebaSegmenter(); + _extractor = new TfidfExtractor(); + + // 仅添加JiebaNet词典中可能缺失的特定业务词汇 + AddCustomWords(); + } + + /// + /// 添加自定义词典 - 仅添加JiebaNet词典中可能缺失的特定词汇 + /// + private void AddCustomWords() + { + try + { + // 只添加可能缺失的特定业务词汇 + // 大部分常用词(如"美团"、"支付宝"等)JiebaNet已内置 + var customWords = new[] + { + "水电费", "物业费", "燃气费" // 复合词,确保作为整体识别 // TODO 做成配置文件 让 AI定期提取复合词汇填入到这边 + }; + + foreach (var word in customWords) + { + _segmenter.AddWord(word); + } + + if (customWords.Length > 0) + { + _logger.LogDebug("已加载 {Count} 个自定义词汇", customWords.Length); + } + } + catch (Exception ex) + { + _logger.LogWarning(ex, "添加自定义词典失败"); + } + } + + public List ExtractKeywords(string text, int topN = 5) + { + if (string.IsNullOrWhiteSpace(text)) + { + return new List(); + } + + try + { + // 使用 TF-IDF 算法提取关键词(已内置停用词过滤) + var keywords = _extractor.ExtractTags(text, topN, new List()); + + // 过滤单字,保留有意义的词 + var filteredKeywords = keywords + .Where(k => k.Length >= 2) + .Distinct() + .ToList(); + + // 如果过滤后没有关键词,使用基础分词并选择最长的词 + if (filteredKeywords.Count == 0) + { + var segments = Segment(text); + filteredKeywords = segments + .Where(s => s.Length >= 2) + .OrderByDescending(s => s.Length) + .Take(topN) + .Distinct() + .ToList(); + } + + // 如果还是没有,返回原文的前10个字符 + if (filteredKeywords.Count == 0 && text.Length > 0) + { + filteredKeywords.Add(text.Length > 10 ? text.Substring(0, 10) : text); + } + + _logger.LogDebug("从文本 '{Text}' 中提取关键词: {Keywords}", + text, string.Join(", ", filteredKeywords)); + + return filteredKeywords; + } + catch (Exception ex) + { + _logger.LogError(ex, "提取关键词失败,文本: {Text}", text); + // 降级处理:返回原文 + return new List { text.Length > 10 ? text.Substring(0, 10) : text }; + } + } + + public List Segment(string text) + { + if (string.IsNullOrWhiteSpace(text)) + { + return new List(); + } + + try + { + // 执行分词 + var segments = _segmenter.Cut(text).ToList(); + + // 过滤空白和停用词 + var filteredSegments = segments + .Where(s => !string.IsNullOrWhiteSpace(s) && s.Trim().Length > 0) + .Select(s => s.Trim()) + .ToList(); + + return filteredSegments; + } + catch (Exception ex) + { + _logger.LogError(ex, "分词失败,文本: {Text}", text); + return new List { text }; + } + } +} diff --git a/Web/.eslintcache b/Web/.eslintcache new file mode 100644 index 0000000..dac762e --- /dev/null +++ b/Web/.eslintcache @@ -0,0 +1 @@ +[{"D:\\codes\\others\\EmailBill\\Web\\src\\views\\ClassificationSmart.vue":"1","D:\\codes\\others\\EmailBill\\Web\\eslint.config.js":"2","D:\\codes\\others\\EmailBill\\Web\\public\\service-worker.js":"3","D:\\codes\\others\\EmailBill\\Web\\src\\api\\billImport.js":"4","D:\\codes\\others\\EmailBill\\Web\\src\\api\\emailRecord.js":"5","D:\\codes\\others\\EmailBill\\Web\\src\\api\\log.js":"6","D:\\codes\\others\\EmailBill\\Web\\src\\api\\message.js":"7","D:\\codes\\others\\EmailBill\\Web\\src\\api\\request.js":"8","D:\\codes\\others\\EmailBill\\Web\\src\\api\\statistics.js":"9","D:\\codes\\others\\EmailBill\\Web\\src\\api\\transactionCategory.js":"10","D:\\codes\\others\\EmailBill\\Web\\src\\api\\transactionPeriodic.js":"11","D:\\codes\\others\\EmailBill\\Web\\src\\api\\transactionRecord.js":"12","D:\\codes\\others\\EmailBill\\Web\\src\\App.vue":"13","D:\\codes\\others\\EmailBill\\Web\\src\\components\\ClassifyPicker.vue":"14","D:\\codes\\others\\EmailBill\\Web\\src\\components\\TransactionDetail.vue":"15","D:\\codes\\others\\EmailBill\\Web\\src\\components\\TransactionList.vue":"16","D:\\codes\\others\\EmailBill\\Web\\src\\main.js":"17","D:\\codes\\others\\EmailBill\\Web\\src\\registerServiceWorker.js":"18","D:\\codes\\others\\EmailBill\\Web\\src\\router\\index.js":"19","D:\\codes\\others\\EmailBill\\Web\\src\\stores\\auth.js":"20","D:\\codes\\others\\EmailBill\\Web\\src\\stores\\counter.js":"21","D:\\codes\\others\\EmailBill\\Web\\src\\stores\\message.js":"22","D:\\codes\\others\\EmailBill\\Web\\src\\views\\BalanceView.vue":"23","D:\\codes\\others\\EmailBill\\Web\\src\\views\\BillAnalysisView.vue":"24","D:\\codes\\others\\EmailBill\\Web\\src\\views\\CalendarView.vue":"25","D:\\codes\\others\\EmailBill\\Web\\src\\views\\ClassificationBatch.vue":"26","D:\\codes\\others\\EmailBill\\Web\\src\\views\\ClassificationEdit.vue":"27","D:\\codes\\others\\EmailBill\\Web\\src\\views\\ClassificationNLP.vue":"28","D:\\codes\\others\\EmailBill\\Web\\src\\views\\EmailRecord.vue":"29","D:\\codes\\others\\EmailBill\\Web\\src\\views\\LoginView.vue":"30","D:\\codes\\others\\EmailBill\\Web\\src\\views\\LogView.vue":"31","D:\\codes\\others\\EmailBill\\Web\\src\\views\\MessageView.vue":"32","D:\\codes\\others\\EmailBill\\Web\\src\\views\\PeriodicRecord.vue":"33","D:\\codes\\others\\EmailBill\\Web\\src\\views\\SettingView.vue":"34","D:\\codes\\others\\EmailBill\\Web\\src\\views\\StatisticsView.vue":"35","D:\\codes\\others\\EmailBill\\Web\\src\\views\\TransactionsRecord.vue":"36","D:\\codes\\others\\EmailBill\\Web\\vite.config.js":"37"},{"size":12129,"mtime":1766998646236,"results":"38","hashOfConfig":"39"},{"size":596,"mtime":1766397301264,"results":"40","hashOfConfig":"41"},{"size":3763,"mtime":1766643343649,"results":"42","hashOfConfig":"41"},{"size":1786,"mtime":1766493393933,"results":"43","hashOfConfig":"41"},{"size":1828,"mtime":1766628922810,"results":"44","hashOfConfig":"41"},{"size":868,"mtime":1766997952394,"results":"45","hashOfConfig":"41"},{"size":1670,"mtime":1766988985063,"results":"46","hashOfConfig":"41"},{"size":2280,"mtime":1766737267237,"results":"47","hashOfConfig":"41"},{"size":2968,"mtime":1766740438323,"results":"48","hashOfConfig":"41"},{"size":1916,"mtime":1766716056978,"results":"49","hashOfConfig":"41"},{"size":2855,"mtime":1766991229153,"results":"50","hashOfConfig":"41"},{"size":5791,"mtime":1766725491308,"results":"51","hashOfConfig":"41"},{"size":4370,"mtime":1766988985057,"results":"52","hashOfConfig":"39"},{"size":4162,"mtime":1766991229152,"results":"53","hashOfConfig":"39"},{"size":8877,"mtime":1766995651564,"results":"54","hashOfConfig":"39"},{"size":8006,"mtime":1766732771392,"results":"55","hashOfConfig":"39"},{"size":562,"mtime":1766971736444,"results":"56","hashOfConfig":"41"},{"size":2903,"mtime":1766998608620,"results":"57","hashOfConfig":"41"},{"size":3069,"mtime":1766997952394,"results":"58","hashOfConfig":"41"},{"size":1345,"mtime":1766640160651,"results":"59","hashOfConfig":"41"},{"size":306,"mtime":1766397238501,"results":"60","hashOfConfig":"41"},{"size":529,"mtime":1766988985056,"results":"61","hashOfConfig":"41"},{"size":1498,"mtime":1766971736456,"results":"62","hashOfConfig":"39"},{"size":7872,"mtime":1766971736459,"results":"63","hashOfConfig":"39"},{"size":7374,"mtime":1766995651564,"results":"64","hashOfConfig":"39"},{"size":12825,"mtime":1766971736468,"results":"65","hashOfConfig":"39"},{"size":6993,"mtime":1766993465076,"results":"66","hashOfConfig":"39"},{"size":8753,"mtime":1766995651587,"results":"67","hashOfConfig":"39"},{"size":12489,"mtime":1766995651561,"results":"68","hashOfConfig":"39"},{"size":2145,"mtime":1766978070072,"results":"69","hashOfConfig":"39"},{"size":9634,"mtime":1766997952394,"results":"70","hashOfConfig":"39"},{"size":6774,"mtime":1766995651567,"results":"71","hashOfConfig":"39"},{"size":22374,"mtime":1766995651561,"results":"72","hashOfConfig":"39"},{"size":5264,"mtime":1766997952404,"results":"73","hashOfConfig":"39"},{"size":28355,"mtime":1766995651561,"results":"74","hashOfConfig":"39"},{"size":15032,"mtime":1766995651561,"results":"75","hashOfConfig":"39"},{"size":702,"mtime":1766737248019,"results":"76","hashOfConfig":"41"},{"filePath":"77","messages":"78","suppressedMessages":"79","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"1xefihw",{"filePath":"80","messages":"81","suppressedMessages":"82","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"19tzkw5",{"filePath":"83","messages":"84","suppressedMessages":"85","errorCount":1,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"source":null},{"filePath":"86","messages":"87","suppressedMessages":"88","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"89","messages":"90","suppressedMessages":"91","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"92","messages":"93","suppressedMessages":"94","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"95","messages":"96","suppressedMessages":"97","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"98","messages":"99","suppressedMessages":"100","errorCount":1,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"source":null},{"filePath":"101","messages":"102","suppressedMessages":"103","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"104","messages":"105","suppressedMessages":"106","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"107","messages":"108","suppressedMessages":"109","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"110","messages":"111","suppressedMessages":"112","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"113","messages":"114","suppressedMessages":"115","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"116","messages":"117","suppressedMessages":"118","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"119","messages":"120","suppressedMessages":"121","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"122","messages":"123","suppressedMessages":"124","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"125","messages":"126","suppressedMessages":"127","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"128","messages":"129","suppressedMessages":"130","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"131","messages":"132","suppressedMessages":"133","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"134","messages":"135","suppressedMessages":"136","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"137","messages":"138","suppressedMessages":"139","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"140","messages":"141","suppressedMessages":"142","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"143","messages":"144","suppressedMessages":"145","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"146","messages":"147","suppressedMessages":"148","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"149","messages":"150","suppressedMessages":"151","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"152","messages":"153","suppressedMessages":"154","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"155","messages":"156","suppressedMessages":"157","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"158","messages":"159","suppressedMessages":"160","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"161","messages":"162","suppressedMessages":"163","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"164","messages":"165","suppressedMessages":"166","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"167","messages":"168","suppressedMessages":"169","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"170","messages":"171","suppressedMessages":"172","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"173","messages":"174","suppressedMessages":"175","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"176","messages":"177","suppressedMessages":"178","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"179","messages":"180","suppressedMessages":"181","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"182","messages":"183","suppressedMessages":"184","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"185","messages":"186","suppressedMessages":"187","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"D:\\codes\\others\\EmailBill\\Web\\src\\views\\ClassificationSmart.vue",[],[],"D:\\codes\\others\\EmailBill\\Web\\eslint.config.js",[],[],"D:\\codes\\others\\EmailBill\\Web\\public\\service-worker.js",["188"],[],"D:\\codes\\others\\EmailBill\\Web\\src\\api\\billImport.js",[],[],"D:\\codes\\others\\EmailBill\\Web\\src\\api\\emailRecord.js",[],[],"D:\\codes\\others\\EmailBill\\Web\\src\\api\\log.js",[],[],"D:\\codes\\others\\EmailBill\\Web\\src\\api\\message.js",[],[],"D:\\codes\\others\\EmailBill\\Web\\src\\api\\request.js",["189"],[],"D:\\codes\\others\\EmailBill\\Web\\src\\api\\statistics.js",[],[],"D:\\codes\\others\\EmailBill\\Web\\src\\api\\transactionCategory.js",[],[],"D:\\codes\\others\\EmailBill\\Web\\src\\api\\transactionPeriodic.js",[],[],"D:\\codes\\others\\EmailBill\\Web\\src\\api\\transactionRecord.js",[],[],"D:\\codes\\others\\EmailBill\\Web\\src\\App.vue",[],[],"D:\\codes\\others\\EmailBill\\Web\\src\\components\\ClassifyPicker.vue",[],[],"D:\\codes\\others\\EmailBill\\Web\\src\\components\\TransactionDetail.vue",[],[],"D:\\codes\\others\\EmailBill\\Web\\src\\components\\TransactionList.vue",[],[],"D:\\codes\\others\\EmailBill\\Web\\src\\main.js",[],[],"D:\\codes\\others\\EmailBill\\Web\\src\\registerServiceWorker.js",[],[],"D:\\codes\\others\\EmailBill\\Web\\src\\router\\index.js",[],[],"D:\\codes\\others\\EmailBill\\Web\\src\\stores\\auth.js",[],[],"D:\\codes\\others\\EmailBill\\Web\\src\\stores\\counter.js",[],[],"D:\\codes\\others\\EmailBill\\Web\\src\\stores\\message.js",[],[],"D:\\codes\\others\\EmailBill\\Web\\src\\views\\BalanceView.vue",[],[],"D:\\codes\\others\\EmailBill\\Web\\src\\views\\BillAnalysisView.vue",[],[],"D:\\codes\\others\\EmailBill\\Web\\src\\views\\CalendarView.vue",[],[],"D:\\codes\\others\\EmailBill\\Web\\src\\views\\ClassificationBatch.vue",[],[],"D:\\codes\\others\\EmailBill\\Web\\src\\views\\ClassificationEdit.vue",[],[],"D:\\codes\\others\\EmailBill\\Web\\src\\views\\ClassificationNLP.vue",[],[],"D:\\codes\\others\\EmailBill\\Web\\src\\views\\EmailRecord.vue",[],[],"D:\\codes\\others\\EmailBill\\Web\\src\\views\\LoginView.vue",[],[],"D:\\codes\\others\\EmailBill\\Web\\src\\views\\LogView.vue",[],[],"D:\\codes\\others\\EmailBill\\Web\\src\\views\\MessageView.vue",[],[],"D:\\codes\\others\\EmailBill\\Web\\src\\views\\PeriodicRecord.vue",[],[],"D:\\codes\\others\\EmailBill\\Web\\src\\views\\SettingView.vue",[],[],"D:\\codes\\others\\EmailBill\\Web\\src\\views\\StatisticsView.vue",[],[],"D:\\codes\\others\\EmailBill\\Web\\src\\views\\TransactionsRecord.vue",[],[],"D:\\codes\\others\\EmailBill\\Web\\vite.config.js",[],[],{"ruleId":"190","severity":2,"message":"191","line":129,"column":5,"nodeType":"192","messageId":"193","endLine":129,"endColumn":12},{"ruleId":"194","severity":2,"message":"195","line":59,"column":11,"nodeType":"196","messageId":"197","endLine":59,"endColumn":43,"suggestions":"198"},"no-undef","'clients' is not defined.","Identifier","undef","no-case-declarations","Unexpected lexical declaration in case block.","VariableDeclaration","unexpected",["199"],{"messageId":"200","fix":"201","desc":"202"},"addBrackets",{"range":"203","text":"204"},"Add {} brackets around the case block.",[1274,1513],"{ message = '未授权,请重新登录'\r\n // 清除登录状态并跳转到登录页\r\n const authStore = useAuthStore()\r\n authStore.logout()\r\n router.push({ name: 'login', query: { redirect: router.currentRoute.value.fullPath } })\r\n break }"] \ No newline at end of file diff --git a/Web/src/components/SmartClassifyButton.vue b/Web/src/components/SmartClassifyButton.vue new file mode 100644 index 0000000..3158693 --- /dev/null +++ b/Web/src/components/SmartClassifyButton.vue @@ -0,0 +1,287 @@ + + + + + diff --git a/Web/src/registerServiceWorker.js b/Web/src/registerServiceWorker.js index 7c09967..aae2477 100644 --- a/Web/src/registerServiceWorker.js +++ b/Web/src/registerServiceWorker.js @@ -1,4 +1,4 @@ -/* eslint-disable no-console */ + export function register() { if ('serviceWorker' in navigator) { diff --git a/Web/src/views/CalendarView.vue b/Web/src/views/CalendarView.vue index e7ca570..bbee147 100644 --- a/Web/src/views/CalendarView.vue +++ b/Web/src/views/CalendarView.vue @@ -17,15 +17,26 @@ position="bottom" :style="{ height: '85%' }" round - closeable >