381 lines
9.7 KiB
Go
381 lines
9.7 KiB
Go
|
// 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))
|
|||
|
}
|
|||
|
}
|