package excel import ( "fmt" "io" "reflect" "time" "git.toowon.com/jimmy/go-common/tools" "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" } // 检查工作表是否已存在 sheetIndex, err := e.file.GetSheetIndex(sheetName) if err != nil || sheetIndex == 0 { // 工作表不存在,需要创建 // 如果sheetName不是"Sheet1",且默认"Sheet1"存在,则删除它 if sheetName != "Sheet1" { defaultIndex, _ := e.file.GetSheetIndex("Sheet1") if defaultIndex > 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 { return fmt.Errorf("failed to get sheet index: %w", err) } } // 设置活动工作表 if 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" } // 检查工作表是否已存在 sheetIndex, err := e.file.GetSheetIndex(sheetName) if err != nil || sheetIndex == 0 { // 工作表不存在,需要创建 // 如果sheetName不是"Sheet1",且默认"Sheet1"存在,则删除它 if sheetName != "Sheet1" { defaultIndex, _ := e.file.GetSheetIndex("Sheet1") if defaultIndex > 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 { return fmt.Errorf("failed to get sheet index: %w", err) } } // 设置活动工作表 if 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 } // AdaptTimeFormatter 适配器函数:将tools包的格式化函数转换为Excel Format字段需要的函数类型 // 允许直接使用tools包的任何格式化函数 // // 示例: // // // 直接使用tools.FormatDate // Format: excel.AdaptTimeFormatter(tools.FormatDate) // // // 使用自定义格式化函数 // Format: excel.AdaptTimeFormatter(func(t time.Time) string { // return tools.Format(t, "2006-01-02 15:04:05", "Asia/Shanghai") // }) func AdaptTimeFormatter(fn func(time.Time, ...string) string) func(interface{}) string { return func(value interface{}) string { if t, ok := value.(time.Time); ok { return fn(t) } return "" } } // formatDateTime 格式化日期时间(内部便捷函数) // 用于ExportColumn的Format字段 // layout: 时间格式,如 "2006-01-02 15:04:05" // timezone: 可选时区,如果为空则使用时间对象本身的时区 // 直接调用 tools.Format() 方法 func formatDateTime(layout string, timezone ...string) func(interface{}) string { return AdaptTimeFormatter(func(t time.Time, _ ...string) string { return tools.Format(t, layout, timezone...) }) } // formatDate 格式化日期(内部便捷函数) // 用于ExportColumn的Format字段,格式:2006-01-02 // 直接调用 tools.FormatDate() 方法 var formatDate = AdaptTimeFormatter(tools.FormatDate) // formatDateTimeDefault 格式化日期时间(内部便捷函数) // 用于ExportColumn的Format字段,格式:2006-01-02 15:04:05 // 直接调用 tools.FormatDateTime() 方法 var formatDateTimeDefault = AdaptTimeFormatter(tools.FormatDateTime) // formatTime 格式化时间(内部便捷函数) // 用于ExportColumn的Format字段,格式:15:04:05 // 直接调用 tools.FormatTime() 方法 var formatTime = AdaptTimeFormatter(tools.FormatTime)