first
parent
2066e2e4eb
commit
dcb536dc16
|
@ -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
|
||||
}
|
|
@ -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,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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))
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
)
|
|
@ -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=
|
Loading…
Reference in New Issue