mirror of
https://gitee.com/xiangheng/x_admin.git
synced 2025-10-16 21:20:43 +08:00
重要:更新TsTime,excel2导出不使用tag
This commit is contained in:
97
server/util/excel2/excel.go
Normal file
97
server/util/excel2/excel.go
Normal file
@@ -0,0 +1,97 @@
|
||||
package excel2
|
||||
|
||||
import (
|
||||
"github.com/xuri/excelize/v2"
|
||||
)
|
||||
|
||||
type Excel struct {
|
||||
F *excelize.File // excel 对象
|
||||
TitleStyle int // 表头样式
|
||||
HeadStyle int // 表头样式
|
||||
ContentStyle1 int // 主体样式1,无背景色
|
||||
ContentStyle2 int // 主体样式2,有背景色
|
||||
}
|
||||
|
||||
// 初始化
|
||||
func ExcelInit() (e *Excel) {
|
||||
e = &Excel{}
|
||||
// excel构建
|
||||
e.F = excelize.NewFile()
|
||||
// 初始化样式
|
||||
e.getTitleRowStyle()
|
||||
e.getHeadRowStyle()
|
||||
e.getDataRowStyle()
|
||||
return e
|
||||
}
|
||||
|
||||
// 获取边框样式
|
||||
func getBorder() []excelize.Border {
|
||||
return []excelize.Border{ // 边框
|
||||
{Type: "top", Color: "000000", Style: 1},
|
||||
{Type: "bottom", Color: "000000", Style: 1},
|
||||
{Type: "left", Color: "000000", Style: 1},
|
||||
{Type: "right", Color: "000000", Style: 1},
|
||||
}
|
||||
}
|
||||
|
||||
// 标题样式
|
||||
func (e *Excel) getTitleRowStyle() {
|
||||
e.TitleStyle, _ = e.F.NewStyle(&excelize.Style{
|
||||
Alignment: &excelize.Alignment{ // 对齐方式
|
||||
Horizontal: "center", // 水平对齐居中
|
||||
Vertical: "center", // 垂直对齐居中
|
||||
},
|
||||
Fill: excelize.Fill{ // 背景颜色
|
||||
Type: "pattern",
|
||||
Color: []string{"#fff2cc"},
|
||||
Pattern: 1,
|
||||
},
|
||||
Font: &excelize.Font{ // 字体
|
||||
Bold: true,
|
||||
Size: 16,
|
||||
},
|
||||
Border: getBorder(),
|
||||
})
|
||||
}
|
||||
|
||||
// 列头行样式
|
||||
func (e *Excel) getHeadRowStyle() {
|
||||
e.HeadStyle, _ = e.F.NewStyle(&excelize.Style{
|
||||
Alignment: &excelize.Alignment{ // 对齐方式
|
||||
Horizontal: "center", // 水平对齐居中
|
||||
Vertical: "center", // 垂直对齐居中
|
||||
WrapText: true, // 自动换行
|
||||
},
|
||||
Fill: excelize.Fill{ // 背景颜色
|
||||
Type: "pattern",
|
||||
Color: []string{"#FDE9D8"},
|
||||
Pattern: 1,
|
||||
},
|
||||
Font: &excelize.Font{ // 字体
|
||||
Bold: true,
|
||||
Size: 14,
|
||||
},
|
||||
Border: getBorder(),
|
||||
})
|
||||
}
|
||||
|
||||
// 数据行样式
|
||||
func (e *Excel) getDataRowStyle() {
|
||||
style := excelize.Style{}
|
||||
style.Border = getBorder()
|
||||
style.Alignment = &excelize.Alignment{
|
||||
Horizontal: "center", // 水平对齐居中
|
||||
Vertical: "center", // 垂直对齐居中
|
||||
WrapText: true, // 自动换行
|
||||
}
|
||||
style.Font = &excelize.Font{
|
||||
Size: 12,
|
||||
}
|
||||
e.ContentStyle1, _ = e.F.NewStyle(&style)
|
||||
style.Fill = excelize.Fill{ // 背景颜色
|
||||
Type: "pattern",
|
||||
Color: []string{"#cce7f5"},
|
||||
Pattern: 1,
|
||||
}
|
||||
e.ContentStyle2, _ = e.F.NewStyle(&style)
|
||||
}
|
219
server/util/excel2/excel_export.go
Normal file
219
server/util/excel2/excel_export.go
Normal file
@@ -0,0 +1,219 @@
|
||||
package excel2
|
||||
|
||||
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 {
|
||||
remainder := (columnNumber - 1) % 26
|
||||
columnName = string(rune('A'+remainder)) + columnName
|
||||
columnNumber = (columnNumber - 1) / 26
|
||||
}
|
||||
return columnName
|
||||
}
|
||||
|
||||
// NormalDynamicExport 导出excel
|
||||
//
|
||||
// 需要在传入的结构体中的字段加上tag:excel:"title:列头名称;index:列下标(从0开始);"
|
||||
//
|
||||
// list 要导出的数据
|
||||
// sheet 文档
|
||||
// title 标题
|
||||
// changeHead map[string]string{"Id": "账号", "Name": "真实姓名"}
|
||||
func NormalDynamicExport(list interface{}, sheet string, title string, changeHead map[string]string) (file *excelize.File, err error) {
|
||||
e := ExcelInit()
|
||||
err = ExportExcel(sheet, title, list, changeHead, e)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return e.F, err
|
||||
}
|
||||
|
||||
// ExportExcel excel导出
|
||||
func ExportExcel(sheet, title string, 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, changeHead, dataValue)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// 构造数据行
|
||||
err = normalBuildDataRow(e, sheet, endColName, dataRow, dataValue)
|
||||
return
|
||||
}
|
||||
|
||||
// 构造表头(endColName 最后一列的列名 dataRow 数据行开始的行号)
|
||||
func normalBuildTitle(e *Excel, sheet, title string, 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
|
||||
}
|
||||
|
||||
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 string, row int, 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
|
||||
}
|
||||
|
||||
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 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
|
||||
}
|
||||
}
|
173
server/util/excel2/excel_export_2.go
Normal file
173
server/util/excel2/excel_export_2.go
Normal file
@@ -0,0 +1,173 @@
|
||||
package excel2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/xuri/excelize/v2"
|
||||
)
|
||||
|
||||
type Col struct {
|
||||
Name string
|
||||
Key string
|
||||
Width int
|
||||
}
|
||||
|
||||
// GetExcelColumnName2 根据列数生成 Excel 列名
|
||||
func GetExcelColumnName2(columnNumber int) string {
|
||||
columnName := ""
|
||||
for columnNumber > 0 {
|
||||
remainder := (columnNumber - 1) % 26
|
||||
columnName = string(rune('A'+remainder)) + columnName
|
||||
columnNumber = (columnNumber - 1) / 26
|
||||
}
|
||||
return columnName
|
||||
}
|
||||
|
||||
// NormalDynamicExport 导出excel
|
||||
//
|
||||
// 需要在传入的结构体中的字段加上tag:excel:"title:列头名称;index:列下标(从0开始);"
|
||||
//
|
||||
// list 要导出的数据
|
||||
//
|
||||
// cols 列
|
||||
//
|
||||
// sheet 文档
|
||||
// title 标题
|
||||
func NormalDynamicExport2(lists []map[string]interface{}, cols []Col, sheet string, title string) (file *excelize.File, err error) {
|
||||
e := ExcelInit()
|
||||
err = ExportExcel2(sheet, title, lists, cols, e)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return e.F, err
|
||||
}
|
||||
|
||||
// ExportExcel2 excel导出
|
||||
func ExportExcel2(sheet, title string, lists []map[string]interface{}, cols []Col, e *Excel) (err error) {
|
||||
index, _ := e.F.GetSheetIndex(sheet)
|
||||
if index < 0 { // 如果sheet名称不存在
|
||||
e.F.NewSheet(sheet)
|
||||
}
|
||||
// 构造表头
|
||||
endColName, startDataRow, err := normalBuildTitle2(e, sheet, title, cols)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// 构造数据行
|
||||
err = normalBuildDataRow2(e, sheet, endColName, startDataRow, lists, cols)
|
||||
return
|
||||
}
|
||||
|
||||
// 构造表头(endColName 最后一列的列名 startDataRow 数据行开始的行号)
|
||||
func normalBuildTitle2(e *Excel, sheet, title string, cols []Col) (endColName string, startDataRow int, err error) {
|
||||
var titleRowData []interface{} // 列头行
|
||||
for i, colTitle := range cols {
|
||||
endColName := GetExcelColumnName2(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 = GetExcelColumnName2(len(titleRowData)) // 根据列数生成 Excel 列名
|
||||
if title != "" {
|
||||
startDataRow = 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 {
|
||||
startDataRow = 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 normalBuildDataRow2(e *Excel, sheet, endColName string, startDataRow int, lists []map[string]interface{}, cols []Col) (err error) {
|
||||
//实时写入数据
|
||||
for i := 0; i < len(lists); i++ {
|
||||
startCol := fmt.Sprintf("A%d", startDataRow)
|
||||
endCol := fmt.Sprintf("%s%d", endColName, startDataRow)
|
||||
|
||||
var rowData []interface{} // 数据列
|
||||
|
||||
list := lists[i]
|
||||
for j := 0; j < len(cols); j++ {
|
||||
col := cols[j]
|
||||
val := list[col.Key]
|
||||
// switch val.(type) {
|
||||
|
||||
// default:
|
||||
// v, _ := json.Marshal(list[col.Key])
|
||||
// rowData = append(rowData, v)
|
||||
// }
|
||||
rowData = append(rowData, val)
|
||||
}
|
||||
|
||||
// // 替换
|
||||
// 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)
|
||||
// }
|
||||
|
||||
if startDataRow%2 == 0 {
|
||||
_ = e.F.SetCellStyle(sheet, startCol, endCol, e.ContentStyle2)
|
||||
} else {
|
||||
_ = e.F.SetCellStyle(sheet, startCol, endCol, e.ContentStyle1)
|
||||
}
|
||||
|
||||
_ = e.F.SetRowHeight(sheet, startDataRow, float64(25)) // 默认行高25
|
||||
|
||||
if err = e.F.SetSheetRow(sheet, startCol, &rowData); err != nil {
|
||||
return
|
||||
}
|
||||
startDataRow++
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 下载
|
||||
func DownLoadExcel2(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
|
||||
}
|
||||
}
|
176
server/util/excel2/excel_import.go
Normal file
176
server/util/excel2/excel_import.go
Normal file
@@ -0,0 +1,176 @@
|
||||
package excel2
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
||||
"github.com/xuri/excelize/v2"
|
||||
)
|
||||
|
||||
func GetExcelData(file multipart.File, dst interface{}) (err error) {
|
||||
// 创建缓冲区
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
// 将文件内容复制到缓冲区
|
||||
_, err = io.Copy(buf, file)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
// c.String(http.StatusInternalServerError, "读取失败")
|
||||
err = errors.New("读取失败")
|
||||
return
|
||||
}
|
||||
// 创建Excel文件对象
|
||||
f, err := excelize.OpenReader(bytes.NewReader(buf.Bytes()))
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
// c.String(http.StatusInternalServerError, "Excel读取失败")
|
||||
err = errors.New("Excel读取失败")
|
||||
return
|
||||
}
|
||||
err = ImportExcel(f, dst, 1, 2)
|
||||
// if err != nil {
|
||||
// fmt.Println(err)
|
||||
// }
|
||||
return err
|
||||
}
|
||||
|
||||
// ImportExcel 导入数据(单个sheet)
|
||||
// 需要在传入的结构体中的字段加上tag:excel:"title:列头名称;"
|
||||
// f 获取到的excel对象、dst 导入目标对象【传指针】
|
||||
// headIndex 表头的索引,从0开始(用于获取表头名字)
|
||||
// startRow 头行行数(从第startRow+1行开始扫)
|
||||
func ImportExcel(f *excelize.File, dst interface{}, headIndex, startRow int) (err error) {
|
||||
sheetName := f.GetSheetName(0) // 单个sheet时,默认读取第一个sheet
|
||||
err = importData(f, dst, sheetName, headIndex, startRow)
|
||||
return
|
||||
}
|
||||
|
||||
// ImportBySheet 导入数据(读取指定sheet)sheetName Sheet名称
|
||||
func ImportBySheet(f *excelize.File, dst interface{}, sheetName string, headIndex, startRow int) (err error) {
|
||||
// 当需要读取多个sheet时,可以通过下面的方式,来调用 ImportBySheet 这个函数
|
||||
//sheetList := f.GetSheetList()
|
||||
//for _, sheetName := range sheetList {
|
||||
// ImportBySheet(f,dst,sheetName,headIndex,startRow)
|
||||
//}
|
||||
err = importData(f, dst, sheetName, headIndex, startRow)
|
||||
return
|
||||
}
|
||||
|
||||
// 获取在数组中得下标
|
||||
func GetIndex(items []string, item string) int {
|
||||
for i, v := range items {
|
||||
if v == item {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// 判断数组中是否包含指定元素
|
||||
// func IsContain(items interface{}, item interface{}) bool {
|
||||
// switch items.(type) {
|
||||
// case []int:
|
||||
// intArr := items.([]int)
|
||||
// for _, value := range intArr {
|
||||
// if value == item.(int) {
|
||||
// return true
|
||||
// }
|
||||
// }
|
||||
// case []string:
|
||||
// strArr := items.([]string)
|
||||
// for _, value := range strArr {
|
||||
// if value == item.(string) {
|
||||
// return true
|
||||
// }
|
||||
// }
|
||||
// default:
|
||||
// return false
|
||||
// }
|
||||
// return false
|
||||
// }
|
||||
|
||||
// 解析数据
|
||||
func importData(f *excelize.File, dst interface{}, sheetName string, headIndex, startRow int) (err error) {
|
||||
rows, err := f.GetRows(sheetName) // 获取所有行
|
||||
if err != nil {
|
||||
err = errors.New(sheetName + "工作表不存在")
|
||||
return
|
||||
}
|
||||
dataValue := reflect.ValueOf(dst) // 取目标对象的元素类型、字段类型和 tag
|
||||
// 判断数据的类型
|
||||
if dataValue.Kind() != reflect.Ptr || dataValue.Elem().Kind() != reflect.Slice {
|
||||
err = errors.New("invalid data type")
|
||||
}
|
||||
heads := []string{} // 表头
|
||||
dataType := dataValue.Elem().Type().Elem() // 获取导入目标对象的类型信息
|
||||
// 遍历行,解析数据并填充到目标对象中
|
||||
for rowIndex, row := range rows {
|
||||
if rowIndex == headIndex {
|
||||
heads = row
|
||||
}
|
||||
if rowIndex < startRow { // 跳过头行
|
||||
continue
|
||||
}
|
||||
newData := reflect.New(dataType).Elem() // 创建新的目标对象
|
||||
// 遍历目标对象的字段
|
||||
for i := 0; i < dataType.NumField(); i++ {
|
||||
// 这里要用构造函数,构造函数里指定了Index默认值为-1,当目标结构体的tag没有指定index的话,那么 excelTag.Index 就一直为0
|
||||
// 那么 row[excelizeIndex] 就始终是 row[0],始终拿的是第一列的数据
|
||||
var excelTag = NewExcelTag()
|
||||
field := dataType.Field(i) // 获取字段信息和tag
|
||||
tag := field.Tag.Get(ExcelTagKey)
|
||||
if tag == "" { // 如果tag不存在,则跳过
|
||||
continue
|
||||
}
|
||||
err = excelTag.GetTag(tag)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
cellValue := ""
|
||||
if excelTag.Index >= 0 { // 当tag里指定了index时,根据这个index来拿数据
|
||||
excelizeIndex := excelTag.Index // 解析tag的值
|
||||
if excelizeIndex >= len(row) { // 防止下标越界
|
||||
continue
|
||||
}
|
||||
cellValue = row[excelizeIndex] // 获取单元格的值
|
||||
} else { // 否则根据表头名称来拿数据
|
||||
var index = GetIndex(heads, excelTag.Name)
|
||||
if index != -1 {
|
||||
if index >= len(row) { // 防止下标越界
|
||||
continue
|
||||
}
|
||||
cellValue = row[index] // 获取单元格的值
|
||||
}
|
||||
}
|
||||
fmt.Println("Type.Name:", field.Type.Name(), field.Type.Kind())
|
||||
// 根据字段类型设置值
|
||||
switch field.Type.Kind() {
|
||||
case reflect.Int:
|
||||
v, _ := strconv.ParseInt(cellValue, 10, 64)
|
||||
newData.Field(i).SetInt(v)
|
||||
case reflect.Int64:
|
||||
v, _ := strconv.ParseInt(cellValue, 10, 64)
|
||||
newData.Field(i).SetInt(v)
|
||||
case reflect.Uint:
|
||||
v, _ := strconv.ParseUint(cellValue, 10, 64)
|
||||
newData.Field(i).SetUint(v)
|
||||
case reflect.Uint8:
|
||||
v, _ := strconv.ParseUint(cellValue, 10, 64)
|
||||
newData.Field(i).SetUint(v)
|
||||
case reflect.Uint16:
|
||||
v, _ := strconv.ParseUint(cellValue, 10, 64)
|
||||
newData.Field(i).SetUint(v)
|
||||
case reflect.String:
|
||||
newData.Field(i).SetString(cellValue)
|
||||
}
|
||||
}
|
||||
// 将新的目标对象添加到导入目标对象的slice中
|
||||
dataValue.Elem().Set(reflect.Append(dataValue.Elem(), newData))
|
||||
}
|
||||
return
|
||||
}
|
74
server/util/excel2/excel_tag.go
Normal file
74
server/util/excel2/excel_tag.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package excel2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 定义正则表达式模式
|
||||
const (
|
||||
ExcelTagKey = "excel"
|
||||
Pattern = "name:(.*?);|index:(.*?);|width:(.*?);|needMerge:(.*?);|replace:(.*?);"
|
||||
)
|
||||
|
||||
type ExcelTag struct {
|
||||
Value interface{}
|
||||
Name string // 表头标题
|
||||
Index int // 列下标(从0开始)
|
||||
Width int // 列宽
|
||||
NeedMerge bool // 是否需要合并
|
||||
Replace string // 替换(需要替换的内容_替换后的内容。比如:1_未开始 ==> 表示1替换为未开始)
|
||||
}
|
||||
|
||||
// 构造函数,返回一个带有默认值的 ExcelTag 实例
|
||||
func NewExcelTag() ExcelTag {
|
||||
return ExcelTag{
|
||||
// 导入时会根据这个下标来拿单元格的值,当目标结构体字段没有设置index时,
|
||||
// 解析字段tag值时Index没读到就一直默认为0,拿单元格的值时,就始终拿的是第一列的值
|
||||
Index: -1, // 设置 Index 的默认值为 -1
|
||||
}
|
||||
}
|
||||
|
||||
// 读取字段tag值
|
||||
func (e *ExcelTag) GetTag(tag string) (err error) {
|
||||
// 编译正则表达式
|
||||
re := regexp.MustCompile(Pattern)
|
||||
matches := re.FindAllStringSubmatch(tag, -1)
|
||||
if len(matches) > 0 {
|
||||
for _, match := range matches {
|
||||
for i, val := range match {
|
||||
if i != 0 && val != "" {
|
||||
e.setValue(match, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
err = errors.New("未匹配到值")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 设置ExcelTag 对应字段的值
|
||||
func (e *ExcelTag) setValue(tag []string, value string) {
|
||||
if strings.Contains(tag[0], "name") {
|
||||
e.Name = value
|
||||
}
|
||||
if strings.Contains(tag[0], "index") {
|
||||
v, _ := strconv.ParseInt(value, 10, 8)
|
||||
e.Index = int(v)
|
||||
}
|
||||
if strings.Contains(tag[0], "width") {
|
||||
v, _ := strconv.ParseInt(value, 10, 8)
|
||||
e.Width = int(v)
|
||||
}
|
||||
if strings.Contains(tag[0], "needMerge") {
|
||||
v, _ := strconv.ParseBool(value)
|
||||
e.NeedMerge = v
|
||||
}
|
||||
if strings.Contains(tag[0], "replace") {
|
||||
e.Replace = value
|
||||
}
|
||||
}
|
76
server/util/excel2/excel_test.go
Normal file
76
server/util/excel2/excel_test.go
Normal file
@@ -0,0 +1,76 @@
|
||||
// 测试源码的文件名以 _test.go 结尾。
|
||||
// 测试函数的函数名以 Test 开头。
|
||||
// 函数签名为 func (t *testing.T)。
|
||||
|
||||
// https://blog.csdn.net/weixin_43165220/article/details/132939884
|
||||
|
||||
package excel2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/xuri/excelize/v2"
|
||||
)
|
||||
|
||||
type Test struct {
|
||||
Id string `excel:"name:用户账号;"`
|
||||
Name string `excel:"name:用户姓名;index:1;"`
|
||||
Email string `excel:"name:用户邮箱;width:25;"`
|
||||
Com string `excel:"name:所属公司;"`
|
||||
Dept string `excel:"name:所在部门;"`
|
||||
RoleKey string `excel:"name:角色代码;"`
|
||||
RoleName string `excel:"name:角色名称;replace:1_超级管理员,2_普通用户;"`
|
||||
Remark string `excel:"name:备注;width:40;"`
|
||||
}
|
||||
|
||||
// 导出
|
||||
func TestExport(t *testing.T) {
|
||||
var testList = []Test{
|
||||
{"fuhua", "符华", "fuhua@123.com", "太虚剑派", "开发部", "CJGLY", "1", "备注备注"},
|
||||
{"baiye", "白夜", "baiye@123.com", "天命科技有限公司", "执行部", "PTYG", "2", ""},
|
||||
{"chiling", "炽翎", "chiling@123.com", "太虚剑派", "行政部", "PTYG", "2", "备注备注备注备注"},
|
||||
{"yunmo", "云墨", "yunmo@123.com", "太虚剑派", "财务部", "CJGLY", "1", ""},
|
||||
{"yuelun", "月轮", "yuelun@123.com", "天命科技有限公司", "执行部", "CJGLY", "1", ""},
|
||||
{"xunyu", "迅羽",
|
||||
"xunyu@123.com哈哈哈哈哈哈哈哈这里是最大行高测试哈哈哈哈哈哈哈哈这11111111111里是最大行高测试哈哈哈哈哈哈哈哈这里是最大行高测试",
|
||||
"天命科技有限公司", "开发部", "PTYG", "2",
|
||||
"备注备注备注备注com哈哈哈哈哈哈哈哈这里是最大行高测试哈哈哈哈哈哈哈哈这里是最大行高测试哈哈哈哈哈哈哈哈这里是最大行高测里是最大行高测试哈哈哈哈哈哈哈哈这里是最大行高测试"},
|
||||
}
|
||||
changeHead := map[string]string{"Id": "账号", "Name": "真实姓名"}
|
||||
//f, err := excel.NormalExport(testList, "Sheet1", "用户信息", "Id,Email,", true, true, changeHead)
|
||||
f, err := NormalDynamicExport(testList, "Sheet1", "用户信息", changeHead)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
f.Path = "./测试.xlsx"
|
||||
if err := f.Save(); err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 导入
|
||||
func TestImports(t *testing.T) {
|
||||
f, err := excelize.OpenFile("./测试.xlsx")
|
||||
if err != nil {
|
||||
fmt.Println("文件打开失败")
|
||||
}
|
||||
importList := []Test{}
|
||||
err = ImportExcel(f, &importList, 1, 2)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
for _, t := range importList {
|
||||
fmt.Println(t)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetExcelColumnName(t *testing.T) {
|
||||
for i := 0; i < 100; i++ {
|
||||
var col = GetExcelColumnName(i)
|
||||
fmt.Println("col:", col)
|
||||
}
|
||||
|
||||
}
|
BIN
server/util/excel2/测试.xlsx
Normal file
BIN
server/util/excel2/测试.xlsx
Normal file
Binary file not shown.
Reference in New Issue
Block a user