动态表头报表的绘制与导出
- 软件开发
- 2025-09-15 10:57:01

目录
一、效果图
二、整体思路
三、代码区
一、效果图
根据选择的日期范围动态生成表头(eg:2025.2.24--2025.03.03)每个日期又分为白班、夜班;数据列表中对产线合并单元格。支持按原格式导出对应的报表excel。
点击空白区可新增工时数据;点击蓝色字体可以编辑工时数据。
二、整体思路前端绘制报表,分成两部分,一部分固定列头,一部分动态列头。固定列头就按照table正常情况绘制;动态且多级列头部分:根据选择的日期范围在前端获取该范围内日期列表,另外对于后端传来的数据进行处理(生成动态字段(日期_班次),并将数据跟生成的动态表头字段一 一对应),参考el-table中多级表头的实现(Table 表格 | Element Plus)
后端引入NPOI,创建XSSFWorkbook工作簿,填充每个单元格的数据,生成excel表格。点击导出按钮,调用后端接口,生成报表excel。
三、代码区html
<el-tabs type="border-card"> <!-- tab1 --> <el-tab-pane label="人力统计报表"> <el-form :model="queryParams" label-position="right" inline ref="queryRef" :rules="rulesQueryReport"> <!-- 查询条件 --> <el-form-item label="生产线" prop="lineCode"> <el-select clearable v-model="queryParams.lineCode" placeholder="请选择生产线" filterable @change="queryLineSelectChange" style="width: 100%;"> <el-option v-for="item in options.mes_line_list" :key="item.id" :label="item.lineName" :value="item.lineCode"> <span class="fl">{{ item.innerLineCode }}</span> <span class="fr" style="color: var(--el-text-color-secondary);">{{ item.lineName }}</span> </el-option> </el-select> </el-form-item> <el-form-item label="日期" prop="dateRangeWorkDate"> <el-date-picker v-model="dateRangeWorkDate" type="daterange" range-separator="到" start-placeholder="开始日期" end-placeholder="结束日期" value-format="YYYY-MM-DD" :shortcuts="shortcuts" @change="handleDateChange" :clearable="false" /> </el-form-item> <el-form-item> <el-button icon="search" type="primary" @click="handleQuery">{{ $t('btn.search') }}</el-button> <el-button icon="refresh" @click="resetQuery">{{ $t('btn.reset') }}</el-button> </el-form-item> </el-form> <el-row :gutter="15" class="mb10"> <el-col :span="1.5"> <el-button type="primary" v-hasPermi="['datareport:meshumanreport:add']" plain icon="plus" @click="handleAdd"> {{ $t('btn.add') }} </el-button> </el-col> <el-col :span="1.5"> <el-button type="warning" plain icon="download" @click="handleExport" v-hasPermi="['datareport:meshumanreport:export']"> {{ $t('btn.export') }} </el-button> </el-col> </el-row> <!-- 添加横向滚动容器 --> <div style="max-height:600px; overflow: hidden; position: relative;"> <!-- 外层容器处理纵向滚动 --> <div style="max-height: 600px; overflow-y: auto;"> <!-- 内层容器处理横向滚动 --> <div style="min-width: 100%; display: inline-block;"> <!-- 动态表格 --> <el-table :data="dataList" :span-method="spanMethod" v-loading="loading" ref="table" border header-cell-class-name="el-table-header-cell" highlight-current-row :max-height="600" :cell-style="cellStyle" :header-cell-style="headerStyle" @cell-click="handleCellClick"> <el-table-column prop="lineName" label="产线名称" fixed width="120" align="center" /> <el-table-column prop="userName" label="姓名" fixed width="120" align="center" /> <el-table-column prop="total" label="合计" fixed width="100" align="center" /> <el-table-column prop="postNames" label="岗位" fixed width="100" align="center" /> <el-table-column prop="userLaborAttribute" label="劳务属性" fixed width="100" align="center" /> <!-- 动态日期列 --> <el-table-column v-for="(dateCol, index) in dynamicHeaders" :key="index" :label="dateCol.date" align="center"> <el-table-column v-for="shift in ['白班', '夜班']" :key="shift" :prop="`${dateCol.date}_${shift}`" :label="shift" width="100" align="center"> </el-table-column> </el-table-column> </el-table> </div> </div> <!-- 添加底部阴影提示 --> <div v-show="showHorizontalScrollHint" style="position: absolute; bottom: 0; width: 100%; height: 6px; background: linear-gradient(to top, rgba(0,0,0,0.1), transparent);"> </div> </div> </el-tab-pane> </el-tabs>js
<script setup name="meshumanreport"> import { listMesHumanReport, getMesHumanReportList, addMesHumanReport, delMesHumanReport, updateMesHumanReport, updateMesHumanReportDetailPartial, getMesHumanReport, } from '@/api/dataReport/meshumanreport.js' import { getMesLineList } from '@/api/factoryManage/mesline.js' import dayjs from 'dayjs'; import auth from '@/plugins/auth' const { proxy } = getCurrentInstance() const ids = ref([]) const loading = ref(false) const loadingLeft = ref(false) const loadingRight = ref(false) const showSearch = ref(true) const queryParams = reactive({ pageNum: 1, pageSize: 10, sort: '', sortType: 'asc', workShopCode: undefined, workShopName: undefined, lineCode: undefined, lineName: undefined, }) const queryParamsUserNav = reactive({ pageNum: 1, pageSize: 20, sortType: 'asc', deptId: undefined, postId: undefined, userId: undefined, }) const total = ref(0) const totalLeft = ref(0) const totalRight = ref(0) const dataList = ref([]) const dataListLeft = ref([]) const dataListRight = ref([]) const dataListLeftChosed = ref([]) const dataListRightChosed = ref([]) const queryRef = ref() const queryRefUserNav = ref() const defaultTime = ref([new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 2, 1, 23, 59, 59)]) const deptOptions = ref([]) const postOptions = ref([]) const userOptions = ref([]) const headers = ref(null) const standardDisabled = ref(true) const tableHeight = ref(570) var dictParams = [ { dictType: "mes_classes_type" }, ] proxy.getDicts(dictParams).then((response) => { response.data.forEach((element) => { state.options[element.dictType] = element.list }) }) //当前时间 const now = new Date(); // 工作日期范围(默认当月) // const dateRangeWorkDate = ref([]) //设置:subtract往前推 add往后 const dateRangeWorkDate = ref([dayjs().subtract(1, 'day').format('YYYY-MM-DD'), dayjs().format('YYYY-MM-DD')]) const shortcuts = [ { text: '最近一周', value: () => { const end = new Date() const start = new Date() start.setTime(start.getTime() - 3600 * 1000 * 24 * 7) return [start, end] }, }, { text: '最近一个月', value: () => { const end = new Date() const start = new Date() start.setTime(start.getTime() - 3600 * 1000 * 24 * 30) return [start, end] }, }, { text: '最近一季度', value: () => { const end = new Date() const start = new Date() start.setTime(start.getTime() - 3600 * 1000 * 24 * 90) return [start, end] }, }, ] //日期范围变化时,触发表单校验(因为el-date-picker 选中的值不会自动通知表单验证状态,所以这里要单独处理) function handleDateChange() { dataList.value = [] // console.log(dateRangeWorkDate.value) // queryParams.dateRangeWorkDate = dateRangeWorkDate.value // // 查询报表数据 // getList() } // 动态表头生成 // const dateRangeWorkDate = ref([]) const dynamicHeaders = computed(() => { console.log(dateRangeWorkDate.value) if (!dateRangeWorkDate.value || dateRangeWorkDate.value.length !== 2) return [] const dates = [] const start = new Date(dateRangeWorkDate.value[0]) const end = new Date(dateRangeWorkDate.value[1]) while (start <= end) { dates.push({ date: start.toISOString().split('T')[0], shifts: ['白班', '夜班'] }) start.setDate(start.getDate() + 1) } console.log(dates) return dates }) // 合并单元格逻辑 const spanMethod = ({ row, column, rowIndex, columnIndex }) => { if (column.property === 'lineName') { // 确保数据已按 lineName 排序 const currentLine = row.lineName; let rowspan = 1; // 仅当是当前产线的第一行时计算合并行数 if (rowIndex === 0 || dataList.value[rowIndex - 1].lineName !== currentLine) { for (let i = rowIndex + 1; i < dataList.value.length; i++) { if (dataList.value[i].lineName === currentLine) { rowspan++; } else { break; } } return { rowspan, colspan: 1 }; } else { return { rowspan: 0, colspan: 0 }; } } }; // 查询报表数据 function getList() { queryParams.dateRangeWorkDate = dateRangeWorkDate.value proxy.addDateRange(queryParams, dateRangeWorkDate.value, 'WorkDate'); console.log(queryParams) // 表单校验 console.log(queryRef.value) // if (queryRef.value) { // proxy.$refs["queryRef"].validate((valid) => { // if (valid) { loading.value = true getMesHumanReportList(queryParams).then(res => { const { code, data } = res console.log(res) if (code == 200) { // dataList.value = data dataList.value = processData(data) } loading.value = false }).catch(() => { loading.value = false }) // } // }) // } } // 数据处理 const processData = (rawData) => { console.log(rawData) return rawData.map(item => { // const formatted = { // lineName: item.lineName, // userName: item.userName, // total: item.totalHours + 'h' // } const formatted = { ...item } formatted.total = item.totalHours + 'h' item.details.forEach(d => { d.date = dayjs(d.date).format('YYYY-MM-DD'); if (d.dayShift != null) { formatted[`${d.date}_白班`] = d.dayShift + 'h' } if (d.nightShift != null) { formatted[`${d.date}_夜班`] = d.nightShift + 'h' } }) console.log(rawData) console.log(formatted) return formatted }) } // 查询 function handleQuery() { queryParams.pageNum = 1 getList() } // 重置查询操作 function resetQuery() { proxy.resetForm("queryRef") dateRangeWorkDate.value = [dayjs().subtract(1, 'day').format('YYYY-MM-DD'), dayjs().format('YYYY-MM-DD')] queryParams.dateRangeWorkDate = dateRangeWorkDate.value handleQuery() } //单元格点击事件 function handleCellClick(row, column, cell, event) { if (auth.hasPermi('datareport:meshumanreport:edit')) { console.log(row) console.log(column) console.log(row[column.property]) // 如果是需要编辑的列,则打开编辑框 //没有数据的列不允许编辑,后续可以去掉row[column.property] != undefined限制,扩展点击空白地方新增数据 if ((column.property.includes('白班') || column.property.includes('夜班'))) { // 显示编辑框 openEditCell.value = true; formEdit.value = {} formEdit.value = { ...row, workDate: column.property.split('_')[0], workShift: column.property.split('_')[1], } //标准工时 //调用接口查主表数据,没有则让他手动输入,默认12h listMesHumanReport({ lineCode: row.lineCode, workDate: formEdit.value.workDate, workShift: formEdit.value.workShift }).then(res => { const { code, data } = res console.log(res) if (code == 200) { if (data.result.length > 0) { standardDisabled.value = true formEdit.value.standardWorkHours = data.result[0].standardWorkHours } else { standardDisabled.value = false formEdit.value.standardWorkHours = 12 } } }) //实际工时 if (row[column.property] != undefined && row[column.property] != null) { formEdit.value.actualWorkHours = row[column.property].split('h')[0]; } } } } // 导出按钮操作 function handleExport() { proxy .$confirm("是否确认导出人力报表数据?", "警告", { confirmButtonText: "确定", cancelButtonText: "取消", type: "warning", }) .then(async () => { await proxy.downFile('/DataReport/MesHumanReport/exportReport', { ...queryParams }) }) } function cellStyle(row, rowIndex) { console.log(row) if ((row.column.property.includes('白班') || row.column.property.includes('夜班'))) { // if (row.column.property === 'lineName') { console.log(row) return { color: '#409eff', } } } /**************************************************** form操作 ****************************************************/ const formRef = ref() const formRefEdit = ref() const formRefUserNav = ref() const title = ref('') const titleUserNav = ref('') // 操作类型 1、add 2、edit 3、view const opertype = ref(0) const open = ref(false) const openUserNav = ref(false) const openEditCell = ref(false) const state = reactive({ single: true, multiple: true, form: {}, formUserNav: {}, formEdit: {}, rules: { //产线 lineCode: [{ required: true, message: '请选择产线', trigger: 'change' },], //标准工时 standardWorkHours: [{ required: true, message: '请输入标准工时', trigger: ['change', 'blur'] },], workDate: [{ required: true, message: '请选择日期范围', trigger: ['change', 'blur'] },], workShift: [{ required: true, message: '请选择班次', trigger: 'change' },], }, rulesQueryReport: { //产线 // lineCode: [{ required: true, message: '请选择产线', trigger: 'change' },], //日期 dateRangeWorkDate: [{ required: true, message: '请选择日期范围', trigger: 'blur' },], }, rulesEdit: { lineName: [{ required: true, message: '请输入产线', trigger: 'blur' },], userName: [{ required: true, message: '请输入人员', trigger: 'blur' },], workDate: [{ required: true, message: '请选择日期', trigger: 'change' },], workShift: [{ required: true, message: '请选择班次', trigger: 'change' },], standardWorkHours: [{ required: true, message: '请输入标准工时', trigger: 'change' },], actualWorkHours: [{ required: true, message: '请输入实际工时', trigger: 'change' },], }, options: { // 产线列表 mes_line_list: [], // 班次 选项列表 格式 eg:{ dictLabel: '标签', dictValue: '0'} mes_classes_type: [], } }) const { form, formUserNav, formEdit, rules, rulesQueryReport, rulesEdit, options, single, multiple } = toRefs(state) //保存编辑单元格数据 function editSubmit() { // 保存编辑单元格数据 proxy.$refs["formRefEdit"].validate((valid) => { if (valid) { //提交表单 console.log(formEdit.value) updateMesHumanReportDetailPartial(formEdit.value).then((res) => { console.log(res) openEditCell.value = false if (res.code == 200) { proxy.$modal.msgSuccess("操作成功!") getList() } else { // proxy.$modal.msgError("操作失败") } }).catch(() => { proxy.$modal.msgError("操作失败!") }) } }) } // 取消编辑单元格数据 function editCancel() { openEditCell.value = false formEdit.value = {} } function getDropDownList() { // 获取生产线列表 getMesLineList().then(res => { console.log(res) const { code, data } = res if (code == 200) { state.options.mes_line_list = data } }) handleQuery() getDropDownList() </script>C#
Service层 导出功能
/// <summary> /// 导出人力报工报表数据 /// </summary> /// <param name="parm"></param> /// <returns></returns> public (string, string) ExportMesHumanReport(MesHumanReportQueryDto parm) { var data = GetMesHumanReportResponse(parm); List<DateTime> dateList = new List<DateTime>(); var start = parm.BeginWorkDate.Value.Date; var end = parm.EndWorkDate.Value.Date; while (start <= end) { dateList.Add(start); start = start.AddDays(1); } //创建工作簿 IWorkbook workbook = new XSSFWorkbook(); //创建工作表 ISheet sheet = workbook.CreateSheet("人力报工统计"); #region 表头 //创建第一行表头 IRow headerRow1 = sheet.CreateRow(0); headerRow1.CreateCell(0).SetCellValue("产线"); headerRow1.CreateCell(1).SetCellValue("姓名"); headerRow1.CreateCell(2).SetCellValue("合计(h)"); headerRow1.CreateCell(3).SetCellValue("岗位"); headerRow1.CreateCell(4).SetCellValue("劳务属性"); int fixedColumnCount = 5; for (int i = 0, j = 0; i < dateList.Count; i++, j = j + 2) { headerRow1.CreateCell(fixedColumnCount + j).SetCellValue(dateList[i].ToCommonDateString()); headerRow1.CreateCell(fixedColumnCount + j + 1).SetCellValue(string.Empty); //合并表头列单元格 sheet.AddMergedRegion(new CellRangeAddress(0, 0, fixedColumnCount + j, fixedColumnCount + j + 1)); } //创建第二行表头 IRow headerRow2 = sheet.CreateRow(1); for (int i = 0, j = 0; i < dateList.Count; i++, j = j + 2) { headerRow2.CreateCell(fixedColumnCount + j).SetCellValue("白班"); headerRow2.CreateCell(fixedColumnCount + j + 1).SetCellValue("夜班"); } //合并表头行单元格 for (int i = 0; i < fixedColumnCount; i++) { sheet.AddMergedRegion(new CellRangeAddress(0, 1, i, i)); } #endregion #region 表体 //创建数据行 int rowIndex = 2; //记录当前产线的起始行索引和结束行索引,为合并产线做准备 int startRowIndex = 2; int lineCode = data.Count > 0 ? data.FirstOrDefault().LineCode ?? 0 : 0; int endRowIndex = 2; foreach (var item in data) { IRow dataRow = sheet.CreateRow(rowIndex); dataRow.CreateCell(0).SetCellValue(item.LineName); dataRow.CreateCell(1).SetCellValue(item.UserName); dataRow.CreateCell(2).SetCellValue(item.TotalHours.ToString()); dataRow.CreateCell(3).SetCellValue(item.PostNames); dataRow.CreateCell(4).SetCellValue(item.UserLaborAttribute); for (int i = 0, j = 0; i < dateList.Count; i++, j = j + 2) { var dateData = item.Details.Where(x => x.Date == dateList[i]).ToList(); if (dateData != null && dateData.Count > 0) { var dayShift = dateData.FirstOrDefault(x => x.DayShift != null)?.DayShift; var nightShift = dateData.FirstOrDefault(x => x.NightShift != null)?.NightShift; dataRow.CreateCell(fixedColumnCount + j).SetCellValue(dayShift == null ? string.Empty : dayShift.ToString()); dataRow.CreateCell(fixedColumnCount + j + 1).SetCellValue(nightShift == null ? string.Empty : nightShift.ToString()); } else { dataRow.CreateCell(fixedColumnCount + j).SetCellValue(string.Empty); dataRow.CreateCell(fixedColumnCount + j + 1).SetCellValue(string.Empty); } } //合并产线单元格 if (lineCode == item.LineCode) { endRowIndex++; } else { if (endRowIndex - startRowIndex > 1) { sheet.AddMergedRegion(new CellRangeAddress(startRowIndex, endRowIndex - 1, 0, 0)); } startRowIndex = rowIndex; endRowIndex = rowIndex + 1; // 移到下一行 lineCode = item.LineCode ?? 0; } rowIndex++; } // 处理最后一组合并 if (endRowIndex - startRowIndex > 1) { sheet.AddMergedRegion(new CellRangeAddress(startRowIndex, endRowIndex - 1, 0, 0)); } #endregion //调整列宽,设置第一列自动宽度 //sheet.AutoSizeColumn(0); //写入到内存流 //MemoryStream ms = new MemoryStream(); //workbook.Write(ms); //ms.Flush(); //return ms; var fileName = "人力报工统计"; IWebHostEnvironment webHostEnvironment = (IWebHostEnvironment)App.ServiceProvider.GetService(typeof(IWebHostEnvironment)); var sFileName = $"{fileName}{DateTime.Now:MM-dd-HHmmss}.xlsx"; string fullPath = Path.Combine(webHostEnvironment.WebRootPath, "export", sFileName); Directory.CreateDirectory(Path.GetDirectoryName(fullPath)); using (var fileStream = new FileStream(fullPath, FileMode.Create)) { workbook.Write(fileStream); } return (sFileName, fullPath); }查询接口返回实体结构
/// <summary> /// 报表单条产线数据 /// </summary> public class MesHumanReportLineResponse : MesHumanReportDetailDto { /// <summary> /// 总工时 /// </summary> public decimal? TotalHours { get; set; } /// <summary> /// 每日工时详情 /// </summary> public List<MesHumanReportDetailResponse> Details { get; set; } } public class MesHumanReportDetailResponse { /// <summary> /// 日期 /// </summary> public DateTime? Date { get; set; } /// <summary> /// 白班工时 /// </summary> public decimal? DayShift { get; set; } /// <summary> /// 夜班工时 /// </summary> public decimal? NightShift { get; set; } // 夜班 } /// <summary> /// 人力报工-明细输入输出对象 /// </summary> public class MesHumanReportDetailDto { /// <summary> /// 主键Id /// </summary> [ExcelIgnore] public int Id { get; set; } /// <summary> /// 主表id /// </summary> [ExcelIgnore] public int MasterId { get; set; } /// <summary> /// 车间名称 /// </summary> [ExcelColumn(Name = "车间名称")] [ExcelColumnName("车间名称")] public string WorkShopName { get; set; } /// <summary> /// 产线编号 /// </summary> [ExcelColumn(Name = "产线编号")] [ExcelColumnName("产线编号")] public int? LineCode { get; set; } /// <summary> /// 产线名称 /// </summary> [ExcelColumn(Name = "产线名称")] [ExcelColumnName("产线名称")] public string LineName { get; set; } /// <summary> /// 开始时间 /// </summary> [ExcelColumn(Name = "开始时间", Format = "yyyy-MM-dd HH:mm:ss")] [ExcelColumnName("开始时间")] public DateTime? StartTime { get; set; } /// <summary> /// 结束时间 /// </summary> [ExcelColumn(Name = "结束时间", Format = "yyyy-MM-dd HH:mm:ss")] [ExcelColumnName("结束时间")] public DateTime? EndTime { get; set; } /// <summary> /// 日期 /// </summary> [ExcelColumn(Name = "日期")] [ExcelColumnName("日期")] public DateTime? WorkDate { get; set; } /// <summary> /// 班次 /// </summary> [ExcelColumn(Name = "班次")] [ExcelColumnName("班次")] public string WorkShift { get; set; } /// <summary> /// 实际工时(h) /// </summary> [ExcelColumn(Name = "实际工时(h)")] [ExcelColumnName("实际工时(h)")] public decimal? ActualWorkHours { get; set; } /// <summary> /// 标准工时(h) /// </summary> [ExcelColumn(Name = "标准工时(h)")] [ExcelColumnName("标准工时(h)")] public decimal? StandardWorkHours { get; set; } /// <summary> /// 用户id /// </summary> [ExcelIgnore] public long UserId { get; set; } /// <summary> /// 用户工号 /// </summary> [ExcelColumn(Name = "用户工号")] [ExcelColumnName("用户工号")] public string UserCode { get; set; } /// <summary> /// 用户姓名 /// </summary> [ExcelColumn(Name = "用户姓名")] [ExcelColumnName("用户姓名")] public string UserName { get; set; } /// <summary> /// 部门id /// </summary> [ExcelIgnore] public long? DeptId { get; set; } /// <summary> /// 部门名称 /// </summary> [ExcelIgnore] public string DeptName { get; set; } /// <summary> /// 岗位id (用逗号拼接) /// </summary> [ExcelIgnore] public string PostIds { get; set; } /// <summary> /// 岗位名称 (用逗号拼接) /// </summary> [ExcelColumn(Name = "岗位名称")] [ExcelColumnName("岗位名称")] public string PostNames { get; set; } /// <summary> /// 用户劳务属性(合同工、劳务工、第三方) /// </summary> [ExcelIgnore] public string UserLaborAttribute { get; set; } /// <summary> /// 用户划分(分摊、专属) /// </summary> [ExcelIgnore] public string UserDivide { get; set; } /// <summary> /// 创建日期 /// </summary> [ExcelIgnore] public DateTime? CreateTime { get; set; } /// <summary> /// 创建者 /// </summary> [ExcelIgnore] public string CreateBy { get; set; } /// <summary> /// 更新时间 /// </summary> [ExcelIgnore] public DateTime? UpdateTime { get; set; } /// <summary> /// 更新者 /// </summary> [ExcelIgnore] public string UpdateBy { get; set; } /// <summary> /// 备注信息 /// </summary> [ExcelColumn(Name = "备注信息")] [ExcelColumnName("备注信息")] public string Remark { get; set; } /// <summary> /// 是否删除(软删除标志) /// </summary> [ExcelIgnore] public bool IsDeleted { get; set; } }Service层 查询功能(该部分可以跳过,其中逻辑跟源项目相关,仅做记录)
/// <summary> /// 获取人力报工报表数据 /// </summary> /// <param name="parm"></param> /// <returns></returns> public List<MesHumanReportLineResponse> GetMesHumanReportResponse(MesHumanReportQueryDto parm) { var resultList = new List<MesHumanReportLineResponse>(); try { if (parm.BeginWorkDate == null || parm.EndWorkDate == null) { throw new ArgumentException("开始日期和结束日期不能为空!"); } #region 条件查询 var list = Queryable().Where(x => x.WorkDate >= parm.BeginWorkDate.Value.Date && x.WorkDate <= parm.EndWorkDate.Value.Date && !x.IsDeleted) .WhereIF(parm.LineCode != null, x => x.LineCode == parm.LineCode) .OrderBy(x => x.WorkDate) .Includes(x => x.MesHumanReportDetailNav.Where(w => !w.IsDeleted).ToList()) .ToList(); var detaiList = list.SelectMany(x => x.MesHumanReportDetailNav).ToList(); //根据产线分组 var lineGroup = detaiList.GroupBy(x => x.LineCode).ToList(); foreach (var item in lineGroup) { //根据人员分组 var userGroup = item.GroupBy(x => x.UserCode).ToList(); foreach (var user in userGroup) { var data = new MesHumanReportLineResponse(); data.LineCode = item.Key; data.LineName = item.FirstOrDefault().LineName; data.UserCode = user.Key; data.UserId = user.FirstOrDefault().UserId; data.UserName = user.FirstOrDefault().UserName; data.DeptId = user.FirstOrDefault().DeptId; data.DeptName = user.FirstOrDefault().DeptName; data.PostIds = user.FirstOrDefault().PostIds; data.PostNames = user.FirstOrDefault().PostNames; List<MesHumanReportDetailResponse> detailList = new List<MesHumanReportDetailResponse>(); data.Details = detailList; data.TotalHours = user.Sum(x => x.ActualWorkHours ?? 0); //根据日期分组 var dateGroup = user.GroupBy(x => x.WorkDate).OrderBy(x => x.Key).ToList(); foreach (var date in dateGroup) { //根据班次分组 var shiftGroup = date.GroupBy(x => x.WorkShift).OrderBy(x => x.Key).ToList(); foreach (var shift in shiftGroup) { //计算总工时 var totalHours = shift.Sum(x => x.StandardWorkHours); //计算实际工时 var actualHours = shift.Sum(x => x.ActualWorkHours); //计算缺勤工时 //var absenceHours = shift.Sum(x => x.StandardWorkHours - x.ActualWorkHours); //计算出勤率 //var attendanceRate = (actualHours / totalHours) * 100; //计算缺勤率 //var absenceRate = (absenceHours / totalHours) * 100; var detail = new MesHumanReportDetailResponse(); detail.Date = date.Key; if (shift.Key == "白班") { detail.DayShift = actualHours; } else { detail.NightShift = actualHours; } detailList.Add(detail); } } resultList.Add(data); } } //return resultList; #endregion } catch (Exception ex) { logger.Error(ex, "获取人力报工报表数据异常!"); throw ex; } try { //查询智能制造平台用户列表 int pageIndex = 1; // 设置页码 int pageSize = 9999; // 每页大小 var apiResult = HttpHelper.HttpGet($"{_apiUrl}api/common/getUserListByPost?pageNum={pageIndex}&pageSize={pageSize}"); //获取apiResult中data中的数据 if (!string.IsNullOrEmpty(apiResult)) { var json = JObject.Parse(apiResult); if ((int)json["code"] == 200) { JArray resultArray = (JArray)json["data"]["result"]; // 👈 直接获取结果数组 // 遍历 resultArray,并映射到 SysUserDto 列表 var userList = new List<SysUserDto>(); foreach (var item in resultArray) { var user = new SysUserDto { UserId = (int)item["userId"], UserName = item["userName"].ToString(), NickName = item["nickName"].ToString(), Email = (string)item["email"], // 可能为 null Remark = (string)item["remark"], // 可能为 null Phonenumber = (string)item["phonenumber"], // 可能为 null Sex = (int)item["sex"], DeptId = (int)item["deptId"], DeptName = item["deptName"].ToString(), PostNames = item["postNames"].ToString(), UserLaborAttribute = item["userLaborAttribute"].ToString(), UserDivide = item["userDivide"].ToString() // 其他属性... }; userList.Add(user); } //用户基础信息映射到报表数据 resultList.ForEach(w => { var user = userList.FirstOrDefault(x => w.UserId == x.UserId || w.UserCode == x.UserName); if (user != null) { w.PostNames = user.PostNames; w.UserLaborAttribute = user.UserLaborAttribute; w.UserDivide = user.UserDivide; } }); } } return resultList; } catch (Exception ex) { logger.Error(ex, "获取智能制造平台用户列表异常!"); throw ex; } }动态表头报表的绘制与导出由讯客互联软件开发栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“动态表头报表的绘制与导出”