package handler import ( "fmt" "net/http" "time" "github.com/gin-gonic/gin" "github.com/xuri/excelize/v2" "billai-server/repository" ) type ExportBillsRequest struct { StartDate string `form:"start_date"` EndDate string `form:"end_date"` Category string `form:"category"` Type string `form:"type"` IncomeExpense string `form:"income_expense"` } func ExportBills(c *gin.Context) { var req ExportBillsRequest if err := c.ShouldBindQuery(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "result": false, "message": "参数解析失败: " + err.Error(), }) return } filter := buildFilterFromRequest(req) repo := repository.GetRepository() if repo == nil { c.JSON(http.StatusInternalServerError, gin.H{ "result": false, "message": "数据库未连接", }) return } bills, err := repo.GetCleanedBills(filter) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{ "result": false, "message": "查询失败: " + err.Error(), }) return } f := excelize.NewFile() sheet := "账单" f.SetSheetName("Sheet1", sheet) headers := []string{"时间", "来源", "分类", "交易对方", "商品说明", "收/支", "金额", "支付方式", "状态", "备注"} for i, header := range headers { cell, _ := excelize.CoordinatesToCellName(i+1, 1) f.SetCellValue(sheet, cell, header) } for idx, bill := range bills { row := idx + 2 f.SetCellValue(sheet, fmt.Sprintf("A%d", row), bill.Time.Time().Format("2006-01-02 15:04:05")) f.SetCellValue(sheet, fmt.Sprintf("B%d", row), bill.BillType) f.SetCellValue(sheet, fmt.Sprintf("C%d", row), bill.Category) f.SetCellValue(sheet, fmt.Sprintf("D%d", row), bill.Merchant) f.SetCellValue(sheet, fmt.Sprintf("E%d", row), bill.Description) f.SetCellValue(sheet, fmt.Sprintf("F%d", row), bill.IncomeExpense) f.SetCellValue(sheet, fmt.Sprintf("G%d", row), bill.Amount) f.SetCellValue(sheet, fmt.Sprintf("H%d", row), bill.PayMethod) f.SetCellValue(sheet, fmt.Sprintf("I%d", row), bill.Status) f.SetCellValue(sheet, fmt.Sprintf("J%d", row), bill.Remark) } f.SetColWidth(sheet, "A", "A", 20) f.SetColWidth(sheet, "B", "B", 8) f.SetColWidth(sheet, "C", "C", 12) f.SetColWidth(sheet, "D", "D", 20) f.SetColWidth(sheet, "E", "E", 30) f.SetColWidth(sheet, "F", "F", 8) f.SetColWidth(sheet, "G", "G", 12) f.SetColWidth(sheet, "H", "H", 15) f.SetColWidth(sheet, "I", "I", 10) f.SetColWidth(sheet, "J", "J", 20) filename := fmt.Sprintf("bills_%s.xlsx", time.Now().Format("20060102_150405")) c.Header("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filename)) c.Header("Access-Control-Expose-Headers", "Content-Disposition") if err := f.Write(c.Writer); err != nil { fmt.Printf("导出 Excel 失败: %v\n", err) } } func buildFilterFromRequest(req ExportBillsRequest) map[string]interface{} { filter := make(map[string]interface{}) if req.StartDate != "" || req.EndDate != "" { timeFilter := make(map[string]interface{}) if req.StartDate != "" { startTime, err := time.ParseInLocation("2006-01-02", req.StartDate, time.Local) if err == nil { timeFilter["$gte"] = startTime } } if req.EndDate != "" { endTime, err := time.ParseInLocation("2006-01-02", req.EndDate, time.Local) 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 } filter["is_deleted"] = false return filter }