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:
@@ -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
38
client/src/api/backup.ts
Normal 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
32
client/src/api/export.ts
Normal 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()}`
|
||||
}
|
||||
@@ -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
15
client/src/api/health.ts
Normal 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' })
|
||||
}
|
||||
@@ -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
47
client/src/api/tag.ts
Normal 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 })
|
||||
}
|
||||
@@ -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 })
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user