导入导出excel示例

This commit is contained in:
xiangheng
2024-01-20 01:20:42 +08:00
parent d7f13cb80b
commit ea481b867c
10 changed files with 659 additions and 5 deletions

View File

@@ -0,0 +1,254 @@
package excel
import (
"errors"
"fmt"
"net/http"
"reflect"
"sort"
"strconv"
"strings"
"github.com/xuri/excelize/v2"
)
// GetExcelColumnName 根据列数生成 Excel 列名
func GetExcelColumnName(columnNumber int) string {
columnName := ""
for columnNumber > 0 {
columnNumber--
columnName = fmt.Sprint('A'+columnNumber%26) + columnName
columnNumber /= 26
}
return columnName
}
// ExportExcel excel导出
func ExportExcel(sheet, title, fields string, isGhbj, isIgnore bool, list interface{}, changeHead map[string]string, e *Excel) (err error) {
index, _ := e.F.GetSheetIndex(sheet)
if index < 0 { // 如果sheet名称不存在
e.F.NewSheet(sheet)
}
// 构造excel表格
// 取目标对象的元素类型、字段类型和 tag
dataValue := reflect.ValueOf(list)
// 判断数据的类型
if dataValue.Kind() != reflect.Slice {
err = errors.New("invalid data type")
return
}
// 构造表头
endColName, dataRow, err := normalBuildTitle(e, sheet, title, fields, isIgnore, changeHead, dataValue)
if err != nil {
return
}
// 构造数据行
err = normalBuildDataRow(e, sheet, endColName, fields, dataRow, isGhbj, isIgnore, dataValue)
return
}
// ================================= 普通导出 =================================
// NormalDownLoad 导出excel并下载单个sheet
func NormalDownLoad(fileName, sheet, title string, isGhbj bool, list interface{}, res http.ResponseWriter) error {
f, err := NormalDynamicExport(list, sheet, title, "", isGhbj, false, nil)
if err != nil {
return err
}
DownLoadExcel(fileName, res, f)
return nil
}
// NormalDynamicDownLoad 动态导出excel并下载单个sheet
// isIgnore 是否忽略指定字段true 要忽略的字段 false 要导出的字段)
// fields 选择的字段多个字段用逗号隔开最后一个字段后面也要加逗号字段1,字段2,字段3,
// changeHead 要改变表头的字段,格式是{"字段1":"更改的表头1","字段2":"更改的表头2"}
func NormalDynamicDownLoad(fileName, sheet, title, fields string, isGhbj, isIgnore bool,
list interface{}, changeHead map[string]string, res http.ResponseWriter) error {
f, err := NormalDynamicExport(list, sheet, title, fields, isGhbj, isIgnore, changeHead)
if err != nil {
return err
}
DownLoadExcel(fileName, res, f)
return nil
}
// NormalDynamicExport 导出excel
// ** 需要在传入的结构体中的字段加上tagexcelize:"title:列头名称;index:列下标(从0开始);"
// list 需要导出的对象数组、sheet sheet名称、title 标题、isGhbj 是否设置隔行背景色
func NormalDynamicExport(list interface{}, sheet, title, fields string, isGhbj, isIgnore bool, changeHead map[string]string) (file *excelize.File, err error) {
e := ExcelInit()
err = ExportExcel(sheet, title, fields, isGhbj, isIgnore, list, changeHead, e)
if err != nil {
return
}
return e.F, err
}
// 构造表头endColName 最后一列的列名 dataRow 数据行开始的行号)
func normalBuildTitle(e *Excel, sheet, title, fields string, isIgnore bool, changeHead map[string]string, dataValue reflect.Value) (endColName string, dataRow int, err error) {
dataType := dataValue.Type().Elem() // 获取导入目标对象的类型信息
var exportTitle []ExcelTag // 遍历目标对象的字段
for i := 0; i < dataType.NumField(); i++ {
var excelTag ExcelTag
field := dataType.Field(i) // 获取字段信息和tag
tag := field.Tag.Get(ExcelTagKey)
if tag == "" { // 如果非导出则跳过
continue
}
if fields != "" { // 选择要导出或要忽略的字段
if isIgnore && strings.Contains(fields, field.Name+",") { // 忽略指定字段
continue
}
if !isIgnore && !strings.Contains(fields, field.Name+",") { // 导出指定字段
continue
}
}
err = excelTag.GetTag(tag)
if err != nil {
return
}
// 更改指定字段的表头标题
if changeHead != nil && changeHead[field.Name] != "" {
excelTag.Name = changeHead[field.Name]
}
exportTitle = append(exportTitle, excelTag)
}
// 排序
sort.Slice(exportTitle, func(i, j int) bool {
return exportTitle[i].Index < exportTitle[j].Index
})
var titleRowData []interface{} // 列头行
for i, colTitle := range exportTitle {
endColName := GetExcelColumnName(i + 1)
if colTitle.Width > 0 { // 根据给定的宽度设置列宽
_ = e.F.SetColWidth(sheet, endColName, endColName, float64(colTitle.Width))
} else {
_ = e.F.SetColWidth(sheet, endColName, endColName, float64(20)) // 默认宽度为20
}
titleRowData = append(titleRowData, colTitle.Name)
}
endColName = GetExcelColumnName(len(titleRowData)) // 根据列数生成 Excel 列名
if title != "" {
dataRow = 3 // 如果有title那么从第3行开始就是数据行第1行是title第2行是表头
e.F.SetCellValue(sheet, "A1", title)
e.F.MergeCell(sheet, "A1", endColName+"1") // 合并标题单元格
e.F.SetCellStyle(sheet, "A1", endColName+"1", e.TitleStyle)
e.F.SetRowHeight(sheet, 1, float64(30)) // 第一行行高
e.F.SetRowHeight(sheet, 2, float64(30)) // 第二行行高
e.F.SetCellStyle(sheet, "A2", endColName+"2", e.HeadStyle)
if err = e.F.SetSheetRow(sheet, "A2", &titleRowData); err != nil {
return
}
} else {
dataRow = 2 // 如果没有title那么从第2行开始就是数据行第1行是表头
e.F.SetRowHeight(sheet, 1, float64(30))
e.F.SetCellStyle(sheet, "A1", endColName+"1", e.HeadStyle)
if err = e.F.SetSheetRow(sheet, "A1", &titleRowData); err != nil {
return
}
}
return
}
// 构造数据行
func normalBuildDataRow(e *Excel, sheet, endColName, fields string, row int, isGhbj, isIgnore bool, dataValue reflect.Value) (err error) {
//实时写入数据
for i := 0; i < dataValue.Len(); i++ {
startCol := fmt.Sprintf("A%d", row)
endCol := fmt.Sprintf("%s%d", endColName, row)
item := dataValue.Index(i)
typ := item.Type()
num := item.NumField()
var exportRow []ExcelTag
maxLen := 0 // 记录这一行中,数据最多的单元格的值的长度
//遍历结构体的所有字段
for j := 0; j < num; j++ {
dataField := typ.Field(j) //获取到struct标签需要通过reflect.Type来获取tag标签的值
tagVal := dataField.Tag.Get(ExcelTagKey)
if tagVal == "" { // 如果非导出则跳过
continue
}
if fields != "" { // 选择要导出或要忽略的字段
if isIgnore && strings.Contains(fields, dataField.Name+",") { // 忽略指定字段
continue
}
if !isIgnore && !strings.Contains(fields, dataField.Name+",") { // 导出指定字段
continue
}
}
var dataCol ExcelTag
err = dataCol.GetTag(tagVal)
fieldData := item.FieldByName(dataField.Name) // 取字段值
if fieldData.Type().String() == "string" { // string类型的才计算长度
rwsTemp := fieldData.Len() // 当前单元格内容的长度
if rwsTemp > maxLen { //这里取每一行中的每一列字符长度最大的那一列的字符
maxLen = rwsTemp
}
}
// 替换
if dataCol.Replace != "" {
split := strings.Split(dataCol.Replace, ",")
for j := range split {
s := strings.Split(split[j], "_") // 根据下划线进行分割格式需要替换的内容_替换后的内容
value := fieldData.String()
if strings.Contains(fieldData.Type().String(), "int") {
value = strconv.Itoa(int(fieldData.Int()))
} else if fieldData.Type().String() == "bool" {
value = strconv.FormatBool(fieldData.Bool())
} else if strings.Contains(fieldData.Type().String(), "float") {
value = strconv.FormatFloat(fieldData.Float(), 'f', -1, 64)
}
if s[0] == value {
dataCol.Value = s[1]
}
}
} else {
dataCol.Value = fieldData
}
if err != nil {
return
}
exportRow = append(exportRow, dataCol)
}
// 排序
sort.Slice(exportRow, func(i, j int) bool {
return exportRow[i].Index < exportRow[j].Index
})
var rowData []interface{} // 数据列
for _, colTitle := range exportRow {
rowData = append(rowData, colTitle.Value)
}
if isGhbj && row%2 == 0 {
_ = e.F.SetCellStyle(sheet, startCol, endCol, e.ContentStyle2)
} else {
_ = e.F.SetCellStyle(sheet, startCol, endCol, e.ContentStyle1)
}
if maxLen > 25 { // 自适应行高
d := maxLen / 25
f := 25 * d
_ = e.F.SetRowHeight(sheet, row, float64(f))
} else {
_ = e.F.SetRowHeight(sheet, row, float64(25)) // 默认行高25
}
if err = e.F.SetSheetRow(sheet, startCol, &rowData); err != nil {
return
}
row++
}
return
}
// 下载
func DownLoadExcel(fileName string, res http.ResponseWriter, file *excelize.File) {
// 设置响应头
res.Header().Set("Content-Type", "text/html; charset=UTF-8")
res.Header().Set("Content-Type", "application/octet-stream")
res.Header().Set("Content-Disposition", "attachment; filename="+fileName+".xlsx")
res.Header().Set("Access-Control-Expose-Headers", "Content-Disposition")
err := file.Write(res) // 写入Excel文件内容到响应体
if err != nil {
http.Error(res, err.Error(), http.StatusInternalServerError)
return
}
}