xlsx/export/xlsx.go

186 lines
4.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

package export
import (
"fmt"
"reflect"
"strconv"
"strings"
"github.com/xuri/excelize/v2"
)
type Exporter struct {
File string
Path string
Sheets []string
Titles []Title
Data interface{}
xlsx *excelize.File
GlobalTitleStyle *excelize.Style
GlobalCellStyle *excelize.Style
preLocation *CellLocation
}
func NewExporter() *Exporter {
return &Exporter{
xlsx: excelize.NewFile(),
}
}
func DefaultExporter() *Exporter {
return &Exporter{
xlsx: excelize.NewFile(),
GlobalTitleStyle: DefaultTitleStyle(),
GlobalCellStyle: DefaultCellStyle(),
Sheets: []string{"Sheet1"},
}
}
func (e *Exporter) newSheet() error {
for _, sheet := range e.Sheets {
_, err := e.xlsx.NewSheet(sheet)
if err != nil {
return fmt.Errorf("init sheet error:%s", err)
}
}
return nil
}
func (e *Exporter) Export(sheetIndex int) error {
if len(e.Sheets) == 0 {
return fmt.Errorf("excel file has no sheet")
}
err := e.newSheet()
if err != nil {
return err
}
if len(e.Titles) == 0 {
return fmt.Errorf("excel title is null")
}
sheet := e.Sheets[sheetIndex]
e.SetTitle(sheet)
v := reflect.ValueOf(e.Data)
if v.Kind() != reflect.Pointer {
return fmt.Errorf("data must be pointer")
}
switch v.Elem().Kind() {
case reflect.Slice:
for i := 0; i < v.Elem().Len(); i++ {
value := v.Elem().Index(i)
if value.Kind() != reflect.Struct {
return fmt.Errorf("element must be struct")
}
e.dealElement(sheet, value)
}
case reflect.Struct:
e.dealElement(sheet, v.Elem())
default:
return fmt.Errorf("data must be slice or struct")
}
return e.Save()
}
func (e *Exporter) dealElement(sheet string, data reflect.Value) {
for i := 0; i < data.NumField(); i++ {
field := data.Field(i)
t := data.Type().Field(i)
tag, ok := t.Tag.Lookup("export")
switch field.Kind() {
case reflect.Struct:
// 如果是结构体类型但是没有export标签则进行递归否则直接处理例如time.Time类型是结构体但是可能需要导出
if !ok {
e.dealElement(sheet, field)
} else {
e.writeCell(sheet, field, tag)
}
// 该逻辑有待验证
case reflect.Slice:
for j := 0; j < field.Len(); j++ {
value := field.Index(j)
if value.Kind() == reflect.Struct {
e.dealElement(sheet, value)
}
}
default:
if ok {
e.writeCell(sheet, field, tag)
}
}
}
}
func (e *Exporter) writeCell(sheet string, field reflect.Value, tag string) {
tagMap := dealTag(tag)
x, _ := strconv.Atoi(tagMap["x"])
y, _ := strconv.Atoi(tagMap["y"])
colSpan, _ := strconv.Atoi(tagMap["colspan"])
rowSpan, _ := strconv.Atoi(tagMap["rowspan"])
var location Location
if e.preLocation != nil {
if x > e.preLocation.RightX {
y = e.preLocation.TopY
} else if x <= e.preLocation.RightX {
y = e.preLocation.BottomY + 1
}
}
location = Location{
X: x,
Y: y,
}
value := value(field)
cell := Cell{
Location: location,
Rowspan: rowSpan,
Colspan: colSpan,
Value: value,
}
e.SetCell(sheet, cell)
}
func value(field reflect.Value) (val string) {
mthV := field.MethodByName("String")
if !mthV.IsValid() && field.CanAddr() {
mthV = field.Addr().MethodByName("String")
}
if mthV.IsValid() && mthV.Kind() == reflect.Func && mthV.Type().NumIn() == 0 {
callV := mthV.Call(nil)
if len(callV) > 0 {
val = fmt.Sprintf("%s", callV[0].String())
return
}
}
if field.CanUint() {
val = fmt.Sprintf("%d", field.Uint())
} else if field.CanInt() {
val = fmt.Sprintf("%d", field.Int())
} else if field.CanFloat() {
val = fmt.Sprintf("%f", field.Float())
} else {
val = fmt.Sprintf("%s", field.String())
}
return
}
func dealTag(tag string) map[string]string {
tag_list := strings.Split(tag, ",")
tag_map := make(map[string]string)
for _, tag := range tag_list {
tag_kv := strings.Split(tag, ":")
if len(tag_kv) != 2 {
continue
}
tag_map[tag_kv[0]] = tag_kv[1]
}
return tag_map
}
func (e *Exporter) Save() error {
return e.xlsx.SaveAs(fmt.Sprintf("%s/%s", e.Path, e.File))
}