第一种方法
项目地址:https://github.com/tealeg/xlsx
go get -u github.com/tealeg/xlsx
func test1() {
// rows 为查询出的多条记录
filename := fmt.Sprintf(test1.%d.xlsx", time.Now().Unix())
type item struct {
Rank int
Userid int
UserName string
NickName string
Phone string
Departid int
FirstDepart string
SecondDepart string
ThirdDepart string
Score int
Update string
}
list := make([]item, 0)
var rk = 1
var userid int
var award_value int
var createat int
for rows.Next() {
rows.Scan(&userid, &award_value, &createat)
it := item{
Rank: rk,
Userid: userid,
Score: award_value,
Update: time.Unix(int64(createat), 0).Format(os.Getenv("DATE_FORMAT_SECOND")),
}
list = append(list, it)
rk++
}
file := xlsx.NewFile()
sheet, _ := file.AddSheet("Sheet1")
var row *xlsx.Row
var cell *xlsx.Cell
// 字段标题,和 item 字段一一对应
headers := []string{"名称", "用户ID", "对接账号", "姓名", "电话", "部门ID", "一级部门", "二级部门", "三级部门", "总积分", "最后更新时间"}
// 设置单元格宽度
sheet.SetColWidth(len(headers)-1, len(headers)-1, 15)
row = sheet.AddRow()
row.SetHeightCM(1.5)
// 第一行大标题
cell = row.AddCell()
// 合并单元格
cell.Merge(len(headers), 0)
// 设置单元格样式
style := cell.GetStyle()
style.Alignment = xlsx.Alignment{
Horizontal: "center", // 水平居中
Vertical: "center", // 垂直居中
}
// 设置字体,大小
style.Font = xlsx.Font{
Size: 16,
Name: "Calibri",
Bold: true,
}
cell.SetStyle(style)
cell.Value = "test1"
row = sheet.AddRow()
for _, v := range headers {
cell = row.AddCell()
style = cell.GetStyle()
style.Font = xlsx.Font{
Size: 10,
Name: "Calibri",
}
cell.Value = v
}
for _, v := range list {
row = sheet.AddRow()
getValue := reflect.ValueOf(v)
n := getValue.NumField()
for i := 0; i < n; i++ {
cell = row.AddCell()
// 设置单元格样式
style = cell.GetStyle()
style.Font = xlsx.Font{
Size: 10,
Name: "Calibri",
}
cell.SetStyle(style)
val := getValue.Field(i)
switch val.Kind() {
case reflect.String:
cell.Value = val.Interface().(string)
case reflect.Int:
cell.Value = strconv.Itoa(val.Interface().(int))
}
}
}
err = file.Save("../../../storage/read3/" + filename)
if err != nil {
return err
}
return nil
}
大部分的时候我们会将结构体切片的数据写入表格,而且结构体又不能遍历,如果字段过多,那么写起来就很麻烦,并且调整起来也格外麻烦。
反射的话字段都要可导出的。
关于结构体切片的反射可以参考 https://blog.csdn.net/raoxiaoya/article/details/111182338
另外一种方法是360出品的
项目地址:https://github.com/360EntSecGroup-Skylar/excelize
go get -u github.com/360EntSecGroup-Skylar/excelize/v2
这个包使用起来可配置性很强。说明较为详细,说明大部分在方法的注释里面。
func test2() {
// rows 为查询出的多条记录
filename := fmt.Sprintf(test2.%d.xlsx", time.Now().Unix())
type item struct {
Rank int
Userid int
UserName string
NickName string
Phone string
Departid int
FirstDepart string
SecondDepart string
ThirdDepart string
Score int
Update string
}
list := make([]item, 0)
var rk = 1
var userid int
var award_value int
var createat int
for rows.Next() {
rows.Scan(&userid, &award_value, &createat)
it := item{
Rank: rk,
Userid: userid,
Score: award_value,
Phone: "15919901990",
Update: time.Unix(int64(createat), 0).Format(os.Getenv("DATE_FORMAT_SECOND")),
}
list = append(list, it)
rk++
}
// 数据导出 start
// 字段标题,和 item 字段一一对应
headers := []string{"排名", "用户ID", "对接账号", "姓名", "电话", "部门ID", "一级部门", "二级部门", "三级部门", "总积分", "最后更新时间"}
// 默认存在第一个工作簿是 Sheet1 首字母要大写,否则会报错。
// 如果想额外的创建工作簿,可以使用,sheet2 := file.NewSheet("Sheet2"),工作簿的名称不区分大小写。
// 如果有多个工作簿,可以使用 file.SetActiveSheet(index) 来指定打开文件时focus到哪个工作簿
sheet1 := "Sheet1"
file := excelize.NewFile()
// 65 -> A
ln := len(headers)
character := ""
if ln <= 26 {
character = string(64 + ln)
} else if ln <= 52 {
character = "A" + string(64+ln-26)
} else {
character = "AA" + string(64+ln-52)
}
/* -------------------- 第一行大标题 -------------------- */
// 设置行高
err = file.SetRowHeight(sheet1, 1, 40)
if err != nil {
return err
}
// 合并单元格
err = file.MergeCell(sheet1, "A1", character+"1")
if err != nil {
return err
}
// 设置单元格样式:对齐;字体,大小;单元格边框
styleTitle, _ := file.NewStyle(`{"alignment":{"horizontal":"center","vertical":"center"},"font":{"bold":true,"italic":false,"family":"Calibri","size":16,"color":"#000000"},"border":[{"type":"left","color":"#3FAD08","style":0},{"type":"top","color":"#3FAD08","style":0},{"type":"bottom","color":"#3FAD08","style":2},{"type":"right","color":"#3FAD08","style":0}]}`)
err = file.SetCellStyle(sheet1, "A1", character+"1", styleTitle)
if err != nil {
return err
}
err = file.SetCellValue(sheet1, "A1", "test2")
if err != nil {
return err
}
/* -------------------- 字段标题 -------------------- */
styleHeader, _ := file.NewStyle(`{"alignment":{"horizontal":"center","vertical":"center"},"font":{"bold":false,"italic":false,"family":"Calibri","size":10,"color":"#000000"}}`)
err = file.SetCellStyle(sheet1, "A2", character+"2", styleHeader)
if err != nil {
return err
}
for k, v := range headers {
chr := ""
if k < 26 {
chr = string(64 + k + 1)
} else if k < 52 {
chr = "A" + string(64+k+1-26)
} else {
chr = "AA" + string(64+k+1-52)
}
err = file.SetCellValue(sheet1, chr+"2", v)
if err != nil {
return err
}
}
// 设置最后一列宽度
err = file.SetColWidth(sheet1, "C", character, 20)
if err != nil {
return err
}
// 冻结窗口:冻结第一行和第二行
err = file.SetPanes(sheet1, `{"freeze":true,"split":false,"x_split":0,"y_split":2}`)
if err != nil {
return err
}
///* -------------------- 填充行数据 -------------------- */
line := 3
for _, v := range list {
lineChr := strconv.Itoa(line)
// 设置样式
err = file.SetCellStyle(sheet1, "A"+lineChr, character+lineChr, styleHeader)
if err != nil {
return err
}
// 反射获取数据
getValue := reflect.ValueOf(v)
n := getValue.NumField()
for i := 0; i < n; i++ {
val := getValue.Field(i)
chr := ""
if i < 26 {
chr = string(64 + i + 1)
} else if i < 52 {
chr = "A" + string(64+i+1-26)
} else {
chr = "AA" + string(64+i+1-52)
}
err = file.SetCellValue(sheet1, chr+lineChr, val.Interface())
if err != nil {
return err
}
}
line++
}
err = file.SaveAs("../../../storage/" + filename)
if err != nil {
return err
}
return nil
}
关于单元格边框的设置,比如 "border":[{"type":"left","color":"#3FAD08","style":0}
其中 style 为线的类型,在 styles.go 中有说明
最后基于这个样式简单封装了一个方法
func OutPutDataWithXLSX(list interface{}, headers []string, title string, filepath string, filename string) (err error) {
// 创建目录结构
err = file.CheckAndMakeDirAll(filepath)
if err != nil {
return err
}
filename = filepath + "/" + filename
// 默认存在第一个工作簿是 Sheet1 首字母要大写,否则会报错。
// 如果想额外的创建工作簿,可以使用,sheet2 := file.NewSheet("Sheet2"),工作簿的名称不区分大小写。
// 如果有多个工作簿,可以使用 file.SetActiveSheet(index) 来指定打开文件时focus到哪个工作簿
sheet1 := "Sheet1"
f := excelize.NewFile()
character := string(65 + len(headers) - 1)
/* -------------------- 第一行大标题 -------------------- */
// 设置行高
err = f.SetRowHeight(sheet1, 1, 40)
if err != nil {
return err
}
// 合并单元格
err = f.MergeCell(sheet1, "A1", character+"1")
if err != nil {
return err
}
// 设置单元格样式:对齐;字体,大小;单元格边框
styleTitle, _ := f.NewStyle(`{"alignment":{"horizontal":"center","vertical":"center"},"font":{"bold":true,"italic":false,"family":"Calibri","size":16,"color":"#000000"},"border":[{"type":"left","color":"#3FAD08","style":0},{"type":"top","color":"#3FAD08","style":0},{"type":"bottom","color":"#3FAD08","style":2},{"type":"right","color":"#3FAD08","style":0}]}`)
err = f.SetCellStyle(sheet1, "A1", character+"1", styleTitle)
if err != nil {
return err
}
err = f.SetCellValue(sheet1, "A1", title)
if err != nil {
return err
}
/* -------------------- 字段标题 -------------------- */
styleHeader, _ := f.NewStyle(`{"alignment":{"horizontal":"center","vertical":"center"},"font":{"bold":false,"italic":false,"family":"Calibri","size":10,"color":"#000000"}}`)
err = f.SetCellStyle(sheet1, "A2", character+"2", styleHeader)
if err != nil {
return err
}
for k, v := range headers {
err = f.SetCellValue(sheet1, string(65+k)+"2", v)
if err != nil {
return err
}
}
// 设置最后一列宽度
err = f.SetColWidth(sheet1, "C", character, 20)
if err != nil {
return err
}
// 冻结窗口:冻结第一行和第二行
err = f.SetPanes(sheet1, `{"freeze":true,"split":false,"x_split":0,"y_split":2}`)
if err != nil {
return err
}
///* -------------------- 填充行数据 -------------------- */
getValue := reflect.ValueOf(list)
if getValue.Kind() != reflect.Slice {
return errors.New("list must be slice")
}
length := getValue.Len()
if length > 0 {
line := 3
for i := 0; i < length; i++ {
value := getValue.Index(i)
typel := value.Type()
if typel.Kind() != reflect.Struct {
return errors.New("list must be slice of struct")
}
lineChr := strconv.Itoa(line)
// 设置样式
err = f.SetCellStyle(sheet1, "A"+lineChr, character+lineChr, styleHeader)
if err != nil {
return err
}
n := value.NumField()
for i := 0; i < n; i++ {
val := value.Field(i)
err = f.SetCellValue(sheet1, string(65+i)+lineChr, val.Interface())
if err != nil {
return err
}
}
line++
}
}
err = f.SaveAs(filename)
if err != nil {
return err
}
return nil
}
调用
filepath := "../../../storage/" + taskmodel.TaskCate
filename := fmt.Sprintf("test2.%d.xlsx", time.Now().Unix())
title := "test2"
headers := []string{"排名", "用户ID", "对接账号", "姓名", "电话", "部门ID", "一级部门", "二级部门", "三级部门", "总积分", "最后更新时间", "毫秒"}
err = util.OutPutDataWithXLSX(list, headers, title, filepath, filename)