feat: 完善项目架构并增强分析页面功能
- 新增项目文档和 Docker 配置 - 添加 README.md 和 TODO.md 项目文档 - 为各服务添加 Dockerfile 和 docker-compose 配置 - 重构后端架构 - 新增 adapter 层(HTTP/Python 适配器) - 新增 repository 层(数据访问抽象) - 新增 router 模块统一管理路由 - 新增账单处理 handler - 扩展前端 UI 组件库 - 新增 Calendar、DateRangePicker、Drawer、Popover 等组件 - 集成 shadcn-svelte 组件库 - 增强分析页面功能 - 添加时间范围筛选器(支持本月默认值) - 修复 DateRangePicker 默认值显示问题 - 优化数据获取和展示逻辑 - 完善分析器服务 - 新增 FastAPI 服务接口 - 改进账单清理器实现
This commit is contained in:
165
server/handler/bills.go
Normal file
165
server/handler/bills.go
Normal file
@@ -0,0 +1,165 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"billai-server/model"
|
||||
"billai-server/repository"
|
||||
)
|
||||
|
||||
// ListBillsRequest 账单列表请求参数
|
||||
type ListBillsRequest struct {
|
||||
Page int `form:"page"` // 页码,从 1 开始
|
||||
PageSize int `form:"page_size"` // 每页数量,默认 20
|
||||
StartDate string `form:"start_date"` // 开始日期 YYYY-MM-DD
|
||||
EndDate string `form:"end_date"` // 结束日期 YYYY-MM-DD
|
||||
Category string `form:"category"` // 分类筛选
|
||||
Type string `form:"type"` // 账单类型 alipay/wechat
|
||||
IncomeExpense string `form:"income_expense"` // 收支类型 收入/支出
|
||||
}
|
||||
|
||||
// ListBillsResponse 账单列表响应
|
||||
type ListBillsResponse struct {
|
||||
Result bool `json:"result"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Data *ListBillsData `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// ListBillsData 账单列表数据
|
||||
type ListBillsData struct {
|
||||
Total int64 `json:"total"` // 总记录数
|
||||
TotalExpense float64 `json:"total_expense"` // 筛选条件下的总支出
|
||||
TotalIncome float64 `json:"total_income"` // 筛选条件下的总收入
|
||||
Page int `json:"page"` // 当前页码
|
||||
PageSize int `json:"page_size"` // 每页数量
|
||||
Pages int `json:"pages"` // 总页数
|
||||
Bills []model.CleanedBill `json:"bills"` // 账单列表
|
||||
}
|
||||
|
||||
// ListBills 获取清洗后的账单列表
|
||||
func ListBills(c *gin.Context) {
|
||||
var req ListBillsRequest
|
||||
if err := c.ShouldBindQuery(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, ListBillsResponse{
|
||||
Result: false,
|
||||
Message: "参数解析失败: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 设置默认值
|
||||
if req.Page < 1 {
|
||||
req.Page = 1
|
||||
}
|
||||
if req.PageSize < 1 {
|
||||
req.PageSize = 20
|
||||
}
|
||||
if req.PageSize > 100 {
|
||||
req.PageSize = 100 // 限制最大每页数量
|
||||
}
|
||||
|
||||
// 构建筛选条件
|
||||
filter := make(map[string]interface{})
|
||||
|
||||
// 时间范围筛选
|
||||
if req.StartDate != "" || req.EndDate != "" {
|
||||
timeFilter := make(map[string]interface{})
|
||||
if req.StartDate != "" {
|
||||
startTime, err := time.Parse("2006-01-02", req.StartDate)
|
||||
if err == nil {
|
||||
timeFilter["$gte"] = startTime
|
||||
}
|
||||
}
|
||||
if req.EndDate != "" {
|
||||
endTime, err := time.Parse("2006-01-02", req.EndDate)
|
||||
if err == nil {
|
||||
// 结束日期包含当天,所以加一天
|
||||
endTime = endTime.Add(24 * time.Hour)
|
||||
timeFilter["$lt"] = endTime
|
||||
}
|
||||
}
|
||||
if len(timeFilter) > 0 {
|
||||
filter["time"] = timeFilter
|
||||
}
|
||||
}
|
||||
|
||||
// 分类筛选
|
||||
if req.Category != "" {
|
||||
filter["category"] = req.Category
|
||||
}
|
||||
|
||||
// 账单类型筛选
|
||||
if req.Type != "" {
|
||||
filter["bill_type"] = req.Type
|
||||
}
|
||||
|
||||
// 收支类型筛选
|
||||
if req.IncomeExpense != "" {
|
||||
filter["income_expense"] = req.IncomeExpense
|
||||
}
|
||||
|
||||
// 获取数据
|
||||
repo := repository.GetRepository()
|
||||
if repo == nil {
|
||||
c.JSON(http.StatusInternalServerError, ListBillsResponse{
|
||||
Result: false,
|
||||
Message: "数据库未连接",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 获取账单列表(带分页)
|
||||
bills, total, err := repo.GetCleanedBillsPaged(filter, req.Page, req.PageSize)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, ListBillsResponse{
|
||||
Result: false,
|
||||
Message: "查询失败: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 获取聚合统计
|
||||
totalExpense, totalIncome, err := repo.GetBillsAggregate(filter)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, ListBillsResponse{
|
||||
Result: false,
|
||||
Message: "统计失败: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 计算总页数
|
||||
pages := int(total) / req.PageSize
|
||||
if int(total)%req.PageSize > 0 {
|
||||
pages++
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, ListBillsResponse{
|
||||
Result: true,
|
||||
Data: &ListBillsData{
|
||||
Total: total,
|
||||
TotalExpense: totalExpense,
|
||||
TotalIncome: totalIncome,
|
||||
Page: req.Page,
|
||||
PageSize: req.PageSize,
|
||||
Pages: pages,
|
||||
Bills: bills,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// parsePageParam 解析分页参数
|
||||
func parsePageParam(s string, defaultVal int) int {
|
||||
if s == "" {
|
||||
return defaultVal
|
||||
}
|
||||
val, err := strconv.Atoi(s)
|
||||
if err != nil || val < 1 {
|
||||
return defaultVal
|
||||
}
|
||||
return val
|
||||
}
|
||||
Reference in New Issue
Block a user