first commit
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, reactive, ref } from 'vue';
|
||||
import LucideIcon from '../../../components/common/LucideIcon.vue';
|
||||
import TransactionItem from '../../../components/transactions/TransactionItem.vue';
|
||||
import {
|
||||
useTransactionsQuery,
|
||||
useCreateTransactionMutation,
|
||||
useDeleteTransactionMutation
|
||||
} from '../../../composables/useTransactions';
|
||||
import type { TransactionPayload } from '../../../types/transaction';
|
||||
|
||||
type FilterOption = 'all' | 'expense' | 'income';
|
||||
|
||||
const filter = ref<FilterOption>('all');
|
||||
const showSheet = ref(false);
|
||||
|
||||
const form = reactive({
|
||||
title: '',
|
||||
amount: '',
|
||||
category: '',
|
||||
type: 'expense',
|
||||
notes: '',
|
||||
source: 'manual'
|
||||
});
|
||||
|
||||
const { data: transactions } = useTransactionsQuery();
|
||||
const createTransaction = useCreateTransactionMutation();
|
||||
const deleteTransaction = useDeleteTransactionMutation();
|
||||
|
||||
const filters: Array<{ label: string; value: FilterOption }> = [
|
||||
{ label: '全部', value: 'all' },
|
||||
{ label: '支出', value: 'expense' },
|
||||
{ label: '收入', value: 'income' }
|
||||
];
|
||||
|
||||
const filteredTransactions = computed(() => {
|
||||
if (!transactions.value) return [];
|
||||
if (filter.value === 'all') return transactions.value;
|
||||
return transactions.value.filter((txn) => txn.type === filter.value);
|
||||
});
|
||||
|
||||
const isSaving = computed(() => createTransaction.isPending.value);
|
||||
|
||||
const setFilter = (value: FilterOption) => {
|
||||
filter.value = value;
|
||||
};
|
||||
|
||||
const resetForm = () => {
|
||||
form.title = '';
|
||||
form.amount = '';
|
||||
form.category = '';
|
||||
form.type = 'expense';
|
||||
form.source = 'manual';
|
||||
form.notes = '';
|
||||
};
|
||||
|
||||
const submit = async () => {
|
||||
if (!form.title || !form.amount) return;
|
||||
const payload: TransactionPayload = {
|
||||
title: form.title,
|
||||
amount: Math.abs(Number(form.amount)),
|
||||
category: form.category || '未分类',
|
||||
type: form.type as TransactionPayload['type'],
|
||||
source: form.source as TransactionPayload['source'],
|
||||
status: 'pending',
|
||||
currency: 'CNY',
|
||||
notes: form.notes || undefined,
|
||||
occurredAt: new Date().toISOString()
|
||||
};
|
||||
await createTransaction.mutateAsync(payload);
|
||||
showSheet.value = false;
|
||||
resetForm();
|
||||
};
|
||||
|
||||
const removeTransaction = async (id: string) => {
|
||||
await deleteTransaction.mutateAsync(id);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="p-6 pt-10 pb-24 space-y-6">
|
||||
<header class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm text-gray-500">轻松管理你的收支</p>
|
||||
<h1 class="text-3xl font-bold text-gray-900">交易记录</h1>
|
||||
</div>
|
||||
<button
|
||||
class="px-4 py-2 bg-indigo-500 text-white rounded-xl font-medium hover:bg-indigo-600"
|
||||
@click="showSheet = true"
|
||||
>
|
||||
新增
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<div class="bg-white rounded-2xl p-2 flex space-x-2 border border-gray-100">
|
||||
<button
|
||||
v-for="item in filters"
|
||||
:key="item.value"
|
||||
class="flex-1 py-2 rounded-lg text-sm font-medium"
|
||||
:class="filter === item.value ? 'bg-indigo-500 text-white' : 'text-gray-500'"
|
||||
@click="setFilter(item.value)"
|
||||
>
|
||||
{{ item.label }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div
|
||||
v-for="transaction in filteredTransactions"
|
||||
:key="transaction.id"
|
||||
class="relative group"
|
||||
>
|
||||
<TransactionItem :transaction="transaction" />
|
||||
<button
|
||||
class="absolute top-4 right-4 opacity-0 group-hover:opacity-100 transition-opacity text-gray-400 hover:text-red-500"
|
||||
@click="removeTransaction(transaction.id)"
|
||||
>
|
||||
<LucideIcon name="trash-2" :size="18" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="showSheet"
|
||||
class="fixed inset-0 bg-black/40 flex items-end justify-center"
|
||||
@click.self="showSheet = false"
|
||||
>
|
||||
<div class="w-full max-w-xl bg-white rounded-t-3xl p-6 space-y-4">
|
||||
<div class="flex justify-between items-center">
|
||||
<h2 class="text-lg font-semibold text-gray-900">快速记一笔</h2>
|
||||
<button @click="showSheet = false">
|
||||
<LucideIcon name="x" :size="24" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="space-y-4">
|
||||
<label class="flex flex-col text-sm font-medium text-gray-700 space-y-2">
|
||||
标题
|
||||
<input v-model="form.title" type="text" class="px-4 py-2 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-indigo-500" />
|
||||
</label>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<label class="flex flex-col text-sm font-medium text-gray-700 space-y-2">
|
||||
金额
|
||||
<input v-model="form.amount" type="number" min="0" class="px-4 py-2 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-indigo-500" />
|
||||
</label>
|
||||
<label class="flex flex-col text-sm font-medium text-gray-700 space-y-2">
|
||||
类型
|
||||
<select v-model="form.type" class="px-4 py-2 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-indigo-500">
|
||||
<option value="expense">支出</option>
|
||||
<option value="income">收入</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<label class="flex flex-col text-sm font-medium text-gray-700 space-y-2">
|
||||
分类
|
||||
<input v-model="form.category" type="text" placeholder="如:餐饮" class="px-4 py-2 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-indigo-500" />
|
||||
</label>
|
||||
<label class="flex flex-col text-sm font-medium text-gray-700 space-y-2">
|
||||
来源
|
||||
<select v-model="form.source" class="px-4 py-2 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-indigo-500">
|
||||
<option value="manual">手动</option>
|
||||
<option value="notification">通知自动</option>
|
||||
<option value="ocr">票据识别</option>
|
||||
<option value="ai">AI 推荐</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="flex flex-col text-sm font-medium text-gray-700 space-y-2">
|
||||
备注
|
||||
<textarea v-model="form.notes" rows="2" class="px-4 py-2 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-indigo-500" />
|
||||
</label>
|
||||
<button
|
||||
class="w-full bg-indigo-500 text-white py-3 rounded-xl font-semibold hover:bg-indigo-600 disabled:opacity-60"
|
||||
:disabled="isSaving"
|
||||
@click="submit"
|
||||
>
|
||||
{{ isSaving ? '保存中...' : '保存' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user