From e3d9bbbcc5d8c9e33b9bd9334fbd46b508272961 Mon Sep 17 00:00:00 2001 From: Jimmy Xue Date: Sun, 28 Dec 2025 12:15:37 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B0=83=E6=95=B4Excel=E5=AF=BC=E5=87=BA?= =?UTF-8?q?=E6=97=B6=E4=BD=BF=E7=94=A8=E7=9A=84=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/excel.md | 77 +++++++++++++++++------- docs/factory.md | 2 +- examples/excel_example.go | 6 +- excel/excel.go | 123 +++++++++++++++++++++++++------------- 4 files changed, 142 insertions(+), 66 deletions(-) diff --git a/docs/excel.md b/docs/excel.md index acfe5cd..bf1bc7b 100644 --- a/docs/excel.md +++ b/docs/excel.md @@ -13,6 +13,7 @@ Excel导出工具提供了将数据导出到Excel文件的功能,支持结构 - **自定义格式化**:支持自定义字段值的格式化函数 - **自动列宽**:自动调整列宽以适应内容 - **表头样式**:自动应用表头样式(加粗、背景色等) +- **智能工作表管理**:自动处理工作表的创建和删除,避免产生空sheet - **ExportData接口**:支持实现ExportData接口进行高级定制 ## 使用方法 @@ -114,7 +115,7 @@ columns := []factory.ExportColumn{ Header: "创建时间", Field: "CreatedAt", Width: 20, - Format: excel.FormatDateTimeDefault, // 使用便捷的格式化函数 + Format: excel.AdaptTimeFormatter(tools.FormatDateTime), // 使用适配器直接调用tools函数 }, { Header: "状态", @@ -215,8 +216,18 @@ file.SetCellStyle("Sheet2", "A1", "A1", style) **返回:** 错误信息 +**工作表处理逻辑:** +- 如果 `sheetName` 为空或未指定,默认使用 "Sheet1" +- 如果指定的工作表已存在,直接使用该工作表 +- 如果指定的工作表不存在,会自动创建新工作表 +- 如果使用自定义名称(非"Sheet1"),会自动删除默认的"Sheet1"工作表,避免产生空sheet + **示例:** ```go +// 使用默认Sheet1 +fac.ExportToExcel(w, "", columns, users) + +// 使用自定义工作表名称 fac.ExportToExcel(w, "用户列表", columns, users) ``` @@ -232,8 +243,18 @@ fac.ExportToExcel(w, "用户列表", columns, users) **返回:** 错误信息 +**工作表处理逻辑:** +- 如果 `sheetName` 为空或未指定,默认使用 "Sheet1" +- 如果指定的工作表已存在,直接使用该工作表 +- 如果指定的工作表不存在,会自动创建新工作表 +- 如果使用自定义名称(非"Sheet1"),会自动删除默认的"Sheet1"工作表,避免产生空sheet + **示例:** ```go +// 使用默认Sheet1 +fac.ExportToExcelFile("users.xlsx", "", columns, users) + +// 使用自定义工作表名称 fac.ExportToExcelFile("users.xlsx", "用户列表", columns, users) ``` @@ -270,38 +291,42 @@ type ExportColumn struct { - `Width`: 列宽,0表示自动调整 - `Format`: 格式化函数,用于自定义字段值的显示格式 -### 便捷函数 +### 格式化函数适配器 -#### excel.FormatDateTime(layout string) func(interface{}) string +#### excel.AdaptTimeFormatter(fn func(time.Time, ...string) string) func(interface{}) string -创建日期时间格式化函数。 +适配器函数:将tools包的格式化函数转换为Excel Format字段需要的函数类型。 **参数:** -- `layout`: 时间格式,如 "2006-01-02 15:04:05" +- `fn`: tools包的格式化函数(如 `tools.FormatDate`、`tools.FormatDateTime` 等) -**返回:** 格式化函数 +**返回:** Excel Format字段需要的格式化函数 + +**说明:** +- 允许直接使用tools包的任何格式化函数 +- 推荐使用此适配器,避免重复实现格式化逻辑 +- 与factory中的FormatDateTime等方法保持一致 **示例:** ```go -Format: excel.FormatDateTime("2006-01-02 15:04:05") -``` +import ( + "git.toowon.com/jimmy/go-common/excel" + "git.toowon.com/jimmy/go-common/tools" +) -#### excel.FormatDate(value interface{}) string +// 使用tools.FormatDate +Format: excel.AdaptTimeFormatter(tools.FormatDate) -格式化日期(格式:2006-01-02)。 +// 使用tools.FormatDateTime +Format: excel.AdaptTimeFormatter(tools.FormatDateTime) -**示例:** -```go -Format: excel.FormatDate -``` +// 使用tools.FormatTime +Format: excel.AdaptTimeFormatter(tools.FormatTime) -#### excel.FormatDateTimeDefault(value interface{}) string - -格式化日期时间(格式:2006-01-02 15:04:05)。 - -**示例:** -```go -Format: excel.FormatDateTimeDefault +// 使用自定义格式化函数 +Format: excel.AdaptTimeFormatter(func(t time.Time) string { + return tools.Format(t, "2006-01-02 15:04:05", "Asia/Shanghai") +}) ``` ### ExportData接口 @@ -356,7 +381,7 @@ func exportUsersHandler(w http.ResponseWriter, r *http.Request) { Header: "创建时间", Field: "CreatedAt", Width: 20, - Format: excel.FormatDateTimeDefault, + Format: excel.AdaptTimeFormatter(tools.FormatDateTime), }, { Header: "状态", @@ -411,6 +436,10 @@ func main() { 5. **嵌套字段**:支持嵌套字段访问(如 "User.Name"),但需要确保字段路径正确 6. **格式化函数**:格式化函数返回的字符串会直接写入Excel单元格 7. **列宽设置**:Width为0时会自动调整列宽,但可能影响性能(大数据量时建议设置固定宽度) +8. **工作表处理**: + - 使用默认"Sheet1"时,会直接使用默认工作表,不会产生空sheet + - 使用自定义工作表名称时,会自动删除默认的"Sheet1",确保文件只有一个工作表 + - 如果指定的工作表已存在,会直接使用,不会重复创建 ## 最佳实践 @@ -419,6 +448,10 @@ func main() { 3. **使用格式化函数**:对于日期时间、状态等字段,使用格式化函数提高可读性 4. **错误处理**:始终检查导出方法的返回值 5. **HTTP响应**:导出到HTTP响应时,记得设置正确的Content-Type和Content-Disposition头 +6. **工作表命名**: + - 推荐使用有意义的工作表名称(如"用户列表"、"订单数据"等),提高可读性 + - 如果不指定工作表名称,会使用默认的"Sheet1" + - 工具会自动处理工作表的创建和删除,确保不会产生空sheet ## 示例 diff --git a/docs/factory.md b/docs/factory.md index 8defa01..cbabe35 100644 --- a/docs/factory.md +++ b/docs/factory.md @@ -179,7 +179,7 @@ columns := []factory.ExportColumn{ Header: "创建时间", Field: "CreatedAt", Width: 20, - Format: excel.FormatDateTimeDefault, + Format: excel.AdaptTimeFormatter(tools.FormatDateTime), }, } diff --git a/examples/excel_example.go b/examples/excel_example.go index 0ba8247..c8f7857 100644 --- a/examples/excel_example.go +++ b/examples/excel_example.go @@ -8,6 +8,7 @@ import ( "git.toowon.com/jimmy/go-common/excel" "git.toowon.com/jimmy/go-common/factory" + "git.toowon.com/jimmy/go-common/tools" ) // User 用户结构体示例 @@ -111,7 +112,7 @@ func example3(fac *factory.Factory) { Header: "创建时间", Field: "CreatedAt", Width: 20, - Format: excel.FormatDateTimeDefault, // 使用便捷的格式化函数 + Format: excel.AdaptTimeFormatter(tools.FormatDateTime), // 使用适配器直接调用tools函数 }, { Header: "状态", @@ -174,7 +175,7 @@ func (d *UserExportData) GetExportColumns() []excel.ExportColumn { Header: "创建时间", Field: "CreatedAt", Width: 20, - Format: excel.FormatDateTimeDefault, + Format: excel.AdaptTimeFormatter(tools.FormatDateTime), }, { Header: "状态", @@ -230,4 +231,3 @@ func (w *mockResponseWriter) Write(data []byte) (int, error) { func (w *mockResponseWriter) WriteHeader(statusCode int) { // 模拟实现 } - diff --git a/excel/excel.go b/excel/excel.go index 43e65fa..da12883 100644 --- a/excel/excel.go +++ b/excel/excel.go @@ -6,6 +6,7 @@ import ( "reflect" "time" + "git.toowon.com/jimmy/go-common/tools" "github.com/xuri/excelize/v2" ) @@ -94,21 +95,33 @@ func (e *Excel) ExportToWriter(w io.Writer, sheetName string, columns []ExportCo sheetName = "Sheet1" } - // 删除默认工作表(如果存在) - index, err := e.file.GetSheetIndex("Sheet1") - if err == nil && index > 0 { - e.file.DeleteSheet("Sheet1") - } + // 检查工作表是否已存在 + sheetIndex, err := e.file.GetSheetIndex(sheetName) + if err != nil || sheetIndex == 0 { + // 工作表不存在,需要创建 + // 如果sheetName不是"Sheet1",且默认"Sheet1"存在,则删除它 + if sheetName != "Sheet1" { + defaultIndex, _ := e.file.GetSheetIndex("Sheet1") + if defaultIndex > 0 { + e.file.DeleteSheet("Sheet1") + } + } - // 创建新工作表 - _, err = e.file.NewSheet(sheetName) - if err != nil { - return fmt.Errorf("failed to create sheet: %w", err) + // 创建新工作表 + _, err = e.file.NewSheet(sheetName) + if err != nil { + return fmt.Errorf("failed to create sheet: %w", err) + } + + // 重新获取工作表索引 + sheetIndex, err = e.file.GetSheetIndex(sheetName) + if err != nil { + return fmt.Errorf("failed to get sheet index: %w", err) + } } // 设置活动工作表 - sheetIndex, err := e.file.GetSheetIndex(sheetName) - if err == nil && sheetIndex > 0 { + if sheetIndex > 0 { e.file.SetActiveSheet(sheetIndex) } @@ -210,21 +223,33 @@ func (e *Excel) ExportToFile(filePath string, sheetName string, columns []Export sheetName = "Sheet1" } - // 删除默认工作表(如果存在) - index, err := e.file.GetSheetIndex("Sheet1") - if err == nil && index > 0 { - e.file.DeleteSheet("Sheet1") - } + // 检查工作表是否已存在 + sheetIndex, err := e.file.GetSheetIndex(sheetName) + if err != nil || sheetIndex == 0 { + // 工作表不存在,需要创建 + // 如果sheetName不是"Sheet1",且默认"Sheet1"存在,则删除它 + if sheetName != "Sheet1" { + defaultIndex, _ := e.file.GetSheetIndex("Sheet1") + if defaultIndex > 0 { + e.file.DeleteSheet("Sheet1") + } + } - // 创建新工作表 - _, err = e.file.NewSheet(sheetName) - if err != nil { - return fmt.Errorf("failed to create sheet: %w", err) + // 创建新工作表 + _, err = e.file.NewSheet(sheetName) + if err != nil { + return fmt.Errorf("failed to create sheet: %w", err) + } + + // 重新获取工作表索引 + sheetIndex, err = e.file.GetSheetIndex(sheetName) + if err != nil { + return fmt.Errorf("failed to get sheet index: %w", err) + } } // 设置活动工作表 - sheetIndex, err := e.file.GetSheetIndex(sheetName) - if err == nil && sheetIndex > 0 { + if sheetIndex > 0 { e.file.SetActiveSheet(sheetIndex) } @@ -451,31 +476,49 @@ func (e *Excel) getColumnMaxWidth(sheetName string, colIndex int, maxRow int) fl return maxWidth } -// FormatDateTime 格式化日期时间(便捷函数) -// 用于ExportColumn的Format字段 -func FormatDateTime(layout string) func(interface{}) string { +// AdaptTimeFormatter 适配器函数:将tools包的格式化函数转换为Excel Format字段需要的函数类型 +// 允许直接使用tools包的任何格式化函数 +// +// 示例: +// +// // 直接使用tools.FormatDate +// Format: excel.AdaptTimeFormatter(tools.FormatDate) +// +// // 使用自定义格式化函数 +// Format: excel.AdaptTimeFormatter(func(t time.Time) string { +// return tools.Format(t, "2006-01-02 15:04:05", "Asia/Shanghai") +// }) +func AdaptTimeFormatter(fn func(time.Time, ...string) string) func(interface{}) string { return func(value interface{}) string { if t, ok := value.(time.Time); ok { - return t.Format(layout) + return fn(t) } return "" } } -// FormatDate 格式化日期(便捷函数) -// 用于ExportColumn的Format字段,格式:2006-01-02 -func FormatDate(value interface{}) string { - if t, ok := value.(time.Time); ok { - return t.Format("2006-01-02") - } - return "" +// formatDateTime 格式化日期时间(内部便捷函数) +// 用于ExportColumn的Format字段 +// layout: 时间格式,如 "2006-01-02 15:04:05" +// timezone: 可选时区,如果为空则使用时间对象本身的时区 +// 直接调用 tools.Format() 方法 +func formatDateTime(layout string, timezone ...string) func(interface{}) string { + return AdaptTimeFormatter(func(t time.Time, _ ...string) string { + return tools.Format(t, layout, timezone...) + }) } -// FormatDateTimeDefault 格式化日期时间(便捷函数) +// formatDate 格式化日期(内部便捷函数) +// 用于ExportColumn的Format字段,格式:2006-01-02 +// 直接调用 tools.FormatDate() 方法 +var formatDate = AdaptTimeFormatter(tools.FormatDate) + +// formatDateTimeDefault 格式化日期时间(内部便捷函数) // 用于ExportColumn的Format字段,格式:2006-01-02 15:04:05 -func FormatDateTimeDefault(value interface{}) string { - if t, ok := value.(time.Time); ok { - return t.Format("2006-01-02 15:04:05") - } - return "" -} +// 直接调用 tools.FormatDateTime() 方法 +var formatDateTimeDefault = AdaptTimeFormatter(tools.FormatDateTime) + +// formatTime 格式化时间(内部便捷函数) +// 用于ExportColumn的Format字段,格式:15:04:05 +// 直接调用 tools.FormatTime() 方法 +var formatTime = AdaptTimeFormatter(tools.FormatTime)