feat:添加本地数据库

This commit is contained in:
2025-11-25 17:12:09 +08:00
parent bc1a909a4d
commit b23514cfe6
12 changed files with 2432 additions and 634 deletions

181
src/stores/transactions.js Normal file
View File

@@ -0,0 +1,181 @@
import { defineStore } from 'pinia'
import { computed, ref } from 'vue'
import {
deleteTransaction,
fetchTransactions,
insertTransaction,
updateTransaction,
} from '../services/transactionService'
const toIsoDay = (value) => {
const date = value instanceof Date ? value : new Date(value)
return date.toISOString().split('T')[0]
}
const formatDayLabel = (dayKey) => {
const todayKey = toIsoDay(new Date())
const yesterdayKey = toIsoDay(new Date(Date.now() - 86400000))
if (dayKey === todayKey) return '今天'
if (dayKey === yesterdayKey) return '昨天'
const [y, m, d] = dayKey.split('-').map((part) => Number(part))
const localDate = new Date(y, m - 1, d)
return new Intl.DateTimeFormat('zh-CN', {
month: 'numeric',
day: 'numeric',
weekday: 'short',
}).format(localDate)
}
export const useTransactionStore = defineStore(
'transactions',
() => {
const transactions = ref([])
const initialized = ref(false)
const loading = ref(false)
const errorMessage = ref('')
const filters = ref({
category: 'all',
showOnlyToday: false,
})
const sortedTransactions = computed(() =>
[...transactions.value].sort((a, b) => new Date(b.date) - new Date(a.date)),
)
const todaysTransactions = computed(() => {
const todayKey = toIsoDay(new Date())
return sortedTransactions.value.filter((tx) => toIsoDay(tx.date) === todayKey)
})
const totalExpense = computed(() =>
sortedTransactions.value.reduce((sum, tx) => (tx.amount < 0 ? sum + tx.amount : sum), 0),
)
const totalIncome = computed(() =>
sortedTransactions.value.reduce((sum, tx) => (tx.amount > 0 ? sum + tx.amount : sum), 0),
)
const todaysExpense = computed(() =>
todaysTransactions.value.reduce((sum, tx) => (tx.amount < 0 ? sum + tx.amount : sum), 0),
)
const todaysIncome = computed(() =>
todaysTransactions.value.reduce((sum, tx) => (tx.amount > 0 ? sum + tx.amount : sum), 0),
)
const latestTransactions = computed(() => sortedTransactions.value.slice(0, 5))
const groupedTransactions = computed(() => {
const appliedCategory = filters.value.category
const showTodayOnly = filters.value.showOnlyToday
const groups = new Map()
sortedTransactions.value.forEach((tx) => {
if (appliedCategory !== 'all' && tx.category !== appliedCategory) return
if (showTodayOnly && toIsoDay(tx.date) !== toIsoDay(new Date())) return
const dayKey = toIsoDay(tx.date)
if (!groups.has(dayKey)) {
groups.set(dayKey, [])
}
groups.get(dayKey).push(tx)
})
return Array.from(groups.entries())
.sort((a, b) => new Date(b[0]) - new Date(a[0]))
.map(([dayKey, records]) => ({
dayKey,
label: formatDayLabel(dayKey),
totalExpense: records
.filter((tx) => tx.amount < 0)
.reduce((sum, tx) => sum + Math.abs(tx.amount), 0),
items: records,
}))
})
const hydrateTransactions = async () => {
loading.value = true
errorMessage.value = ''
try {
const rows = await fetchTransactions()
transactions.value = rows
initialized.value = true
} catch (err) {
errorMessage.value = err?.message || '无法加载本地账本'
throw err
} finally {
loading.value = false
}
}
const ensureInitialized = async () => {
if (!initialized.value && !loading.value) {
await hydrateTransactions()
}
}
const addTransaction = async (payload) => {
const normalized = {
...payload,
date: payload.date ? new Date(payload.date).toISOString() : new Date().toISOString(),
}
const created = await insertTransaction(normalized)
transactions.value = [created, ...transactions.value]
return created
}
const editTransaction = async (payload) => {
const normalized = {
...payload,
date: payload.date ? new Date(payload.date).toISOString() : new Date().toISOString(),
}
const updated = await updateTransaction(normalized)
if (updated) {
transactions.value = transactions.value.map((tx) => (tx.id === updated.id ? updated : tx))
}
return updated
}
const removeTransaction = async (id) => {
await deleteTransaction(id)
transactions.value = transactions.value.filter((tx) => tx.id !== id)
}
const upsertFromNotification = async (payload) => {
const existing = transactions.value.find((tx) => tx.id === payload.id)
if (existing) {
return editTransaction(payload)
}
return addTransaction(payload)
}
const findById = (id) => transactions.value.find((tx) => tx.id === id)
return {
transactions,
initialized,
loading,
errorMessage,
filters,
sortedTransactions,
groupedTransactions,
todaysTransactions,
todaysExpense,
todaysIncome,
totalExpense,
totalIncome,
latestTransactions,
hydrateTransactions,
ensureInitialized,
addTransaction,
editTransaction,
removeTransaction,
upsertFromNotification,
findById,
}
},
{
persist: {
paths: ['filters'],
},
},
)