feat:添加本地数据库
This commit is contained in:
@@ -1,156 +1,184 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useTransactionStore } from '../stores/transactions'
|
||||
|
||||
// 按日期分组的交易列表(当前为纯前端 Mock 数据,后续会接 Pinia/SQLite)
|
||||
const groupedTransactions = ref([
|
||||
{
|
||||
date: '今天, 5月20日',
|
||||
totalExpense: '42.50',
|
||||
items: [
|
||||
{
|
||||
title: '全家便利店',
|
||||
type: 'expense',
|
||||
amount: '24.50',
|
||||
time: '10:30',
|
||||
icon: 'ph-coffee',
|
||||
bg: 'bg-orange-100',
|
||||
color: 'text-orange-600',
|
||||
note: '早餐',
|
||||
},
|
||||
{
|
||||
title: '滴滴出行',
|
||||
type: 'expense',
|
||||
amount: '18.00',
|
||||
time: '09:15',
|
||||
icon: 'ph-taxi',
|
||||
bg: 'bg-blue-100',
|
||||
color: 'text-blue-600',
|
||||
note: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
date: '昨天, 5月19日',
|
||||
totalExpense: '380.00',
|
||||
items: [
|
||||
{
|
||||
title: '工资收入',
|
||||
type: 'income',
|
||||
amount: '12,500.00',
|
||||
time: '18:00',
|
||||
icon: 'ph-wallet',
|
||||
bg: 'bg-emerald-100',
|
||||
color: 'text-emerald-600',
|
||||
note: '包含奖金',
|
||||
},
|
||||
{
|
||||
title: '乐刻健身',
|
||||
type: 'expense',
|
||||
amount: '380.00',
|
||||
time: '12:30',
|
||||
icon: 'ph-barbell',
|
||||
bg: 'bg-purple-100',
|
||||
color: 'text-purple-600',
|
||||
note: '月卡续费',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
date: '5月18日',
|
||||
totalExpense: '120.00',
|
||||
items: [
|
||||
{
|
||||
title: '山姆会员店',
|
||||
type: 'expense',
|
||||
amount: '120.00',
|
||||
time: '15:40',
|
||||
icon: 'ph-basket',
|
||||
bg: 'bg-red-100',
|
||||
color: 'text-red-600',
|
||||
note: '零食采购',
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
const router = useRouter()
|
||||
const transactionStore = useTransactionStore()
|
||||
const { groupedTransactions, filters, loading } = storeToRefs(transactionStore)
|
||||
const deletingId = ref('')
|
||||
|
||||
const categoryChips = [
|
||||
{ label: '全部', value: 'all', icon: 'ph-asterisk' },
|
||||
{ label: '餐饮', value: 'Food', icon: 'ph-bowl-food' },
|
||||
{ label: '通勤', value: 'Transport', icon: 'ph-taxi' },
|
||||
{ label: '健康', value: 'Health', icon: 'ph-heartbeat' },
|
||||
{ label: '买菜', value: 'Groceries', icon: 'ph-basket' },
|
||||
{ label: '收入', value: 'Income', icon: 'ph-wallet' },
|
||||
{ label: '其他', value: 'Uncategorized', icon: 'ph-dots-three-outline' },
|
||||
]
|
||||
|
||||
const setCategory = (value) => {
|
||||
filters.value.category = value
|
||||
}
|
||||
|
||||
const toggleTodayOnly = () => {
|
||||
filters.value.showOnlyToday = !filters.value.showOnlyToday
|
||||
}
|
||||
|
||||
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 handleEdit = (item) => {
|
||||
router.push({ name: 'add', query: { id: item.id } })
|
||||
}
|
||||
|
||||
const handleDelete = async (item) => {
|
||||
if (deletingId.value) return
|
||||
const confirmed = window.confirm('确认删除这条流水吗?')
|
||||
if (!confirmed) return
|
||||
deletingId.value = item.id
|
||||
try {
|
||||
await transactionStore.removeTransaction(item.id)
|
||||
} finally {
|
||||
deletingId.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
const hasData = computed(() => groupedTransactions.value.length > 0)
|
||||
|
||||
transactionStore.ensureInitialized()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-4 pb-10 animate-fade-in">
|
||||
<!-- 顶部月份与统计卡片 -->
|
||||
<div class="space-y-5 pb-10 animate-fade-in">
|
||||
<div class="sticky top-0 z-20 bg-warmOffwhite/95 backdrop-blur-sm py-2">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h2 class="text-2xl font-extrabold text-stone-800">账单明细</h2>
|
||||
<div
|
||||
class="flex items-center gap-2 bg-white border border-stone-100 px-3 py-1.5 rounded-full shadow-sm"
|
||||
<button
|
||||
class="flex items-center gap-2 bg-white border border-stone-100 px-3 py-1.5 rounded-full shadow-sm text-xs font-bold text-stone-500"
|
||||
@click="router.push({ name: 'add' })"
|
||||
>
|
||||
<span class="text-sm font-bold text-stone-600">2024年 5月</span>
|
||||
<i class="ph-bold ph-caret-down text-xs text-stone-400" />
|
||||
</div>
|
||||
<i class="ph-bold ph-plus" /> 新增
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 汇总统计 -->
|
||||
<div class="flex gap-4 overflow-x-auto hide-scrollbar pb-2">
|
||||
<div
|
||||
class="shrink-0 bg-stone-700 text-white px-5 py-3 rounded-2xl shadow-lg shadow-stone-200 flex flex-col justify-center min-w-[140px]"
|
||||
<div class="flex gap-2 overflow-x-auto pb-2 hide-scrollbar">
|
||||
<button
|
||||
v-for="chip in categoryChips"
|
||||
:key="chip.value"
|
||||
class="px-3 py-1.5 rounded-full text-xs font-bold flex items-center gap-1 border transition"
|
||||
:class="
|
||||
filters.category === chip.value
|
||||
? 'bg-gradient-warm text-white border-transparent shadow-sm'
|
||||
: 'bg-white text-stone-500 border-stone-100'
|
||||
"
|
||||
@click="setCategory(chip.value)"
|
||||
>
|
||||
<span class="text-xs text-white/60 mb-1">总支出</span>
|
||||
<span class="font-bold text-xl">¥ 8,215.40</span>
|
||||
</div>
|
||||
<div
|
||||
class="shrink-0 bg-white text-stone-800 border border-stone-100 px-5 py-3 rounded-2xl flex flex-col justify-center min-w-[140px]"
|
||||
<i :class="['text-sm', chip.icon]" />
|
||||
{{ chip.label }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between text-[11px] text-stone-400 font-bold px-1">
|
||||
<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'"
|
||||
@click="toggleTodayOnly"
|
||||
>
|
||||
<span class="text-xs text-stone-400 mb-1">总收入</span>
|
||||
<span class="font-bold text-xl text-emerald-500">+ ¥ 12,500</span>
|
||||
</div>
|
||||
<i class="ph-bold ph-sun-dim text-xs" />
|
||||
今日
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 按日期分组列表 -->
|
||||
<div class="space-y-6 pb-10">
|
||||
<div v-for="(group, gIndex) in groupedTransactions" :key="gIndex">
|
||||
<div class="flex justify-between items-center mb-3 px-1">
|
||||
<h4 class="text-sm font-bold text-stone-400">{{ group.date }}</h4>
|
||||
<span class="text-xs text-stone-300 font-bold">支出 {{ group.totalExpense }}</span>
|
||||
<div v-if="loading" class="text-center text-stone-400 py-10 text-sm">
|
||||
正在同步本地账本...
|
||||
</div>
|
||||
|
||||
<div v-else-if="hasData" class="space-y-6 pb-10">
|
||||
<div
|
||||
v-for="group in groupedTransactions"
|
||||
:key="group.dayKey"
|
||||
class="space-y-3"
|
||||
>
|
||||
<div class="flex justify-between items-center px-1">
|
||||
<div>
|
||||
<h4 class="text-sm font-bold text-stone-500">{{ group.label }}</h4>
|
||||
<p class="text-[10px] text-stone-400">
|
||||
支出 ¥{{ group.totalExpense.toFixed(2) }}
|
||||
</p>
|
||||
</div>
|
||||
<span class="text-xs text-stone-300 font-bold">{{ group.dayKey }}</span>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-3xl p-1 shadow-sm border border-stone-50">
|
||||
<div class="bg-white rounded-3xl p-1 shadow-sm border border-stone-50 divide-y divide-stone-50">
|
||||
<div
|
||||
v-for="(item, iIndex) in group.items"
|
||||
:key="iIndex"
|
||||
class="flex items-center justify-between p-4 hover:bg-stone-50 rounded-2xl transition"
|
||||
v-for="item in group.items"
|
||||
:key="item.id"
|
||||
class="flex items-center justify-between p-4 hover:bg-stone-50 rounded-2xl transition group"
|
||||
@click="handleEdit(item)"
|
||||
>
|
||||
<div class="flex items-center gap-4">
|
||||
<div
|
||||
:class="[
|
||||
'w-10 h-10 rounded-full flex items-center justify-center text-lg shrink-0',
|
||||
item.bg,
|
||||
item.color,
|
||||
]"
|
||||
class="w-11 h-11 rounded-2xl bg-stone-100 flex items-center justify-center text-stone-500 group-active:scale-95 transition"
|
||||
>
|
||||
<i :class="['ph-fill', item.icon]" />
|
||||
<i
|
||||
:class="[
|
||||
'ph-bold text-lg',
|
||||
item.amount < 0 ? 'ph-arrow-up-right' : 'ph-arrow-down-left',
|
||||
]"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<p class="font-bold text-stone-800 text-sm">
|
||||
{{ item.title }}
|
||||
{{ item.merchant }}
|
||||
</p>
|
||||
<p class="text-xs text-stone-400 mt-0.5">
|
||||
{{ item.time }}
|
||||
<span v-if="item.note" class="text-stone-300">| {{ item.note }}</span>
|
||||
{{ formatTime(item.date) }}
|
||||
<span v-if="item.category" class="text-stone-300">
|
||||
· {{ item.category }}
|
||||
</span>
|
||||
<span v-if="item.note" class="text-stone-300">
|
||||
· {{ item.note }}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<span
|
||||
:class="[
|
||||
'font-bold text-base',
|
||||
item.type === 'expense' ? 'text-stone-800' : 'text-emerald-500',
|
||||
]"
|
||||
>
|
||||
{{ item.type === 'expense' ? '-' : '+' }} {{ item.amount }}
|
||||
</span>
|
||||
<div class="text-right">
|
||||
<p
|
||||
:class="[
|
||||
'font-bold text-base',
|
||||
item.amount < 0 ? 'text-stone-800' : 'text-emerald-600',
|
||||
]"
|
||||
>
|
||||
{{ formatAmount(item.amount) }}
|
||||
</p>
|
||||
<button
|
||||
class="text-[11px] text-stone-400 mt-1"
|
||||
:disabled="deletingId === item.id"
|
||||
@click.stop="handleDelete(item)"
|
||||
>
|
||||
{{ deletingId === item.id ? '删除中...' : '删除' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-else
|
||||
class="py-12 text-center text-stone-400 text-sm border border-dashed border-stone-100 rounded-3xl"
|
||||
>
|
||||
暂无符合筛选条件的记录
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user