package excel import ( "context" "encoding/json" "fmt" "strconv" "time" "github.com/samber/lo" "github.com/tiger1103/gfast/v3/internal/app/system/dao" "github.com/tiger1103/gfast/v3/internal/app/system/model" "github.com/tiger1103/gfast/v3/internal/app/system/model/entity" "github.com/xuri/excelize/v2" ) // 记录累计完成量和下一周计划量 type ExcelInfo struct { // 累计完成量 Accumulated int // 下一周计划量 NextWeek int } // 导出单个方阵 func ExportExcel(fangZhenID string, startAt, endAt string) ([]*model.WorkStatusProgressRes, map[string]ExcelInfo, error) { ctx := context.Background() type scheduler struct { // 实体 WorkStatus *entity.WorkSchedule // 总进度 FinishedPtr int // 总量 Total int } type Schdule map[string]scheduler Get := func(scheduleMap Schdule, workID string) (int, int) { if scheduler, ok := scheduleMap[workID]; ok { return scheduler.FinishedPtr, scheduler.Total } return 0, 0 } // 获取指定方阵ID的所有计划 ScheduleData := []entity.WorkSchedule{} if err := dao.WorkSchedule.Ctx(ctx).Where(dao.WorkSchedule.Columns().FangzhenId, fangZhenID). Scan(&ScheduleData); err != nil { return nil, nil, err } // 存储指定 workID 的累计完成量 workMap := make(map[string]ExcelInfo) // 计算下一周的日期 nextWeekStart := lo.Must(time.Parse("2006-01-02", startAt)).AddDate(0, 0, 7) nextWeekEnd := lo.Must(time.Parse("2006-01-02", endAt)).AddDate(0, 0, 7) scheduleMap := make(Schdule) // 遍历 ScheduleData 将数据按照 WorkID 分组 lo.ForEach(ScheduleData, func(item entity.WorkSchedule, _ int) { if work, ok := workMap[item.WorkId]; ok { // 累加完成量 work.Accumulated += item.FinishedNum workMap[item.WorkId] = work } else { workMap[item.WorkId] = ExcelInfo{ Accumulated: item.FinishedNum, } } // 如果计划处于下一周,则添加到下一周计划量中 stime := lo.Must(time.Parse("2006-01-02", item.StartAt.Format("2006-01-02"))) if stime.After(nextWeekStart) && stime.Before(nextWeekEnd) { var scheduleDetails []model.WorkScheduleDetail if err := json.Unmarshal([]byte(item.Detail), &scheduleDetails); err != nil { return } nextweekPlanNum := 0 for _, detail := range scheduleDetails { // 如果 Date 大于开始时间并小于结束时间 if detail.Date <= endAt { nextweekPlanNum += detail.PlanNum } } work := workMap[item.WorkId] work.NextWeek += nextweekPlanNum workMap[item.WorkId] = work } // 如果开始时间大于当前时间,则跳过 startTime := item.StartAt.Format("Y-m-d") if startTime > startAt { return } // 如果不存在则直接添加 _, ok := scheduleMap[item.WorkId] if !ok { scheduleMap[item.WorkId] = scheduler{ WorkStatus: &item, } } // 反序列化 item.Detail 字段 var scheduleDetails []model.WorkScheduleDetail if err := json.Unmarshal([]byte(item.Detail), &scheduleDetails); err != nil { return } lo.ForEach(scheduleDetails, func(detail model.WorkScheduleDetail, _ int) { // 如果 Date 大于开始时间并小于结束时间 if detail.Date <= endAt { scheduler := scheduleMap[item.WorkId] scheduler.Total += detail.PlanNum scheduler.FinishedPtr += detail.FinishedNum scheduleMap[item.WorkId] = scheduler } }) }) // 获取指定方阵ID的所有项目 workStatusList := []entity.WorkStatus{} if err := dao.WorkStatus.Ctx(ctx).Where(dao.WorkStatus.Columns().FangzhenId, fangZhenID).Scan(&workStatusList); err != nil { return nil, nil, err } // 父列表 parentList := []entity.WorkStatus{} // 子列表,使用 map 存储,键为父元素的 ID childrenMap := make(map[int][]entity.WorkStatus) // 遍历所有数据,将父级和子集分开 for i := 0; i < len(workStatusList); i++ { item := workStatusList[i] if item.Parent == 0 { parentList = append(parentList, item) } else { childrenMap[item.Parent] = append(childrenMap[item.Parent], item) } } index := 1 projectList := make([]*model.WorkStatusProgressRes, 0, len(parentList)) // 遍历父级,将子集添加到父级的字段中 for _, parent := range parentList { projectItem := model.WorkStatusProgressRes{ ID: index, WorkID: parent.WorkId, Startat: parent.StartAt.String(), Endat: parent.EndAt.String(), Name: parent.WorkName, Total: parent.Total, Finished: parent.Finished, Status: parent.Status, WorkType: parent.Type, Children: make([]model.WorkStatusProgressRes, 0, len(workStatusList)), } index++ // 子类的总量和完成量 var subTotal, subDone int // 从 map 中获取子元素,将子元素添加到父元素的字段中 for _, child := range childrenMap[int(parent.Id)] { // 如果计划数量等于实际数量,且原状态非3,则更新状态为2 if child.Total == child.Finished && child.Status != 3 { // 并且 total,finished 不为 0 if child.Total != 0 && child.Finished != 0 { child.Status = 2 } } // 根据 WorkID 获取计划总量和实际进度 finishedPtr, total := Get(scheduleMap, child.WorkId) subTotal += child.Total subDone += child.Finished projectItem.Children = append(projectItem.Children, model.WorkStatusProgressRes{ ID: index, WorkID: child.WorkId, Startat: child.StartAt.String(), Endat: child.EndAt.String(), Name: child.WorkName, Total: child.Total, Finished: child.Finished, IsPercent: child.IsPercent, Status: child.Status, WorkType: child.Type, PlanProgress: &model.PlanProgress{ Finished: &finishedPtr, Total: &total, }, }) index++ } // 修改父项目的总量和进度 projectItem.Total = subTotal projectItem.Finished = subDone projectList = append(projectList, &projectItem) } return projectList, workMap, nil } // 导出一个子项目 func AccumulateProject(subProjectID string, startAt, endAt string) ([]*model.WorkStatusProgressRes, map[string]ExcelInfo, error) { fangzhenList := []entity.QianqiFangzhen{} if err := dao.QianqiFangzhen.Ctx(context.Background()). Where(dao.QianqiFangzhen.Columns().ProjectId, subProjectID).Scan(&fangzhenList); err != nil { return nil, nil, err } cumulativeProjects := []*model.WorkStatusProgressRes{} // 用于判断是否为第一次迭代 firstTime := true excelMap := make(map[string]ExcelInfo) // 遍历项目列表 for index := 0; index < len(fangzhenList); index++ { // 获取当前项目 project := fangzhenList[index] // 获取当前项目的子项目 childProjects, emap, err := ExportExcel(strconv.Itoa(project.Id), startAt, endAt) if err != nil { return nil, nil, err } excelMap = lo.Assign(emap, excelMap) // 如果是第一次迭代,将子项目列表赋值给累积项目列表 if firstTime { cumulativeProjects = childProjects // 更新标志变量,表示已经不是第一次迭代 firstTime = false continue } // 遍历子项目列表 for childIndex := 0; childIndex < len(childProjects); childIndex++ { // 获取当前子项目和对应的累积项目 singleChild := childProjects[childIndex] cumulativeChild := cumulativeProjects[childIndex] // 更新累积项目的总数和完成数 cumulativeChild.Total += singleChild.Total cumulativeChild.Finished += singleChild.Finished // 如果当前子项目还有子项目,也进行同样的操作 if len(singleChild.Children) > 0 { for subChildIndex := 0; subChildIndex < len(singleChild.Children); subChildIndex++ { // 获取当前子项目的子项目和对应的累积项目 singleSubChild := singleChild.Children[subChildIndex] cumulativeSubChild := cumulativeChild.Children[subChildIndex] // 更新累积项目的总数和完成数 cumulativeSubChild.Total += singleSubChild.Total cumulativeSubChild.Finished += singleSubChild.Finished } } } } return cumulativeProjects, excelMap, nil } // 传递一个 []*model.WorkStatusProgressRes,将数据写入到 excel 中 func WriteExcel(tableName string, list []*model.WorkStatusProgressRes, aiResult map[string]int) (*excelize.File, error) { f, err := excelize.OpenFile("resource/template.xlsx") if err != nil { return nil, err } // 创建一个工作表 sheetName := "Sheet1" headerPrefixes := []string{"一、", "二、", "三、", "四、", "五、", "六、", "七、", "八、", "九、", "十、", "十一、", "十二、"} childPrefixes := []string{"1、", "2、", "3、", "4、", "5、", "6、", "7、", "8、", "9、", "10、", "11、", "12、"} // 修改表头 f.SetCellValue(sheetName, "A1", tableName) // 遍历 list rowIndex := 3 lo.ForEach(list, func(item *model.WorkStatusProgressRes, index int) { setExcelValues(f, sheetName, rowIndex, item, headerPrefixes[index], aiResult) rowIndex++ // 遍历子集 lo.ForEach(item.Children, func(child model.WorkStatusProgressRes, childIndex int) { setExcelValues(f, sheetName, rowIndex, &child, childPrefixes[childIndex], aiResult) rowIndex++ }) }) return f, nil } func setExcelValues(f *excelize.File, sheetName string, i int, item *model.WorkStatusProgressRes, prefix string, aiResult map[string]int) { // 单元格 A f.SetCellValue(sheetName, fmt.Sprintf("A%d", i), prefix+item.Name) // 父名 f.SetCellValue(sheetName, fmt.Sprintf("B%d", i), item.Total) // 总量 f.SetCellValue(sheetName, fmt.Sprintf("C%d", i), item.Finished) // 完成 var percentComplete float64 if item.Total != 0 { percentComplete = float64(item.Finished) / float64(item.Total) * 100 } else { percentComplete = 0 } f.SetCellValue(sheetName, fmt.Sprintf("D%d", i), fmt.Sprintf("%.f%%", percentComplete)) // 总量完成百分比 if item.PlanProgress != nil { total := 0 if item.PlanProgress.Total != nil { total = *item.PlanProgress.Total } f.SetCellValue(sheetName, fmt.Sprintf("E%d", i), total) // 计划量 // AI 填报数量 F // 用 workID 作为 key,获取 aiResult 中的值,如果不存在则为 0 aiResultValue := aiResult[item.WorkID] f.SetCellValue(sheetName, fmt.Sprintf("F%d", i), aiResultValue) finished := 0 if item.PlanProgress.Finished != nil { finished = *item.PlanProgress.Finished } f.SetCellValue(sheetName, fmt.Sprintf("G%d", i), finished) // 计划完成量 var plannedPercentComplete float64 if item.PlanProgress != nil && item.PlanProgress.Finished != nil && item.PlanProgress.Total != nil && *item.PlanProgress.Total != 0 { plannedPercentComplete = float64(*item.PlanProgress.Finished) / float64(*item.PlanProgress.Total) * 100 } else { plannedPercentComplete = 0 } f.SetCellValue(sheetName, fmt.Sprintf("H%d", i), fmt.Sprintf("%.f%%", plannedPercentComplete)) // 计划完成百分比 } } // 根据方阵ID获取子项目名 func GetSubProjectName(fangZhenID string) (string, string) { // select sb.project_name // from sub_project as sb // left join qianqi_fangzhen as qf on qf.project_id = sb.id // where qf.id = 1959; result, err := dao.SubProject.Ctx(context.Background()).As("sb"). Where("qf.id", fangZhenID). InnerJoin("qianqi_fangzhen as qf", "qf.project_id = sb.id"). Fields("sb.project_name,qf.name").All() if err != nil { return "", "" } // 获取子项目名 subProjectName := result[0].GMap().Get("project_name").(string) // 方阵名 fangZhenName := result[0].GMap().Get("name").(string) return subProjectName, fangZhenName } // 根据子项目ID获取其主项目名和子项目名 func GetNameById(subProjectID string) (string, string) { // select sp.project_name as projectName,sb.project_name as subProjectName // from sys_project as sp // inner join sub_project as sb on sb.project_id = sp.id // where sb.id = 23; result, err := dao.SubProject.Ctx(context.Background()).As("sb"). Where("sb.id", subProjectID). InnerJoin("sys_project as sp", "sb.project_id = sp.id"). Fields("sp.project_name as projectName,sb.project_name as subProjectName").All() if err != nil { return "", "" } // 获取主项目名 projectName := result[0].GMap().Get("projectName").(string) // 获取子项目名 subProjectName := result[0].GMap().Get("subProjectName").(string) return projectName, subProjectName } func SetExcelValue(f *excelize.File, sheetName string, index int, item model.WorkStatusProgressRes, excelmap map[string]ExcelInfo, postionName string) { // 设计数量 F10 total := item.Total // 本周完成量 G10 finished := item.Finished // 累计完成量和下周计划量 accumulated, nextWeek := 0, 0 if data, exists := excelmap[item.WorkID]; exists { accumulated = data.Accumulated nextWeek = data.NextWeek } // 累计完成百分比 单元格 I10 percentComplete := 0.0 if total != 0 { percentComplete = float64(accumulated) / float64(total) * 100 } // 名称 f.SetCellValue(sheetName, fmt.Sprintf("%s%d", "C", index), item.Name) f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", "C", index), fmt.Sprintf("%s%d", "C", index), lo.Must(f.NewStyle(Style))) // 位置 f.SetCellValue(sheetName, fmt.Sprintf("%s%d", "D", index), postionName) f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", "D", index), fmt.Sprintf("%s%d", "D", index), lo.Must(f.NewStyle(Style))) // 单位 样式 f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", "E", index), fmt.Sprintf("%s%d", "E", index), lo.Must(f.NewStyle(Style))) // 设计数量 f.SetCellValue(sheetName, fmt.Sprintf("%s%d", "F", index), total) f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", "F", index), fmt.Sprintf("%s%d", "F", index), lo.Must(f.NewStyle(Style))) // 本周 f.SetCellValue(sheetName, fmt.Sprintf("%s%d", "G", index), finished) f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", "G", index), fmt.Sprintf("%s%d", "G", index), lo.Must(f.NewStyle(Style))) // 累计完成量 f.SetCellValue(sheetName, fmt.Sprintf("%s%d", "H", index), accumulated) f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", "H", index), fmt.Sprintf("%s%d", "H", index), lo.Must(f.NewStyle(Style))) // 累计完成百分比 f.SetCellValue(sheetName, fmt.Sprintf("%s%d", "I", index), fmt.Sprintf("%.f%%", percentComplete)) f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", "I", index), fmt.Sprintf("%s%d", "I", index), lo.Must(f.NewStyle(Style))) // 合并 jk 单元格 _ = f.MergeCell(sheetName, fmt.Sprintf("J%d", index), fmt.Sprintf("K%d", index)) f.SetCellStyle(sheetName, fmt.Sprintf("J%d", index), fmt.Sprintf("K%d", index), lo.Must(f.NewStyle(Style))) // 水平居中 jk 单元格 f.SetCellStyle(sheetName, fmt.Sprintf("J%d", index), fmt.Sprintf("K%d", index), lo.Must(f.NewStyle(&excelize.Style{ Border: []excelize.Border{ {Type: "left", Color: "000000", Style: 1}, {Type: "top", Color: "000000", Style: 1}, {Type: "bottom", Color: "000000", Style: 1}, {Type: "right", Color: "000000", Style: 1}, }, Fill: excelize.Fill{}, Font: nil, Alignment: &excelize.Alignment{ Horizontal: "center", // 水平居中 Indent: 0, JustifyLastLine: false, ReadingOrder: 0, RelativeIndent: 0, ShrinkToFit: false, TextRotation: 0, Vertical: "", // 垂直居中 WrapText: false, }, }))) // 下周计划量 f.SetCellValue(sheetName, fmt.Sprintf("%s%d", "J", index), nextWeek) // L f.SetCellStyle(sheetName, fmt.Sprintf("%s%d", "L", index), fmt.Sprintf("%s%d", "L", index), lo.Must(f.NewStyle(Style))) } func GetWeekNumbers(endTime string) (int, error) { end, err := time.Parse(time.DateOnly, endTime) if err != nil { return 0, fmt.Errorf("解析结束时间错误: %w", err) } _, endWeek := end.ISOWeek() return endWeek, nil } func CalculateTotalProgress(list []*model.WorkStatusProgressRes) float64 { var totalFinished, total int var calculate func(w model.WorkStatusProgressRes) calculate = func(w model.WorkStatusProgressRes) { totalFinished += w.Finished total += w.Total for _, child := range w.Children { calculate(child) } } for _, item := range list { calculate(*item) } if total == 0 { return 0 } return (float64(totalFinished) / float64(total)) * 100 }