refactor: API 请求整合与 Bug 修复

前端重构:
- 新增 api/ 目录统一管理所有 API 请求
- 所有 stores 和 pages 改为使用 api 模块
- 修复日期格式化函数,支持 Date 对象和 ISO 字符串
- 修复 TransactionItem 使用 CategoryIcon 组件
- 优化 ChartWrapper 区分 H5 和小程序环境

后端修复:
- 修复 auth 中间件 PUBLIC_PATHS 缺少 demo-login
- 修复备份工具命令注入漏洞,改用 execFile
- 修复 stats 路由 type 参数验证
- 优化分类排序为批量 SQL 更新
- 合并迁移和删除为数据库事务原子操作
- 修复交易和统计日期返回格式
- 新增自动备份功能(启动时备份)
This commit is contained in:
2026-06-02 17:41:35 +08:00
parent d5e5655b88
commit db81ad8eb2
26 changed files with 908 additions and 258 deletions

View File

@@ -1,25 +1,19 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { request } from '@/utils/request'
import * as api from '@/api/budget'
import { getCurrentMonth } from '@/utils/format'
export interface Budget {
id: number
amount: number
month: string
}
// 重新导出类型以保持向后兼容
export type Budget = api.Budget
export const useBudgetStore = defineStore('budget', () => {
const budget = ref<Budget | null>(null)
const budget = ref<api.Budget | null>(null)
const loading = ref(false)
async function fetchBudget(month?: string) {
loading.value = true
try {
budget.value = await request<Budget>({
url: '/budget',
data: { month: month || getCurrentMonth() }
})
budget.value = await api.getBudget(month || getCurrentMonth())
} catch {
budget.value = null
} finally {
@@ -28,11 +22,7 @@ export const useBudgetStore = defineStore('budget', () => {
}
async function setBudget(amount: number, month?: string) {
await request({
url: '/budget',
method: 'POST',
data: { amount, month: month || getCurrentMonth() }
})
await api.setBudget(amount, month || getCurrentMonth())
await fetchBudget(month)
}

View File

@@ -1,25 +1,18 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { request } from '@/utils/request'
import * as api from '@/api/category'
export interface Category {
id: number
name: string
icon: string
color: string
type: 'expense' | 'income'
sort_order: number
is_custom: number
}
// 重新导出类型以保持向后兼容
export type Category = api.Category
export const useCategoryStore = defineStore('category', () => {
const categories = ref<Category[]>([])
const categories = ref<api.Category[]>([])
const loading = ref(false)
async function fetchCategories() {
loading.value = true
try {
categories.value = await request<Category[]>({ url: '/categories' })
categories.value = await api.getCategories()
} catch {
categories.value = []
} finally {
@@ -36,19 +29,29 @@ export const useCategoryStore = defineStore('category', () => {
}
async function addCategory(data: { name: string; icon: string; color: string; type: 'expense' | 'income' }) {
const result = await request<{ id: number }>({
url: '/categories',
method: 'POST',
data
})
const result = await api.createCategory(data)
await fetchCategories()
return result.id
}
async function deleteCategory(id: number) {
await request({ url: `/categories/${id}`, method: 'DELETE' })
async function updateCategory(id: number, data: { name: string; color: string }) {
await api.updateCategory(id, data)
await fetchCategories()
}
return { categories, loading, fetchCategories, getByType, getById, addCategory, deleteCategory }
async function deleteCategory(id: number) {
await api.deleteCategory(id)
await fetchCategories()
}
async function migrateCategory(fromId: number, toId: number) {
await api.migrateCategory(fromId, toId)
}
async function sortCategories(ids: number[]) {
await api.sortCategories(ids)
await fetchCategories()
}
return { categories, loading, fetchCategories, getByType, getById, addCategory, updateCategory, deleteCategory, migrateCategory, sortCategories }
})

View File

@@ -1,40 +1,21 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { request } from '@/utils/request'
import * as api from '@/api/stats'
import { getCurrentMonth } from '@/utils/format'
export interface Overview {
expense: number
income: number
count: number
daily: number
}
export interface CategoryStat {
id: number
name: string
icon: string
color: string
amount: number
count: number
}
export interface TrendPoint {
date: string
amount: number
}
// 重新导出类型以保持向后兼容
export type Overview = api.Overview
export type CategoryStat = api.CategoryStat
export type TrendPoint = api.TrendPoint
export const useStatsStore = defineStore('stats', () => {
const overview = ref<Overview>({ expense: 0, income: 0, count: 0, daily: 0 })
const categoryStats = ref<CategoryStat[]>([])
const trendData = ref<TrendPoint[]>([])
const overview = ref<api.Overview>({ expense: 0, income: 0, count: 0, daily: 0 })
const categoryStats = ref<api.CategoryStat[]>([])
const trendData = ref<api.TrendPoint[]>([])
async function fetchOverview(month?: string) {
try {
overview.value = await request<Overview>({
url: '/stats/overview',
data: { month: month || getCurrentMonth() }
})
overview.value = await api.getOverview({ month: month || getCurrentMonth() })
} catch {
overview.value = { expense: 0, income: 0, count: 0, daily: 0 }
}
@@ -42,10 +23,7 @@ export const useStatsStore = defineStore('stats', () => {
async function fetchCategoryStats(month?: string, type: string = 'expense') {
try {
categoryStats.value = await request<CategoryStat[]>({
url: '/stats/category',
data: { month: month || getCurrentMonth(), type }
})
categoryStats.value = await api.getCategoryStats(month || getCurrentMonth(), type)
} catch {
categoryStats.value = []
}
@@ -53,10 +31,7 @@ export const useStatsStore = defineStore('stats', () => {
async function fetchTrend(month?: string, type: string = 'expense') {
try {
trendData.value = await request<TrendPoint[]>({
url: '/stats/trend',
data: { month: month || getCurrentMonth(), type }
})
trendData.value = await api.getTrend(month || getCurrentMonth(), type)
} catch {
trendData.value = []
}

View File

@@ -1,39 +1,17 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { request } from '@/utils/request'
export interface Transaction {
id: number
amount: number
type: 'expense' | 'income'
category_id: number
note?: string
date: string
category_name?: string
category_icon?: string
category_color?: string
}
export interface TransactionList {
list: Transaction[]
total: number
page: number
pageSize: number
}
import * as api from '@/api/transaction'
export const useTransactionStore = defineStore('transaction', () => {
const transactions = ref<Transaction[]>([])
const transactions = ref<api.Transaction[]>([])
const total = ref(0)
const loading = ref(false)
const currentPage = ref(1)
async function fetchTransactions(params?: { type?: string; startDate?: string; endDate?: string; page?: number; pageSize?: number }) {
async function fetchTransactions(params?: api.TransactionParams) {
loading.value = true
try {
const data = await request<TransactionList>({
url: '/transactions',
data: params
})
const data = await api.getTransactions({ page: 1, ...params })
transactions.value = data.list
total.value = data.total
currentPage.value = data.page
@@ -45,26 +23,34 @@ export const useTransactionStore = defineStore('transaction', () => {
}
}
async function addTransaction(data: { amount: number; type: string; category_id?: number; note?: string; date: string }) {
const result = await request<{ id: number }>({
url: '/transactions',
method: 'POST',
data
})
async function addTransaction(data: {
amount: number
type: 'expense' | 'income'
category_id?: number
note?: string
date: string
}) {
const result = await api.createTransaction(data)
return result.id
}
async function updateTransaction(id: number, data: { amount: number; type: string; category_id?: number; note?: string; date: string }) {
await request({ url: `/transactions/${id}`, method: 'PUT', data })
async function updateTransaction(id: number, data: {
amount: number
type: 'expense' | 'income'
category_id?: number
note?: string
date: string
}) {
await api.updateTransaction(id, data)
}
async function deleteTransaction(id: number) {
await request({ url: `/transactions/${id}`, method: 'DELETE' })
await api.deleteTransaction(id)
}
async function fetchTransactionById(id: number): Promise<Transaction | undefined> {
async function fetchTransactionById(id: number) {
try {
return await request<Transaction>({ url: `/transactions/${id}` })
return await api.getTransaction(id)
} catch {
return undefined
}