ai_scheduler/internal/tools/bbxt/excel.go

326 lines
9.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package bbxt
import (
"fmt"
"reflect"
"sort"
"github.com/go-kratos/kratos/v2/log"
"github.com/xuri/excelize/v2"
)
// 最简单的通用函数
func (b *BbxtTools) SimpleFillExcel(templatePath, outputPath string, dataSlice interface{}) error {
// 1. 打开模板
f, err := excelize.OpenFile(templatePath)
if err != nil {
return err
}
defer f.Close()
sheet := f.GetSheetName(0)
// 1.1 获取第二行模板样式
resellerTplRow := 2
styleIDReseller, err := f.GetCellStyle(sheet, fmt.Sprintf("A%d", resellerTplRow))
if err != nil {
log.Errorf("获取分销商总计样式失败: %v", err)
styleIDReseller = 0
}
// 1.2 获取分销商总计行高
rowHeightReseller, err := f.GetRowHeight(sheet, resellerTplRow)
if err != nil {
log.Errorf("获取分销商总计行高失败: %v", err)
rowHeightReseller = 31 // 默认高度
}
// 2. 反射获取切片数据
v := reflect.ValueOf(dataSlice)
if v.Kind() != reflect.Slice {
return fmt.Errorf("dataSlice must be a slice")
}
// 3. 从第2行开始填充
row := 2
for i := 0; i < v.Len(); i++ {
item := v.Index(i).Interface()
currentRow := row + i
// 4. 将item转换为一行数据
var rowData []interface{}
// 如果是切片
if reflect.TypeOf(item).Kind() == reflect.Slice {
itemV := reflect.ValueOf(item)
for j := 0; j < itemV.Len(); j++ {
rowData = append(rowData, itemV.Index(j).Interface())
}
} else if reflect.TypeOf(item).Kind() == reflect.Struct {
itemV := reflect.ValueOf(item)
for j := 0; j < itemV.NumField(); j++ {
if itemV.Field(j).CanInterface() {
rowData = append(rowData, itemV.Field(j).Interface())
}
}
} else {
rowData = []interface{}{item}
}
// 4.1 设置行高
f.SetRowHeight(sheet, currentRow, rowHeightReseller)
// 5. 填充到Excel
for col, value := range rowData {
cell := fmt.Sprintf("%c%d", 'A'+col, currentRow)
f.SetCellValue(sheet, cell, value)
}
// 5.1 使用第二行模板样式
if styleIDReseller != 0 {
f.SetCellStyle(sheet, fmt.Sprintf("A%d", currentRow), fmt.Sprintf("B%d", currentRow), styleIDReseller)
}
}
// 6. 保存
return f.SaveAs(outputPath)
}
// 分销商负利润详情填充excel
// 1.使用模板文件作为输出文件
// 2.分销商总计使用第二行样式(宽高、背景、颜色等)
// 3.商品详情使用第三行样式(宽高、背景、颜色等)
// 4.保存为新文件
func (b *BbxtTools) resellerDetailFillExcel(templatePath, outputPath string, dataSlice []*ResellerLoss) error {
// 1. 读取模板
f, err := excelize.OpenFile(templatePath)
if err != nil {
return err
}
defer f.Close()
sheet := f.GetSheetName(0)
// 获取模板样式1第二行-分销商总计
resellerTplRow := 2
styleIDReseller, err := f.GetCellStyle(sheet, fmt.Sprintf("A%d", resellerTplRow))
if err != nil {
log.Errorf("获取分销商总计样式失败: %v", err)
styleIDReseller = 0
}
rowHeightReseller, err := f.GetRowHeight(sheet, resellerTplRow)
if err != nil {
log.Errorf("获取分销商总计行高失败: %v", err)
rowHeightReseller = 31 // 默认高度
}
// 获取模板样式2第三行-产品亏损明细
productTplRow := 3
styleIDProduct, err := f.GetCellStyle(sheet, fmt.Sprintf("A%d", productTplRow))
if err != nil {
log.Errorf("获取商品详情样式失败: %v", err)
styleIDProduct = 0
}
rowHeightProduct, err := f.GetRowHeight(sheet, productTplRow)
if err != nil {
log.Errorf("获取商品详情行高失败: %v", err)
rowHeightProduct = 25 // 默认高度
}
currentRow := 2
for _, reseller := range dataSlice {
// 3. 填充经销商数据 (ResellerName, Total)
// 设置行高
f.SetRowHeight(sheet, currentRow, rowHeightReseller)
// 设置单元格值
f.SetCellValue(sheet, fmt.Sprintf("A%d", currentRow), reseller.ResellerName)
f.SetCellValue(sheet, fmt.Sprintf("B%d", currentRow), reseller.Total)
// 应用样式
if styleIDReseller != 0 {
f.SetCellStyle(sheet, fmt.Sprintf("A%d", currentRow), fmt.Sprintf("B%d", currentRow), styleIDReseller)
}
currentRow++
// 4. 填充产品亏损明细
// 先对 ProductLoss 进行排序
var products []ProductLoss
for _, p := range reseller.ProductLoss {
products = append(products, p)
}
// 按 Loss 升序排序 (亏损越多越靠前,负数越小)
sort.Slice(products, func(i, j int) bool {
return products[i].Loss < products[j].Loss
})
for _, p := range products {
// 设置行高
f.SetRowHeight(sheet, currentRow, rowHeightProduct)
// 设置单元格值
f.SetCellValue(sheet, fmt.Sprintf("A%d", currentRow), fmt.Sprintf("·%s", p.ProductName))
f.SetCellValue(sheet, fmt.Sprintf("B%d", currentRow), p.Loss)
// 应用样式
if styleIDProduct != 0 {
f.SetCellStyle(sheet, fmt.Sprintf("A%d", currentRow), fmt.Sprintf("B%d", currentRow), styleIDProduct)
}
currentRow++
}
}
// buffer, err := f.WriteToBuffer()
// if err != nil {
// return err
// }
// return buffer.Bytes(), nil
// 6. 保存
return f.SaveAs(outputPath)
}
// 分销商负利润详情填充excel-V2
// 1.使用模板文件作为输出文件,从第二行开始填充
// 2.整体为3列1.分销商名称以ResellerName为分组分销商名称列使用的样式为 2.商品名称p.ProductName 3.亏损金额p.Loss
// 3.分销商名称列使用的样式为 A2商品名称、亏损金额使用的样式为 B2、C2样式包括宽高、背景、颜色等
// 4.以ResellerName分组合并单元格
// 5.在文件末尾使用“合计”,合计行样式为模板第四行
// 6.保存为新文件
func (b *BbxtTools) resellerDetailFillExcelV2(templatePath, outputPath string, dataSlice []*ResellerLoss) error {
// 1. 读取模板
f, err := excelize.OpenFile(templatePath)
if err != nil {
return err
}
defer f.Close()
sheet := f.GetSheetName(0)
// ---------------- 样式获取 ----------------
// 模板第2行数据行样式
tplRowData := 2
styleA2, err := f.GetCellStyle(sheet, fmt.Sprintf("A%d", tplRowData))
if err != nil {
styleA2 = 0
}
// B2和C2通常样式一致这里取B2作为明细列样式
styleB2, err := f.GetCellStyle(sheet, fmt.Sprintf("B%d", tplRowData))
if err != nil {
styleB2 = 0
}
styleC2, err := f.GetCellStyle(sheet, fmt.Sprintf("C%d", tplRowData))
if err != nil {
styleC2 = 0
}
rowHeightData, err := f.GetRowHeight(sheet, tplRowData)
if err != nil {
rowHeightData = 20
}
// 模板第4行合计行样式
tplRowTotal := 4
styleTotalA, err := f.GetCellStyle(sheet, fmt.Sprintf("A%d", tplRowTotal))
if err != nil {
styleTotalA = 0
}
styleTotalB, err := f.GetCellStyle(sheet, fmt.Sprintf("B%d", tplRowTotal))
if err != nil {
styleTotalB = 0
}
styleTotalC, err := f.GetCellStyle(sheet, fmt.Sprintf("C%d", tplRowTotal))
if err != nil {
styleTotalC = 0
}
rowHeightTotal, err := f.GetRowHeight(sheet, tplRowTotal)
if err != nil {
rowHeightTotal = 30
}
// ----------------------------------------
currentRow := 2
totalLoss := 0.0
for _, reseller := range dataSlice {
// 排序 ProductLoss
var products []ProductLoss
for _, p := range reseller.ProductLoss {
products = append(products, p)
}
sort.Slice(products, func(i, j int) bool {
return products[i].Loss < products[j].Loss
})
startRow := currentRow
// 填充该经销商的所有产品
for _, p := range products {
// 设置行高
f.SetRowHeight(sheet, currentRow, rowHeightData)
// 设置值
f.SetCellValue(sheet, fmt.Sprintf("A%d", currentRow), reseller.ResellerName)
f.SetCellValue(sheet, fmt.Sprintf("B%d", currentRow), p.ProductName)
f.SetCellValue(sheet, fmt.Sprintf("C%d", currentRow), p.Loss)
// 设置样式
if styleA2 != 0 {
f.SetCellStyle(sheet, fmt.Sprintf("A%d", currentRow), fmt.Sprintf("A%d", currentRow), styleA2)
}
if styleB2 != 0 {
f.SetCellStyle(sheet, fmt.Sprintf("B%d", currentRow), fmt.Sprintf("B%d", currentRow), styleB2)
}
if styleC2 != 0 {
f.SetCellStyle(sheet, fmt.Sprintf("C%d", currentRow), fmt.Sprintf("C%d", currentRow), styleC2)
}
totalLoss += p.Loss
currentRow++
}
endRow := currentRow - 1
// 合并单元格 (如果多于1行)
if endRow > startRow {
f.MergeCell(sheet, fmt.Sprintf("A%d", startRow), fmt.Sprintf("A%d", endRow))
}
}
// ---------------- 填充合计行 ----------------
// 设置行高
f.SetRowHeight(sheet, currentRow, rowHeightTotal)
f.SetCellValue(sheet, fmt.Sprintf("A%d", currentRow), "合计")
// B列留空C列填充总亏损
f.SetCellValue(sheet, fmt.Sprintf("C%d", currentRow), totalLoss)
// 设置合计行样式
if styleTotalA != 0 {
f.SetCellStyle(sheet, fmt.Sprintf("A%d", currentRow), fmt.Sprintf("A%d", currentRow), styleTotalA)
}
if styleTotalB != 0 {
f.SetCellStyle(sheet, fmt.Sprintf("B%d", currentRow), fmt.Sprintf("B%d", currentRow), styleTotalB)
}
if styleTotalC != 0 {
f.SetCellStyle(sheet, fmt.Sprintf("C%d", currentRow), fmt.Sprintf("C%d", currentRow), styleTotalC)
}
// 取消合并合计行的A、B列
// f.MergeCell(sheet, fmt.Sprintf("A%d", currentRow), fmt.Sprintf("B%d", currentRow))
// 6. 保存
return f.SaveAs(outputPath)
}
// excel2picPy 将excel转换为图片python
// python 接口如下:
// curl --location --request POST 'http://192.168.6.109:8010/api/v1/convert' \
// --header 'Content-Type: multipart/form-data; boundary=--------------------------952147881043913664015069' \
// --form 'file=@"C:\\Users\\Administrator\\Downloads\\销售同比分析2025-12-29 0-12点.xlsx"' \
// --form 'sheet_name="销售同比分析"'
func (b *BbxtTools) excel2picPy(templatePath string, excelBytes []byte) ([]byte, error) {
return nil, nil
// return picBytes, nil
}