195 lines
4.2 KiB
Go
195 lines
4.2 KiB
Go
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"])
|
||
loop := tagMap["loop"]
|
||
var location Location
|
||
if e.preLocation != nil {
|
||
if loop != "" {
|
||
y = e.preLocation.BottomY
|
||
if x <= e.preLocation.RightX {
|
||
x = e.preLocation.RightX + 1
|
||
}
|
||
} else {
|
||
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 {
|
||
defer e.xlsx.Close()
|
||
return e.xlsx.SaveAs(fmt.Sprintf("%s/%s", e.Path, e.File))
|
||
}
|