diff --git a/README.md b/README.md index 8bc211c..9d3b883 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,10 @@ # xlsx -excel的导出导入(目前仅完成导出) \ No newline at end of file +excel的导出导入(目前仅完成导出) + +## 导出功能 +如果要使用导出,则传入的数据必须为指针,类型可以是struct或者元素为struct的slice。 + +通过tag来确认需要导出的字段,内容为`export:”x:1,y:1”`,注意下x,y分别表是该字段在excel表格中的起始位置x为col,y为row,且tag中的x在数据中需要顺序列出。 + +详情请看测试文件 \ No newline at end of file diff --git a/export/cell.go b/export/cell.go index 41c2789..f90ce54 100644 --- a/export/cell.go +++ b/export/cell.go @@ -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 } diff --git a/export/style.go b/export/style.go index 8eeb7c8..a284d9f 100644 --- a/export/style.go +++ b/export/style.go @@ -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, + }, + }, + } +} diff --git a/export/title.go b/export/title.go index dd7be12..0eac5d2 100644 --- a/export/title.go +++ b/export/title.go @@ -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) diff --git a/export/xlsx.go b/export/xlsx.go index a73adae..134693e 100644 --- a/export/xlsx.go +++ b/export/xlsx.go @@ -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{ diff --git a/export_test.go b/export1_test.go similarity index 89% rename from export_test.go rename to export1_test.go index 57edb79..d0cf0fc 100644 --- a/export_test.go +++ b/export1_test.go @@ -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{ diff --git a/export2_test.go b/export2_test.go new file mode 100644 index 0000000..329dfae --- /dev/null +++ b/export2_test.go @@ -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) +} diff --git a/export3_test.go b/export3_test.go new file mode 100644 index 0000000..6b6febd --- /dev/null +++ b/export3_test.go @@ -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") +} diff --git a/export4_test.go b/export4_test.go new file mode 100644 index 0000000..cab8a10 --- /dev/null +++ b/export4_test.go @@ -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) + +} diff --git a/test.xlsx b/test.xlsx index 72fae1c..7a2bab3 100755 Binary files a/test.xlsx and b/test.xlsx differ diff --git a/test2.xlsx b/test2.xlsx new file mode 100755 index 0000000..ffb2e7a Binary files /dev/null and b/test2.xlsx differ diff --git a/test3.xlsx b/test3.xlsx new file mode 100755 index 0000000..7d211bc Binary files /dev/null and b/test3.xlsx differ diff --git a/test4.xlsx b/test4.xlsx new file mode 100755 index 0000000..60793bc Binary files /dev/null and b/test4.xlsx differ