package handler import ( "encoding/csv" "fmt" "net/http" "net/url" "strconv" "strings" "time" "github.com/gin-gonic/gin" ) const exportMaxRows = 5000 func csvEscape(s string) string { return strings.ReplaceAll(s, "\r\n", " ") } func writeCSVResponse(c *gin.Context, filename string, header []string, rows [][]string) { if !strings.HasSuffix(strings.ToLower(filename), ".csv") { filename += ".csv" } // 兼容 Excel 中文:UTF-8 BOM c.Header("Content-Type", "text/csv; charset=utf-8") c.Header("Content-Disposition", fmt.Sprintf("attachment; filename*=UTF-8''%s", url.PathEscape(filename))) c.Status(http.StatusOK) if _, err := c.Writer.Write([]byte{0xEF, 0xBB, 0xBF}); err != nil { return } w := csv.NewWriter(c.Writer) _ = w.Write(header) for _, row := range rows { for i := range row { row[i] = csvEscape(row[i]) } _ = w.Write(row) } w.Flush() } func formatCSVTime(t time.Time) string { if t.IsZero() { return "" } return t.In(time.Local).Format("2006-01-02 15:04:05") } func formatCSVTimePtr(t *time.Time) string { if t == nil { return "" } return formatCSVTime(*t) } func formatUintPtr(v *uint) string { if v == nil { return "" } return strconv.FormatUint(uint64(*v), 10) } func formatIntPtr(v *int) string { if v == nil { return "" } return strconv.Itoa(*v) }