+
+
+
+ {{ t('ai.category.button') }}
+
+
-
+
+
+ {{ t('ai.decorate.emojiButton') }}
+
-
+
diff --git a/frontend/src/views/TagManage.vue b/frontend/src/views/TagManage.vue
index 16d35f5..14e3088 100644
--- a/frontend/src/views/TagManage.vue
+++ b/frontend/src/views/TagManage.vue
@@ -5,6 +5,7 @@ import { useI18n } from 'vue-i18n'
import { useMessage, useDialog, NButton, NSpace, NDataTable, NModal, NForm, NFormItem, NInput, NInputNumber, NColorPicker, NTag } from 'naive-ui'
import type { DataTableColumns } from 'naive-ui'
import { getTagList, createTag, updateTag, deleteTag } from '../api/tag'
+import { getAiConfig, suggestColor } from '../api/ai'
const message = useMessage() // 消息提示
const dialog = useDialog() // 对话框
@@ -25,6 +26,14 @@ const loading = ref(false) // 列表加载状态
const showModal = ref(false) // 新增/编辑弹窗显示状态
const editingId = ref(null) // 当前编辑的标签 ID(null 表示新增)
const form = ref<{ name: string; color: string; sortOrder: number }>({ name: '', color: '#18a058', sortOrder: 0 }) // 表单数据
+const aiEnabled = ref(false) // AI 是否启用,用于控制推荐入口显示
+const aiLoading = ref(false) // AI 推荐请求状态
+
+// 预设色板:覆盖常用区分色,便于快速选择
+const colorSwatches = [
+ '#18a058', '#2080f0', '#f0a020', '#d03050', '#7c3aed', '#13c2c2',
+ '#eb2f96', '#fa8c16', '#2f54eb', '#52c41a', '#8c8c8c', '#000000',
+]
/** 创建表格列配置 */
function createColumns(): DataTableColumns {
@@ -35,7 +44,22 @@ function createColumns(): DataTableColumns {
return h(NTag, { color: { color: row.color || '#2080f0', textColor: '#fff' } }, { default: () => row.name })
},
},
- { title: t('tag.color'), key: 'color', width: 140 },
+ {
+ title: t('tag.color'), key: 'color', width: 160,
+ render(row) {
+ // 色块 + 色值,直观展示标签颜色
+ const color = row.color || '#2080f0'
+ return h('div', { style: { display: 'flex', alignItems: 'center', gap: '6px' } }, [
+ h('span', {
+ style: {
+ display: 'inline-block', width: '16px', height: '16px', borderRadius: '4px',
+ background: color, border: '1px solid rgba(0, 0, 0, 0.1)',
+ },
+ }),
+ h('span', { style: { color: '#666' } }, color),
+ ])
+ },
+ },
{ title: t('common.sort'), key: 'sortOrder', width: 80 },
{
title: t('common.operation'), key: 'actions', width: 180,
@@ -120,8 +144,39 @@ function handleDelete(id: number) {
})
}
-// 组件挂载时加载标签数据
-onMounted(fetchData)
+/** 加载 AI 启用状态,用于控制推荐入口显示 */
+async function loadAiConfig() {
+ try {
+ const res: any = await getAiConfig()
+ aiEnabled.value = !!res.data.enabled
+ } catch (e) {
+ // 静默处理,推荐入口保持隐藏
+ aiEnabled.value = false
+ }
+}
+
+/** AI 推荐标签颜色 */
+async function handleAiColor() {
+ if (!form.value.name) {
+ message.warning(t('ai.decorate.nameRequired'))
+ return
+ }
+ aiLoading.value = true
+ try {
+ const res: any = await suggestColor(form.value.name)
+ form.value.color = res.data
+ } catch (e: any) {
+ message.error(e.message || t('ai.decorate.failed'))
+ } finally {
+ aiLoading.value = false
+ }
+}
+
+// 组件挂载时加载标签数据与 AI 配置
+onMounted(() => {
+ fetchData()
+ loadAiConfig()
+})
@@ -139,10 +194,13 @@ onMounted(fetchData)
-
+
+
+ {{ t('ai.decorate.colorButton') }}
+
-
+
diff --git a/frontend/src/views/TransactionList.vue b/frontend/src/views/TransactionList.vue
index ad3a36f..073472f 100644
--- a/frontend/src/views/TransactionList.vue
+++ b/frontend/src/views/TransactionList.vue
@@ -6,10 +6,10 @@ import { useMessage, useDialog, NButton, NSpace, NDataTable, NTag, NModal, NForm
import { EyeOutline, EyeOffOutline, CloseCircleOutline, SparklesOutline, BulbOutline } from '@vicons/ionicons5'
import type { DataTableColumns, AutoCompleteOption } from 'naive-ui'
import { getTransactionPage, createTransaction, updateTransaction, deleteTransaction, exportTransaction, toggleTransactionStats, toggleRemarkHidden, batchUpdateCategory, batchTags } from '../api/transaction'
-import { getAiConfig, suggestByAi, applyAiSuggestions, queryByAi, parseRecordByAi } from '../api/ai'
+import { getAiConfig, suggestByAi, suggestOneByAi, applyAiSuggestions, queryByAi, parseRecordByAi, parseCategoryByAi, parseTagByAi } from '../api/ai'
import type { AiSuggestion, AiQueryResult, AiRecordResult } from '../api/ai'
-import { getCategoryList } from '../api/category'
-import { getTagList } from '../api/tag'
+import { getCategoryList, createCategory } from '../api/category'
+import { getTagList, createTag } from '../api/tag'
import BillImportModal from './BillImportModal.vue'
const message = useMessage() // 消息提示
@@ -83,8 +83,47 @@ const showBatchModal = ref(false) // 批量设置分类弹窗显示状态
const showAiModal = ref(false) // AI 建议弹窗显示状态
const aiEnabled = ref(false) // AI 建议是否启用
const aiLoading = ref(false) // AI 建议生成状态
+const aiFormLoading = ref(false) // 编辑弹窗内 AI 推荐状态
+/** AI 建议范围偏好存储键 */
+const AI_SCOPE_KEY = 'finance.transaction.aiScope'
+
+/** 读取上次的 AI 建议范围偏好,非法值回退 BOTH */
+function loadAiScope(): string {
+ try {
+ const saved = localStorage.getItem(AI_SCOPE_KEY)
+ return saved && ['BOTH', 'CATEGORY', 'TAG'].includes(saved) ? saved : 'BOTH'
+ } catch (e) {
+ return 'BOTH'
+ }
+}
+
+const aiScope = ref(loadAiScope()) // 批量 AI 建议范围:BOTH/CATEGORY/TAG(记忆用户偏好)
+// 批量 AI 建议范围选项
+const aiScopeOptions = computed(() => [
+ { label: t('ai.scope.both'), value: 'BOTH' },
+ { label: t('ai.scope.category'), value: 'CATEGORY' },
+ { label: t('ai.scope.tag'), value: 'TAG' },
+])
+
+// 记住用户选择的 AI 建议范围
+watch(aiScope, (value) => {
+ try {
+ localStorage.setItem(AI_SCOPE_KEY, value)
+ } catch (e) {
+ // 忽略存储异常
+ }
+})
+const aiNewLoading = ref(false) // 编辑弹窗内 AI 生成新分类状态
+const aiNewSaving = ref(false) // 新分类保存状态
+// AI 生成但尚未保存的新分类,需用户确认后再落库
+const pendingCategory = ref<{ name: string; type: string; icon: string } | null>(null)
+const aiTagLoading = ref(false) // 编辑弹窗内标签 AI 请求状态
+const aiTagSaving = ref(false) // 新标签保存状态
+// AI 生成但尚未保存的新标签,需用户确认后再落库
+const pendingTag = ref<{ name: string; color: string } | null>(null)
const aiApplying = ref(false) // AI 建议应用状态
const aiSuggestions = ref([]) // AI 建议列表
+const checkedAiRowKeys = ref([]) // AI 建议弹窗中已勾选的代表记录ID
const aiQueryText = ref('') // AI 自然语言查询输入
const aiQuerying = ref(false) // AI 查询解析状态
const showAiQueryModal = ref(false) // AI 查询结果弹窗显示状态
@@ -106,6 +145,9 @@ const batchIncludeInStats = ref<'null' | 'true' | 'false'>('null') // 批量设
const batchAddTagIds = ref([]) // 批量添加的标签
const batchRemoveTagIds = ref([]) // 批量移除的标签
const batching = ref(false) // 批量设置提交状态
+// 当前表格是否包含收入/支出记录,用于按需展示批量分类项
+const hasIncomeRecords = computed(() => transactions.value.some((t) => t.type === 'INCOME'))
+const hasExpenseRecords = computed(() => transactions.value.some((t) => t.type === 'EXPENSE'))
// 批量设置计入统计选项
const batchStatsOptions = computed(() => [
@@ -190,6 +232,21 @@ const categoryFilterOptions = computed(() => [
// 标签筛选项
const tagFilterOptions = computed(() => allTags.value.map((t) => ({ label: t.name, value: t.id })))
+// 标签筛选下拉项:额外提供「无标签」选项(仅筛选使用,编辑/批量选择不包含)
+const tagFilterSelectOptions = computed(() => [
+ { label: t('transaction.untagged'), value: 0 },
+ ...tagFilterOptions.value,
+])
+
+/** 将标签筛选拆分为标签ID与无标签标记,供查询与导出复用 */
+function buildTagFilter() {
+ const ids = filterTag.value.filter((v) => v !== 0)
+ return {
+ tagIds: ids.length > 0 ? ids : undefined,
+ untagged: filterTag.value.includes(0) ? true : undefined,
+ }
+}
+
/** 将分类筛选拆分为分类ID与未分类标记,供查询与导出复用 */
function buildCategoryFilter() {
const ids = filterCategory.value.filter((v) => v !== 0)
@@ -427,6 +484,13 @@ async function setRemarkHidden(row: TransactionItem, hidden: boolean) {
/** 创建表格列配置 */
function createColumns(): DataTableColumns {
return [
+ {
+ // 序号列:按分页计算全局序号
+ title: t('transaction.column.index'), key: 'index', width: 70, align: 'center',
+ render(_row, index) {
+ return (page.value - 1) * pageSize.value + index + 1
+ },
+ },
{
title: t('transaction.column.transactionTime'), key: 'transactionTime', width: 170,
render(row) {
@@ -562,7 +626,7 @@ async function fetchData() {
remark: filterRemark.value.trim() || undefined,
sortField: sortField.value,
sortOrder: sortOrder.value,
- tagIds: filterTag.value.length > 0 ? filterTag.value : undefined,
+ ...buildTagFilter(),
})
transactions.value = res.data.records
total.value = res.data.total
@@ -598,6 +662,8 @@ function handleViewRaw(row: TransactionItem) {
function handleAdd() {
editingId.value = null
form.value = { categoryId: null, type: 'EXPENSE', amount: null, paymentMethod: '', remark: '', transactionTime: Date.now(), includeInStats: true, tagIds: [] }
+ pendingCategory.value = null
+ pendingTag.value = null
showModal.value = true
}
@@ -614,12 +680,15 @@ function handleEdit(row: TransactionItem) {
includeInStats: row.includeInStats ?? true,
tagIds: (row.tags || []).map((t) => t.id),
}
+ pendingCategory.value = null
+ pendingTag.value = null
showModal.value = true
}
/** 提交表单:校验 -> 新增或更新 -> 刷新列表 */
async function handleSubmit() {
- if (!form.value.categoryId) {
+ // 编辑已有记录时允许不设置分类,便于只修改标签等信息
+ if (!form.value.categoryId && !editingId.value) {
message.warning(t('transaction.selectCategory'))
return
}
@@ -736,12 +805,15 @@ async function handleAiSuggest() {
aiLoading.value = true
try {
const ids = transactions.value.map((t) => t.id)
- const res: any = await suggestByAi(ids)
+ const res: any = await suggestByAi(ids, aiScope.value)
aiSuggestions.value = res.data || []
if (aiSuggestions.value.length === 0) {
message.warning(t('ai.noSuggestion'))
return
}
+ // 默认全选,用户可取消不需要的建议
+ checkedAiRowKeys.value = aiSuggestions.value.map((s) => s.transactionId)
+ message.success(t('ai.aggregated', { groups: aiSuggestions.value.length, count: aiTotalCount.value }))
showBatchModal.value = false
showAiModal.value = true
} catch (e: any) {
@@ -751,18 +823,197 @@ async function handleAiSuggest() {
}
}
+/** 编辑弹窗内 AI 推荐分类:基于表单当前内容(类型、金额、支付方式、备注) */
+async function handleAiSuggestOne() {
+ aiFormLoading.value = true
+ try {
+ const res: any = await suggestOneByAi({
+ type: form.value.type,
+ amount: form.value.amount,
+ paymentMethod: form.value.paymentMethod || undefined,
+ remark: form.value.remark || undefined,
+ })
+ const suggestion = res.data
+ if (!suggestion || suggestion.categoryId == null) {
+ message.warning(t('ai.noSuggestion'))
+ return
+ }
+ form.value.categoryId = suggestion.categoryId
+ message.success(t('ai.oneApplied'))
+ } catch (e: any) {
+ message.error(e.message || t('ai.suggestFailed'))
+ } finally {
+ aiFormLoading.value = false
+ }
+}
+
+/** 编辑弹窗内根据账单信息生成新分类(暂不保存,等待用户确认) */
+async function handleAiNewCategory() {
+ if (!form.value.remark && !form.value.amount) {
+ message.warning(t('ai.newCategory.needInfo'))
+ return
+ }
+ aiNewLoading.value = true
+ try {
+ const res: any = await parseCategoryByAi(buildCategoryPrompt())
+ const data = res.data
+ if (!data || !data.name) {
+ message.warning(t('ai.category.failed'))
+ return
+ }
+ // 类型跟随当前记录,避免与所选收支类型不一致
+ pendingCategory.value = { name: data.name, type: form.value.type, icon: data.icon || '' }
+ } catch (e: any) {
+ message.error(e.message || t('ai.category.failed'))
+ } finally {
+ aiNewLoading.value = false
+ }
+}
+
+/** 拼接用于生成分类的账单描述 */
+function buildCategoryPrompt() {
+ const typeLabel = form.value.type === 'INCOME' ? t('common.types.income') : t('common.types.expense')
+ const parts = [`这是一笔${typeLabel}记录`]
+ if (form.value.remark) parts.push(`备注:${form.value.remark}`)
+ if (form.value.amount) parts.push(`金额:${form.value.amount}`)
+ if (form.value.paymentMethod) parts.push(`支付方式:${getPaymentLabel(form.value.paymentMethod)}`)
+ parts.push('请生成一个能涵盖这类记录的记账分类')
+ return parts.join(',')
+}
+
+/** 确认保存 AI 生成的新分类并选用 */
+async function handleSaveNewCategory() {
+ const pending = pendingCategory.value
+ if (!pending) {
+ return
+ }
+ // 已存在同名同类型分类时直接选用,避免重复创建
+ const existing = categories.value.find((c) => c.label === pending.name && c.type === pending.type)
+ if (existing) {
+ form.value.categoryId = existing.value
+ message.info(t('ai.newCategory.exists'))
+ pendingCategory.value = null
+ return
+ }
+ aiNewSaving.value = true
+ try {
+ await createCategory({ name: pending.name, type: pending.type, icon: pending.icon, sortOrder: 0 })
+ await fetchCategories()
+ // 排序值相同,取同名同类型中的最后一个(最新创建)
+ const created = [...categories.value].reverse().find((c) => c.label === pending.name && c.type === pending.type)
+ if (created) {
+ form.value.categoryId = created.value
+ }
+ message.success(t('ai.newCategory.saved'))
+ pendingCategory.value = null
+ } catch (e: any) {
+ message.error(e.message || t('common.operationFailed'))
+ } finally {
+ aiNewSaving.value = false
+ }
+}
+
+/** 编辑弹窗内 AI 推荐已有标签:基于表单当前内容,追加到已选标签 */
+async function handleAiTagSuggest() {
+ aiTagLoading.value = true
+ try {
+ const res: any = await suggestOneByAi({
+ type: form.value.type,
+ amount: form.value.amount,
+ paymentMethod: form.value.paymentMethod || undefined,
+ remark: form.value.remark || undefined,
+ })
+ const suggestion = res.data
+ if (!suggestion || !suggestion.tagIds || suggestion.tagIds.length === 0) {
+ message.warning(t('ai.noSuggestion'))
+ return
+ }
+ // 合并去重,保留已选标签
+ form.value.tagIds = Array.from(new Set([...form.value.tagIds, ...suggestion.tagIds]))
+ message.success(t('ai.tagApplied'))
+ } catch (e: any) {
+ message.error(e.message || t('ai.suggestFailed'))
+ } finally {
+ aiTagLoading.value = false
+ }
+}
+
+/** 编辑弹窗内根据账单信息生成新标签(暂不保存,等待用户确认) */
+async function handleAiNewTag() {
+ if (!form.value.remark && !form.value.amount) {
+ message.warning(t('ai.newTag.needInfo'))
+ return
+ }
+ aiTagLoading.value = true
+ try {
+ const res: any = await parseTagByAi(buildCategoryPrompt())
+ const data = res.data
+ if (!data || !data.name) {
+ message.warning(t('ai.newTag.failed'))
+ return
+ }
+ pendingTag.value = { name: data.name, color: data.color || '#2080f0' }
+ } catch (e: any) {
+ message.error(e.message || t('ai.newTag.failed'))
+ } finally {
+ aiTagLoading.value = false
+ }
+}
+
+/** 确认保存 AI 生成的新标签并选用 */
+async function handleSaveNewTag() {
+ const pending = pendingTag.value
+ if (!pending) {
+ return
+ }
+ // 已存在同名标签时直接选用,避免重复创建
+ const existing = allTags.value.find((tag) => tag.name === pending.name)
+ if (existing) {
+ addTagToForm(existing.id)
+ message.info(t('ai.newTag.exists'))
+ pendingTag.value = null
+ return
+ }
+ aiTagSaving.value = true
+ try {
+ await createTag({ name: pending.name, color: pending.color, sortOrder: 0 })
+ await fetchTags()
+ // 排序值相同,取同名中的最后一个(最新创建)
+ const created = [...allTags.value].reverse().find((tag) => tag.name === pending.name)
+ if (created) {
+ addTagToForm(created.id)
+ }
+ message.success(t('ai.newTag.saved'))
+ pendingTag.value = null
+ } catch (e: any) {
+ message.error(e.message || t('common.operationFailed'))
+ } finally {
+ aiTagSaving.value = false
+ }
+}
+
+/** 将标签追加到表单已选标签中(去重) */
+function addTagToForm(tagId: number) {
+ if (!form.value.tagIds.includes(tagId)) {
+ form.value.tagIds = [...form.value.tagIds, tagId]
+ }
+}
+
/** 一键应用全部 AI 建议:覆盖分类并追加标签 */
async function handleAiApply() {
- if (aiSuggestions.value.length === 0) {
+ const selected = new Set(checkedAiRowKeys.value)
+ const chosen = aiSuggestions.value.filter((s) => selected.has(s.transactionId))
+ if (chosen.length === 0) {
+ message.warning(t('ai.applyNone'))
return
}
aiApplying.value = true
try {
- const payload = aiSuggestions.value.map((s) => ({
- transactionId: s.transactionId,
- categoryId: s.categoryId,
- tagIds: s.tagIds || [],
- }))
+ // 仅提交勾选的建议,聚合建议按组展开为逐条记录
+ const payload = chosen.flatMap((s) => {
+ const ids = s.transactionIds?.length ? s.transactionIds : [s.transactionId]
+ return ids.map((id) => ({ transactionId: id, categoryId: s.categoryId, tagIds: s.tagIds || [] }))
+ })
const res: any = await applyAiSuggestions(payload)
message.success(t('ai.applySuccess', { count: res.data }))
showAiModal.value = false
@@ -1042,7 +1293,9 @@ const activeFilterTags = computed(() => {
})
filterTag.value.forEach((id) => {
- const name = allTags.value.find((tag) => tag.id === id)?.name || String(id)
+ const name = id === 0
+ ? t('transaction.untagged')
+ : (allTags.value.find((tag) => tag.id === id)?.name || String(id))
tags.push({
key: `tag:${id}`,
label: name,
@@ -1075,7 +1328,7 @@ function applyTagClear(tag: { clear: () => void }) {
handleSearch()
}
-/** AI 建议行:合并交易记录信息用于展示 */
+/** AI 建议行:合并代表交易记录信息用于展示 */
const aiRows = computed(() =>
aiSuggestions.value.map((s) => ({
...s,
@@ -1083,28 +1336,46 @@ const aiRows = computed(() =>
})),
)
-/** AI 建议表格列定义(随语言切换响应式更新) */
-const aiColumns = computed>(() => [
- {
- title: t('ai.column.record'), key: 'record', minWidth: 200,
- render: (row) => {
- const tx = row.transaction
- if (!tx) return `#${row.transactionId}`
- const time = tx.transactionTime?.replace('T', ' ').substring(0, 16) || '-'
- const sign = tx.type === 'INCOME' ? '+' : '-'
- return h('div', null, [
- h('div', null, `${time} ${sign}${tx.amount.toFixed(2)}`),
- h('div', { style: { color: '#999', fontSize: '12px' } }, tx.remark || '-'),
- ])
+/** 已勾选 AI 建议覆盖的记录总数(聚合组按组内记录数累加) */
+const aiTotalCount = computed(() => {
+ const selected = new Set(checkedAiRowKeys.value)
+ return aiSuggestions.value
+ .filter((s) => selected.has(s.transactionId))
+ .reduce((sum, s) => sum + (s.transactionIds?.length || 1), 0)
+})
+
+/** AI 建议表格列定义(随语言切换与建议范围响应式更新) */
+const aiColumns = computed>(() => {
+ const columns: DataTableColumns = [
+ { type: 'selection' },
+ {
+ title: t('ai.column.record'), key: 'record', minWidth: 200,
+ render: (row) => {
+ const tx = row.transaction
+ if (!tx) return `#${row.transactionId}`
+ const time = tx.transactionTime?.replace('T', ' ').substring(0, 16) || '-'
+ const sign = tx.type === 'INCOME' ? '+' : '-'
+ return h('div', null, [
+ h('div', null, `${time} ${sign}${tx.amount.toFixed(2)}`),
+ h('div', { style: { color: '#999', fontSize: '12px' } }, tx.remark || '-'),
+ ])
+ },
},
- },
- { title: t('ai.column.category'), key: 'category', width: 130, render: (row) => row.categoryName || '-' },
- {
- title: t('ai.column.tags'), key: 'tags', minWidth: 150,
- render: (row) => (row.tagNames && row.tagNames.length > 0 ? row.tagNames.join('、') : '-'),
- },
- { title: t('ai.column.reason'), key: 'reason', minWidth: 180, ellipsis: { tooltip: true } },
-])
+ { title: t('ai.column.count'), key: 'count', width: 90, render: (row) => row.count ?? 1 },
+ ]
+ // 按建议范围展示对应列
+ if (aiScope.value !== 'TAG') {
+ columns.push({ title: t('ai.column.category'), key: 'category', width: 130, render: (row) => row.categoryName || '-' })
+ }
+ if (aiScope.value !== 'CATEGORY') {
+ columns.push({
+ title: t('ai.column.tags'), key: 'tags', minWidth: 150,
+ render: (row) => (row.tagNames && row.tagNames.length > 0 ? row.tagNames.join('、') : '-'),
+ })
+ }
+ columns.push({ title: t('ai.column.reason'), key: 'reason', minWidth: 180, ellipsis: { tooltip: true } })
+ return columns
+})
/** 打开批量设置分类弹窗 */
function handleBatchOpen() {
@@ -1171,7 +1442,7 @@ async function handleExport() {
remark: filterRemark.value.trim() || undefined,
sortField: sortField.value,
sortOrder: sortOrder.value,
- tagIds: filterTag.value.length > 0 ? filterTag.value : undefined,
+ ...buildTagFilter(),
})
const blob = new Blob([res.data], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
const url = window.URL.createObjectURL(blob)
@@ -1325,7 +1596,7 @@ onMounted(() => {
{{ t('transaction.column.tags') }}
{
-
+
+
+ {{ t('ai.pickButton') }}
+ {{ t('ai.newCategory.button') }}
+
+
+
+ {{ t('ai.newCategory.hint') }}
+ {{ pendingCategory.icon }} {{ pendingCategory.name }}
+ {{ t('ai.newCategory.save') }}
+ {{ t('common.cancel') }}
+
@@ -1444,15 +1726,27 @@ onMounted(() => {
-
+
+
+ {{ t('ai.pickButton') }}
+ {{ t('ai.newTag.button') }}
+
+
+
+ {{ t('ai.newTag.hint') }}
+ {{ pendingTag.name }}
+ {{ t('ai.newTag.save') }}
+ {{ t('common.cancel') }}
+
{{ t('transaction.includeInStatsHint') }}
@@ -1478,9 +1772,12 @@ onMounted(() => {
- {{ t('ai.suggestButton') }}
+
+
+ {{ t('ai.suggestButton') }}
+
-
+
{
:placeholder="t('transaction.notSetExpense')"
/>
-
+
{