From c2979182f4a3db2f8b8ed205e8d37160d6c1e672 Mon Sep 17 00:00:00 2001 From: len <867869344@qq.com> Date: Fri, 8 Sep 2023 17:14:09 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BA=86=E5=8F=AF=E4=BB=A5?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE=E8=A1=A8=E6=A0=BC=E4=BD=8D=E7=BD=AE=E7=9A=84?= =?UTF-8?q?=E6=96=B9=E6=B3=95=EF=BC=8C=E6=B7=BB=E5=8A=A0=E4=BA=86=E5=8F=AF?= =?UTF-8?q?=E4=BB=A5=E9=80=9A=E8=BF=87excel=E8=A1=A8=E6=A0=BC=E5=90=8D?= =?UTF-8?q?=E7=A7=B0=20=E8=AE=BE=E7=BD=AE=E5=80=BC,=E8=AE=BE=E7=BD=AEstyle?= =?UTF-8?q?,=20=E5=90=88=E5=B9=B6=E5=8D=95=E5=85=83=E6=A0=BC=E7=9A=84?= =?UTF-8?q?=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- excel.go | 214 ++++++++++++++++++++++++++++++++++++++---------- excel_tag.go | 3 +- excel_test.go | 60 +++++++++++--- go.mod | 13 ++- go.sum | 1 - helloworld.xlsx | Bin 6799 -> 6799 bytes position.xlsx | Bin 0 -> 6804 bytes 7 files changed, 233 insertions(+), 58 deletions(-) create mode 100755 position.xlsx diff --git a/excel.go b/excel.go index a01320c..84854e2 100644 --- a/excel.go +++ b/excel.go @@ -1,16 +1,16 @@ package structexcel import ( - "bytes" "fmt" - "github.com/pkg/errors" - "github.com/xuri/excelize/v2" "io" "net/http" "reflect" "sort" "strconv" "strings" + + "github.com/pkg/errors" + "github.com/xuri/excelize/v2" ) type ExcelRemarks interface { @@ -71,24 +71,24 @@ func (e *Excel) Close() error { // Bytes 吐字节 func (e *Excel) Bytes() ([]byte, error) { - buf := bytes.NewBuffer(nil) - if err := e.File.Write(buf); err != nil { + buf, err := e.File.WriteToBuffer() + if err != nil { return nil, err } return buf.Bytes(), nil } // Response proto -//func (e *Excel) Response(filename string) (*commonProto.Excel, error) { -// bt, err := e.Bytes() -// if err != nil { -// return nil, err -// } -// return &commonProto.Excel{ -// FileName: filename, -// Raw: bt, -// }, nil -//} +// func (e *Excel) Response(filename string) (*commonProto.Excel, error) { +// bt, err := e.Bytes() +// if err != nil { +// return nil, err +// } +// return &commonProto.Excel{ +// FileName: filename, +// Raw: bt, +// }, nil +// } // SaveAs 保存为文件 func (e *Excel) SaveAs() error { @@ -115,6 +115,7 @@ func (e *Excel) AddSheet(name string) (*Sheet, error) { autoCreateHeader: true, row: 0, col: 0, + position: NewCell(0, 0), header: make(excelHeaderSlice, 0), }, nil } @@ -132,6 +133,7 @@ func (e *Excel) OpenSheet(sheetName string) (*Sheet, error) { Excel: e.File, SheetName: sheetName, autoCreateHeader: false, + position: NewCell(0, 0), row: 0, col: 0, }, nil @@ -147,6 +149,7 @@ func (e *Excel) OpenSheetByMap(sheetName string) (*Sheet, error) { Excel: e.File, SheetName: name, autoCreateHeader: false, + position: NewCell(0, 0), row: 0, col: 0, }, nil @@ -182,10 +185,142 @@ type Sheet struct { index int // sheet index autoCreateHeader bool hasRemarks bool + position *Cell // 表格左上角坐标 row int col int } +type Cell struct { + row int + col int +} + +func NewCell(row, col int) *Cell { + return &Cell{row, col} +} + +func NewCellByName(cellName string) *Cell { + colStr, row, err := excelize.SplitCellName(cellName) + if err != nil { + return nil + } + col, err := excelize.ColumnNameToNumber(colStr) + if err != nil { + return nil + } + return &Cell{row, col} +} + +func (c *Cell) GetRow() int { + return c.row +} + +func (c *Cell) SetRow(row int) { + c.row = row +} + +func (c *Cell) SetCol(col int) { + c.col = col +} + +func (c *Cell) GetCol() int { + return c.col +} + +func (s *Sheet) SetPosition(row, col int) { + s.position = NewCell(row, col) + s.row = 0 + s.col = 0 +} + +func (s *Sheet) Col() int { + return s.position.col + s.col +} + +func (s *Sheet) Row() int { + return s.position.row + s.row +} + +func (s *Sheet) SetCellStyle(hRow, hCol, vRow, vCol, styleID int) (err error) { + hCell, err := s.axis(NewCell(hRow, hCol)) + if err != nil { + return err + } + vCell, err := s.axis(NewCell(vRow, vCol)) + if err != nil { + return err + } + return s.Excel.SetCellStyle(s.SheetName, hCell, vCell, styleID) +} + +func (s *Sheet) SetCellStyleByName(hCellName, vCellName string, styleID int) (err error) { + hCell, err := s.axis(NewCellByName(hCellName)) + if err != nil { + return err + } + vCell, err := s.axis(NewCellByName(vCellName)) + if err != nil { + return err + } + return s.Excel.SetCellStyle(s.SheetName, hCell, vCell, styleID) +} + +func ColumnNameToNumber(colName string) int { + _col, err := excelize.ColumnNameToNumber(colName) + if err != nil { + return 0 + } + return _col +} + +func (s *Sheet) MergeCellByName(hCellName, vCellName string) error { + hCell, err := s.axis(NewCellByName(hCellName)) + if err != nil { + return err + } + vCell, err := s.axis(NewCellByName(vCellName)) + if err != nil { + return err + } + return s.Excel.MergeCell(s.SheetName, hCell, vCell) +} + +func (s *Sheet) MergeCell(hRow, hCol, vRow, vCol int) error { + hCell, err := s.axis(NewCell(hRow, hCol)) + if err != nil { + return err + } + vCell, err := s.axis(NewCell(vRow, vCol)) + if err != nil { + return err + } + return s.Excel.MergeCell(s.SheetName, hCell, vCell) +} + +func (s *Sheet) SetCellValueByName(cellName string, val any) error { + cell, err := s.axis(NewCellByName(cellName)) + if err != nil { + return err + } + return s.Excel.SetCellValue(s.SheetName, cell, val) +} + +func (s *Sheet) SetCellValue(row, col int, val any) error { + cell, err := s.axis(NewCell(row, col)) + if err != nil { + return err + } + return s.Excel.SetCellValue(s.SheetName, cell, val) +} + +func (s *Sheet) SetCellRichText(row, col int, runs []excelize.RichTextRun) (err error) { + cell, err := s.axis(NewCell(row, col)) + if err != nil { + return err + } + return s.Excel.SetCellRichText(s.SheetName, cell, runs) +} + func (s *Sheet) GetIndex() int { return s.index } @@ -211,12 +346,12 @@ func (s *Sheet) addCol() *Sheet { return s } -func (s *Sheet) axis(row, col int) (string, error) { - _col, err := excelize.ColumnNumberToName(col) +func (s *Sheet) axis(cell *Cell) (string, error) { + _col, err := excelize.ColumnNumberToName(cell.col + s.position.col) if err != nil { return "", errors.Wrap(err, "excelize") } - return excelize.JoinCellName(_col, row) + return excelize.JoinCellName(_col, cell.row+s.position.row) } func (s *Sheet) fieldIsNil(data reflect.Value, index int) bool { @@ -248,9 +383,9 @@ func (s *Sheet) expandHeader(dataValue reflect.Value, index int, col int) int { } } } - if field.Kind() == reflect.Slice { + // if field.Kind() == reflect.Slice { - } + // } } sort.Strings(keyList) header := getElem(dataValue.Index(0)) @@ -284,7 +419,7 @@ func (s *Sheet) transferHeaders(data reflect.Value) *Sheet { panic("表头解析支持 struct | slice") } typee := value.Type() - + s.header = make(excelHeaderSlice, 0, len(s.header)) for i := 0; i < typee.NumField(); i++ { fieldType := value.Field(i) header := ParseExcelHeaderTag(typee.Field(i).Tag.Get("excel"), col) @@ -352,11 +487,7 @@ func (s *Sheet) AddHeader(data interface{}) error { continue } s.addCol() - axis, err := s.axis(s.row, s.col) - if err != nil { - return err - } - if err = s.setCellValue(axis, v, v.headerName); err != nil { + if err := s.setCellValue(s.row, s.col, v, v.headerName); err != nil { return err } } @@ -368,12 +499,8 @@ func (s *Sheet) AddHeader(data interface{}) error { func (s *Sheet) AddRemark(remark string, row, col int) error { s.addRow(row) - axis, err := s.axis(row, col) - if err != nil { - return err - } - s.Excel.MergeCell(s.SheetName, "A1", axis) - if err = s.Excel.SetCellValue(s.SheetName, "A1", remark); err != nil { + s.MergeCell(1, 1, row, col) + if err := s.SetCellValue(1, 1, remark); err != nil { return err } s.hasRemarks = true @@ -386,7 +513,7 @@ func (s *Sheet) AddRemark(remark string, row, col int) error { }); err != nil { return nil } else { - return s.Excel.SetCellStyle(s.SheetName, "A1", axis, style) + return s.SetCellStyle(1, 1, row, col, style) } } @@ -402,11 +529,11 @@ func (s *Sheet) autoAddRemarks(data reflect.Value) error { return nil } -func (s *Sheet) setCellValue(axis string, header *excelHeaderField, data interface{}) error { +func (s *Sheet) setCellValue(row, col int, header *excelHeaderField, data interface{}) error { if header.font == nil { - return s.Excel.SetCellValue(s.SheetName, axis, data) + return s.SetCellValue(row, col, data) } else { - return s.Excel.SetCellRichText(s.SheetName, axis, []excelize.RichTextRun{ + return s.SetCellRichText(row, col, []excelize.RichTextRun{ { Font: header.font, Text: fmt.Sprint(data), @@ -416,7 +543,7 @@ func (s *Sheet) setCellValue(axis string, header *excelHeaderField, data interfa } // AddData 遍历slice,导出数据 -func (s *Sheet) AddData(data interface{}) error { +func (s *Sheet) AddData(data any) error { dataType := reflect.TypeOf(data) dataValue := reflect.ValueOf(data) if dataType.Kind() != reflect.Slice { @@ -424,8 +551,8 @@ func (s *Sheet) AddData(data interface{}) error { } if dataValue.Len() == 0 { - _ = s.Excel.SetCellValue(s.SheetName, "A1", "没有数据") - _ = s.Excel.MergeCell(s.SheetName, "A1", "C1") + _ = s.SetCellValueByName("A1", "没有数据") + _ = s.MergeCellByName("A1", "B1") return nil } @@ -460,15 +587,13 @@ func (s *Sheet) AddData(data interface{}) error { continue } if !header.expand { - axis, _ := s.axis(s.row, header.Col) - if err := s.setCellValue(axis, header, value); err != nil { + if err := s.setCellValue(s.row, header.Col, header, value); err != nil { return err } } else { for _, key := range value.MapKeys() { if eHeader, ok := headerNameMap[key.String()]; ok { - axis, _ := s.axis(s.row, eHeader.Col) - if err := s.setCellValue(axis, header, value.MapIndex(key)); err != nil { + if err := s.setCellValue(s.row, eHeader.Col, header, value.MapIndex(key)); err != nil { return err } } @@ -627,8 +752,7 @@ func (s *Sheet) readBody(rows [][]string, data reflect.Value) (interface{}, erro if !field.CanSet() { continue } - axis, _ := s.axis(rn+2, col+1) - + axis, _ := s.axis(NewCell(rn+2, col+1)) switch field.Kind() { case reflect.Map: if value, err := s.cellToValue(field.Type().Elem(), cell, axis); err != nil { diff --git a/excel_tag.go b/excel_tag.go index 5c4d779..5b0c3cd 100644 --- a/excel_tag.go +++ b/excel_tag.go @@ -2,10 +2,11 @@ package structexcel import ( "fmt" - "github.com/xuri/excelize/v2" "regexp" "strconv" "strings" + + "github.com/xuri/excelize/v2" ) type excelHeaderField struct { diff --git a/excel_test.go b/excel_test.go index 165feee..4dbf6ec 100644 --- a/excel_test.go +++ b/excel_test.go @@ -21,14 +21,11 @@ func (f foo) GatherHeaderRows() int { func (f foo) GatherHeader(sheet *Sheet) error { style, _ := sheet.GetCenterStyle() - - headerLine := "7" - - sheet.Excel.SetCellValue(sheet.SheetName, "A"+headerLine, "个人信息") - sheet.Excel.MergeCell(sheet.SheetName, "A"+headerLine, "C"+headerLine) - sheet.Excel.SetCellStyle(sheet.SheetName, "A"+headerLine, "C"+headerLine, style) - sheet.Excel.SetCellValue(sheet.SheetName, "D"+headerLine, "假期信息") - sheet.Excel.MergeCell(sheet.SheetName, "D"+headerLine, "I"+headerLine) + sheet.SetCellValueByName("A7", "个人信息") + sheet.MergeCellByName("A7", "C7") + sheet.SetCellStyleByName("A7", "C7", style) + sheet.SetCellValueByName("D7", "假期信息") + sheet.MergeCellByName("D7", "I7") return nil } @@ -41,6 +38,50 @@ func (f foo) Remarks() (string, int, int) { `, 6, 9 } +func TestNewPositionExcel(t *testing.T) { + excel := NewExcel("position.xlsx") + defer excel.File.Close() + data := make([]*foo, 0) + age := 28 + data = append(data, &foo{ + Name: "h", + Age: &age, + Height: 181, + Holiday: map[string]bool{ + "2022-01-27": false, + "2022-01-28": true, + "2022-01-29": true, + }, + }, &foo{ + Name: "o", + Age: &age, + Height: 182, + Holiday: map[string]bool{ + "2022-01-27": true, + "2022-01-28": true, + "2022-01-30": true, + "2022-02-09": true, + "2022-12-09": true, + }, + }) + + sheet, err := excel.AddSheet("hello") + if err != nil { + t.Fatal(err) + } + sheet.SetPosition(2, 1) + if err = sheet.AddData(data); err != nil { + t.Error(err) + return + } + if err = excel.SaveAs(); err != nil { + t.Errorf("文件保存失败: %s", err.Error()) + return + } + dir, _ := os.Getwd() + fmt.Println("当前路径:", dir) +} + func TestNewExcel(t *testing.T) { excel := NewExcel("helloworld.xlsx") defer excel.File.Close() @@ -82,7 +123,6 @@ func TestNewExcel(t *testing.T) { } dir, _ := os.Getwd() fmt.Println("当前路径:", dir) - return } func TestParseExcelHeaderTag(t *testing.T) { @@ -95,7 +135,7 @@ func TestParseExcelHeaderTag(t *testing.T) { } func TestReadData(t *testing.T) { - excel, err := OpenExcel("helloword.xlsx") + excel, err := OpenExcel("helloworld.xlsx") if err != nil { t.Error(err) } diff --git a/go.mod b/go.mod index fe0036d..ced70de 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,19 @@ module github.com/douyacun/go-struct-excel -go 1.15 +go 1.20 require ( github.com/pkg/errors v0.9.1 github.com/xuri/excelize/v2 v2.7.1 ) + +require ( + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/richardlehane/mscfb v1.0.4 // indirect + github.com/richardlehane/msoleps v1.0.3 // indirect + github.com/xuri/efp v0.0.0-20220603152613-6918739fd470 // indirect + github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22 // indirect + golang.org/x/crypto v0.8.0 // indirect + golang.org/x/net v0.9.0 // indirect + golang.org/x/text v0.9.0 // indirect +) diff --git a/go.sum b/go.sum index 58b94be..b44efaa 100644 --- a/go.sum +++ b/go.sum @@ -63,7 +63,6 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/helloworld.xlsx b/helloworld.xlsx index 9d405526dbb3f55ba982ceee34d77ff610476c65..50fbd39187d5c3256df89426f35cd6fc2946139d 100755 GIT binary patch delta 156 zcmeA-?KhnuwRt_uT8_z`OkXxzG5RxZmJ`lq+9;*KxH*RR5bNezW+TSUouUm)li4Mu zm>3u)D@$53nX^ugleA`HVxK%)Qgw2pgcK9dfVmP%lbwLfg`AV)B$OvJN=Y$&;F_!; zEH&9&N?UKx zVq##Jyiihh5|E|9I9WzgdGa9%DW-g;$@e6bCjSSDCNoc#meQHbDIvu>m4$(!B1gZt zq%tS9cyghn$Yd9w+B2M!6C|vergKl8E1^1hA5e=E@8nyOs*~-3OcvqEJls-~OC<#+ IyGwZh03J~;P5=M^ diff --git a/position.xlsx b/position.xlsx new file mode 100755 index 0000000000000000000000000000000000000000..7feb8100d5ac2f47365283c52bcd8a4d826a7a5b GIT binary patch literal 6804 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 OM<7&XLZFz0f%!j)#)fSG literal 0 HcmV?d00001