- 后端新增 /api/bills/export 接口,支持当前筛选条件导出全部记录 - 使用 excelize 库生成 xlsx 格式文件 - 前端账单管理页面添加导出按钮 - 更新 Go 版本到 1.24 以支持 excelize 依赖
135 lines
3.7 KiB
Go
135 lines
3.7 KiB
Go
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
|
|
}
|