From d9e9fa9f53a1b36588e2be9c5ce330b61d03e562 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AD=99=E8=AF=9A?= Date: Sun, 11 Jan 2026 11:21:13 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E5=A4=9A=E4=B8=AA?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=E7=9A=84=E9=AB=98=E5=BA=A6=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?=EF=BC=8C=E7=A1=AE=E4=BF=9D=E6=9B=B4=E5=A5=BD=E7=9A=84=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E4=BD=93=E9=AA=8C=EF=BC=9B=E6=9B=B4=E6=96=B0=E4=BA=A4?= =?UTF-8?q?=E6=98=93=E8=AE=B0=E5=BD=95=E6=8E=A7=E5=88=B6=E5=99=A8=E4=BB=A5?= =?UTF-8?q?=E5=A4=84=E7=90=86=E6=99=BA=E8=83=BD=E5=88=86=E7=B1=BB=E7=BB=93?= =?UTF-8?q?=E6=9E=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Service/BudgetService.cs | 2 +- Service/SmartHandleService.cs | 16 ++++- Web/src/api/billImport.js | 4 +- Web/src/components/Budget/BudgetCard.vue | 7 +-- Web/src/components/Global/GlobalAddBill.vue | 2 +- Web/src/components/ReasonGroupList.vue | 2 +- Web/src/components/SmartClassifyButton.vue | 44 +++++++++----- Web/src/components/TransactionDetail.vue | 4 +- Web/src/styles/rich-content.css | 59 +++++++++---------- Web/src/views/CalendarView.vue | 2 +- Web/src/views/ClassificationNLP.vue | 2 +- Web/src/views/EmailRecord.vue | 4 +- Web/src/views/MessageView.vue | 3 +- Web/src/views/PeriodicRecord.vue | 2 +- Web/src/views/StatisticsView.vue | 2 +- .../TransactionRecordController.cs | 53 ++++++++++++++++- 16 files changed, 138 insertions(+), 70 deletions(-) diff --git a/Service/BudgetService.cs b/Service/BudgetService.cs index 8b14592..43c8a42 100644 --- a/Service/BudgetService.cs +++ b/Service/BudgetService.cs @@ -211,7 +211,7 @@ public class BudgetService( result.Limit = totalLimit; result.Current = totalCurrent; - result.Rate = totalLimit > 0 ? (totalCurrent / totalLimit) * 100 : 0; + result.Rate = totalLimit > 0 ? totalCurrent / totalLimit * 100 : 0; return result; } diff --git a/Service/SmartHandleService.cs b/Service/SmartHandleService.cs index 65a1387..f325d76 100644 --- a/Service/SmartHandleService.cs +++ b/Service/SmartHandleService.cs @@ -119,9 +119,14 @@ public class SmartHandleService( 输出格式要求(强制): - 请使用 NDJSON(每行一个独立的 JSON 对象,末尾以换行符分隔),不要输出数组。 - - 每行的JSON格式严格为:{"reason": "交易摘要", "type": 0, "classify": "分类名称"} + - 每行的JSON格式严格为: + { + "reason": "交易摘要", + "type": Number, // 交易类型,0=支出,1=收入,2=不计入收支 + "classify": "分类名称" + } - 不要输出任何解释性文字、编号、标点或多余的文本 - - 如果无法判断分类,请将 "classify" 设为 "其他",并确保仍然输出 JSON 行 + - 如果无法判断分类,请不要输出改行的JSON对象 只输出按行的JSON对象(NDJSON),不要有其他文字说明。 """; @@ -151,7 +156,12 @@ public class SmartHandleService( { if (sendedIds.Add(id)) { - var resultJson = JsonSerializer.Serialize(new { id, result.Classify, result.Type }); + var resultJson = JsonSerializer.Serialize(new + { + id, + result.Classify, + result.Type + }); chunkAction(("data", resultJson)); } } diff --git a/Web/src/api/billImport.js b/Web/src/api/billImport.js index aaa8226..e55e0b9 100644 --- a/Web/src/api/billImport.js +++ b/Web/src/api/billImport.js @@ -1,5 +1,6 @@ import axios from 'axios' import { showToast } from 'vant' +import { useAuthStore } from '@/stores/auth' /** * 账单导入相关 API @@ -21,7 +22,8 @@ export const uploadBillFile = (file, type) => { method: 'post', data: formData, headers: { - 'Content-Type': 'multipart/form-data' + 'Content-Type': 'multipart/form-data', + Authorization: `Bearer ${useAuthStore().token || ''}` }, timeout: 60000 // 文件上传增加超时时间 }).then(response => { diff --git a/Web/src/components/Budget/BudgetCard.vue b/Web/src/components/Budget/BudgetCard.vue index c36ec4c..bec281f 100644 --- a/Web/src/components/Budget/BudgetCard.vue +++ b/Web/src/components/Budget/BudgetCard.vue @@ -44,7 +44,7 @@
-
+
-

{{ budget.name }}

+

{{ budget.name }}

@@ -164,7 +164,7 @@ { overflow: hidden; text-overflow: ellipsis; flex-shrink: 0; - max-width: 120px; } .card-subtitle { diff --git a/Web/src/components/Global/GlobalAddBill.vue b/Web/src/components/Global/GlobalAddBill.vue index 222888d..9040a8c 100644 --- a/Web/src/components/Global/GlobalAddBill.vue +++ b/Web/src/components/Global/GlobalAddBill.vue @@ -9,7 +9,7 @@ diff --git a/Web/src/components/ReasonGroupList.vue b/Web/src/components/ReasonGroupList.vue index 88167d2..dfd2f32 100644 --- a/Web/src/components/ReasonGroupList.vue +++ b/Web/src/components/ReasonGroupList.vue @@ -56,7 +56,7 @@ v-model="showTransactionList" :title="selectedGroup?.reason || '交易记录'" :subtitle="groupTransactionsTotal ? `共 ${groupTransactionsTotal} 笔交易` : ''" - height="85%" + height="75%" > - @@ -39,6 +37,7 @@ const emit = defineEmits(['update', 'save', 'notifyDonedTransactionId']) const loading = ref(false) const saving = ref(false) const classifiedResults = ref([]) +const lockClassifiedResults = ref(false) const isAllCompleted = ref(false) let toastInstance = null @@ -47,7 +46,8 @@ const hasTransactions = computed(() => { }) const hasClassifiedResults = computed(() => { - return isAllCompleted.value && classifiedResults.value.length > 0 + // Show save state once we have any classified result, even if not all batches finished + return classifiedResults.value.length > 0 && lockClassifiedResults.value === false }) // 按钮类型 @@ -92,6 +92,8 @@ const handleClick = () => { * 保存分类结果 */ const handleSaveClassify = async () => { + if (saving.value || loading.value) return + try { saving.value = true showToast({ @@ -145,12 +147,23 @@ const handleSaveClassify = async () => { } } -/** - * 处理智能分类 - */ const handleSmartClassify = async () => { + if (loading.value || saving.value) { + showToast('当前有任务正在进行,请稍后再试') + return + } + + loading.value = true + if (!props.transactions || props.transactions.length === 0) { showToast('没有可分类的交易记录') + loading.value = false + return + } + + if(lockClassifiedResults.value) { + showToast('当前有分类任务正在进行,请稍后再试') + loading.value = false return } @@ -158,17 +171,12 @@ const handleSmartClassify = async () => { isAllCompleted.value = false classifiedResults.value = [] - const batchSize = 30 + const batchSize = 3 let processedCount = 0 try { - loading.value = true - // 清除之前的Toast - if (toastInstance) { - closeToast() - } - - // 等待父组件的 beforeClassify 事件处理完成(如果有返回 Promise) TODO 没有生效 + lockClassifiedResults.value = true + // 等待父组件的 beforeClassify 事件处理完成(如果有返回 Promise) if (props.onBeforeClassify) { const shouldContinue = await props.onBeforeClassify() if (shouldContinue === false) { @@ -323,6 +331,7 @@ const handleSmartClassify = async () => { }) } finally { loading.value = false + lockClassifiedResults.value = false // 确保Toast被清除 if (toastInstance) { setTimeout(() => { @@ -342,6 +351,11 @@ const removeClassifiedTransaction = (transactionId) => { * 重置组件状态 */ const reset = () => { + if(lockClassifiedResults.value) { + showToast('当前有分类任务正在进行,无法重置') + return + } + isAllCompleted.value = false classifiedResults.value = [] loading.value = false diff --git a/Web/src/components/TransactionDetail.vue b/Web/src/components/TransactionDetail.vue index b1674fb..0744cc6 100644 --- a/Web/src/components/TransactionDetail.vue +++ b/Web/src/components/TransactionDetail.vue @@ -2,7 +2,7 @@