Files
echo/src/views/ListView.vue

185 lines
6.2 KiB
Vue
Raw Normal View History

2025-11-24 17:23:46 +08:00
<script setup>
2025-11-25 17:12:09 +08:00
import { computed, ref } from 'vue'
import { useRouter } from 'vue-router'
import { storeToRefs } from 'pinia'
import { useTransactionStore } from '../stores/transactions'
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()
2025-11-24 17:23:46 +08:00
</script>
<template>
2025-11-25 17:12:09 +08:00
<div class="space-y-5 pb-10 animate-fade-in">
2025-11-24 17:23:46 +08:00
<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>
2025-11-25 17:12:09 +08:00
<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' })"
2025-11-24 17:23:46 +08:00
>
2025-11-25 17:12:09 +08:00
<i class="ph-bold ph-plus" /> 新增
</button>
2025-11-24 17:23:46 +08:00
</div>
2025-11-25 17:12:09 +08:00
<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)"
2025-11-24 17:23:46 +08:00
>
2025-11-25 17:12:09 +08:00
<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"
2025-11-24 17:23:46 +08:00
>
2025-11-25 17:12:09 +08:00
<i class="ph-bold ph-sun-dim text-xs" />
今日
</button>
2025-11-24 17:23:46 +08:00
</div>
</div>
2025-11-25 17:12:09 +08:00
<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>
2025-11-24 17:23:46 +08:00
</div>
2025-11-25 17:12:09 +08:00
<div class="bg-white rounded-3xl p-1 shadow-sm border border-stone-50 divide-y divide-stone-50">
2025-11-24 17:23:46 +08:00
<div
2025-11-25 17:12:09 +08:00
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)"
2025-11-24 17:23:46 +08:00
>
<div class="flex items-center gap-4">
<div
2025-11-25 17:12:09 +08:00
class="w-11 h-11 rounded-2xl bg-stone-100 flex items-center justify-center text-stone-500 group-active:scale-95 transition"
2025-11-24 17:23:46 +08:00
>
2025-11-25 17:12:09 +08:00
<i
:class="[
'ph-bold text-lg',
item.amount < 0 ? 'ph-arrow-up-right' : 'ph-arrow-down-left',
]"
/>
2025-11-24 17:23:46 +08:00
</div>
<div>
<p class="font-bold text-stone-800 text-sm">
2025-11-25 17:12:09 +08:00
{{ item.merchant }}
2025-11-24 17:23:46 +08:00
</p>
<p class="text-xs text-stone-400 mt-0.5">
2025-11-25 17:12:09 +08:00
{{ 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>
2025-11-24 17:23:46 +08:00
</p>
</div>
</div>
2025-11-25 17:12:09 +08:00
<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>
2025-11-24 17:23:46 +08:00
</div>
</div>
</div>
</div>
2025-11-25 17:12:09 +08:00
<div
v-else
class="py-12 text-center text-stone-400 text-sm border border-dashed border-stone-100 rounded-3xl"
>
暂无符合筛选条件的记录
</div>
2025-11-24 17:23:46 +08:00
</div>
</template>