feat:新增ai分析,新增储值账户,优化通知规则
This commit is contained in:
@@ -1,6 +1,12 @@
|
||||
<script setup>
|
||||
import { computed, ref } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { getAiStatusMeta } from '../config/aiStatus.js'
|
||||
import {
|
||||
getEntryTypeLabel,
|
||||
getTransferSummary,
|
||||
isStoredValueAccountType,
|
||||
} from '../config/ledger.js'
|
||||
import { CATEGORY_CHIPS, getCategoryLabel } from '../config/transactionCategories.js'
|
||||
import { useTransactionStore } from '../stores/transactions'
|
||||
import { useUiStore } from '../stores/ui'
|
||||
@@ -19,14 +25,62 @@ const toggleTodayOnly = () => {
|
||||
filters.value.showOnlyToday = !filters.value.showOnlyToday
|
||||
}
|
||||
|
||||
const formatAmount = (value) =>
|
||||
`${value >= 0 ? '+' : '-'} 楼${Math.abs(value || 0).toFixed(2)}`
|
||||
const formatAmount = (value) => `${value >= 0 ? '+' : '-'} ¥${Math.abs(value || 0).toFixed(2)}`
|
||||
|
||||
const formatTime = (value) =>
|
||||
new Intl.DateTimeFormat('zh-CN', { hour: '2-digit', minute: '2-digit' }).format(
|
||||
new Date(value),
|
||||
)
|
||||
|
||||
const resolveLedgerBadge = (transaction) => {
|
||||
if (transaction.entryType === 'transfer') {
|
||||
return {
|
||||
label: getEntryTypeLabel(transaction.entryType),
|
||||
icon: 'ph-arrows-left-right',
|
||||
className: 'bg-cyan-50 text-cyan-700 border-cyan-200',
|
||||
}
|
||||
}
|
||||
if (isStoredValueAccountType(transaction.fundSourceType)) {
|
||||
return {
|
||||
label: '储值支付',
|
||||
icon: 'ph-wallet',
|
||||
className: 'bg-teal-50 text-teal-700 border-teal-200',
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
const resolveAiBadge = (transaction) => {
|
||||
if (!transaction?.aiStatus || ['idle', 'failed'].includes(transaction.aiStatus)) {
|
||||
return null
|
||||
}
|
||||
return getAiStatusMeta(transaction.aiStatus)
|
||||
}
|
||||
|
||||
const formatLedgerHint = (transaction) => {
|
||||
if (transaction.entryType === 'transfer') {
|
||||
return getTransferSummary(transaction)
|
||||
}
|
||||
if (isStoredValueAccountType(transaction.fundSourceType)) {
|
||||
return `从${transaction.fundSourceName || '储值账户'}扣款`
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
const formatAiHint = (transaction) => {
|
||||
if (transaction.aiStatus === 'running') {
|
||||
return '正在补全分类与标签'
|
||||
}
|
||||
if (transaction.aiStatus === 'suggested' && transaction.aiCategory) {
|
||||
return `建议分类:${getCategoryLabel(transaction.aiCategory)}`
|
||||
}
|
||||
if (transaction.aiStatus === 'applied') {
|
||||
const tags = (transaction.aiTags || []).slice(0, 2).join(' · ')
|
||||
return tags ? `已补全:${tags}` : '已完成自动补全'
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
const handleEdit = (item) => {
|
||||
uiStore.openAddEntry(item.id)
|
||||
}
|
||||
@@ -77,7 +131,7 @@ const hasData = computed(() => groupedTransactions.value.length > 0)
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between text-[11px] text-stone-400 font-bold px-1">
|
||||
<span>长按一条记录可快速编辑</span>
|
||||
<span>点按一条记录可快速编辑</span>
|
||||
<button
|
||||
class="flex items-center gap-1 px-2 py-1 rounded-full border border-stone-200"
|
||||
:class="filters.showOnlyToday ? 'bg-gradient-warm text-white border-transparent' : 'bg-white'"
|
||||
@@ -103,7 +157,7 @@ const hasData = computed(() => groupedTransactions.value.length > 0)
|
||||
<div>
|
||||
<h4 class="text-sm font-bold text-stone-500">{{ group.label }}</h4>
|
||||
<p class="text-[10px] text-stone-400">
|
||||
支出 楼{{ group.totalExpense.toFixed(2) }}
|
||||
支出 ¥{{ group.totalExpense.toFixed(2) }}
|
||||
</p>
|
||||
</div>
|
||||
<span class="text-xs text-stone-300 font-bold">{{ group.dayKey }}</span>
|
||||
@@ -128,18 +182,48 @@ const hasData = computed(() => groupedTransactions.value.length > 0)
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<p class="font-bold text-stone-800 text-sm">
|
||||
{{ item.merchant }}
|
||||
</p>
|
||||
<div class="flex items-center gap-2 flex-wrap">
|
||||
<p class="font-bold text-stone-800 text-sm">
|
||||
{{ item.merchant }}
|
||||
</p>
|
||||
<span
|
||||
v-if="resolveLedgerBadge(item)"
|
||||
class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full border text-[10px] font-bold"
|
||||
:class="resolveLedgerBadge(item).className"
|
||||
>
|
||||
<i :class="['ph-bold text-[10px]', resolveLedgerBadge(item).icon]" />
|
||||
{{ resolveLedgerBadge(item).label }}
|
||||
</span>
|
||||
<span
|
||||
v-if="resolveAiBadge(item)"
|
||||
class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full border text-[10px] font-bold"
|
||||
:class="resolveAiBadge(item).className"
|
||||
>
|
||||
<i
|
||||
:class="[
|
||||
'ph-bold text-[10px]',
|
||||
resolveAiBadge(item).icon,
|
||||
resolveAiBadge(item).iconClassName,
|
||||
]"
|
||||
/>
|
||||
{{ resolveAiBadge(item).label }}
|
||||
</span>
|
||||
</div>
|
||||
<p class="text-xs text-stone-400 mt-0.5">
|
||||
{{ formatTime(item.date) }}
|
||||
<span v-if="item.category" class="text-stone-300">
|
||||
路 {{ getCategoryLabel(item.category) }}
|
||||
· {{ getCategoryLabel(item.category) }}
|
||||
</span>
|
||||
<span v-if="item.note" class="text-stone-300">
|
||||
路 {{ item.note }}
|
||||
· {{ item.note }}
|
||||
</span>
|
||||
</p>
|
||||
<p v-if="formatLedgerHint(item)" class="text-[10px] text-stone-400 mt-1">
|
||||
{{ formatLedgerHint(item) }}
|
||||
</p>
|
||||
<p v-if="formatAiHint(item)" class="text-[10px] text-stone-400 mt-1">
|
||||
{{ formatAiHint(item) }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
|
||||
Reference in New Issue
Block a user