From 3a77e0e32b66e825ede5f54728e6db2e5fa0f946 Mon Sep 17 00:00:00 2001 From: fuzhongyun <15339891972@163.com> Date: Tue, 30 Dec 2025 10:22:19 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BD=BF=E7=94=A8=E9=A2=9D=E5=A4=96?= =?UTF-8?q?=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/tools/bbxt/bbxt.go | 180 ++-------------------------- internal/tools/bbxt/bbxt_test.go | 7 +- internal/tools/bbxt/excel.go | 194 +++++++++++++++++++++++++++++++ tmpl/excel_temp/kshj_gt.xlsx | Bin 9473 -> 9491 bytes 4 files changed, 210 insertions(+), 171 deletions(-) create mode 100644 internal/tools/bbxt/excel.go diff --git a/internal/tools/bbxt/bbxt.go b/internal/tools/bbxt/bbxt.go index d7d00ef..6d593db 100644 --- a/internal/tools/bbxt/bbxt.go +++ b/internal/tools/bbxt/bbxt.go @@ -3,12 +3,8 @@ package bbxt import ( "ai_scheduler/internal/pkg" "fmt" - "reflect" "sort" "time" - - "github.com/gofiber/fiber/v2/log" - "github.com/xuri/excelize/v2" ) type BbxtTools struct { @@ -91,7 +87,18 @@ func (b *BbxtTools) StatisOursProductLossSumTotal(ct []string) (err error) { reseller.ProductLoss[info.OursProductId] = productLoss } } + + // 按经销商总亏损排序 + resellers := make([]*ResellerLoss, 0, len(resellerMap)) for _, v := range resellerMap { + resellers = append(resellers, v) + } + sort.Slice(resellers, func(i, j int) bool { + return resellers[i].Total < resellers[j].Total + }) + + // 构建分组 + for _, v := range resellers { if v.Total <= -100 { total = append(total, []string{ fmt.Sprintf("%s", v.ResellerName), @@ -114,168 +121,3 @@ func (b *BbxtTools) StatisOursProductLossSumTotal(ct []string) (err error) { } return err } - -// 最简单的通用函数 -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), 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++ - } - } - - // 6. 保存 - return f.SaveAs(outputPath) -} diff --git a/internal/tools/bbxt/bbxt_test.go b/internal/tools/bbxt/bbxt_test.go index 628c675..b6b2275 100644 --- a/internal/tools/bbxt/bbxt_test.go +++ b/internal/tools/bbxt/bbxt_test.go @@ -1,13 +1,16 @@ package bbxt -import "testing" +import ( + "testing" + "time" +) func Test_StatisOursProductLossSumApiTotal(t *testing.T) { o, err := NewBbxtTools() if err != nil { panic(err) } - err = o.StatisOursProductLossSumTotal([]string{"2025-12-28+00:00:00", "2025-12-28+23:59:59.999"}) + err = o.DailyReport(time.Date(2025, 12, 30, 0, 0, 0, 0, time.Local)) t.Log(err) diff --git a/internal/tools/bbxt/excel.go b/internal/tools/bbxt/excel.go new file mode 100644 index 0000000..3711aa0 --- /dev/null +++ b/internal/tools/bbxt/excel.go @@ -0,0 +1,194 @@ +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) +} + +// 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 +} diff --git a/tmpl/excel_temp/kshj_gt.xlsx b/tmpl/excel_temp/kshj_gt.xlsx index c826635797dcc5b89a8eb2b89a85592c445b16c9..6933f633648e61451b2be2ea5d6371174a19dcf2 100755 GIT binary patch delta 4202 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 MK={HA&p*t60ON@J%>V!Z delta 4159 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