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 { return e.xlsx.SaveAs(fmt.Sprintf("%s/%s", e.Path, e.File)) }