Files
hospital-admin-api/utils/export.go
T

380 lines
8.2 KiB
Go

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
}