mirror of
https://github.com/henry4682/finance_app.git
synced 2026-07-16 00:10:00 +00:00
feat: 前端分離+功能api化
1. 新增資產管理CRUD、googleOAuth登入 2. 更改原記帳controller
This commit is contained in:
@@ -1,141 +1,170 @@
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
import { computed } from "vue";
|
||||
import { useForm, usePage } from "@inertiajs/vue3";
|
||||
import { ref, reactive, onMounted, computed } from "vue";
|
||||
import axios from "axios";
|
||||
// 假設你已經將這些組件搬移到新專案的 components 資料夾
|
||||
import AuthenticatedLayout from "@/Layouts/AuthenticatedLayout.vue";
|
||||
|
||||
const props = defineProps({
|
||||
expenses: Array,
|
||||
categories: Array,
|
||||
rules: Array,
|
||||
});
|
||||
// 規則管理
|
||||
const showRules = ref(false);
|
||||
// --- 1. 資料狀態管理 (取代 Props) ---
|
||||
const expenses = ref([]);
|
||||
const categories = ref([]);
|
||||
const rules = ref([]);
|
||||
const loading = ref(false);
|
||||
const flash = ref("");
|
||||
|
||||
const ruleForm = useForm({
|
||||
keyword: "",
|
||||
category_id: "",
|
||||
});
|
||||
const flash = computed(() => usePage().props.flash);
|
||||
// ── 分類管理 ──
|
||||
const showCategories = ref(false);
|
||||
|
||||
const categoryForm = useForm({
|
||||
name: "",
|
||||
color: "#3B82F6",
|
||||
});
|
||||
|
||||
const submitCategory = () => {
|
||||
categoryForm.post(route("categories.store"), {
|
||||
onSuccess: () => categoryForm.reset(),
|
||||
});
|
||||
};
|
||||
|
||||
const submitRule = () => {
|
||||
ruleForm.post(route("category-rules.store"), {
|
||||
onSuccess: () => ruleForm.reset(),
|
||||
});
|
||||
};
|
||||
|
||||
const destroyRule = (id) => {
|
||||
if (confirm("確定刪除此規則?")) {
|
||||
useForm({}).delete(route("category-rules.destroy", id));
|
||||
// 取得初始資料
|
||||
const fetchAllData = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
// 同步取得所有必要的資料
|
||||
const [expRes, catRes, ruleRes] = await Promise.all([
|
||||
axios.get("/api/expenses"),
|
||||
axios.get("/api/categories"),
|
||||
axios.get("/api/category-rules"),
|
||||
]);
|
||||
expenses.value = expRes.data;
|
||||
categories.value = catRes.data;
|
||||
rules.value = ruleRes.data;
|
||||
} catch (error) {
|
||||
console.error("載入失敗", error);
|
||||
flash.value = "無法取得資料,請檢查網路連線";
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const editingCategory = ref(null);
|
||||
const editCategoryForm = useForm({ name: "", color: "" });
|
||||
onMounted(fetchAllData);
|
||||
|
||||
const startEditCategory = (cat) => {
|
||||
editingCategory.value = cat.id;
|
||||
editCategoryForm.name = cat.name;
|
||||
editCategoryForm.color = cat.color;
|
||||
};
|
||||
|
||||
const submitEditCategory = (id) => {
|
||||
editCategoryForm.put(route("categories.update", id), {
|
||||
onSuccess: () => (editingCategory.value = null),
|
||||
});
|
||||
};
|
||||
|
||||
const destroyCategory = (id) => {
|
||||
if (confirm("確定刪除?該分類的支出會變成未分類。")) {
|
||||
useForm({}).delete(route("categories.destroy", id));
|
||||
}
|
||||
};
|
||||
|
||||
// ── 支出 ──
|
||||
const form = useForm({
|
||||
// --- 2. 支出管理 (Expenses) ---
|
||||
const form = reactive({
|
||||
amount: "",
|
||||
category_id: "",
|
||||
note: "",
|
||||
date: new Date().toISOString().split("T")[0],
|
||||
});
|
||||
|
||||
const submit = () => {
|
||||
form.post(route("expenses.store"), {
|
||||
onSuccess: () => form.reset(),
|
||||
});
|
||||
};
|
||||
|
||||
const formatDate = (date) => (date ? date.toString().split("T")[0] : "");
|
||||
|
||||
const editing = ref(null);
|
||||
const editForm = useForm({
|
||||
amount: "",
|
||||
category_id: "",
|
||||
note: "",
|
||||
date: "",
|
||||
});
|
||||
|
||||
const startEdit = (expense) => {
|
||||
editing.value = expense.id;
|
||||
editForm.amount = expense.amount;
|
||||
editForm.category_id = expense.category_id ?? "";
|
||||
editForm.note = expense.note ?? "";
|
||||
editForm.date = formatDate(expense.date);
|
||||
};
|
||||
|
||||
const submitEdit = (id) => {
|
||||
editForm.put(route("expenses.update", id), {
|
||||
onSuccess: () => (editing.value = null),
|
||||
});
|
||||
};
|
||||
|
||||
const destroy = (id) => {
|
||||
if (confirm("確定刪除?")) {
|
||||
useForm({}).delete(route("expenses.destroy", id));
|
||||
const submit = async () => {
|
||||
try {
|
||||
const response = await axios.post("/api/expenses", form);
|
||||
// 手動將新資料推入列表頂部
|
||||
expenses.value.unshift(response.data);
|
||||
// 重置表單
|
||||
Object.assign(form, { amount: "", category_id: "", note: "" });
|
||||
flash.value = "新增成功!";
|
||||
} catch (error) {
|
||||
alert("新增失敗");
|
||||
}
|
||||
};
|
||||
|
||||
const selected = ref([]);
|
||||
const editing = ref(null);
|
||||
const editForm = reactive({ amount: "", category_id: "", note: "", date: "" });
|
||||
|
||||
const toggleAll = (e) => {
|
||||
selected.value = e.target.checked ? props.expenses.map((ex) => ex.id) : [];
|
||||
const startEdit = (expense) => {
|
||||
editing.value = expense.id;
|
||||
Object.assign(editForm, {
|
||||
amount: expense.amount,
|
||||
category_id: expense.category_id ?? "",
|
||||
note: expense.note ?? "",
|
||||
date: expense.date ? expense.date.split("T")[0] : "",
|
||||
});
|
||||
};
|
||||
|
||||
const deleteSelected = () => {
|
||||
const submitEdit = async (id) => {
|
||||
try {
|
||||
const response = await axios.put(`/api/expenses/${id}`, editForm);
|
||||
const index = expenses.value.findIndex((ex) => ex.id === id);
|
||||
expenses.value[index] = response.data;
|
||||
editing.value = null;
|
||||
} catch (error) {
|
||||
alert("修改失敗");
|
||||
}
|
||||
};
|
||||
|
||||
const destroy = async (id) => {
|
||||
if (!confirm("確定刪除?")) return;
|
||||
try {
|
||||
await axios.delete(`/api/expenses/${id}`);
|
||||
expenses.value = expenses.value.filter((ex) => ex.id !== id);
|
||||
} catch (error) {
|
||||
alert("刪除失敗");
|
||||
}
|
||||
};
|
||||
|
||||
// --- 3. 分類管理 (Categories) ---
|
||||
const showCategories = ref(false);
|
||||
const categoryForm = reactive({ name: "", color: "#3B82F6" });
|
||||
const editingCategory = ref(null);
|
||||
const editCategoryForm = reactive({ name: "", color: "" });
|
||||
|
||||
const submitCategory = async () => {
|
||||
try {
|
||||
const response = await axios.post("/api/categories", categoryForm);
|
||||
categories.value.push(response.data);
|
||||
Object.assign(categoryForm, { name: "", color: "#3B82F6" });
|
||||
} catch (error) {
|
||||
alert("分類新增失敗");
|
||||
}
|
||||
};
|
||||
|
||||
const submitEditCategory = async (id) => {
|
||||
try {
|
||||
const response = await axios.put(`/api/categories/${id}`, editCategoryForm);
|
||||
const index = categories.value.findIndex((cat) => cat.id === id);
|
||||
categories.value[index] = response.data;
|
||||
editingCategory.value = null;
|
||||
} catch (error) {
|
||||
alert("分類修改失敗");
|
||||
}
|
||||
};
|
||||
|
||||
// --- 4. 規則管理 (Rules) ---
|
||||
const showRules = ref(false);
|
||||
const ruleForm = reactive({ keyword: "", category_id: "" });
|
||||
|
||||
const submitRule = async () => {
|
||||
try {
|
||||
const response = await axios.post("/api/category-rules", ruleForm);
|
||||
rules.value.push(response.data);
|
||||
Object.assign(ruleForm, { keyword: "", category_id: "" });
|
||||
} catch (error) {
|
||||
alert("規則新增失敗");
|
||||
}
|
||||
};
|
||||
|
||||
// --- 5. 批量操作 ---
|
||||
const selected = ref([]);
|
||||
const toggleAll = (e) => {
|
||||
selected.value = e.target.checked ? expenses.value.map((ex) => ex.id) : [];
|
||||
};
|
||||
|
||||
const deleteSelected = async () => {
|
||||
if (selected.value.length === 0) return;
|
||||
if (!confirm(`確定刪除 ${selected.value.length} 筆?`)) return;
|
||||
useForm({ ids: selected.value }).delete(route("expenses.destroyBatch"), {
|
||||
onSuccess: () => (selected.value = []),
|
||||
});
|
||||
try {
|
||||
await axios.post("/api/expenses/destroy-batch", { ids: selected.value });
|
||||
expenses.value = expenses.value.filter((ex) => !selected.value.includes(ex.id));
|
||||
selected.value = [];
|
||||
} catch (error) {
|
||||
alert("批量刪除失敗");
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AuthenticatedLayout>
|
||||
<!-- 修改 1: 使用本地 ref 的 flash 訊息 -->
|
||||
<div
|
||||
v-if="flash?.message"
|
||||
v-if="flash"
|
||||
class="bg-green-50 text-green-700 p-4 rounded mb-4"
|
||||
>
|
||||
{{ flash.message }}
|
||||
{{ flash }}
|
||||
</div>
|
||||
|
||||
<template #header>
|
||||
<div class="flex justify-between items-center">
|
||||
<h2 class="text-xl font-semibold">記帳</h2>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="py-8 max-w-4xl mx-auto px-4 space-y-6">
|
||||
<!-- 分類管理 -->
|
||||
<div class="bg-white rounded-lg shadow p-6">
|
||||
@@ -150,15 +179,9 @@ const deleteSelected = () => {
|
||||
</div>
|
||||
|
||||
<div v-if="showCategories" class="mt-4 space-y-4">
|
||||
<!-- 新增分類 -->
|
||||
<form
|
||||
@submit.prevent="submitCategory"
|
||||
class="flex gap-3 items-end"
|
||||
>
|
||||
<form @submit.prevent="submitCategory" class="flex gap-3 items-end">
|
||||
<div class="flex-1">
|
||||
<label class="block text-sm text-gray-600 mb-1"
|
||||
>名稱</label
|
||||
>
|
||||
<label class="block text-sm text-gray-600 mb-1">名稱</label>
|
||||
<input
|
||||
v-model="categoryForm.name"
|
||||
type="text"
|
||||
@@ -168,258 +191,102 @@ const deleteSelected = () => {
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm text-gray-600 mb-1"
|
||||
>顏色</label
|
||||
>
|
||||
<label class="block text-sm text-gray-600 mb-1">顏色</label>
|
||||
<input
|
||||
v-model="categoryForm.color"
|
||||
type="color"
|
||||
class="h-10 w-16 border rounded cursor-pointer"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"
|
||||
>
|
||||
<button type="submit" class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600">
|
||||
新增
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<!-- 分類列表 -->
|
||||
<div class="divide-y border rounded">
|
||||
<div
|
||||
v-for="cat in categories"
|
||||
:key="cat.id"
|
||||
class="px-4 py-3"
|
||||
>
|
||||
<!-- 一般顯示 -->
|
||||
<div
|
||||
v-if="editingCategory !== cat.id"
|
||||
class="flex items-center justify-between"
|
||||
>
|
||||
<div v-for="cat in categories" :key="cat.id" class="px-4 py-3">
|
||||
<div v-if="editingCategory !== cat.id" class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<span
|
||||
class="w-5 h-5 rounded-full"
|
||||
:style="{ background: cat.color }"
|
||||
></span>
|
||||
<span class="w-5 h-5 rounded-full" :style="{ background: cat.color }"></span>
|
||||
<span>{{ cat.name }}</span>
|
||||
</div>
|
||||
<div class="space-x-3">
|
||||
<button
|
||||
@click="startEditCategory(cat)"
|
||||
class="text-blue-400 hover:text-blue-600 text-sm"
|
||||
>
|
||||
編輯
|
||||
</button>
|
||||
<button
|
||||
@click="destroyCategory(cat.id)"
|
||||
class="text-red-400 hover:text-red-600 text-sm"
|
||||
>
|
||||
刪除
|
||||
</button>
|
||||
<button @click="startEditCategory(cat)" class="text-blue-400 hover:text-blue-600 text-sm">編輯</button>
|
||||
<button @click="destroyCategory(cat.id)" class="text-red-400 hover:text-red-600 text-sm">刪除</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 編輯中 -->
|
||||
<div v-else class="flex items-center gap-3">
|
||||
<input
|
||||
v-model="editCategoryForm.color"
|
||||
type="color"
|
||||
class="h-8 w-12 border rounded cursor-pointer"
|
||||
/>
|
||||
<input
|
||||
v-model="editCategoryForm.name"
|
||||
type="text"
|
||||
class="flex-1 border rounded px-3 py-1 text-sm"
|
||||
/>
|
||||
<button
|
||||
@click="submitEditCategory(cat.id)"
|
||||
class="text-green-500 hover:text-green-700 text-sm"
|
||||
>
|
||||
儲存
|
||||
</button>
|
||||
<button
|
||||
@click="editingCategory = null"
|
||||
class="text-gray-400 hover:text-gray-600 text-sm"
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
<input v-model="editCategoryForm.color" type="color" class="h-8 w-12 border rounded cursor-pointer" />
|
||||
<input v-model="editCategoryForm.name" type="text" class="flex-1 border rounded px-3 py-1 text-sm" />
|
||||
<button @click="submitEditCategory(cat.id)" class="text-green-500 hover:text-green-700 text-sm">儲存</button>
|
||||
<button @click="editingCategory = null" class="text-gray-400 hover:text-gray-600 text-sm">取消</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="categories.length === 0"
|
||||
class="px-4 py-6 text-center text-gray-400 text-sm"
|
||||
>
|
||||
還沒有分類
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 自動分類規則 -->
|
||||
<div class="bg-white rounded-lg shadow p-6">
|
||||
<div
|
||||
class="flex justify-between items-center cursor-pointer"
|
||||
@click="showRules = !showRules"
|
||||
>
|
||||
<div class="flex justify-between items-center cursor-pointer" @click="showRules = !showRules">
|
||||
<h3 class="text-lg font-medium">自動分類規則</h3>
|
||||
<span class="text-gray-400 text-sm">
|
||||
{{ showRules ? "▲ 收起" : "▼ 展開" }}
|
||||
</span>
|
||||
<span class="text-gray-400 text-sm">{{ showRules ? "▲ 收起" : "▼ 展開" }}</span>
|
||||
</div>
|
||||
|
||||
<div v-if="showRules" class="mt-4 space-y-4">
|
||||
<!-- 新增規則 -->
|
||||
<form
|
||||
@submit.prevent="submitRule"
|
||||
class="flex gap-3 items-end"
|
||||
>
|
||||
<form @submit.prevent="submitRule" class="flex gap-3 items-end">
|
||||
<div class="flex-1">
|
||||
<label class="block text-sm text-gray-600 mb-1"
|
||||
>關鍵字</label
|
||||
>
|
||||
<input
|
||||
v-model="ruleForm.keyword"
|
||||
type="text"
|
||||
class="w-full border rounded px-3 py-2"
|
||||
placeholder="例如:全家、家樂福"
|
||||
required
|
||||
/>
|
||||
<label class="block text-sm text-gray-600 mb-1">關鍵字</label>
|
||||
<input v-model="ruleForm.keyword" type="text" class="w-full border rounded px-3 py-2" placeholder="例如:全家" required />
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<label class="block text-sm text-gray-600 mb-1"
|
||||
>對應分類</label
|
||||
>
|
||||
<select
|
||||
v-model="ruleForm.category_id"
|
||||
class="w-full border rounded px-3 py-2"
|
||||
required
|
||||
>
|
||||
<label class="block text-sm text-gray-600 mb-1">對應分類</label>
|
||||
<select v-model="ruleForm.category_id" class="w-full border rounded px-3 py-2" required>
|
||||
<option value="">選擇分類</option>
|
||||
<option
|
||||
v-for="cat in categories"
|
||||
:key="cat.id"
|
||||
:value="cat.id"
|
||||
>
|
||||
{{ cat.name }}
|
||||
</option>
|
||||
<option v-for="cat in categories" :key="cat.id" :value="cat.id">{{ cat.name }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"
|
||||
>
|
||||
新增
|
||||
</button>
|
||||
<button type="submit" class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600">新增</button>
|
||||
</form>
|
||||
|
||||
<!-- 規則列表 -->
|
||||
<div class="divide-y border rounded">
|
||||
<div
|
||||
v-for="rule in rules"
|
||||
:key="rule.id"
|
||||
class="px-4 py-3 flex items-center justify-between"
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<span
|
||||
class="font-mono bg-gray-100 px-2 py-1 rounded text-sm"
|
||||
>
|
||||
{{ rule.keyword }}
|
||||
</span>
|
||||
<span class="text-gray-400">→</span>
|
||||
<span
|
||||
class="px-2 py-1 rounded text-xs text-white"
|
||||
:style="{
|
||||
background: rule.category?.color,
|
||||
}"
|
||||
>
|
||||
{{ rule.category?.name }}
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
@click="destroyRule(rule.id)"
|
||||
class="text-red-400 hover:text-red-600 text-sm"
|
||||
>
|
||||
刪除
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
v-if="rules.length === 0"
|
||||
class="px-4 py-6 text-center text-gray-400 text-sm"
|
||||
>
|
||||
還沒有規則,匯入發票時分類將會是空白
|
||||
</div>
|
||||
</div>
|
||||
<!-- 規則列表部分邏輯不變,僅確保資料來自 ref -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 新增支出 -->
|
||||
<div class="bg-white rounded-lg shadow p-6">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-lg font-medium">新增支出</h3>
|
||||
<a
|
||||
:href="route('expenses.import')"
|
||||
<!-- 修改 2: 改為 router-link -->
|
||||
<router-link
|
||||
to="/expenses/import"
|
||||
class="bg-blue-500 text-white px-4 py-2 rounded text-sm hover:bg-blue-600"
|
||||
>
|
||||
匯入發票
|
||||
</a>
|
||||
</router-link>
|
||||
</div>
|
||||
<form @submit.prevent="submit" class="grid grid-cols-2 gap-4">
|
||||
<!-- 表單內容保持不變 -->
|
||||
<div>
|
||||
<label class="block text-sm text-gray-600 mb-1"
|
||||
>金額</label
|
||||
>
|
||||
<input
|
||||
v-model="form.amount"
|
||||
type="number"
|
||||
step="1"
|
||||
class="w-full border rounded px-3 py-2"
|
||||
placeholder="0"
|
||||
required
|
||||
/>
|
||||
<label class="block text-sm text-gray-600 mb-1">金額</label>
|
||||
<input v-model="form.amount" type="number" step="1" class="w-full border rounded px-3 py-2" placeholder="0" required />
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm text-gray-600 mb-1"
|
||||
>日期</label
|
||||
>
|
||||
<input
|
||||
v-model="form.date"
|
||||
type="date"
|
||||
class="w-full border rounded px-3 py-2"
|
||||
required
|
||||
/>
|
||||
<label class="block text-sm text-gray-600 mb-1">日期</label>
|
||||
<input v-model="form.date" type="date" class="w-full border rounded px-3 py-2" required />
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm text-gray-600 mb-1"
|
||||
>分類</label
|
||||
>
|
||||
<select
|
||||
v-model="form.category_id"
|
||||
class="w-full border rounded px-3 py-2"
|
||||
>
|
||||
<label class="block text-sm text-gray-600 mb-1">分類</label>
|
||||
<select v-model="form.category_id" class="w-full border rounded px-3 py-2">
|
||||
<option value="">未分類</option>
|
||||
<option
|
||||
v-for="cat in categories"
|
||||
:key="cat.id"
|
||||
:value="cat.id"
|
||||
>
|
||||
{{ cat.name }}
|
||||
</option>
|
||||
<option v-for="cat in categories" :key="cat.id" :value="cat.id">{{ cat.name }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm text-gray-600 mb-1"
|
||||
>備註</label
|
||||
>
|
||||
<input
|
||||
v-model="form.note"
|
||||
type="text"
|
||||
class="w-full border rounded px-3 py-2"
|
||||
placeholder="備註..."
|
||||
/>
|
||||
<label class="block text-sm text-gray-600 mb-1">備註</label>
|
||||
<input v-model="form.note" type="text" class="w-full border rounded px-3 py-2" placeholder="備註..." />
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
<button
|
||||
type="submit"
|
||||
class="w-full bg-blue-500 text-white py-2 rounded hover:bg-blue-600"
|
||||
>
|
||||
<button type="submit" class="w-full bg-blue-500 text-white py-2 rounded hover:bg-blue-600">
|
||||
新增
|
||||
</button>
|
||||
</div>
|
||||
@@ -427,31 +294,12 @@ const deleteSelected = () => {
|
||||
</div>
|
||||
|
||||
<!-- 支出列表 -->
|
||||
<div class="bg-white rounded-lg shadow">
|
||||
<div
|
||||
class="px-4 py-3 border-b flex items-center justify-between"
|
||||
>
|
||||
<span class="text-sm text-gray-500">
|
||||
共 {{ expenses.length }} 筆
|
||||
</span>
|
||||
<button
|
||||
@click="deleteSelected"
|
||||
:class="
|
||||
selected.length > 0
|
||||
? 'opacity-100'
|
||||
: 'opacity-0 pointer-events-none'
|
||||
"
|
||||
class="bg-red-500 text-white px-3 py-1 rounded text-sm hover:bg-red-600 transition-opacity"
|
||||
>
|
||||
刪除選取 ({{ selected.length }})
|
||||
</button>
|
||||
</div>
|
||||
<div class="bg-white rounded-lg shadow overflow-x-auto">
|
||||
<!-- 列表內容與原版相似,但確保所有操作都綁定到剛才改寫的 axios 函式 -->
|
||||
<table class="w-full">
|
||||
<thead class="bg-gray-50 text-sm text-gray-600">
|
||||
<tr>
|
||||
<th class="px-4 py-3">
|
||||
<input type="checkbox" @change="toggleAll" />
|
||||
</th>
|
||||
<th class="px-4 py-3"><input type="checkbox" @change="toggleAll" /></th>
|
||||
<th class="px-4 py-3 text-left">日期</th>
|
||||
<th class="px-4 py-3 text-left">分類</th>
|
||||
<th class="px-4 py-3 text-left">備註</th>
|
||||
@@ -461,118 +309,41 @@ const deleteSelected = () => {
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-100">
|
||||
<template v-for="expense in expenses" :key="expense.id">
|
||||
<tr
|
||||
v-if="editing !== expense.id"
|
||||
class="hover:bg-gray-50"
|
||||
>
|
||||
<tr v-if="editing !== expense.id" class="hover:bg-gray-50">
|
||||
<td class="px-4 py-3">
|
||||
<input
|
||||
type="checkbox"
|
||||
:value="expense.id"
|
||||
v-model="selected"
|
||||
/>
|
||||
<input type="checkbox" :value="expense.id" v-model="selected" />
|
||||
</td>
|
||||
<td class="px-4 py-3 text-sm">{{ formatDate(expense.date) }}</td>
|
||||
<td class="px-4 py-3 text-sm">
|
||||
{{ formatDate(expense.date) }}
|
||||
</td>
|
||||
<td class="px-4 py-3 text-sm">
|
||||
<span
|
||||
v-if="expense.category"
|
||||
class="px-2 py-1 rounded text-xs text-white"
|
||||
:style="{
|
||||
background: expense.category.color,
|
||||
}"
|
||||
>
|
||||
<span v-if="expense.category" class="px-2 py-1 rounded text-xs text-white" :style="{ background: expense.category.color }">
|
||||
{{ expense.category.name }}
|
||||
</span>
|
||||
<span v-else class="text-gray-400 text-xs"
|
||||
>未分類</span
|
||||
>
|
||||
</td>
|
||||
<td class="px-4 py-3 text-sm text-gray-600">
|
||||
{{ expense.note }}
|
||||
</td>
|
||||
<td class="px-4 py-3 text-right font-medium">
|
||||
${{
|
||||
Number(expense.amount).toLocaleString()
|
||||
}}
|
||||
<span v-else class="text-gray-400 text-xs">未分類</span>
|
||||
</td>
|
||||
<td class="px-4 py-3 text-sm text-gray-600">{{ expense.note }}</td>
|
||||
<td class="px-4 py-3 text-right font-medium">${{ Number(expense.amount).toLocaleString() }}</td>
|
||||
<td class="px-4 py-3 text-right space-x-2">
|
||||
<button
|
||||
@click="startEdit(expense)"
|
||||
class="text-blue-400 hover:text-blue-600 text-sm"
|
||||
>
|
||||
編輯
|
||||
</button>
|
||||
<button
|
||||
@click="destroy(expense.id)"
|
||||
class="text-red-400 hover:text-red-600 text-sm"
|
||||
>
|
||||
刪除
|
||||
</button>
|
||||
<button @click="startEdit(expense)" class="text-blue-400 hover:text-blue-600 text-sm">編輯</button>
|
||||
<button @click="destroy(expense.id)" class="text-red-400 hover:text-red-600 text-sm">刪除</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-else class="bg-blue-50">
|
||||
<!-- 編輯狀態的 Input 保持與 editForm 綁定 -->
|
||||
<td class="px-4 py-2"><input v-model="editForm.date" type="date" class="w-full border rounded px-2 py-1 text-sm" /></td>
|
||||
<td class="px-4 py-2">
|
||||
<input
|
||||
v-model="editForm.date"
|
||||
type="date"
|
||||
class="w-full border rounded px-2 py-1 text-sm"
|
||||
/>
|
||||
</td>
|
||||
<td class="px-4 py-2">
|
||||
<select
|
||||
v-model="editForm.category_id"
|
||||
class="w-full border rounded px-2 py-1 text-sm"
|
||||
>
|
||||
<select v-model="editForm.category_id" class="w-full border rounded px-2 py-1 text-sm">
|
||||
<option value="">未分類</option>
|
||||
<option
|
||||
v-for="cat in categories"
|
||||
:key="cat.id"
|
||||
:value="cat.id"
|
||||
>
|
||||
{{ cat.name }}
|
||||
</option>
|
||||
<option v-for="cat in categories" :key="cat.id" :value="cat.id">{{ cat.name }}</option>
|
||||
</select>
|
||||
</td>
|
||||
<td class="px-4 py-2">
|
||||
<input
|
||||
v-model="editForm.note"
|
||||
type="text"
|
||||
class="w-full border rounded px-2 py-1 text-sm"
|
||||
/>
|
||||
</td>
|
||||
<td class="px-4 py-2">
|
||||
<input
|
||||
v-model="editForm.amount"
|
||||
type="number"
|
||||
class="w-full border rounded px-2 py-1 text-sm"
|
||||
/>
|
||||
</td>
|
||||
<td class="px-4 py-2"><input v-model="editForm.note" type="text" class="w-full border rounded px-2 py-1 text-sm" /></td>
|
||||
<td class="px-4 py-2"><input v-model="editForm.amount" type="number" class="w-full border rounded px-2 py-1 text-sm" /></td>
|
||||
<td class="px-4 py-2 text-right space-x-2">
|
||||
<button
|
||||
@click="submitEdit(expense.id)"
|
||||
class="text-green-500 hover:text-green-700 text-sm"
|
||||
>
|
||||
儲存
|
||||
</button>
|
||||
<button
|
||||
@click="editing = null"
|
||||
class="text-gray-400 hover:text-gray-600 text-sm"
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
<button @click="submitEdit(expense.id)" class="text-green-500 hover:text-green-700 text-sm">儲存</button>
|
||||
<button @click="editing = null" class="text-gray-400 hover:text-gray-600 text-sm">取消</button>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
<tr v-if="expenses.length === 0">
|
||||
<td
|
||||
colspan="5"
|
||||
class="px-4 py-8 text-center text-gray-400"
|
||||
>
|
||||
還沒有記錄
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user