feat: implement order export to Excel with excelize library
Build and Push Docker Image / build-and-push (push) Failing after 16s
Build and Push Docker Image / deploy (push) Has been skipped

This commit is contained in:
nuyue
2026-07-17 05:33:01 +08:00
parent 9107affb85
commit 0d7e0f3b1e
5 changed files with 146 additions and 10 deletions
+95 -1
View File
@@ -1,6 +1,7 @@
package handlers
import (
"fmt"
"math"
"net/http"
"strconv"
@@ -11,6 +12,7 @@ import (
"sale/internal/utils"
"github.com/gin-gonic/gin"
"github.com/xuri/excelize/v2"
)
type CartHandler struct{}
@@ -663,7 +665,99 @@ func (h *OrderHandler) Export(c *gin.Context) {
utils.DB.Preload("OrderItems.Product").Preload("ShippingAddress").Preload("User").
Order("created_at DESC").Find(&orders)
c.JSON(http.StatusOK, gin.H{"data": orders})
// 创建 Excel 文件
f := excelize.NewFile()
defer f.Close()
sheetName := "订单列表"
f.SetSheetName("Sheet1", sheetName)
// 设置表头
headers := []string{"订单ID", "用户名", "订单状态", "商品信息", "商品金额", "运费", "服务费", "通道费", "包装费", "国际计算手续费", "订单总额", "收货人", "联系电话", "收货地址", "快递单号", "创建时间"}
for i, header := range headers {
cell, _ := excelize.CoordinatesToCellName(i+1, 1)
f.SetCellValue(sheetName, cell, header)
}
// 设置表头样式
headerStyle, _ := f.NewStyle(&excelize.Style{
Font: &excelize.Font{Bold: true},
Fill: excelize.Fill{Type: "pattern", Color: []string{"#4472C4"}, Pattern: 1},
Alignment: &excelize.Alignment{Horizontal: "center", Vertical: "center"},
})
f.SetRowStyle(sheetName, 1, 1, headerStyle)
// 状态映射
statusMap := map[string]string{
"pending_payment": "待支付", "pending_confirm": "待确认", "pending_ship": "待发货",
"shipped": "已发货", "completed": "已完成", "refunding": "退款中", "refunded": "已退款", "cancelled": "已取消",
}
// 填充数据
for i, order := range orders {
row := i + 2
// 商品信息
var productsStr string
for _, item := range order.OrderItems {
if productsStr != "" {
productsStr += "; "
}
productsStr += fmt.Sprintf("%s x%d", item.Product.Name, item.Quantity)
}
// 收货地址
var addressStr string
if order.ShippingAddress != nil {
addressStr = order.ShippingAddress.Province + order.ShippingAddress.City + order.ShippingAddress.District + order.ShippingAddress.Address
}
// 填充单元格
f.SetCellValue(sheetName, fmt.Sprintf("A%d", row), order.ID)
f.SetCellValue(sheetName, fmt.Sprintf("B%d", row), order.User.Username)
f.SetCellValue(sheetName, fmt.Sprintf("C%d", row), statusMap[order.Status])
f.SetCellValue(sheetName, fmt.Sprintf("D%d", row), productsStr)
f.SetCellValue(sheetName, fmt.Sprintf("E%d", row), order.Subtotal)
f.SetCellValue(sheetName, fmt.Sprintf("F%d", row), order.ShippingFee)
f.SetCellValue(sheetName, fmt.Sprintf("G%d", row), order.ServiceFee)
f.SetCellValue(sheetName, fmt.Sprintf("H%d", row), order.ChannelFee)
f.SetCellValue(sheetName, fmt.Sprintf("I%d", row), order.PackagingFee)
f.SetCellValue(sheetName, fmt.Sprintf("J%d", row), order.InternationalFee)
f.SetCellValue(sheetName, fmt.Sprintf("K%d", row), order.TotalAmount)
if order.ShippingAddress != nil {
f.SetCellValue(sheetName, fmt.Sprintf("L%d", row), order.ShippingAddress.Name)
f.SetCellValue(sheetName, fmt.Sprintf("M%d", row), order.ShippingAddress.Phone)
f.SetCellValue(sheetName, fmt.Sprintf("N%d", row), addressStr)
}
f.SetCellValue(sheetName, fmt.Sprintf("O%d", row), order.TrackingNumber)
f.SetCellValue(sheetName, fmt.Sprintf("P%d", row), order.CreatedAt.Format("2006-01-02 15:04:05"))
}
// 设置列宽
f.SetColWidth(sheetName, "A", "A", 10)
f.SetColWidth(sheetName, "B", "B", 15)
f.SetColWidth(sheetName, "C", "C", 10)
f.SetColWidth(sheetName, "D", "D", 40)
f.SetColWidth(sheetName, "E", "K", 12)
f.SetColWidth(sheetName, "L", "M", 15)
f.SetColWidth(sheetName, "N", "N", 40)
f.SetColWidth(sheetName, "O", "O", 20)
f.SetColWidth(sheetName, "P", "P", 20)
// 设置响应头
filename := fmt.Sprintf("orders_%s.xlsx", time.Now().Format("20060102150405"))
c.Header("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filename))
c.Header("Content-Transfer-Encoding", "binary")
// 写入响应
buf, err := f.WriteToBuffer()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate Excel file"})
return
}
c.Data(http.StatusOK, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", buf.Bytes())
}
func (h *OrderHandler) CancelOrder(c *gin.Context) {