- 修复: WebSocket OnlineUserMap 加锁, aliasCronMap 改用 sync.Map - 修复: 替换 dgrijalva/jwt-go 为 golang-jwt/jwt (CVE) - 修复: CronJob.Add 缺少 return, resp.ErrorResp 未处理 error 类型 - 修复: token_util 和 login_service 越界访问 - 修复: Logger 级别配置被代码覆盖 - 修复: SysConfig.ConfigValue 字段长度 varchar(1) -> varchar(500) - 修复: Excel 导出列超过26列崩溃 - 修复: 数据库连接池配置过大, Ping goroutine 泄漏 - 修复: config_dao init 绕过缓存层 - 修复: Redis 空值导致 JSON 解析 panic - 修复: 分配角色页面只返回用户已有角色, 未返回所有角色 - 修复: 用户列表因角色关联导致重复行 - 移除: 前端硬编码私钥和默认密码 - 修复: LoginBody 和 updateAuthRole 路径
154 lines
4.3 KiB
Go
154 lines
4.3 KiB
Go
package excels
|
|
|
|
import (
|
|
"github.com/druidcaesa/gotool"
|
|
"github.com/xuri/excelize/v2"
|
|
"reflect"
|
|
"strconv"
|
|
)
|
|
|
|
// 获取title头list
|
|
func getTitle(list []map[string]string) []string {
|
|
titleList := make([]string, 0)
|
|
for _, item := range list {
|
|
for _, s := range item {
|
|
titleList = append(titleList, s)
|
|
}
|
|
}
|
|
return titleList
|
|
}
|
|
|
|
// 列索引转Excel列字母 (0 -> A, 1 -> B, ..., 25 -> Z, 26 -> AA, 27 -> AB, ...)
|
|
func columnIndexToLetter(col int) string {
|
|
letter := ""
|
|
for col >= 0 {
|
|
letter = string(rune('A'+col%26)) + letter
|
|
col = col/26 - 1
|
|
}
|
|
return letter
|
|
}
|
|
|
|
// ExportExcel 导出excel
|
|
func ExportExcel(list []interface{}, title string) (error, *excelize.File) {
|
|
// 获取标题
|
|
headerList, expList := ExcelCreate(list[0])
|
|
headers := getTitle(headerList)
|
|
// 默认存在第一个工作簿是 Sheet1 首字母要大写, 否则会报错。
|
|
// 如果想额外的创建工作簿, 可以使用, sheet2 := file.NewSheet("Sheet2"), 工作簿的名称不区分大小写。
|
|
// 如果有多个工作簿, 可以使用 file.SetActiveSheet(index) 来指定打开文件时focus到哪个工作簿
|
|
sheet1 := "Sheet1"
|
|
files := excelize.NewFile()
|
|
character := columnIndexToLetter(len(headers) - 1)
|
|
/* -------------------- 第一行大标题 -------------------- */
|
|
// 设置行高
|
|
err := files.SetRowHeight(sheet1, 1, 25)
|
|
if err != nil {
|
|
return err, nil
|
|
}
|
|
// 合并单元格
|
|
err = files.MergeCell(sheet1, "A1", character+"1")
|
|
if err != nil {
|
|
return err, nil
|
|
}
|
|
// 设置单元格样式:对齐;字体, 大小;单元格边框
|
|
styleTitle, _ := files.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 = files.SetCellStyle(sheet1, "A1", character+"1", styleTitle)
|
|
if err != nil {
|
|
return err, nil
|
|
}
|
|
|
|
err = files.SetCellValue(sheet1, "A1", title)
|
|
if err != nil {
|
|
return err, nil
|
|
}
|
|
|
|
/* -------------------- 字段标题 -------------------- */
|
|
styleHeader, _ := files.NewStyle(`{"alignment":{"horizontal":"center","vertical":"center"},"font":{"bold":false,"italic":false,"family":"Calibri","size":10,"color":"#000000"}}`)
|
|
err = files.SetCellStyle(sheet1, "A2", character+"2", styleHeader)
|
|
if err != nil {
|
|
return err, nil
|
|
}
|
|
for k, v := range headers {
|
|
err = files.SetCellValue(sheet1, columnIndexToLetter(k)+"2", v)
|
|
if err != nil {
|
|
return err, nil
|
|
}
|
|
}
|
|
// 设置最后一列宽度
|
|
err = files.SetColWidth(sheet1, "C", character, 20)
|
|
if err != nil {
|
|
return err, nil
|
|
}
|
|
// 冻结窗口:冻结第一行和第二行
|
|
err = files.SetPanes(sheet1, `{"freeze":true,"split":false,"x_split":0,"y_split":2}`)
|
|
if err != nil {
|
|
return err, nil
|
|
}
|
|
///* -------------------- 填充行数据 -------------------- */
|
|
line := 3
|
|
for _, v := range list {
|
|
var num = 0
|
|
lineChr := strconv.Itoa(line)
|
|
// 设置样式
|
|
err = files.SetCellStyle(sheet1, "A"+lineChr, character+lineChr, styleHeader)
|
|
if err != nil {
|
|
return err, nil
|
|
}
|
|
// 反射获取数据和类型
|
|
getValue := reflect.ValueOf(v)
|
|
getType := reflect.TypeOf(v)
|
|
n := getValue.NumField()
|
|
for i := 0; i < n; i++ {
|
|
val := getValue.Field(i)
|
|
name := getType.Field(i).Name
|
|
if !getIsTitle(name, headerList) {
|
|
continue
|
|
}
|
|
err = files.SetCellValue(sheet1, columnIndexToLetter(num)+lineChr, getExp(name, expList, val.Interface()))
|
|
if err != nil {
|
|
return err, nil
|
|
}
|
|
num++
|
|
}
|
|
line++
|
|
}
|
|
return nil, files
|
|
}
|
|
|
|
func getIsTitle(name string, headerList []map[string]string) bool {
|
|
flag := false
|
|
for _, m := range headerList {
|
|
if gotool.StrUtils.HasNotEmpty(m[name]) {
|
|
flag = true
|
|
}
|
|
}
|
|
return flag
|
|
}
|
|
|
|
func getExp(name string, expList []map[string][]map[string]string, value interface{}) interface{} {
|
|
for _, m := range expList {
|
|
if len(m[name]) > 0 {
|
|
maps := m[name]
|
|
for _, m2 := range maps {
|
|
if gotool.StrUtils.HasNotEmpty(m2[interfaceToString(value)]) {
|
|
value = m2[interfaceToString(value)]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return value
|
|
}
|
|
|
|
func interfaceToString(inter interface{}) string {
|
|
switch inter.(type) {
|
|
case string:
|
|
return inter.(string)
|
|
case int:
|
|
return strconv.Itoa(inter.(int))
|
|
case int64:
|
|
return strconv.FormatInt(inter.(int64), 10)
|
|
default:
|
|
return inter.(string)
|
|
}
|
|
}
|