// Package excel 导入导出封装 // // 基于 xuri/excelize/v2 实现,支持 xls 与 xlsx package excel import ( "fmt" "mime/multipart" "os" "path/filepath" "strings" "time" "github.com/xuri/excelize/v2" ) // ============================== Excel 导入导出 ============================== // FileOpener 可打开的上传文件接口 // // ghttp.UploadFile 与 multipart.FileHeader 均满足该接口 type FileOpener interface { Open() (multipart.File, error) } // ReadSheet 读取上传文件的首个工作表 // // 返回表头与数据行列表,首行视为表头,所有单元格值统一转为字符串; // 空行会被跳过 func ReadSheet(file FileOpener) (headers []string, rows [][]string, err error) { f, err := file.Open() if err != nil { return nil, nil, err } defer f.Close() xl, err := excelize.OpenReader(f) if err != nil { return nil, nil, fmt.Errorf("解析Excel失败: %v", err) } defer xl.Close() sheet := xl.GetSheetName(0) all, err := xl.GetRows(sheet) if err != nil { return nil, nil, err } if len(all) == 0 { return nil, nil, fmt.Errorf("Excel内容为空") } for _, h := range all[0] { headers = append(headers, strings.TrimSpace(h)) } for _, row := range all[1:] { if isBlankRow(row) { continue } cells := make([]string, len(headers)) for i := range headers { if i < len(row) { cells[i] = strings.TrimSpace(row[i]) } } rows = append(rows, cells) } return headers, rows, nil } // WriteSheet 写出 xlsx 文件 // // 文件写入指定目录,文件名由基础名与时间戳拼接而成; // 返回文件名与完整路径 func WriteSheet(dir, baseName string, headers []string, rows [][]any) (fileName, filePath string, err error) { if err = os.MkdirAll(dir, 0o755); err != nil { return "", "", err } xl := excelize.NewFile() defer xl.Close() sheet := xl.GetSheetName(0) _ = xl.SetSheetName(sheet, "data") sheet = "data" for i, h := range headers { cell, _ := excelize.CoordinatesToCellName(i+1, 1) if err = xl.SetCellValue(sheet, cell, h); err != nil { return "", "", err } } for r, row := range rows { for c, v := range row { cell, _ := excelize.CoordinatesToCellName(c+1, r+2) if err = xl.SetCellValue(sheet, cell, v); err != nil { return "", "", err } } } fileName = fmt.Sprintf("%s_%s.xlsx", baseName, time.Now().Format("20060102150405")) filePath = filepath.Join(dir, fileName) if err = xl.SaveAs(filePath); err != nil { return "", "", err } return fileName, filePath, nil } // isBlankRow 判断是否为空行 // // 所有单元格均为空时返回 true func isBlankRow(row []string) bool { for _, c := range row { if strings.TrimSpace(c) != "" { return false } } return true }