lj-wsdj 2024-05-16 16:53:29 +08:00
parent b52dfc1b1d
commit 5f6aec0aee
13 changed files with 388 additions and 47 deletions

View File

@ -1,3 +1,10 @@
# xlsx
excel的导出导入目前仅完成导出
excel的导出导入目前仅完成导出
## 导出功能
如果要使用导出则传入的数据必须为指针类型可以是struct或者元素为struct的slice。
通过tag来确认需要导出的字段内容为`export:”x:1,y:1”`注意下x,y分别表是该字段在excel表格中的起始位置x为coly为row且tag中的x在数据中需要顺序列出。
详情请看测试文件

View File

@ -10,27 +10,61 @@ type Cell struct {
Value any
// 标题位置(列)
Location Location
// 标题合并列数(除开本身)
// 标题合并列数
Colspan int
// 标题合并行数(除开本身)
// 标题合并行数
Rowspan int
Style *excelize.Style
}
type CellLocation struct {
LeftX int
RightX int
TopY int
BottomY int
IsMerge bool
}
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)
fmt.Println(sheet, start_col, end_col, cell.Value)
if start_col != end_col {
e.xlsx.MergeCell(sheet, start_col, end_col)
}
leftx, topy, _ := excelize.CellNameToCoordinates(start_col)
rightx, bottomy, _ := excelize.CellNameToCoordinates(end_col)
e.xlsx.SetCellValue(sheet, start_col, cell.Value)
CellLocation := CellLocation{
LeftX: leftx,
RightX: rightx,
TopY: topy,
BottomY: bottomy,
IsMerge: start_col != end_col,
}
e.preLocation = &CellLocation
if cell.Style != nil {
style, _ := e.xlsx.NewStyle(cell.Style)
e.xlsx.SetCellStyle(sheet, start_col, end_col, style)
} else if e.GlobalCellStyle != nil {
style, _ := e.xlsx.NewStyle(e.GlobalCellStyle)
e.xlsx.SetCellStyle(sheet, start_col, end_col, style)
}
}
func (e *Exporter) CaculateCell(location Location, rowSpan, colSpan int) (string, string) {
if rowSpan >= 1 {
rowSpan -= 1
} else {
rowSpan = 0
}
if colSpan >= 1 {
colSpan -= 1
} else {
colSpan = 0
}
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)
h_pos, _ := excelize.ColumnNumberToName(location.X + colSpan)
end_col := fmt.Sprintf("%s%d", h_pos, location.Y+rowSpan)
return start_col, end_col
}

View File

@ -40,3 +40,42 @@ func DefaultTitleStyle() *excelize.Style {
},
}
}
func DefaultCellStyle() *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: 4,
},
{
Type: "top",
Color: "000000",
Style: 4,
},
{
Type: "right",
Color: "000000",
Style: 4,
},
{
Type: "bottom",
Color: "000000",
Style: 4,
},
},
}
}

View File

@ -7,9 +7,9 @@ type Title struct {
Name string
// 标题位置(列)
Location Location
// 标题合并列数(除开本身)
// 标题合并列数
Colspan int
// 标题合并行数(除开本身)
// 标题合并行数
Rowspan int
Style *excelize.Style
}
@ -28,6 +28,8 @@ func (e *Exporter) SetTitle(sheet string) {
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)
start_col_number, _, _ := excelize.SplitCellName(start_col)
e.xlsx.SetColWidth(sheet, start_col_number, start_col_number, 20)
if title.Style != nil {
style, _ := e.xlsx.NewStyle(title.Style)
e.xlsx.SetCellStyle(sheet, start_col, end_col, style)

View File

@ -17,6 +17,8 @@ type Exporter struct {
Data interface{}
xlsx *excelize.File
GlobalTitleStyle *excelize.Style
GlobalCellStyle *excelize.Style
preLocation *CellLocation
}
func NewExporter() *Exporter {
@ -29,6 +31,7 @@ func DefaultExporter() *Exporter {
return &Exporter{
xlsx: excelize.NewFile(),
GlobalTitleStyle: DefaultTitleStyle(),
GlobalCellStyle: DefaultCellStyle(),
Sheets: []string{"Sheet1"},
}
}
@ -60,20 +63,24 @@ func (e *Exporter) Export(sheetIndex int) error {
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")
switch v.Elem().Kind() {
case reflect.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)
}
e.dealElement(sheet, value, i)
case reflect.Struct:
e.dealElement(sheet, v.Elem())
default:
return fmt.Errorf("data must be slice or struct")
}
return nil
return e.Save()
}
func (e *Exporter) dealElement(sheet string, data reflect.Value, index int) error {
func (e *Exporter) dealElement(sheet string, data reflect.Value) {
for i := 0; i < data.NumField(); i++ {
field := data.Field(i)
t := data.Type().Field(i)
@ -82,37 +89,44 @@ func (e *Exporter) dealElement(sheet string, data reflect.Value, index int) erro
case reflect.Struct:
// 如果是结构体类型但是没有export标签则进行递归否则直接处理例如time.Time类型是结构体但是可能需要导出
if !ok {
e.dealElement(sheet, field, index)
e.dealElement(sheet, field)
} else {
e.writeCell(sheet, field, tag, index)
e.writeCell(sheet, field, tag)
}
// 该逻辑有待验证
case reflect.Slice:
for j := 0; j < field.Len(); j++ {
value := field.Index(j)
if value.Kind() == reflect.Struct {
e.dealElement(sheet, value, index)
e.dealElement(sheet, value)
}
}
default:
if ok {
e.writeCell(sheet, field, tag, index)
e.writeCell(sheet, field, tag)
}
}
}
return e.Save()
}
func (e *Exporter) writeCell(sheet string, field reflect.Value, tag string, index int) {
func (e *Exporter) writeCell(sheet string, field reflect.Value, tag string) {
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{
var location Location
if e.preLocation != nil {
if x > e.preLocation.RightX {
y = e.preLocation.TopY
} else if x <= e.preLocation.RightX {
y = e.preLocation.BottomY + 1
}
}
location = Location{
X: x,
Y: y + index,
Y: y,
}
value := value(field)
cell := Cell{

View File

@ -49,16 +49,16 @@ func (t Date) MarshalJSON() ([]byte, error) {
}
type Data struct {
Date time.Time `export:"x:7,y:3"`
Name string
TotalInvest Fen `export:"true,x:2,y:3"`
ProjectName string `export:"true,x:1,y:3"`
TotalInvest Fen `export:"true,x:2,y:3"`
ExData2 ExData2
CenterInvest Fen
CityInvest Fen
CompanyInvest Fen
Partner string `export:"true,x:6,y:3"`
Partner string `export:"true,x:6,y:3"`
Date time.Time `export:"x:7,y:3"`
ExData []ExData
ExData2 ExData2
}
type ExData struct {
@ -72,55 +72,44 @@ type ExData2 struct {
CompanyInvest Fen `export:"true,x:5,y:3"`
}
func Test(t *testing.T) {
func TestMain(t *testing.T) {
title := []export.Title{
{
Name: "项目名称",
Location: export.Location{X: 1, Y: 1},
Colspan: 1,
Rowspan: 0,
Rowspan: 2,
},
{
Name: "总投资",
Location: export.Location{X: 2, Y: 1},
Colspan: 1,
Rowspan: 0,
Rowspan: 2,
},
{
Name: "资金来源",
Location: export.Location{X: 3, Y: 1},
Colspan: 0,
Rowspan: 2,
Colspan: 3,
},
{
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,
Rowspan: 2,
},
{
Name: "日期",
Location: export.Location{X: 7, Y: 1},
Colspan: 1,
Rowspan: 0,
Rowspan: 2,
},
}
data := []Data{

115
export2_test.go 100644
View File

@ -0,0 +1,115 @@
package xlsx
import (
"testing"
"git.botann.com/lijun/xlsx/export"
)
type Data2 struct {
Name string
ProjectName string `export:"true,x:1,y:2,rowspan:3"`
TotalInvest int `export:"true,x:2,y:2,rowspan:3"`
Partner string `export:"true,x:3,y:2,rowspan:3"`
Radio []InvestRadio
}
type InvestRadio struct {
Name string `export:"true,x:4,y:2"`
Invest int `export:"true,x:5,y:2"`
}
type ExDataEx struct {
Name string
Invest int
}
func TestMain(T *testing.T) {
title := []export.Title{
{
Name: "项目名称",
Location: export.Location{X: 1, Y: 1},
},
{
Name: "总投资",
Location: export.Location{X: 2, Y: 1},
},
{
Name: "合作主体",
Location: export.Location{X: 3, Y: 1},
},
{
Name: "出资比例",
Location: export.Location{X: 4, Y: 1},
Colspan: 2,
},
}
data := []Data2{
{
Name: "项目1",
ProjectName: "项目1",
TotalInvest: 100,
Partner: "合作主体1",
Radio: []InvestRadio{
{
Name: "1合作主体1",
Invest: 10,
},
{
Name: "1合作主体2",
Invest: 20,
},
{
Name: "1合作主体3",
Invest: 30,
},
},
},
{
Name: "项目2",
ProjectName: "项目2",
TotalInvest: 200,
Partner: "合作主体2",
Radio: []InvestRadio{
{
Name: "2合作主体1",
Invest: 10,
},
{
Name: "2合作主体2",
Invest: 20,
},
{
Name: "2合作主体3",
Invest: 30,
},
},
},
{
Name: "项目3",
ProjectName: "项目3",
TotalInvest: 200,
Partner: "合作主体3",
Radio: []InvestRadio{
{
Name: "3合作主体1",
Invest: 10,
},
{
Name: "3合作主体2",
Invest: 20,
},
{
Name: "3合作主体3",
Invest: 30,
},
},
},
}
exporter := export.DefaultExporter()
exporter.Titles = title
exporter.Data = &data
exporter.File = "test2.xlsx"
exporter.Path = "./"
exporter.Export(0)
}

61
export3_test.go 100644
View File

@ -0,0 +1,61 @@
package xlsx
import (
"fmt"
"testing"
"time"
"github.com/xuri/excelize/v2"
)
func TestMain(T *testing.T) {
f := excelize.NewFile()
defer func() {
if err := f.Close(); err != nil {
fmt.Println(err)
}
}()
index, err := f.NewSheet("Sheet1")
if err != nil {
fmt.Println(err)
return
}
f.SetActiveSheet(index)
f.SetCellValue("Sheet1", "A1", "项目名称")
f.SetCellValue("Sheet1", "B1", "总投资")
f.SetCellValue("Sheet1", "C1", "合作主体")
f.MergeCell("Sheet1", "D1", "E1")
f.SetCellValue("Sheet1", "D1", "出资比例")
f.SetCellValue("Sheet1", "F1", "日期")
f.MergeCell("Sheet1", "F2", "F4")
f.SetCellValue("Sheet1", "F2", time.Now())
f.MergeCell("Sheet1", "A2", "A4")
f.SetCellValue("Sheet1", "A2", "项目1")
f.MergeCell("Sheet1", "B2", "B4")
f.SetCellValue("Sheet1", "B2", 100)
f.MergeCell("Sheet1", "C2", "C4")
f.SetCellValue("Sheet1", "C2", "合作主体1")
f.SetCellValue("Sheet1", "D2", "1合作主体1")
f.SetCellValue("Sheet1", "E2", 10)
f.SetCellValue("Sheet1", "D3", "1合作主体2")
f.SetCellValue("Sheet1", "E3", 20)
f.SetCellValue("Sheet1", "D4", "1合作主体3")
f.SetCellValue("Sheet1", "E4", 30)
f.MergeCell("Sheet1", "F5", "F7")
f.SetCellValue("Sheet1", "F5", time.Now())
f.MergeCell("Sheet1", "A5", "A7")
f.SetCellValue("Sheet1", "A5", "项目2")
f.MergeCell("Sheet1", "B5", "B7")
f.SetCellValue("Sheet1", "B5", 200)
f.MergeCell("Sheet1", "C5", "C7")
f.SetCellValue("Sheet1", "C5", "合作主体2")
f.SetCellValue("Sheet1", "D3", "2合作主体1")
f.SetCellValue("Sheet1", "E3", 10)
f.SetCellValue("Sheet1", "D4", "2合作主体2")
f.SetCellValue("Sheet1", "E4", 20)
f.SetCellValue("Sheet1", "D5", "2合作主体3")
f.SetCellValue("Sheet1", "E5", 30)
f.SaveAs("./test3.xlsx")
}

80
export4_test.go 100644
View File

@ -0,0 +1,80 @@
package xlsx
import (
"testing"
"git.botann.com/lijun/xlsx/export"
)
type Data4 struct {
ProjectName string `export:"true,x:1,y:3"`
ProjectUnit string `export:"true,x:2,y:3"`
CentralInvest int `export:"true,x:3,y:3"`
LocalInvest int `export:"true,x:4,y:3"`
CompanyInvest int `export:"true,x:5,y:3"`
}
type Total struct {
TotalCentralInvest int `export:"true,x:3,y:2"`
TotalLocalInvest int `export:"true,x:4,y:2"`
TotalCompanyInvest int `export:"true,x:5,y:2"`
Data []Data4
}
func TestMain(T *testing.T) {
title := []export.Title{
{
Name: "项目名称",
Location: export.Location{X: 1, Y: 1},
},
{
Name: "项目单位",
Location: export.Location{X: 2, Y: 1},
},
{
Name: "中央投资",
Location: export.Location{X: 3, Y: 1},
},
{
Name: "地方投资",
Location: export.Location{X: 4, Y: 1},
},
{
Name: "公司投资",
Location: export.Location{X: 5, Y: 1},
},
{
Name: "合计",
Location: export.Location{X: 1, Y: 2},
Colspan: 2,
},
}
data := Total{
TotalCentralInvest: 100,
TotalLocalInvest: 200,
TotalCompanyInvest: 300,
Data: []Data4{
{
ProjectName: "项目1",
ProjectUnit: "单位1",
CentralInvest: 10,
LocalInvest: 20,
CompanyInvest: 30,
},
{
ProjectName: "项目2",
ProjectUnit: "单位2",
CentralInvest: 40,
LocalInvest: 50,
CompanyInvest: 60,
},
},
}
exporter := export.DefaultExporter()
exporter.Titles = title
exporter.Data = &data
exporter.File = "test4.xlsx"
exporter.Path = "./"
exporter.Export(0)
}

BIN
test.xlsx

Binary file not shown.

BIN
test2.xlsx 100755

Binary file not shown.

BIN
test3.xlsx 100755

Binary file not shown.

BIN
test4.xlsx 100755

Binary file not shown.