fix
parent
b52dfc1b1d
commit
5f6aec0aee
|
@ -1,3 +1,10 @@
|
|||
# xlsx
|
||||
|
||||
excel的导出导入(目前仅完成导出)
|
||||
excel的导出导入(目前仅完成导出)
|
||||
|
||||
## 导出功能
|
||||
如果要使用导出,则传入的数据必须为指针,类型可以是struct或者元素为struct的slice。
|
||||
|
||||
通过tag来确认需要导出的字段,内容为`export:”x:1,y:1”`,注意下x,y分别表是该字段在excel表格中的起始位置x为col,y为row,且tag中的x在数据中需要顺序列出。
|
||||
|
||||
详情请看测试文件
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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{
|
|
@ -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)
|
||||
}
|
|
@ -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")
|
||||
}
|
|
@ -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)
|
||||
|
||||
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue