diff --git a/pkg/util/excel/example/demo.xlsx b/pkg/util/excel/example/demo.xlsx deleted file mode 100644 index bfaaf7e..0000000 Binary files a/pkg/util/excel/example/demo.xlsx and /dev/null differ diff --git a/pkg/util/excel/example/demo_1671435052.xlsx b/pkg/util/excel/example/demo_1671435052.xlsx deleted file mode 100644 index 3176299..0000000 Binary files a/pkg/util/excel/example/demo_1671435052.xlsx and /dev/null differ diff --git a/pkg/util/excel/example/exportExample_test.go b/pkg/util/excel/example/exportExample_test.go deleted file mode 100644 index f852823..0000000 --- a/pkg/util/excel/example/exportExample_test.go +++ /dev/null @@ -1,70 +0,0 @@ -// Package excel ----------------------------- -// @file : templateInter_test.go -// @author : JJXu -// @contact : wavingbear@163.com -// @time : 2022/7/23 15:59 -// ------------------------------------------- -package example - -import ( - "fmt" - "gingogo2/utils/excel" - "path/filepath" - "strings" - "testing" - "time" -) - -//定义sheet表结构 -type sheet1Define struct { - Xid int `json:"xid" form:"xid" db:"column:xid;comment: "` - Name string `json:"name" form:"name" db:"column:name;comment: "` - Age int `json:"age" form:"age" db:"column:age;comment: "` -} - -func TestExcelTpl_WriteToExcel(t *testing.T) { - var sheet1Data = []sheet1Define{ - {1, "张三", 16}, - {2, "黑猫警长", 18}, - } - - var sheet1 = excel.NewSheet("Sheet1", sheet1Data) - //var suffixFunc = func() string { return fmt.Sprintf("%v", time.Now().Unix()) } - exCreator, err := excel.NewExcelTemplate("demo.xlsx", "./", "./demo.xlsx", sheet1) - if err != nil { - t.Log(err) - } - exCreator.UseOption(func(excel *excel.Excel) { - ext := filepath.Ext(excel.SaveName) - name := strings.Split(excel.SaveName, ext)[0] - excel.SaveName = fmt.Sprintf("%s_%v%s", name, time.Now().Unix(), ext) - }) - path, name, err := exCreator.WriteToFile() - if err != nil { - t.Log(err) - } else { - t.Log(path, name) - } -} - -func TestReadExcel(t *testing.T) { - var file = "demo_1671435052.xlsx" - ex := excel.Excel{OriginFilePath: file} - var datas []sheet1Define - err := ex.ReadSheetData("Sheet1", func(rowIndex int, rows []string) { - if rowIndex == 0 { - //跳过首行 - return - } - datas = append(datas, sheet1Define{ - Xid: excel.Int[int](rows[0]), - Name: rows[1], - Age: excel.Int[int](rows[2]), - }) - }) - if err != nil { - t.Error(err.Error()) - } - fmt.Println(datas) - -} diff --git a/pkg/util/excel/excelInter.go b/pkg/util/excel/excelInter.go deleted file mode 100644 index 1070e6a..0000000 --- a/pkg/util/excel/excelInter.go +++ /dev/null @@ -1,253 +0,0 @@ -// 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) -} diff --git a/pkg/util/excel/excel_test.go b/pkg/util/excel/excel_test.go deleted file mode 100644 index c333e35..0000000 --- a/pkg/util/excel/excel_test.go +++ /dev/null @@ -1,38 +0,0 @@ -// Package excel ----------------------------- -// @file : excel_test.go -// @author : JJXu -// @contact : wavingBear@163.com -// @time : 2022/12/19 16:52:11 -// ------------------------------------------- -package excel - -import ( - "reflect" - "testing" -) - -//定义sheet表结构 -type sheet1Define struct { - Xid int `json:"xid" form:"xid" db:"column:xid;comment: "` - Name string `json:"name" form:"name" db:"column:name;comment: "` - Age int `json:"age" form:"age" db:"column:age;comment: "` -} - -func TestSlice(t *testing.T) { - var sheet1Datas = []sheet1Define{ - {1, "张三", 16}, - {2, "黑猫警长", 18}, - } - - var sheet1 = NewSheet("Sheet1", &sheet1Datas) - d := sheet1.GetData() - v := reflect.ValueOf(d) - switch v.Kind() { - case reflect.Slice: - t.Log(v.Index(0)) - case reflect.Struct: - t.Log(v) - default: - t.Log("错误格式") - } -} diff --git a/pkg/util/excel/img.png b/pkg/util/excel/img.png deleted file mode 100644 index e20fe42..0000000 Binary files a/pkg/util/excel/img.png and /dev/null differ diff --git a/pkg/util/excel/img_1.png b/pkg/util/excel/img_1.png deleted file mode 100644 index fbf06bd..0000000 Binary files a/pkg/util/excel/img_1.png and /dev/null differ diff --git a/pkg/util/excel/options.go b/pkg/util/excel/options.go deleted file mode 100644 index 87cf606..0000000 --- a/pkg/util/excel/options.go +++ /dev/null @@ -1,27 +0,0 @@ -// Package excel ----------------------------- -// @file : options.go -// @author : JJXu -// @contact : wavingBear@163.com -// @time : 2022/12/19 12:41:40 -// ------------------------------------------- -package excel - -import ( - "fmt" - "path/filepath" - "strings" - "time" -) - -type Option func(excel *Excel) - -//func AddSaveFileSuffixWithUnixTime(excel *Excel) { -// excel.SaveName -// fmt.Sprintf("%v", time.Now().Unix()) -//} -// 时间戳作为文件后缀 -func OptionFileNameSuffixWithUnixTime(excel *Excel) { - ext := filepath.Ext(excel.SaveName) - name := strings.Split(excel.SaveName, ext)[0] - excel.SaveName = fmt.Sprintf("%s_%v%s", name, time.Now().Unix(), ext) -} diff --git a/pkg/util/excel/readme.md b/pkg/util/excel/readme.md deleted file mode 100644 index d8a18d6..0000000 --- a/pkg/util/excel/readme.md +++ /dev/null @@ -1,161 +0,0 @@ -# excel文件生成器 -## excel文件生成器简介 -此模块提供了通过`.xlsx`格式的excel模板文件,创建文件并自动添加数据的方法。 -此模块通过设计模式,在"github.com/xuri/excelize/v2"的基础上封装了`ExcelCreator`这一泛型的方法。适用于快速开发报表导出功能。 - -### 基本使用示例 -```go -package main -import ( - "fmt" - "github.com/flipped-aurora/gin-vue-admin/server/utils/excel" - "github.com/flipped-aurora/gin-vue-admin/server/utils/simpletime" - "sync" - "time" -) - -//读写锁 -var exportEpidemicPreventStaticStaticLock sync.RWMutex - -//定义sheet1数据结构 -type EpidemicPreventStaticReport struct { - Xid int `json:"xid" form:"xid" db:"column:xid;comment:序号"` - Time string `json:"time" form:"time" db:"column:time;comment:日期"` - Name string `json:"name" form:"name" db:"column:name;comment:项目名称"` - Code string `json:"code" form:"code" db:"column:code;comment:监督备案号"` - Street string `json:"street" form:"street" db:"column:street;comment:街道"` - State string `json:"state" form:"state" db:"column:state;comment:状态:未完成、已完成"` -} - -//导出数据 -func (m EpidemicPreventStaticReport) WriteToExcel(datas []EpidemicPreventStaticReport) (path string, filename string, err error) { - defer exportEpidemicPreventStaticStaticLock.Unlock() - //实例化sheet1,载入数据 - st1 := excel.NewSheet("Sheet1", datas) - var suffixName = func() string { - return fmt.Sprintf("%v", simpletime.TimeToString(time.Now(), simpletime.TimeFormat.NoSpacer_YMDhms)) - } - //实例化excelCreator - exc, err := excel.NewExcelCreator("防疫日报日完成项目数统计.xlsx", &suffixName, "uploads/template2/file", "uploads/template2/防疫日报日完成项目数统计.xlsx", st1) - if err != nil { - fmt.Println(err.error()) - } - exportEpidemicPreventStaticStaticLock.Lock() - // 导出数据 - return exc.WriteToExcel() -} - -func main(){ - var ( - reportData []EpidemicPreventStaticReport - excel EpidemicPreventStaticReport - ) - - //do something - //.. - //.. - - path,filename,err:=excel.WriteToExcel(reportData) - if err!=nil{ - fmt.Println(err.Error()) - }else{ - fmt.Printf("path:%v\nfilename:%v\n",path,filename) - } -} -``` - -## 使用说明 -此模块的使用流程如下 -### 1. 创建excel模板,定义好工作簿名称和表头 -请注意,程序默认数据都是一行行连续的。如果两条数据之间间隔了一个或多个空行,导出的数据可能会出现错误。 -![img.png](img.png) -### 2. 定义工作簿数据结构 -注意事项: -1. 结构体字段顺序要与工作簿的表头顺序一一对应、命名规则随意; -2. json标签用于数据的转换,同时也便于将结构体直接作为接口请求参数来使用;没有json标签时,数据将会被忽略; -```go -type Sheet1 struct { - Xid int `json:"xid"` - Name string `json:"name"` - Age int `json:"age"` -} -``` -### 3. 准备数据,实例化sheet对象 -数据类型至此结构体和切片
-`NewSheet`方法的第一个参数是工作簿名称,需要与模板文件中的对应,不然导出数据时会报错 -```go -//准备数据 -var sheet1Data = []Sheet1Define{ -{1, "张三", 16}, -{2, "黑猫警长", 18}, -} -//实例化sheet对象 -var sheet1 = excel.NewSheet("Sheet1", &sheet1Data) -``` -### 4 生成excel文件 -### 4.1 直接生成文件 -如果你不需要什么额外操作,只想直接生成excel文件,那么可以直接调用这个方法 -```go -//定义后缀名生成器,如果不需要可以传nil -var suffixFunc = func() string { return fmt.Sprintf("%v", time.Now().Unix()) } -//导出文件 -path,err:=excel.WriteToExcel("demo.xlsx", &suffixFunc, "./", "./demo.xlsx", sheet_1) -``` -#### 生成效果: -![img_1.png](img_1.png) - -### 4.2 `ExcelCreator` -ExcelCreator可以用来对工作簿和工作簿中的数据进行增删改查,以及生成文件。 -#### 4.2.1 实例化`ExcelCreator` -```go - -var suffixFunc = func() string { return fmt.Sprintf("%v", time.Now().Unix()) } //文件后缀名生成方法 -exCreator,err := excel.NewExcel("demo.xlsx", &suffixFunc, "./", "./demo.xlsx", sheet_1) -if err!=nil{ - fmt.Println(err.Error()) -} -//可连续添加多个工作簿或者不传,当然也支持一张工作簿数据的多次传入 -//exCreator,err := excel.NewExcel("demo.xlsx", &suffixFunc, "./", "./demo.xlsx", sheet_1,sheet_2,sheet_3) -//exCreator,err := excel.NewExcel("demo.xlsx", &suffixFunc, "./", "./demo.xlsx") -//exCreator,err := excel.NewExcel("demo.xlsx", &suffixFunc, "./", "./demo.xlsx", sheet_1_1,sheet_1_2,sheet_1_3) - -//不使用文件后缀名 -//exCreator,err := excel.NewExcelCreator("demo.xlsx", nil, "./", "./demo.xlsx", sheet_1) -//exCreator,err := excel.NewExcelCreatorWithoutSuffix("demo.xlsx","./", "./demo.xlsx", sheet_1) -``` -#### `FileName` 和 `filesSuffix`参数的说明 -当设置了`filesSuffix`参数后,创建excel文件时会自动生成文件后缀名。 -如`FileName`设置为"demo.xlsx"、`filesSuffix`返回值为"20200723",那么最终文件名为"demo20200723.xlsx" - -### 4.2.2 新增工作簿 -如果工作簿已存在,数据会组合而不是覆盖 -```go -var err = exCreator.SheetsAdd(sheet_2) -``` -### 4.2.3 删除工作簿 -```go -var err = exCreator.SheetsDelete(sheet_2.SheetName()) -``` - -### 4.2.5 取出工作簿map和缓存数据 -工作簿存储在`ExcelCreator.Sheets`中,存储结构为`map[string][]sheet`。string即工作簿名称。取出后可以进行任意的操作 -```go -var sheets = exCreator.Sheets -datas:= sheets["Sheet1"].GetDatas -//do somthing... -``` - -### 4.2.6 将数据生成到文件 -```go -path,fileName, err := exCreator.WriteToExcel() -if err != nil { - fmt.Println(err.Error()) -} else { - fmt.Println(path) - fmt.Println(fileName) -} -``` - -## 相关问题 -### 1. 为什么不支持通过代码直接生成表头? -鉴于在部分场景下,表头格式比较复杂,且通过代码生成灵活较度差,单元格格式通过代码设置比较繁琐。相比较下,没有直接创建excel文件模板来调整更为直观方便,所以没做此方面的设计。