// Package excel ----------------------------- // @file : templateInter.go // @author : JJXu // @contact : wavingbear@163.com // @time : 2022/7/23 15:34 // ------------------------------------------- package excel import ( "bytes" "errors" "fmt" "github.com/tealeg/xlsx" "github.com/xuri/excelize/v2" "io" "log" "os" "path/filepath" "reflect" "sync" ) var ( ErrSheetNotExist = errors.New("sheet does not exist") ErrSheetDataFormatNotSupport = errors.New("sheet data format not support") //ErrSheetNameNotInTemplate = errors.New("sheet name not in emailTemplate") ) // ====================================================================================================================== // // Sheet define type HeaderRow struct { RowNum int //行号 Values []string } // 抽象工作簿 type Sheet interface { GetData() any //数据,支持struct、[]struct、[]*struct三种类型 SheetName() string //表名称 SheetHeaders() []string //表头名称,没有则跳过插入表头的步骤 GetJsonFiledList() []string //指定字段的排列顺序,默认按照结构体中的顺序排列(表头与数据没有关联,使用这个指定字段插入顺序) } // 实例化工作簿,使用模板生成excel的话用这个 func NewTemplateSheet(sheetName string, datas any) Sheet { return &newSheetDefine{ Datas: datas, Name: sheetName, } } // 实例化工作簿,不通过模板生成excel的话用这个 func NewSheet(sheetName string, datas any, headers []string, jsonFiledList ...[]string) Sheet { st := &newSheetDefine{ Datas: datas, Name: sheetName, Headers: headers, } if jsonFiledList != nil { st.JsonFiledList = jsonFiledList[0] } return st } // 定义一个能够通用的工作簿结构,此结构必须遵循Sheet接口规范 type newSheetDefine struct { Datas any Name string Headers []string JsonFiledList []string } func (s *newSheetDefine) GetData() any { return s.Datas } func (s *newSheetDefine) SheetName() string { return s.Name } func (s *newSheetDefine) SheetHeaders() []string { return s.Headers } func (s *newSheetDefine) GetJsonFiledList() []string { return s.JsonFiledList } //====================================================================================================================== // Sheet define // WriteToExcel 通过模板文件写入数据并另存为 // param fileName : 文件名 // param filesSuffix : 文件后缀名生成函数 // param fileRoot : 导出目录 // param templatePath : 模板文件路径 // param sheets : Sheet类型的数据,类型为[]Sheet // return path : 文件路径 // return exfileName : 导出后的文件名 // return err func WriteToExcel(fileName string, fileRoot string, templatePath string, sheets ...Sheet) (path string, exFileName string, err error) { var exc *Excel exc, err = NewExcelCreatorFromTemplate(fileName, fileRoot, templatePath, sheets...) if err != nil { return } exc.UseOption(OptionFileNameSuffixWithUnixTime) return exc.WriteToFile() } // ReadDataFromExcel 从excel文件读取数据 func ReadDataFromExcel(filepath string, sheetName string, handler func(rowIndex int, rows []string)) error { ex := Excel{OriginFilePath: filepath} return ex.ReadSheetData(sheetName, handler) } // ReadDataFromBytes 从io口读取数据,用户http上传的附件 func ReadDataFromBytes(file io.Reader, sheetName string, handler func(rowIndex int, row []string)) error { exce, err := excelize.OpenReader(file) if err != nil { return err } var ex = Excel{ex: exce} return ex.ReadSheetData(sheetName, handler) } // 读取模板并创建工作表生成器 func NewExcelCreatorFromTemplate(fileName string, fileRoot string, templatePath string, sheets ...Sheet) (exc *Excel, err error) { exc = &Excel{ SaveRoot: fileRoot, SaveName: fileName, OriginFilePath: templatePath, rwLock: sync.RWMutex{}, } if sheets != nil { err = exc.AddSheets(sheets...) if err != nil { return } } return exc, nil } // 新建文件并创建工作表生成器 func NewExcelCreatorWithNewFile(fileName string, fileRoot string, sheets ...Sheet) (exc *Excel, err error) { return NewExcelCreatorFromTemplate(fileName, fileRoot, "", sheets...) } type Excel struct { ex *excelize.File SaveRoot string SaveName string OriginFilePath string Sheets map[string]Sheet rwLock sync.RWMutex Opts []Option After []Option } // UseOption 使用可选项 func (s *Excel) UseOption(opts ...Option) { if opts != nil { s.Opts = append(s.Opts, opts...) } } func (s *Excel) AfterAddData(after ...Option) { if after != nil { s.After = append(s.After, after...) } } // 添加工作簿 // 注意如果添加相同的工作簿,之前的会被覆盖 func (s *Excel) AddSheets(sheets ...Sheet) (err error) { if s.Sheets == nil { s.Sheets = make(map[string]Sheet, 0) } for _, sheet := range sheets { var sheetName = sheet.SheetName() s.Sheets[sheetName] = sheet } return } // 删除工作簿 func (s *Excel) DeleteSheets(sheetName string) error { if s.Sheets == nil { return nil } else if s.Sheets[sheetName] != nil { delete(s.Sheets, sheetName) } else { return ErrSheetNotExist } return nil } // 读取工作簿 func (s *Excel) ReadSheetData(sheetName string, handler func(rowIndex int, row []string)) (err error) { if s.ex == nil { s.ex, err = excelize.OpenFile(s.OriginFilePath) if err != nil { return } } datas, err := s.ex.GetRows(sheetName) for i, row := range datas { handler(i, row) } return nil } // 写入到文件 func (s *Excel) WriteToFile() (path string, fileName string, err error) { if s.ex == nil { if s.OriginFilePath == "" { s.ex = excelize.NewFile() } else { s.ex, err = excelize.OpenFile(s.OriginFilePath) if err != nil { return } } } if s.Opts != nil { for _, opt := range s.Opts { opt(s) } } //插入数据 for sheetName, st := range s.Sheets { //添加表头,没有定义则不插入 if st.SheetHeaders() != nil { for i, c := range st.SheetHeaders() { err = s.ex.SetCellValue(sheetName, GetCellIndex(1, i+1), c) if err != nil { fmt.Println(err.Error()) return } } headerStyleID, errs := s.ex.NewStyle(NewDefaultHeaderStyle()) if errs != nil { fmt.Println(errs) err = errs return } //表头设置为默认样式,边框加粗、字体加粗 if err = s.ex.SetCellStyle(st.SheetName(), "A1", GetCellIndex(1, len(st.SheetHeaders())), headerStyleID); err != nil { return } } //添加数据 var firstRow = s.getFirstEmptyRowIndex(s.ex, sheetName) var SheetData = reflect.ValueOf(st.GetData()) var SheetType = reflect.TypeOf(st.GetData()) writerFunc, exists := writerMap[SheetData.Kind()] if exists { err = writerFunc(s, st, SheetData, SheetType, firstRow) } else { return "", "", ErrSheetDataFormatNotSupport } } if s.After != nil { for _, after := range s.After { after(s) } } //检测并生成目录 _ = os.MkdirAll(s.SaveRoot, os.ModePerm) //保存 path = filepath.ToSlash(filepath.Join(s.SaveRoot, s.SaveName)) fileName = s.SaveName s.rwLock.Lock() if err = s.ex.SaveAs(path); err != nil { log.Println(fmt.Sprintf("save file error :%v", err)) s.rwLock.Unlock() return } s.rwLock.Unlock() return } // getJsonFieldList 获取json字段列表 func (s *Excel) getJsonFieldList(sheetType reflect.Type) (tagList []string) { t := sheetType.Elem() if t.Kind() == reflect.Ptr { t = t.Elem() if t.Kind() != reflect.Struct { return } } for i := 0; i < t.NumField(); i++ { var tag = t.Field(i).Tag.Get("json") if tag != "" { tagList = append(tagList, tag) } } return } // dataToMap 数据转字典 func (s *Excel) dataToMap(sheet reflect.Value, sheetType reflect.Type) (dataMap map[string]any) { dataMap = make(map[string]any) t := sheetType.Elem() //指针类型结构体拿真实的对象 if t.Kind() == reflect.Ptr { t = t.Elem() sheet = sheet.Elem() } for i := 0; i < t.NumField(); i++ { var tag = t.Field(i).Tag.Get("json") if tag != "" { dataMap[t.Field(i).Tag.Get("json")] = sheet.Field(i).Interface() } } return dataMap } // getFirstEmptyRowIndex 获取首个空行的索引位置 func (s *Excel) getFirstEmptyRowIndex(ex *excelize.File, sheetName string) (index int) { rows, err := ex.GetRows(sheetName) if err != nil { return 1 } return len(rows) } func ToExcel(titleList []string, dataList []interface{}) (content io.ReadSeeker) { // 生成一个新的文件 file := xlsx.NewFile() // 添加sheet页 sheet, _ := file.AddSheet("Sheet1") // 插入表头 titleRow := sheet.AddRow() for _, v := range titleList { cell := titleRow.AddCell() cell.Value = v } // 插入内容 for _, v := range dataList { row := sheet.AddRow() row.WriteStruct(v, -1) } var buffer bytes.Buffer _ = file.Write(&buffer) file.Save("1.xlsx") content = bytes.NewReader(buffer.Bytes()) return } // 自动设置单元格宽度 func (s *Excel) autoResetCellWidth(sheetObj Sheet) { // 获取最大字符宽度 maxWidths := make(map[int]int) var sheetData = reflect.ValueOf(sheetObj.GetData()) var rowLen = 1 var columnLen = len(sheetObj.GetJsonFiledList()) if sheetData.Kind() == reflect.Slice { rowLen = sheetData.Len() } limitWidth := 90 for col := 1; col <= columnLen; col++ { var maxWidth int for row := 0; row < rowLen; row++ { value, _ := s.ex.GetCellValue(sheetObj.SheetName(), GetCellIndex(row+1, col)) width := len(value) if width > limitWidth { width = limitWidth } if width > maxWidth { maxWidth = width } } maxWidths[col] = maxWidth } // 设置列宽度 for col, width := range maxWidths { colChar := GetColumnIndex(col) s.ex.SetColWidth(sheetObj.SheetName(), colChar, colChar, float64(width+2)) } }