2024-05-15 15:09:48 +08:00
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func NewExporter() *Exporter {
|
|
|
|
|
return &Exporter{
|
|
|
|
|
xlsx: excelize.NewFile(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func DefaultExporter() *Exporter {
|
|
|
|
|
return &Exporter{
|
|
|
|
|
xlsx: excelize.NewFile(),
|
|
|
|
|
GlobalTitleStyle: DefaultTitleStyle(),
|
|
|
|
|
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")
|
|
|
|
|
}
|
|
|
|
|
if v.Elem().Kind() != reflect.Slice {
|
|
|
|
|
return fmt.Errorf("data must be 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, i)
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (e *Exporter) dealElement(sheet string, data reflect.Value, index int) error {
|
|
|
|
|
for i := 0; i < data.NumField(); i++ {
|
|
|
|
|
field := data.Field(i)
|
2024-05-15 17:09:50 +08:00
|
|
|
|
t := data.Type().Field(i)
|
|
|
|
|
tag, ok := t.Tag.Lookup("export")
|
2024-05-15 15:09:48 +08:00
|
|
|
|
switch field.Kind() {
|
|
|
|
|
case reflect.Struct:
|
2024-05-15 17:09:50 +08:00
|
|
|
|
// 如果是结构体类型,但是没有export标签,则进行递归,否则直接处理(例如time.Time类型是结构体,但是可能需要导出)
|
|
|
|
|
if !ok {
|
|
|
|
|
e.dealElement(sheet, field, index)
|
|
|
|
|
} else {
|
|
|
|
|
e.writeCell(sheet, field, tag, index)
|
|
|
|
|
}
|
2024-05-15 15:09:48 +08:00
|
|
|
|
// 该逻辑有待验证
|
|
|
|
|
case reflect.Slice:
|
|
|
|
|
for j := 0; j < field.Len(); j++ {
|
|
|
|
|
value := field.Index(j)
|
|
|
|
|
if value.Kind() == reflect.Struct {
|
|
|
|
|
e.dealElement(sheet, value, index)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|
if ok {
|
2024-05-15 17:09:50 +08:00
|
|
|
|
e.writeCell(sheet, field, tag, index)
|
2024-05-15 15:09:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
2024-05-15 17:09:50 +08:00
|
|
|
|
}
|
2024-05-15 15:09:48 +08:00
|
|
|
|
}
|
|
|
|
|
return e.Save()
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-15 17:09:50 +08:00
|
|
|
|
func (e *Exporter) writeCell(sheet string, field reflect.Value, tag string, index int) {
|
|
|
|
|
tagMap := dealTag(tag)
|
|
|
|
|
x, _ := strconv.Atoi(tagMap["x"])
|
|
|
|
|
y, _ := strconv.Atoi(tagMap["y"])
|
|
|
|
|
colSpan, _ := strconv.Atoi(tagMap["colspan"])
|
|
|
|
|
rowSpan, _ := strconv.Atoi(tagMap["rowspan"])
|
|
|
|
|
location := Location{
|
|
|
|
|
X: x,
|
|
|
|
|
Y: y + index,
|
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-15 15:09:48 +08:00
|
|
|
|
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))
|
|
|
|
|
}
|