动态表头报表的绘制与导出

目录

一、效果图

二、整体思路

三、代码区


一、效果图

根据选择的日期范围动态生成表头(eg:2025.2.24--2025.03.03)每个日期又分为白班、夜班;数据列表中对产线合并单元格。支持按原格式导出对应的报表excel。

点击空白区可新增工时数据;点击蓝色字体可以编辑工时数据。

二、整体思路

前端绘制报表,分成两部分,一部分固定列头,一部分动态列头。固定列头就按照table正常情况绘制;动态且多级列头部分:根据选择的日期范围在前端获取该范围内日期列表,另外对于后端传来的数据进行处理(生成动态字段(日期_班次),并将数据跟生成的动态表头字段一 一对应),参考el-table中多级表头的实现(Table 表格 | Element Plus

后端引入NPOI,创建XSSFWorkbook工作簿,填充每个单元格的数据,生成excel表格。点击导出按钮,调用后端接口,生成报表excel。

三、代码区

html

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

javascript 复制代码
<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层 导出功能

cs 复制代码
        /// <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);

        }

查询接口返回实体结构

cs 复制代码
    /// <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层 查询功能(该部分可以跳过,其中逻辑跟源项目相关,仅做记录)

cs 复制代码
        /// <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;
            }
        }
相关推荐
小妖66611 分钟前
css3的transform:tanslateZ没有效果
前端·css·css3
customer0824 分钟前
【开源免费】基于SpringBoot+Vue.JS医院药品管理系统(JAVA毕业设计)
java·vue.js·spring boot·后端·开源
见青..33 分钟前
[BUUCTF]web--wp(持续更新中)
前端·web安全·网络安全
二川bro1 小时前
前端模块化管理深度解析:从混沌到秩序的全链路实践指南
前端
JosieBook1 小时前
【前端】在WebStorm中安装Node.js与nvm与npm的详细过程
前端·node.js·webstorm
linweidong1 小时前
希音(Shein)前端面试题集锦和参考答案
前端·arcgis·xss·csrf·前端面试·前端面经·webpack原理
桃子不吃李子1 小时前
npm ERR! code 128 npm ERR! An unknown git error occurred
前端·npm·node.js
思想永无止境1 小时前
解决windows npm无法下载electron包的问题
前端·electron·npm
2401_897930061 小时前
npm install 详解
前端·npm·node.js
suxiaoling@1 小时前
C#读写ini文件
开发语言·c#·上位机开发