增加导出数据到Excel的方法
This commit is contained in:
481
excel/excel.go
Normal file
481
excel/excel.go
Normal file
@@ -0,0 +1,481 @@
|
||||
package excel
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/xuri/excelize/v2"
|
||||
)
|
||||
|
||||
// Excel Excel导出器
|
||||
type Excel struct {
|
||||
file *excelize.File
|
||||
}
|
||||
|
||||
// NewExcel 创建Excel导出器
|
||||
func NewExcel() *Excel {
|
||||
return &Excel{
|
||||
file: excelize.NewFile(),
|
||||
}
|
||||
}
|
||||
|
||||
// ExportColumn 导出列定义
|
||||
type ExportColumn struct {
|
||||
// Header 表头名称
|
||||
Header string
|
||||
// Field 数据字段名(支持嵌套字段,如 "User.Name")
|
||||
Field string
|
||||
// Width 列宽(可选,0表示自动)
|
||||
Width float64
|
||||
// Format 格式化函数(可选,用于自定义字段值的格式化)
|
||||
Format func(value interface{}) string
|
||||
}
|
||||
|
||||
// ExportData 导出数据接口
|
||||
// 实现此接口的结构体可以直接导出
|
||||
type ExportData interface {
|
||||
// GetExportColumns 获取导出列定义
|
||||
GetExportColumns() []ExportColumn
|
||||
// GetExportRows 获取导出数据行
|
||||
GetExportRows() [][]interface{}
|
||||
}
|
||||
|
||||
// ExportToWriter 导出数据到Writer(黑盒模式,推荐使用)
|
||||
// sheetName: 工作表名称(可选,默认为"Sheet1")
|
||||
// columns: 列定义
|
||||
// data: 数据列表(可以是结构体切片或实现了ExportData接口的对象)
|
||||
// 返回错误信息
|
||||
//
|
||||
// 示例1:导出结构体切片
|
||||
//
|
||||
// type User struct {
|
||||
// ID int `json:"id"`
|
||||
// Name string `json:"name"`
|
||||
// Email string `json:"email"`
|
||||
// }
|
||||
//
|
||||
// users := []User{
|
||||
// {ID: 1, Name: "Alice", Email: "alice@example.com"},
|
||||
// {ID: 2, Name: "Bob", Email: "bob@example.com"},
|
||||
// }
|
||||
//
|
||||
// columns := []ExportColumn{
|
||||
// {Header: "ID", Field: "ID"},
|
||||
// {Header: "姓名", Field: "Name"},
|
||||
// {Header: "邮箱", Field: "Email"},
|
||||
// }
|
||||
//
|
||||
// excel := excel.NewExcel()
|
||||
// err := excel.ExportToWriter(w, "用户列表", columns, users)
|
||||
//
|
||||
// 示例2:使用格式化函数
|
||||
//
|
||||
// columns := []ExportColumn{
|
||||
// {Header: "ID", Field: "ID"},
|
||||
// {Header: "姓名", Field: "Name"},
|
||||
// {Header: "创建时间", Field: "CreatedAt", Format: func(v interface{}) string {
|
||||
// if t, ok := v.(time.Time); ok {
|
||||
// return t.Format("2006-01-02 15:04:05")
|
||||
// }
|
||||
// return ""
|
||||
// }},
|
||||
// }
|
||||
//
|
||||
// excel.ExportToWriter(w, "用户列表", columns, users)
|
||||
func (e *Excel) ExportToWriter(w io.Writer, sheetName string, columns []ExportColumn, data interface{}) error {
|
||||
if e.file == nil {
|
||||
e.file = excelize.NewFile()
|
||||
}
|
||||
|
||||
// 设置工作表名称
|
||||
if sheetName == "" {
|
||||
sheetName = "Sheet1"
|
||||
}
|
||||
|
||||
// 删除默认工作表(如果存在)
|
||||
index, err := e.file.GetSheetIndex("Sheet1")
|
||||
if err == nil && index > 0 {
|
||||
e.file.DeleteSheet("Sheet1")
|
||||
}
|
||||
|
||||
// 创建新工作表
|
||||
_, err = e.file.NewSheet(sheetName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create sheet: %w", err)
|
||||
}
|
||||
|
||||
// 设置活动工作表
|
||||
sheetIndex, err := e.file.GetSheetIndex(sheetName)
|
||||
if err == nil && sheetIndex > 0 {
|
||||
e.file.SetActiveSheet(sheetIndex)
|
||||
}
|
||||
|
||||
// 写入表头
|
||||
headerStyle, _ := e.file.NewStyle(&excelize.Style{
|
||||
Font: &excelize.Font{
|
||||
Bold: true,
|
||||
Size: 12,
|
||||
},
|
||||
Fill: excelize.Fill{
|
||||
Type: "pattern",
|
||||
Color: []string{"#E0E0E0"},
|
||||
Pattern: 1,
|
||||
},
|
||||
Alignment: &excelize.Alignment{
|
||||
Horizontal: "center",
|
||||
Vertical: "center",
|
||||
},
|
||||
})
|
||||
|
||||
for i, col := range columns {
|
||||
cell := fmt.Sprintf("%c1", 'A'+i)
|
||||
e.file.SetCellValue(sheetName, cell, col.Header)
|
||||
|
||||
// 设置表头样式
|
||||
e.file.SetCellStyle(sheetName, cell, cell, headerStyle)
|
||||
|
||||
// 设置列宽
|
||||
if col.Width > 0 {
|
||||
colName, _ := excelize.ColumnNumberToName(i + 1)
|
||||
e.file.SetColWidth(sheetName, colName, colName, col.Width)
|
||||
}
|
||||
}
|
||||
|
||||
// 处理数据
|
||||
var rows [][]interface{}
|
||||
|
||||
// 检查数据是否实现了ExportData接口
|
||||
if exportData, ok := data.(ExportData); ok {
|
||||
// 使用接口方法获取数据
|
||||
rows = exportData.GetExportRows()
|
||||
} else {
|
||||
// 处理结构体切片
|
||||
rows, err = e.convertDataToRows(data, columns)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to convert data to rows: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 写入数据行
|
||||
for rowIndex, row := range rows {
|
||||
for colIndex, value := range row {
|
||||
cell := fmt.Sprintf("%c%d", 'A'+colIndex, rowIndex+2) // +2 因为第一行是表头
|
||||
|
||||
// 应用格式化函数
|
||||
var cellValue interface{} = value
|
||||
if colIndex < len(columns) && columns[colIndex].Format != nil {
|
||||
cellValue = columns[colIndex].Format(value)
|
||||
}
|
||||
|
||||
e.file.SetCellValue(sheetName, cell, cellValue)
|
||||
}
|
||||
}
|
||||
|
||||
// 自动调整列宽(如果未设置宽度)
|
||||
for i, col := range columns {
|
||||
if col.Width == 0 {
|
||||
colName, _ := excelize.ColumnNumberToName(i + 1)
|
||||
// 获取列的最大宽度
|
||||
maxWidth := e.getColumnMaxWidth(sheetName, i+1, len(rows)+1)
|
||||
if maxWidth > 0 {
|
||||
e.file.SetColWidth(sheetName, colName, colName, maxWidth)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 写入到Writer
|
||||
return e.file.Write(w)
|
||||
}
|
||||
|
||||
// ExportToFile 导出数据到文件(黑盒模式,推荐使用)
|
||||
// filePath: 文件路径
|
||||
// sheetName: 工作表名称(可选,默认为"Sheet1")
|
||||
// columns: 列定义
|
||||
// data: 数据列表
|
||||
// 返回错误信息
|
||||
//
|
||||
// 示例:
|
||||
//
|
||||
// excel := excel.NewExcel()
|
||||
// err := excel.ExportToFile("users.xlsx", "用户列表", columns, users)
|
||||
func (e *Excel) ExportToFile(filePath string, sheetName string, columns []ExportColumn, data interface{}) error {
|
||||
if e.file == nil {
|
||||
e.file = excelize.NewFile()
|
||||
}
|
||||
|
||||
// 设置工作表名称
|
||||
if sheetName == "" {
|
||||
sheetName = "Sheet1"
|
||||
}
|
||||
|
||||
// 删除默认工作表(如果存在)
|
||||
index, err := e.file.GetSheetIndex("Sheet1")
|
||||
if err == nil && index > 0 {
|
||||
e.file.DeleteSheet("Sheet1")
|
||||
}
|
||||
|
||||
// 创建新工作表
|
||||
_, err = e.file.NewSheet(sheetName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create sheet: %w", err)
|
||||
}
|
||||
|
||||
// 设置活动工作表
|
||||
sheetIndex, err := e.file.GetSheetIndex(sheetName)
|
||||
if err == nil && sheetIndex > 0 {
|
||||
e.file.SetActiveSheet(sheetIndex)
|
||||
}
|
||||
|
||||
// 写入表头
|
||||
headerStyle, _ := e.file.NewStyle(&excelize.Style{
|
||||
Font: &excelize.Font{
|
||||
Bold: true,
|
||||
Size: 12,
|
||||
},
|
||||
Fill: excelize.Fill{
|
||||
Type: "pattern",
|
||||
Color: []string{"#E0E0E0"},
|
||||
Pattern: 1,
|
||||
},
|
||||
Alignment: &excelize.Alignment{
|
||||
Horizontal: "center",
|
||||
Vertical: "center",
|
||||
},
|
||||
})
|
||||
|
||||
for i, col := range columns {
|
||||
cell := fmt.Sprintf("%c1", 'A'+i)
|
||||
e.file.SetCellValue(sheetName, cell, col.Header)
|
||||
|
||||
// 设置表头样式
|
||||
e.file.SetCellStyle(sheetName, cell, cell, headerStyle)
|
||||
|
||||
// 设置列宽
|
||||
if col.Width > 0 {
|
||||
colName, _ := excelize.ColumnNumberToName(i + 1)
|
||||
e.file.SetColWidth(sheetName, colName, colName, col.Width)
|
||||
}
|
||||
}
|
||||
|
||||
// 处理数据
|
||||
var rows [][]interface{}
|
||||
|
||||
// 检查数据是否实现了ExportData接口
|
||||
if exportData, ok := data.(ExportData); ok {
|
||||
// 使用接口方法获取数据
|
||||
rows = exportData.GetExportRows()
|
||||
} else {
|
||||
// 处理结构体切片
|
||||
rows, err = e.convertDataToRows(data, columns)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to convert data to rows: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 写入数据行
|
||||
for rowIndex, row := range rows {
|
||||
for colIndex, value := range row {
|
||||
cell := fmt.Sprintf("%c%d", 'A'+colIndex, rowIndex+2) // +2 因为第一行是表头
|
||||
|
||||
// 应用格式化函数
|
||||
var cellValue interface{} = value
|
||||
if colIndex < len(columns) && columns[colIndex].Format != nil {
|
||||
cellValue = columns[colIndex].Format(value)
|
||||
}
|
||||
|
||||
e.file.SetCellValue(sheetName, cell, cellValue)
|
||||
}
|
||||
}
|
||||
|
||||
// 自动调整列宽(如果未设置宽度)
|
||||
for i, col := range columns {
|
||||
if col.Width == 0 {
|
||||
colName, _ := excelize.ColumnNumberToName(i + 1)
|
||||
// 获取列的最大宽度
|
||||
maxWidth := e.getColumnMaxWidth(sheetName, i+1, len(rows)+1)
|
||||
if maxWidth > 0 {
|
||||
e.file.SetColWidth(sheetName, colName, colName, maxWidth)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 保存文件
|
||||
return e.file.SaveAs(filePath)
|
||||
}
|
||||
|
||||
// GetFile 获取Excel文件对象(高级功能时使用)
|
||||
// 返回excelize.File对象,可用于高级操作
|
||||
//
|
||||
// ℹ️ 推荐使用黑盒方法:
|
||||
// - ExportToWriter():导出到Writer
|
||||
// - ExportToFile():导出到文件
|
||||
//
|
||||
// 仅在需要使用高级功能时获取对象:
|
||||
// - 多工作表操作
|
||||
// - 自定义样式
|
||||
// - 图表、公式等高级功能
|
||||
//
|
||||
// 示例(常用操作,推荐):
|
||||
//
|
||||
// excel := excel.NewExcel()
|
||||
// excel.ExportToFile("users.xlsx", "用户列表", columns, users)
|
||||
//
|
||||
// 示例(高级功能):
|
||||
//
|
||||
// file := excel.GetFile()
|
||||
// file.NewSheet("Sheet2")
|
||||
// file.SetCellValue("Sheet2", "A1", "数据")
|
||||
func (e *Excel) GetFile() *excelize.File {
|
||||
if e.file == nil {
|
||||
e.file = excelize.NewFile()
|
||||
}
|
||||
return e.file
|
||||
}
|
||||
|
||||
// convertDataToRows 将数据转换为行数据
|
||||
func (e *Excel) convertDataToRows(data interface{}, columns []ExportColumn) ([][]interface{}, error) {
|
||||
// 使用反射处理数据
|
||||
val := reflect.ValueOf(data)
|
||||
if val.Kind() == reflect.Ptr {
|
||||
val = val.Elem()
|
||||
}
|
||||
|
||||
if val.Kind() != reflect.Slice {
|
||||
return nil, fmt.Errorf("data must be a slice")
|
||||
}
|
||||
|
||||
rows := make([][]interface{}, 0, val.Len())
|
||||
|
||||
for i := 0; i < val.Len(); i++ {
|
||||
item := val.Index(i)
|
||||
if item.Kind() == reflect.Ptr {
|
||||
item = item.Elem()
|
||||
}
|
||||
|
||||
row := make([]interface{}, len(columns))
|
||||
for j, col := range columns {
|
||||
value := e.getFieldValue(item, col.Field)
|
||||
row[j] = value
|
||||
}
|
||||
rows = append(rows, row)
|
||||
}
|
||||
|
||||
return rows, nil
|
||||
}
|
||||
|
||||
// getFieldValue 获取字段值(支持嵌套字段)
|
||||
func (e *Excel) getFieldValue(v reflect.Value, fieldPath string) interface{} {
|
||||
if v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
}
|
||||
|
||||
if v.Kind() != reflect.Struct {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 处理嵌套字段(如 "User.Name")
|
||||
fields := splitFieldPath(fieldPath)
|
||||
current := v
|
||||
|
||||
for i, fieldName := range fields {
|
||||
if current.Kind() == reflect.Ptr {
|
||||
current = current.Elem()
|
||||
}
|
||||
|
||||
if current.Kind() != reflect.Struct {
|
||||
return nil
|
||||
}
|
||||
|
||||
field := current.FieldByName(fieldName)
|
||||
if !field.IsValid() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 如果是最后一个字段,返回值
|
||||
if i == len(fields)-1 {
|
||||
if field.CanInterface() {
|
||||
return field.Interface()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 继续嵌套查找
|
||||
current = field
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// splitFieldPath 分割字段路径(如 "User.Name" -> ["User", "Name"])
|
||||
func splitFieldPath(path string) []string {
|
||||
result := make([]string, 0)
|
||||
current := ""
|
||||
for _, char := range path {
|
||||
if char == '.' {
|
||||
if current != "" {
|
||||
result = append(result, current)
|
||||
current = ""
|
||||
}
|
||||
} else {
|
||||
current += string(char)
|
||||
}
|
||||
}
|
||||
if current != "" {
|
||||
result = append(result, current)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// getColumnMaxWidth 获取列的最大宽度(用于自动调整列宽)
|
||||
func (e *Excel) getColumnMaxWidth(sheetName string, colIndex int, maxRow int) float64 {
|
||||
maxWidth := 10.0 // 默认最小宽度
|
||||
|
||||
for row := 1; row <= maxRow; row++ {
|
||||
cell, _ := excelize.CoordinatesToCellName(colIndex, row)
|
||||
value, err := e.file.GetCellValue(sheetName, cell)
|
||||
if err == nil {
|
||||
width := float64(len(value)) + 2 // 加2作为边距
|
||||
if width > maxWidth {
|
||||
maxWidth = width
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 限制最大宽度
|
||||
if maxWidth > 50 {
|
||||
maxWidth = 50
|
||||
}
|
||||
|
||||
return maxWidth
|
||||
}
|
||||
|
||||
// FormatDateTime 格式化日期时间(便捷函数)
|
||||
// 用于ExportColumn的Format字段
|
||||
func FormatDateTime(layout string) func(interface{}) string {
|
||||
return func(value interface{}) string {
|
||||
if t, ok := value.(time.Time); ok {
|
||||
return t.Format(layout)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// FormatDate 格式化日期(便捷函数)
|
||||
// 用于ExportColumn的Format字段,格式:2006-01-02
|
||||
func FormatDate(value interface{}) string {
|
||||
if t, ok := value.(time.Time); ok {
|
||||
return t.Format("2006-01-02")
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// FormatDateTimeDefault 格式化日期时间(便捷函数)
|
||||
// 用于ExportColumn的Format字段,格式:2006-01-02 15:04:05
|
||||
func FormatDateTimeDefault(value interface{}) string {
|
||||
if t, ok := value.(time.Time); ok {
|
||||
return t.Format("2006-01-02 15:04:05")
|
||||
}
|
||||
return ""
|
||||
}
|
||||
Reference in New Issue
Block a user