From dcb536dc16b979675c0fadb146f4253cb3872d10 Mon Sep 17 00:00:00 2001 From: lj-wsdj <1134294381@qq.com> Date: Wed, 15 May 2024 15:09:48 +0800 Subject: [PATCH] first --- export/cell.go | 36 +++++++++++++ export/style.go | 42 +++++++++++++++ export/title.go | 40 +++++++++++++++ export/xlsx.go | 132 ++++++++++++++++++++++++++++++++++++++++++++++++ export_test.go | 119 +++++++++++++++++++++++++++++++++++++++++++ go.mod | 16 ++++++ go.sum | 29 +++++++++++ test.xlsx | Bin 0 -> 6534 bytes 8 files changed, 414 insertions(+) create mode 100644 export/cell.go create mode 100644 export/style.go create mode 100644 export/title.go create mode 100644 export/xlsx.go create mode 100644 export_test.go create mode 100644 go.mod create mode 100644 go.sum create mode 100755 test.xlsx diff --git a/export/cell.go b/export/cell.go new file mode 100644 index 0000000..41c2789 --- /dev/null +++ b/export/cell.go @@ -0,0 +1,36 @@ +package export + +import ( + "fmt" + + "github.com/xuri/excelize/v2" +) + +type Cell struct { + Value any + // 标题位置(列) + Location Location + // 标题合并列数(除开本身) + Colspan int + // 标题合并行数(除开本身) + Rowspan int + Style *excelize.Style +} + +func (e *Exporter) SetCell(sheet string, cell Cell) { + start_col, end_col := e.CaculateCell(cell.Location, cell.Rowspan, cell.Colspan) + e.xlsx.MergeCell(sheet, start_col, end_col) + e.xlsx.SetCellValue(sheet, start_col, cell.Value) + if cell.Style != nil { + style, _ := e.xlsx.NewStyle(cell.Style) + e.xlsx.SetCellStyle(sheet, start_col, end_col, style) + } +} + +func (e *Exporter) CaculateCell(location Location, rowSpan, colSpan int) (string, string) { + col_name, _ := excelize.ColumnNumberToName(location.X) + start_col := fmt.Sprintf("%s%d", col_name, location.Y) + h_pos, _ := excelize.ColumnNumberToName(location.X + rowSpan) + end_col := fmt.Sprintf("%s%d", h_pos, location.Y+colSpan) + return start_col, end_col +} diff --git a/export/style.go b/export/style.go new file mode 100644 index 0000000..8eeb7c8 --- /dev/null +++ b/export/style.go @@ -0,0 +1,42 @@ +package export + +import "github.com/xuri/excelize/v2" + +func DefaultTitleStyle() *excelize.Style { + return &excelize.Style{ + Alignment: &excelize.Alignment{ + Horizontal: "center", + Vertical: "center", + }, + Font: &excelize.Font{ + Bold: true, + }, + Fill: excelize.Fill{ + Type: "pattern", + Color: []string{"4aa4ea"}, + Pattern: 1, + }, + Border: []excelize.Border{ + { + Type: "left", + Color: "000000", + Style: 1, + }, + { + Type: "top", + Color: "000000", + Style: 1, + }, + { + Type: "right", + Color: "000000", + Style: 1, + }, + { + Type: "bottom", + Color: "000000", + Style: 1, + }, + }, + } +} diff --git a/export/title.go b/export/title.go new file mode 100644 index 0000000..dd7be12 --- /dev/null +++ b/export/title.go @@ -0,0 +1,40 @@ +package export + +import "github.com/xuri/excelize/v2" + +type Title struct { + // 标题名称 + Name string + // 标题位置(列) + Location Location + // 标题合并列数(除开本身) + Colspan int + // 标题合并行数(除开本身) + Rowspan int + Style *excelize.Style +} + +type Location struct { + X int + Y int +} + +func (e *Exporter) SetGlobalTitleStyle(style *excelize.Style) { + e.GlobalTitleStyle = style +} + +func (e *Exporter) SetTitle(sheet string) { + for _, title := range e.Titles { + start_col, end_col := e.CaculateCell(title.Location, title.Rowspan, title.Colspan) + e.xlsx.MergeCell(sheet, start_col, end_col) + e.xlsx.SetCellValue(sheet, start_col, title.Name) + if title.Style != nil { + style, _ := e.xlsx.NewStyle(title.Style) + e.xlsx.SetCellStyle(sheet, start_col, end_col, style) + } else if e.GlobalTitleStyle != nil { + style, _ := e.xlsx.NewStyle(e.GlobalTitleStyle) + e.xlsx.SetCellStyle(sheet, start_col, end_col, style) + + } + } +} diff --git a/export/xlsx.go b/export/xlsx.go new file mode 100644 index 0000000..a15ecf1 --- /dev/null +++ b/export/xlsx.go @@ -0,0 +1,132 @@ +package export + +import ( + "fmt" + "reflect" + "strconv" + "strings" + + "github.com/xuri/excelize/v2" +) + +type Exporter struct { + File string + Path string + Sheets []string + Titles []Title + Data interface{} + xlsx *excelize.File + GlobalTitleStyle *excelize.Style +} + +func NewExporter() *Exporter { + return &Exporter{ + xlsx: excelize.NewFile(), + } +} + +func DefaultExporter() *Exporter { + return &Exporter{ + xlsx: excelize.NewFile(), + GlobalTitleStyle: DefaultTitleStyle(), + Sheets: []string{"Sheet1"}, + } +} + +func (e *Exporter) newSheet() error { + for _, sheet := range e.Sheets { + _, err := e.xlsx.NewSheet(sheet) + if err != nil { + return fmt.Errorf("init sheet error:%s", err) + } + } + return nil +} + +func (e *Exporter) Export(sheetIndex int) error { + if len(e.Sheets) == 0 { + return fmt.Errorf("excel file has no sheet") + } + err := e.newSheet() + if err != nil { + return err + } + if len(e.Titles) == 0 { + return fmt.Errorf("excel title is null") + } + sheet := e.Sheets[sheetIndex] + e.SetTitle(sheet) + v := reflect.ValueOf(e.Data) + if v.Kind() != reflect.Pointer { + return fmt.Errorf("data must be pointer") + } + if v.Elem().Kind() != reflect.Slice { + return fmt.Errorf("data must be slice") + } + for i := 0; i < v.Elem().Len(); i++ { + value := v.Elem().Index(i) + if value.Kind() != reflect.Struct { + return fmt.Errorf("element must be struct") + } + e.dealElement(sheet, value, i) + } + return nil +} + +func (e *Exporter) dealElement(sheet string, data reflect.Value, index int) error { + for i := 0; i < data.NumField(); i++ { + field := data.Field(i) + switch field.Kind() { + case reflect.Struct: + e.dealElement(sheet, field, index) + // 该逻辑有待验证 + case reflect.Slice: + for j := 0; j < field.Len(); j++ { + value := field.Index(j) + if value.Kind() == reflect.Struct { + e.dealElement(sheet, value, index) + } + } + default: + t := data.Type().Field(i) + tag, ok := t.Tag.Lookup("export") + if ok { + tagMap := dealTag(tag) + x, _ := strconv.Atoi(tagMap["x"]) + y, _ := strconv.Atoi(tagMap["y"]) + colSpan, _ := strconv.Atoi(tagMap["colspan"]) + rowSpan, _ := strconv.Atoi(tagMap["rowspan"]) + location := Location{ + X: x, + Y: y + index, + } + cell := Cell{ + Location: location, + Rowspan: rowSpan, + Colspan: colSpan, + Value: field, + } + e.SetCell(sheet, cell) + } + } + + } + return e.Save() +} + +func dealTag(tag string) map[string]string { + tag_list := strings.Split(tag, ",") + tag_map := make(map[string]string) + for _, tag := range tag_list { + tag_kv := strings.Split(tag, ":") + if len(tag_kv) != 2 { + continue + } + tag_map[tag_kv[0]] = tag_kv[1] + } + return tag_map +} + +func (e *Exporter) Save() error { + return e.xlsx.SaveAs(fmt.Sprintf("%s/%s", e.Path, e.File)) +} diff --git a/export_test.go b/export_test.go new file mode 100644 index 0000000..5aa2cd5 --- /dev/null +++ b/export_test.go @@ -0,0 +1,119 @@ +package xlsx + +import ( + "testing" + + "git.botann.com/lijun/xlsx/export" +) + +type Fen int + +type Data struct { + Name string + TotalInvest Fen `export:"true,x:2,y:3"` + ProjectName string `export:"true,x:1,y:3"` + CenterInvest Fen + CityInvest Fen + CompanyInvest Fen + Partner string `export:"true,x:6,y:3"` + ExData []ExData + ExData2 ExData2 +} + +type ExData struct { + Col1 string + Col2 string +} + +type ExData2 struct { + CenterInvest Fen `export:"true,x:3,y:3"` + CityInvest Fen `export:"true,x:4,y:3"` + CompanyInvest Fen `export:"true,x:5,y:3"` +} + +func Test(t *testing.T) { + title := []export.Title{ + { + Name: "项目名称", + Location: export.Location{X: 1, Y: 1}, + Colspan: 1, + Rowspan: 0, + }, + { + Name: "总投资", + Location: export.Location{X: 2, Y: 1}, + Colspan: 1, + Rowspan: 0, + }, + { + Name: "资金来源", + Location: export.Location{X: 3, Y: 1}, + Colspan: 0, + Rowspan: 2, + }, + { + Name: "中央", + Location: export.Location{X: 3, Y: 2}, + Colspan: 0, + Rowspan: 0, + }, + { + Name: "地方", + Location: export.Location{X: 4, Y: 2}, + Colspan: 0, + Rowspan: 0, + }, + { + Name: "公司", + Location: export.Location{X: 5, Y: 2}, + Colspan: 0, + Rowspan: 0, + }, + { + Name: "合作主体", + Location: export.Location{X: 6, Y: 1}, + Colspan: 1, + Rowspan: 0, + }, + } + data := []Data{ + { + Name: "测试数据1", + TotalInvest: 10000, + ProjectName: "项目1", + CenterInvest: 2000, + CityInvest: 2000, + CompanyInvest: 6000, + Partner: "公司1", + ExData: []ExData{ + {Col1: "test1", Col2: "test2"}, + {Col1: "测试1", Col2: "测试2"}, + }, + ExData2: ExData2{ + CenterInvest: 3000, + CityInvest: 1500, + CompanyInvest: 5500, + }, + }, + { + Name: "测试数据", + TotalInvest: 8000, + ProjectName: "项目2", + CenterInvest: 2000, + CityInvest: 2000, + CompanyInvest: 4000, + Partner: "公司2", + ExData2: ExData2{ + CenterInvest: 6200, + CityInvest: 1200, + CompanyInvest: 2600, + }, + }, + } + exporter := export.DefaultExporter() + exporter.Data = &data + exporter.Titles = title + exporter.File = "test.xlsx" + exporter.Path = "./" + exporter.Export(0) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..fbf2474 --- /dev/null +++ b/go.mod @@ -0,0 +1,16 @@ +module git.botann.com/lijun/xlsx + +go 1.21.5 + +require github.com/xuri/excelize/v2 v2.8.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-20231025114914-d1ff6096ae53 // indirect + github.com/xuri/nfp v0.0.0-20230919160717-d98342af3f05 // indirect + golang.org/x/crypto v0.19.0 // indirect + golang.org/x/net v0.21.0 // indirect + golang.org/x/text v0.14.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..144577c --- /dev/null +++ b/go.sum @@ -0,0 +1,29 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM= +github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk= +github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= +github.com/richardlehane/msoleps v1.0.3 h1:aznSZzrwYRl3rLKRT3gUk9am7T/mLNSnJINvN0AQoVM= +github.com/richardlehane/msoleps v1.0.3/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/xuri/efp v0.0.0-20231025114914-d1ff6096ae53 h1:Chd9DkqERQQuHpXjR/HSV1jLZA6uaoiwwH3vSuF3IW0= +github.com/xuri/efp v0.0.0-20231025114914-d1ff6096ae53/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI= +github.com/xuri/excelize/v2 v2.8.1 h1:pZLMEwK8ep+CLIUWpWmvW8IWE/yxqG0I1xcN6cVMGuQ= +github.com/xuri/excelize/v2 v2.8.1/go.mod h1:oli1E4C3Pa5RXg1TBXn4ENCXDV5JUMlBluUhG7c+CEE= +github.com/xuri/nfp v0.0.0-20230919160717-d98342af3f05 h1:qhbILQo1K3mphbwKh1vNm4oGezE1eF9fQWmNiIpSfI4= +github.com/xuri/nfp v0.0.0-20230919160717-d98342af3f05/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ= +golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4= +golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/test.xlsx b/test.xlsx new file mode 100755 index 0000000000000000000000000000000000000000..3de3c61ef0044c68c0d1d9a96df09e76cd7f993d GIT binary patch literal 6534 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 RVG&(kk1zAQLw)t{e*i!Z5%mB7 literal 0 HcmV?d00001