fonchain-fiee/pkg/utils/excel/excelInter.go
2025-02-19 14:24:15 +08:00

381 lines
9.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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))
}
}