feat: 收支记录批量操作按钮移至表格右上角并优化一键设置作用范围

将批量相关按钮从页头工具栏集中到列表右上角,并让一键设置优先作用于勾选记录、未勾选时作用于当前页全部记录,同时补充区分来源的作用范围提示文案。

变更内容:
- 将「批量操作/退出批量、一键设置、批量删除、建立关系」移至列表右上角(分页统计信息上方)
- 一键设置以打开弹窗时的目标快照为准:有勾选时作用于勾选记录,未勾选时作用于当前页全部记录
- 作用范围提示区分「已勾选」与「未勾选(当前表格展示)」两种文案
- 新增中英日三语文案 batchScopeHintSelected / batchScopeHintPage,移除旧 batchScopeHint

说明:
- 顺带移除 LoginRequest 未使用的 Size 导入与 AiSuggestionServiceImpl 未使用的 BigDecimal 导入
This commit is contained in:
2026-09-26 22:51:40 +08:00
parent 8c7928c309
commit 801da3e196
4 changed files with 39 additions and 21 deletions
+2 -1
View File
@@ -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: '支出カテゴリを設定しない' },
+37 -18
View File
@@ -218,9 +218,15 @@ const batchIncludeInStats = ref<'null' | 'true' | 'false'>('null') // 批量设
const batchAddTagIds = ref<number[]>([]) // 批量添加的标签
const batchRemoveTagIds = ref<number[]>([]) // 批量移除的标签
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<TransactionItem[]>([])
// 目标记录是否来自勾选,用于作用范围提示文案
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(() => {
<span style="color: #999; font-size: 13px;">{{ t('transaction.sortLabel') }}</span>
<NSelect v-model:value="sortValue" :options="sortOptions" style="width: 170px;" />
<NDivider vertical style="margin: 0 4px;" />
<NButton v-if="!selectionMode" @click="enterSelectionMode">{{ t('transaction.batchMode') }}</NButton>
<NButton v-else @click="exitSelectionMode">{{ t('transaction.exitBatchMode') }}</NButton>
<NButton @click="handleBatchOpen">{{ t('transaction.batchSet') }}</NButton>
<NButton v-if="checkedTransactionIds.length > 0" type="error" @click="handleBatchDelete">
{{ t('transaction.batchDelete') }}
</NButton>
<NButton v-if="checkedTransactionIds.length >= 2" type="primary" ghost @click="handleCreateRelation">
{{ t('transaction.relation.createFromSelection') }}
</NButton>
<NButton @click="showImportModal = true">{{ t('transaction.importBill') }}</NButton>
<NButton @click="goChannelManage">{{ t('channel.manageEntry') }}</NButton>
<NButton :loading="exporting" @click="handleExport">{{ t('transaction.exportExcel') }}</NButton>
@@ -2199,6 +2203,19 @@ onBeforeUnmount(() => {
</div>
</NCard>
<!-- 表格右上角:批量操作按钮 -->
<NSpace justify="end" align="center" :size="12" style="margin-bottom: 8px;" :wrap="true">
<NButton v-if="!selectionMode" @click="enterSelectionMode">{{ t('transaction.batchMode') }}</NButton>
<NButton v-else @click="exitSelectionMode">{{ t('transaction.exitBatchMode') }}</NButton>
<NButton @click="handleBatchOpen">{{ t('transaction.batchSet') }}</NButton>
<NButton v-if="checkedTransactionIds.length > 0" type="error" @click="handleBatchDelete">
{{ t('transaction.batchDelete') }}
</NButton>
<NButton v-if="checkedTransactionIds.length >= 2" type="primary" ghost @click="handleCreateRelation">
{{ t('transaction.relation.createFromSelection') }}
</NButton>
</NSpace>
<!-- 表格信息栏(右上角) -->
<NSpace justify="end" align="center" :size="12" style="margin-bottom: 8px;">
<span style="color: #999; font-size: 12px;">
@@ -2349,7 +2366,9 @@ onBeforeUnmount(() => {
<NForm>
<NFormItem :label="t('transaction.batchScope')">
<span style="color: #999; font-size: 12px;">
{{ t('transaction.batchScopeHint', { count: transactions.length }) }}
{{ batchFromSelection
? t('transaction.batchScopeHintSelected', { count: batchTargets.length })
: t('transaction.batchScopeHintPage', { count: batchTargets.length }) }}
</span>
</NFormItem>
<NFormItem v-if="aiEnabled" :label="t('ai.suggestButton')">