package utils import ( "bytes" "fmt" "github.com/xuri/excelize/v2" "reflect" ) // HeaderCellData 表头内容 type HeaderCellData struct { Value string // 值 CellType string // 类型 NumberFmt string // 格式化方式 ColWidth int // 列宽 Colour string // 颜色 } // GenerateSheet 生成表格 func GenerateSheet(header []HeaderCellData, sheetName string) (f *excelize.File, err error) { // 创建工作表 err, f = createSheet(sheetName) if err != nil { return nil, err } // 设置表头 if err := setHeader(f, sheetName, header); err != nil { return nil, err } return f, nil } // 创建工作表 func createSheet(sheetName string) (err error, f *excelize.File) { f = excelize.NewFile() defer func() { _ = f.Close() }() // 创建一个工作表 index, err := f.NewSheet(sheetName) if err != nil { return err, nil } // 设置工作簿的默认工作表 f.SetActiveSheet(index) // 设置工作表默认字体 err = f.SetDefaultFont("宋体") if err != nil { return err, nil } // 设置行高 35-第一行 err = f.SetRowHeight(sheetName, 1, 35) if err != nil { return err, nil } return nil, f } // 设置表头 func setHeader(f *excelize.File, sheetName string, header []HeaderCellData) error { for c, cell := range header { // 获取列名 colName, err := excelize.ColumnNumberToName(c + 1) if err != nil { return err } // 添加单元格的值 if err := f.SetCellValue(sheetName, colName+"1", cell.Value); err != nil { return err } // 单元格颜色填充样式 var fill excelize.Fill if cell.Colour != "" { fill = excelize.Fill{ Type: "pattern", Pattern: 1, Color: []string{cell.Colour}, Shading: 0, } } // 第一行的左、右、下边框 border := []excelize.Border{ {Type: "left", Color: "#000000", Style: 1}, {Type: "right", Color: "#000000", Style: 1}, {Type: "bottom", Color: "#000000", Style: 1}, } // 设置单元格值类型和格式 style, err := f.NewStyle(&excelize.Style{ Alignment: &excelize.Alignment{ Horizontal: "center", Vertical: "center", }, Fill: fill, Border: border, }) if err != nil { return err } if err := f.SetCellStyle(sheetName, colName+"1", colName+"1", style); err != nil { return err } if err := f.SetColWidth(sheetName, colName, colName, float64(cell.ColWidth)); err != nil { return err } } return nil } // Export 导出 func Export(header []HeaderCellData, data []interface{}) (b *bytes.Buffer, err error) { sheetName := "Sheet1" // 生成表格 f, err := GenerateSheet(header, sheetName) if err != nil { return nil, err } alignment := &excelize.Alignment{ Horizontal: "center", Vertical: "center", } row := 1 for _, item := range data { var err error row, err = fillDataWithMerge(f, sheetName, header, item, row, alignment) if err != nil { return nil, err } } buffer, err := f.WriteToBuffer() if err != nil { return nil, err } return buffer, nil // 保存文件 //if err := f.SaveAs("output.xlsx"); err != nil { // return nil, err //} //return nil, errors.New("已导出文件") } // 填充数据 func fillDataWithMerge(f *excelize.File, sheetName string, header []HeaderCellData, data interface{}, row int, alignment *excelize.Alignment) (int, error) { v := reflect.ValueOf(data) // 判断是否是结构体或指向结构体的指针 if v.Kind() == reflect.Ptr { v = v.Elem() } if v.Kind() != reflect.Struct { panic("data must be a struct or a pointer to struct") } var axis string // 坐标值 colOffset := 0 // 列偏移量 mergeNum := 0 // 合并单元格数量。默认为0,每次+1,当存在切片时启用 var mergeNumSlice []string // 需合并单元格列名。默认为空,当存在切片时启用["A","B"] filedNumSlice := 1 // 切片数量,默认为1,此数量为其余需合并的数量 for i := 0; i < v.NumField(); i++ { field := v.Field(i) // 获取字段类型 valueType := field.Kind() if valueType != reflect.Slice { // 获取字段值 cellValue := field.Interface() // 获取列名 colName, err := excelize.ColumnNumberToName(colOffset + 1) if err != nil { return 0, err } // 获取坐标,+1表示去除header行 axis = colName + fmt.Sprintf("%d", row+1) // 填入值 err = f.SetCellValue(sheetName, axis, cellValue) if err != nil { return 0, err } // 设置单元格样式 err = setCellStyle(f, sheetName, axis, header[i], alignment) if err != nil { return 0, err } // 设置行高 35-第一行 err = f.SetRowHeight(sheetName, row+1, 35) if err != nil { return 0, err } // 合并单元格数量 mergeNum = mergeNum + 1 mergeNumSlice = append(mergeNumSlice, colName) } else { var sliceFieldNum int // 切片字段数量 // 最大切片数量,此数量为其余需合并的数量 if filedNumSlice < field.Len() { filedNumSlice = field.Len() } for j := 0; j < field.Len(); j++ { item := field.Index(j).Interface() v1 := reflect.ValueOf(item) for i2 := 0; i2 < field.Index(j).NumField(); i2++ { // 获取字段值 cellValue := v1.Field(i2).Interface() // 获取列名 colName, err := excelize.ColumnNumberToName(i + i2 + 1) if err != nil { return 0, err } // 获取坐标,+1表示去除header行 axis = colName + fmt.Sprintf("%d", row+j+1) // 填入值 err = f.SetCellValue(sheetName, axis, cellValue) if err != nil { return 0, err } // 设置单元格样式 err = setCellStyle(f, sheetName, axis, header[i+i2], alignment) if err != nil { return row, err } // 设置行高 35-第一行 err = f.SetRowHeight(sheetName, row+j+1, 35) if err != nil { return row, err } sliceFieldNum = i2 } } // 列偏移量需增加上切片字段数量 colOffset = colOffset + sliceFieldNum } colOffset++ } // 合并单元格 if filedNumSlice > 1 { for _, s := range mergeNumSlice { // 设置单元格样式 for i := 0; i < filedNumSlice; i++ { axis = s + fmt.Sprintf("%d", row+i+1) column, err := excelize.ColumnNameToNumber(s) if err != nil { return row, err } err = setCellStyle(f, sheetName, axis, header[column-1], alignment) if err != nil { return row, err } } startCell := s + fmt.Sprintf("%d", row+1) endCell := s + fmt.Sprintf("%d", row+filedNumSlice) err := f.MergeCell(sheetName, startCell, endCell) if err != nil { return 0, err } } } // 行数 = 切片数量,如不存在切片filedNumSlice默认为1 row = row + filedNumSlice // 返回当前处理到的行数 return row, nil } // 设置单元格样式 func setCellStyle(f *excelize.File, sheetName, axis string, header HeaderCellData, alignment *excelize.Alignment) error { // 获取现有样式id styleID, err := f.GetCellStyle(sheetName, axis) if err != nil { return err } // 处理样式问题 var style *excelize.Style if styleID == 0 { // 新样式 style = &excelize.Style{ Alignment: alignment, } } else { // 存在现有样式,直接获取 style, err = f.GetStyle(styleID) if err != nil { return err } } if header.CellType == "float" { style.NumFmt = 2 customNumFmt := "0.000" style.CustomNumFmt = &customNumFmt } if header.CellType == "date" { style.NumFmt = 22 customNumFmt := "yyyy-mm-dd hh:mm:ss" style.CustomNumFmt = &customNumFmt } // 颜色-可用,但是目前无需使用 if header.Colour != "" { // 单元格颜色 //fill := excelize.Fill{ // Type: "pattern", // Pattern: 1, // Color: []string{header.Colour}, // Shading: 0, //} // //style.Fill = fill // //// 字体颜色 //font := &excelize.Font{ // Color: header.Colour, //} // //style.Font = font } // 边框 border := []excelize.Border{ {Type: "left", Color: "#000000", Style: 1}, {Type: "right", Color: "#000000", Style: 1}, {Type: "bottom", Color: "#000000", Style: 1}, } style.Border = border newStyle, err := f.NewStyle(style) if err != nil { return err } if err := f.SetCellStyle(sheetName, axis, axis, newStyle); err != nil { return err } return nil }