254 lines
6.4 KiB
Go
254 lines
6.4 KiB
Go
|
// Package excel -----------------------------
|
|||
|
// @file : templateInter.go
|
|||
|
// @author : JJXu
|
|||
|
// @contact : wavingbear@163.com
|
|||
|
// @time : 2022/7/23 15:34
|
|||
|
// -------------------------------------------
|
|||
|
package excel
|
|||
|
|
|||
|
import (
|
|||
|
"errors"
|
|||
|
"fmt"
|
|||
|
"github.com/xuri/excelize/v2"
|
|||
|
"io"
|
|||
|
"log"
|
|||
|
"path/filepath"
|
|||
|
"reflect"
|
|||
|
"sync"
|
|||
|
)
|
|||
|
|
|||
|
var (
|
|||
|
ErrSheetNotExist = errors.New("sheet does not exist")
|
|||
|
ErrSheetDataFrormat = errors.New("sheet data format must be slice or struct")
|
|||
|
//ErrSheetNameNotInTemplate = errors.New("sheet name not in emailTemplate")
|
|||
|
)
|
|||
|
|
|||
|
//======================================================================================================================
|
|||
|
// Sheet define
|
|||
|
|
|||
|
// 抽象工作簿
|
|||
|
type Sheet interface {
|
|||
|
GetData() any
|
|||
|
SheetName() string
|
|||
|
}
|
|||
|
|
|||
|
// 实例化工作簿
|
|||
|
func NewSheet(sheetName string, datas any) Sheet {
|
|||
|
return &newSheetDefine{
|
|||
|
Datas: datas,
|
|||
|
Name: sheetName,
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// 定义一个能够通用的工作簿结构,此结构必须遵循Sheet接口规范
|
|||
|
type newSheetDefine struct {
|
|||
|
Datas any
|
|||
|
Name string
|
|||
|
}
|
|||
|
|
|||
|
func (s *newSheetDefine) GetData() any {
|
|||
|
return s.Datas
|
|||
|
}
|
|||
|
func (s *newSheetDefine) SheetName() string {
|
|||
|
return s.Name
|
|||
|
}
|
|||
|
|
|||
|
//======================================================================================================================
|
|||
|
// 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 = NewExcelTemplate(fileName, fileRoot, templatePath, sheets...)
|
|||
|
if err != nil {
|
|||
|
return
|
|||
|
}
|
|||
|
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, rows []string)) error {
|
|||
|
exce, err := excelize.OpenReader(file)
|
|||
|
if err != nil {
|
|||
|
return err
|
|||
|
}
|
|||
|
var ex = Excel{ex: exce}
|
|||
|
return ex.ReadSheetData(sheetName, handler)
|
|||
|
}
|
|||
|
|
|||
|
// 新建工作表实例
|
|||
|
func NewExcelTemplate(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
|
|||
|
}
|
|||
|
|
|||
|
type Excel struct {
|
|||
|
ex *excelize.File
|
|||
|
SaveRoot string
|
|||
|
SaveName string
|
|||
|
OriginFilePath string
|
|||
|
Sheets map[string]Sheet
|
|||
|
rwLock sync.RWMutex
|
|||
|
Opts []Option
|
|||
|
}
|
|||
|
|
|||
|
// UseOption 使用可选项
|
|||
|
func (s *Excel) UseOption(opts ...Option) {
|
|||
|
if opts != nil {
|
|||
|
s.Opts = append(s.Opts, opts...)
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// 添加工作簿
|
|||
|
// 注意如果添加相同的工作簿,之前的会被覆盖
|
|||
|
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, rows []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, rows := range datas {
|
|||
|
handler(i, rows)
|
|||
|
}
|
|||
|
return nil
|
|||
|
}
|
|||
|
|
|||
|
// 写入到文件
|
|||
|
func (s *Excel) WriteToFile() (path string, fileName string, err error) {
|
|||
|
if s.ex == nil {
|
|||
|
s.ex, err = excelize.OpenFile(s.OriginFilePath)
|
|||
|
if err != nil {
|
|||
|
return
|
|||
|
}
|
|||
|
}
|
|||
|
if s.Opts != nil {
|
|||
|
for _, opt := range s.Opts {
|
|||
|
opt(s)
|
|||
|
}
|
|||
|
}
|
|||
|
//插入数据
|
|||
|
var cellNameList []string
|
|||
|
for sheetName, st := range s.Sheets {
|
|||
|
var firstRow = s.getFirstEmptyRowIndex(s.ex, sheetName)
|
|||
|
var SheetData = reflect.ValueOf(st.GetData())
|
|||
|
var SheetType = reflect.TypeOf(st.GetData())
|
|||
|
switch SheetData.Kind() {
|
|||
|
case reflect.Slice:
|
|||
|
cellNameList = s.getJsonFieldList(SheetType)
|
|||
|
var rowLen = SheetData.Len()
|
|||
|
for i := 0; i < rowLen; i++ {
|
|||
|
var dataMap = s.dataToMap(SheetData.Index(i), SheetType)
|
|||
|
for column, v := range cellNameList {
|
|||
|
var axis = GetCellIndex(i+firstRow+1, column+1)
|
|||
|
err = s.ex.SetCellValue(sheetName, axis, dataMap[v])
|
|||
|
if err != nil {
|
|||
|
fmt.Println(err.Error())
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
case reflect.Struct:
|
|||
|
cellNameList = s.getJsonFieldList(SheetType)
|
|||
|
var dataMap = s.dataToMap(SheetData, SheetType)
|
|||
|
for column, v := range cellNameList {
|
|||
|
var axis = GetCellIndex(firstRow+1, column+1)
|
|||
|
err = s.ex.SetCellValue(sheetName, axis, dataMap[v])
|
|||
|
if err != nil {
|
|||
|
fmt.Println(err.Error())
|
|||
|
}
|
|||
|
}
|
|||
|
default:
|
|||
|
return "", "", ErrSheetDataFrormat
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
//保存
|
|||
|
path = filepath.ToSlash(filepath.Join(s.SaveRoot, 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()
|
|||
|
for i := 0; i < t.NumField(); i++ {
|
|||
|
tagList = append(tagList, t.Field(i).Tag.Get("json"))
|
|||
|
}
|
|||
|
return
|
|||
|
}
|
|||
|
|
|||
|
// dataToMap 数据转字典
|
|||
|
func (s *Excel) dataToMap(sheet reflect.Value, sheetType reflect.Type) (dataMap map[string]any) {
|
|||
|
dataMap = make(map[string]any)
|
|||
|
t := sheetType.Elem()
|
|||
|
for i := 0; i < t.NumField(); i++ {
|
|||
|
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)
|
|||
|
}
|