feat:添加本地数据库
This commit is contained in:
181
src/stores/transactions.js
Normal file
181
src/stores/transactions.js
Normal 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'],
|
||||
},
|
||||
},
|
||||
)
|
||||
Reference in New Issue
Block a user