feat: 前后端功能对齐 - 实现8个对齐差距

P0-1: 分类拖拽排序 - category-manage 添加拖拽 UI
P1-1: 反馈回复展示 - 新增 GET /feedback/mine + 前端我的反馈Tab
P1-2: 备份管理页面 - 新增下载端点 + backup-manage 页面
P2-1: 统计年度/周视图 - stats 支持 period 参数 + 前端维度切换器
P2-2: 数据导入 - 新增 POST /import 端点 + data-import 页面
P2-3: 数据导出服务端化 - 新增 GET /export 流式端点
P3-1: 健康检查展示 - health 移到 auth 前 + admin 状态卡片
P3-2~4: 交易标签系统 - tags CRUD + 交易关联 + 按标签筛选统计

后端: 新增 tag.ts/export.ts 路由, 改造 feedback/backup/transaction/stats
前端: 新增 DragSortList 组件, 3个新页面, 改造 7 个现有页面
QA 修复: 5个严重Bug + 4个潜在问题
This commit is contained in:
2026-06-10 17:30:36 +08:00
parent 5df910c9f1
commit 31f6487d61
36 changed files with 4162 additions and 64 deletions

View File

@@ -54,3 +54,11 @@ export function updateUserRole(id: number, role: 'user' | 'admin') {
export function deleteUser(id: number) {
return request({ url: `/admin/users/${id}`, method: 'DELETE' })
}
// 健康检查类型从 @/api/health 导入
export type { HealthStatus } from '@/api/health'
/** 获取服务健康状态(管理员) */
export function getHealth() {
return request<import('./health').HealthStatus>({ url: '/health' })
}

38
client/src/api/backup.ts Normal file
View File

@@ -0,0 +1,38 @@
import { request } from '@/utils/request'
/** 备份记录 */
export interface Backup {
id: number
name: string
size: number
status: 'pending' | 'completed' | 'failed'
created_at: string
downloadToken?: string
}
/** 备份列表响应 */
export interface BackupList {
list: Backup[]
total: number
}
/** 获取备份列表 */
export function getBackups(params?: { page?: number; pageSize?: number }) {
return request<BackupList>({ url: '/backup', data: params })
}
/** 创建备份 */
export function createBackup() {
return request<{ id: number }>({ url: '/backup', method: 'POST' })
}
/** 删除备份 */
export function deleteBackup(id: number) {
return request({ url: `/backup/${id}`, method: 'DELETE' })
}
/** 获取备份下载地址 */
export function getBackupDownloadUrl(id: number, token: string): string {
const apiBase = typeof window !== 'undefined' ? '/api' : ''
return `${apiBase}/backup/${id}/download?token=${encodeURIComponent(token)}`
}

32
client/src/api/export.ts Normal file
View File

@@ -0,0 +1,32 @@
import { request } from '@/utils/request'
import { API_BASE } from '@/config'
/** 导出参数 */
export interface ExportParams {
startDate: string
endDate: string
type?: 'expense' | 'income' | 'all'
format?: 'csv' | 'json'
}
/** 请求服务端导出(返回流) */
export function requestExport(params: ExportParams) {
return request<{ downloadUrl: string }>({
url: '/export',
data: { ...params, format: params.format || 'csv' }
})
}
/** 获取服务端导出下载 URL */
export function getExportUrl(params: ExportParams): string {
const query = new URLSearchParams()
query.set('startDate', params.startDate)
query.set('endDate', params.endDate)
if (params.type) query.set('type', params.type)
if (params.format) query.set('format', params.format)
const token = uni.getStorageSync('xc:token')
if (token) query.set('token', token)
return `${API_BASE}/export?${query.toString()}`
}

View File

@@ -29,6 +29,22 @@ export function getFeedbackList(params?: { page?: number; pageSize?: number; sta
return request<{ list: Feedback[]; total: number; page: number; pageSize: number }>({ url: '/feedback', data: params })
}
/** 我的反馈条目(简化版) */
export interface MyFeedback {
id: number
type: string
content: string
contact: string
status: 'pending' | 'processed' | 'ignored'
admin_reply?: string
created_at: string
}
/** 获取我的反馈列表 */
export function getMyFeedbacks(params?: { page?: number; pageSize?: number }) {
return request<{ list: MyFeedback[]; total: number; page: number; pageSize: number }>({ url: '/feedback/mine', data: params })
}
/** 更新反馈状态(管理员) */
export function updateFeedbackStatus(id: number, status: Feedback['status'], admin_reply?: string) {
return request({ url: `/feedback/${id}/status`, method: 'PUT', data: { status, admin_reply } })

15
client/src/api/health.ts Normal file
View File

@@ -0,0 +1,15 @@
import { request } from '@/utils/request'
/** 健康检查结果 */
export interface HealthStatus {
status: 'ok' | 'degraded' | 'error'
uptime: number
db: 'connected' | 'disconnected'
version: string
timestamp: string
}
/** 获取服务健康状态 */
export function getHealth() {
return request<HealthStatus>({ url: '/health' })
}

View File

@@ -25,6 +25,7 @@ export interface CategoryStat {
export interface TrendPoint {
date: string
amount: number
label?: string
}
/** 获取概览数据 */
@@ -33,13 +34,13 @@ export function getOverview(params?: { month?: string; startDate?: string; endDa
}
/** 获取分类统计 */
export function getCategoryStats(month?: string, type: string = 'expense', group_id?: number | null) {
return request<CategoryStat[]>({ url: '/stats/category', data: { month, type, group_id } })
export function getCategoryStats(month?: string, type: string = 'expense', group_id?: number | null, period?: 'week' | 'month' | 'year') {
return request<CategoryStat[]>({ url: '/stats/category', data: { month, type, group_id, period } })
}
/** 获取趋势数据 */
export function getTrend(month?: string, type: string = 'expense', group_id?: number | null) {
return request<TrendPoint[]>({ url: '/stats/trend', data: { month, type, group_id } })
export function getTrend(month?: string, type: string = 'expense', group_id?: number | null, period?: 'week' | 'month' | 'year') {
return request<TrendPoint[]>({ url: '/stats/trend', data: { month, type, group_id, period } })
}
/** 智能分类建议结果 */

47
client/src/api/tag.ts Normal file
View File

@@ -0,0 +1,47 @@
import { request } from '@/utils/request'
/** 标签 */
export interface Tag {
id: number
name: string
color: string
created_at: string
}
/** 标签统计 */
export interface TagStat {
tag_id: number
tag_name: string
tag_color: string
count: number
total_amount: number
}
/** 获取标签列表 */
export function getTags() {
return request<Tag[]>({ url: '/tags' })
}
/** 创建标签 */
export function createTag(data: { name: string; color: string }) {
return request<{ id: number }>({ url: '/tags', method: 'POST', data })
}
/** 更新标签 */
export function updateTag(id: number, data: { name?: string; color?: string }) {
return request({ url: `/tags/${id}`, method: 'PUT', data })
}
/** 删除标签 */
export function deleteTag(id: number) {
return request({ url: `/tags/${id}`, method: 'DELETE' })
}
/** 按标签统计 */
export function getTagStats(params?: {
month?: string
period?: 'week' | 'month' | 'year'
type?: 'expense' | 'income'
}) {
return request<TagStat[]>({ url: '/tags/stats/by-tag', data: params })
}

View File

@@ -15,6 +15,14 @@ export interface Transaction {
group_id?: number | null
creator_nickname?: string
creator_avatar?: string
tags?: TagItem[]
}
/** 标签简略信息 */
export interface TagItem {
id: number
name: string
color: string
}
/** 交易列表响应 */
@@ -40,6 +48,7 @@ export interface TransactionParams {
minAmount?: number
maxAmount?: number
keyword?: string
tagId?: number
}
/** 获取交易列表 */
@@ -60,6 +69,7 @@ export function createTransaction(data: {
note?: string
date: string
group_id?: number | null
tagIds?: number[]
}) {
return request<{ id: number }>({ url: '/transactions', method: 'POST', data })
}
@@ -71,6 +81,7 @@ export function updateTransaction(id: number, data: {
category_id?: number
note?: string
date: string
tagIds?: number[]
}) {
return request({ url: `/transactions/${id}`, method: 'PUT', data })
}
@@ -79,3 +90,16 @@ export function updateTransaction(id: number, data: {
export function deleteTransaction(id: number) {
return request({ url: `/transactions/${id}`, method: 'DELETE' })
}
/** 导入交易记录 */
export function importTransactions(data: {
transactions: Array<{
date: string
type: 'expense' | 'income'
category_name: string
amount: number
note?: string
}>
}) {
return request<{ imported: number; skipped: number }>({ url: '/transactions/import', method: 'POST', data })
}