diff --git a/frontend/src/locales/messages.ts b/frontend/src/locales/messages.ts index 5edda35..a0583d7 100644 --- a/frontend/src/locales/messages.ts +++ b/frontend/src/locales/messages.ts @@ -472,7 +472,8 @@ export default { addRecordTitle: { zh: '新增记录', en: 'Add Record', ja: '記録を追加' }, batchTitle: { zh: '一键设置分类与统计', en: 'Batch Set Category & Stats', ja: 'カテゴリと集計を一括設定' }, batchScope: { zh: '作用范围', en: 'Scope', ja: '対象範囲' }, - batchScopeHint: { zh: '将对当前表格展示的 {count} 条记录设置分类与计入统计', en: 'Apply category and stats settings to the {count} records shown in the table', ja: '現在表示中の {count} 件の記録にカテゴリと集計設定を適用します' }, + batchScopeHintSelected: { zh: '将对已勾选的 {count} 条记录设置分类与计入统计', en: 'Apply category and stats settings to the {count} selected record(s)', ja: '選択した {count} 件の記録にカテゴリと集計設定を適用します' }, + batchScopeHintPage: { zh: '未勾选记录,将对当前表格展示的 {count} 条记录设置分类与计入统计', en: 'No selection; apply to the {count} records shown in the table', ja: '未選択のため、現在表示中の {count} 件の記録に適用します' }, expenseCategory: { zh: '支出分类', en: 'Expense Category', ja: '支出カテゴリ' }, incomeCategory: { zh: '收入分类', en: 'Income Category', ja: '収入カテゴリ' }, notSetExpense: { zh: '不设置支出分类', en: 'No expense category', ja: '支出カテゴリを設定しない' }, diff --git a/frontend/src/views/TransactionList.vue b/frontend/src/views/TransactionList.vue index 1417c08..f127646 100644 --- a/frontend/src/views/TransactionList.vue +++ b/frontend/src/views/TransactionList.vue @@ -218,9 +218,15 @@ const batchIncludeInStats = ref<'null' | 'true' | 'false'>('null') // 批量设 const batchAddTagIds = ref([]) // 批量添加的标签 const batchRemoveTagIds = ref([]) // 批量移除的标签 const batching = ref(false) // 批量设置提交状态 -// 当前表格是否包含收入/支出记录,用于按需展示批量分类项 -const hasIncomeRecords = computed(() => transactions.value.some((tx) => tx.type === 'INCOME')) -const hasExpenseRecords = computed(() => transactions.value.some((tx) => tx.type === 'EXPENSE')) +// 一键设置目标记录快照:有勾选时取勾选记录,否则取当前表格展示的记录 +const batchTargets = ref([]) +// 目标记录是否来自勾选,用于作用范围提示文案 +const batchFromSelection = ref(false) +// 目标记录ID列表 +const batchTargetIds = computed(() => batchTargets.value.map((tx) => tx.id)) +// 一键设置目标中是否包含收入/支出记录,用于按需展示批量分类项 +const hasIncomeRecords = computed(() => batchTargets.value.some((tx) => tx.type === 'INCOME')) +const hasExpenseRecords = computed(() => batchTargets.value.some((tx) => tx.type === 'EXPENSE')) // 批量设置计入统计选项 const batchStatsOptions = computed(() => [ @@ -943,13 +949,13 @@ async function loadAiConfig() { /** 请求 AI 建议:对当前表格展示的记录生成分类与标签建议 */ async function handleAiSuggest() { - if (transactions.value.length === 0) { + if (batchTargetIds.value.length === 0) { message.warning(t('transaction.noRecordsToSet')) return } aiLoading.value = true try { - const ids = transactions.value.map((tx) => tx.id) + const ids = batchTargetIds.value const res: any = await suggestByAi(ids, aiScope.value) aiSuggestions.value = res.data || [] if (aiSuggestions.value.length === 0) { @@ -1896,12 +1902,19 @@ function renderTagsCell(row: AiSuggestion) { return items.length > 0 ? h(NSpace, { size: 4, wrap: true }, { default: () => items }) : '-' } -/** 打开批量设置分类弹窗 */ +/** 打开批量设置分类弹窗:有勾选时作用于勾选记录,否则作用于当前表格展示的记录 */ function handleBatchOpen() { - if (transactions.value.length === 0) { + // 勾选记录均属于当前页,按ID过滤当前页即可得到目标记录 + const hasSelection = checkedTransactionIds.value.length > 0 + const targets = hasSelection + ? transactions.value.filter((tx) => checkedTransactionIds.value.includes(tx.id)) + : [...transactions.value] + if (targets.length === 0) { message.warning(t('transaction.noRecordsToSet')) return } + batchTargets.value = targets + batchFromSelection.value = hasSelection batchExpenseCategoryId.value = null batchIncomeCategoryId.value = null batchIncludeInStats.value = 'null' @@ -1920,7 +1933,7 @@ async function handleBatchSubmit() { } batching.value = true try { - const ids = transactions.value.map((tx) => tx.id) + const ids = batchTargetIds.value if (hasCategoryOrStats) { await batchUpdateCategory({ ids, @@ -2024,15 +2037,6 @@ onBeforeUnmount(() => { {{ t('transaction.sortLabel') }} - {{ t('transaction.batchMode') }} - {{ t('transaction.exitBatchMode') }} - {{ t('transaction.batchSet') }} - - {{ t('transaction.batchDelete') }} - - - {{ t('transaction.relation.createFromSelection') }} - {{ t('transaction.importBill') }} {{ t('channel.manageEntry') }} {{ t('transaction.exportExcel') }} @@ -2199,6 +2203,19 @@ onBeforeUnmount(() => { + + + {{ t('transaction.batchMode') }} + {{ t('transaction.exitBatchMode') }} + {{ t('transaction.batchSet') }} + + {{ t('transaction.batchDelete') }} + + + {{ t('transaction.relation.createFromSelection') }} + + + @@ -2349,7 +2366,9 @@ onBeforeUnmount(() => { - {{ t('transaction.batchScopeHint', { count: transactions.length }) }} + {{ batchFromSelection + ? t('transaction.batchScopeHintSelected', { count: batchTargets.length }) + : t('transaction.batchScopeHintPage', { count: batchTargets.length }) }} diff --git a/src/main/java/cn/rosercode/ledgerly/dto/LoginRequest.java b/src/main/java/cn/rosercode/ledgerly/dto/LoginRequest.java index 1776042..570a1d7 100644 --- a/src/main/java/cn/rosercode/ledgerly/dto/LoginRequest.java +++ b/src/main/java/cn/rosercode/ledgerly/dto/LoginRequest.java @@ -1,7 +1,6 @@ package cn.rosercode.ledgerly.dto; import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.Size; import lombok.Data; /** diff --git a/src/main/java/cn/rosercode/ledgerly/service/impl/AiSuggestionServiceImpl.java b/src/main/java/cn/rosercode/ledgerly/service/impl/AiSuggestionServiceImpl.java index 42c670c..266e5af 100644 --- a/src/main/java/cn/rosercode/ledgerly/service/impl/AiSuggestionServiceImpl.java +++ b/src/main/java/cn/rosercode/ledgerly/service/impl/AiSuggestionServiceImpl.java @@ -1,6 +1,5 @@ package cn.rosercode.ledgerly.service.impl; -import java.math.BigDecimal; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.LinkedHashSet;