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

news2025/3/5 3:11:51

目录

一、效果图

二、整体思路

三、代码区


一、效果图

根据选择的日期范围动态生成表头(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;
            }
        }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2309783.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

DeepSeek 助力 Vue3 开发:打造丝滑的密码输入框(Password Input)

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录 Deep…

Linux 基础---文件权限

概念 文件权限是针对文件所有者、文件所属组、其他人这三类人而言的&#xff0c;对应的操作是chmod。设置方式&#xff1a;文字设定法、数字设定法。 文字设定法&#xff1a;r,w,x,- 来描述用户对文件的操作权限数字设定法&#xff1a;0,1,2,3,4,5,6,7 来描述用户对文件的操作…

SpringBoot五:JSR303校验

精心整理了最新的面试资料和简历模板&#xff0c;有需要的可以自行获取 点击前往百度网盘获取 点击前往夸克网盘获取 松散绑定 意思是比如在yaml中写的是last-name&#xff0c;这个和lastName意思是一样的&#xff0c;-后的字母默认是大写的 JSR303校验 就是可以在字段增加…

【计算机网络】考研复试高频知识点总结

文章目录 一、基础概念1、计算机⽹络的定义2、计算机⽹络的目标3、计算机⽹络的组成4、计算机⽹络的分类5、计算机⽹络的拓扑结构6、计算机⽹络的协议7、计算机⽹络的分层结构8、OSI 参考模型9、TCP/IP 参考模型10、五层协议体系结构 二、物理层1、物理层的功能2、传输媒体3、 …

Error Density-dependent Empirical Risk Minimization

经验误差密度依赖的风险最小化 v.s. 经验风险最小化 论文&#xff1a; 《 Error Density-dependent Empirical Risk Minimization》 发表在&#xff1a; ESWA’24 相关代码&#xff1a; github.com/zxlml/EDERM 研究背景 传统的经验风险最小化&#xff08;ERM&#xff09;方…

02_NLP文本预处理之文本张量表示法

文本张量表示法 概念 将文本使用张量进行表示,一般将词汇表示为向量,称为词向量,再由各个词向量按顺序组成矩阵形成文本表示 例如: ["人生", "该", "如何", "起头"]># 每个词对应矩阵中的一个向量 [[1.32, 4,32, 0,32, 5.2],[3…

Spring Boot全局异常处理:“危机公关”团队

目录 一、全局异常处理的作用二、Spring Boot 实现全局异常处理&#xff08;附上代码实例&#xff09;三、总结&#xff1a; &#x1f31f;我的其他文章也讲解的比较有趣&#x1f601;&#xff0c;如果喜欢博主的讲解方式&#xff0c;可以多多支持一下&#xff0c;感谢&#x1…

C# OnnxRuntime部署DAMO-YOLO香烟检测

目录 说明 效果 模型信息 项目 代码 下载 参考 说明 效果 模型信息 Model Properties ------------------------- --------------------------------------------------------------- Inputs ------------------------- name&#xff1a;input tensor&#xff1a;Floa…

[密码学实战]Java生成SM2根证书及用户证书

前言 在国密算法体系中,SM2是基于椭圆曲线密码(ECC)的非对称加密算法,广泛应用于数字证书、签名验签等场景。本文将结合代码实现,详细讲解如何通过Java生成SM2根证书及用户证书,并深入分析其核心原理。 一、证书验证 1.代码运行结果 2.根证书验证 3.用户证书验证 二、…

安装 cnpm 出现 Unsupported URL Type “npm:“: npm:string-width@^4.2.0

Unsupported URL Type "npm:": npm:string-width^4.2.0 可能是 node 版本太低了&#xff0c;需要安装低版本的 cnpm 试试 npm cache clean --force npm config set strict-ssl false npm install -g cnpm --registryhttps://registry.npmmirror.com 改为 npm insta…

探秘基带算法:从原理到5G时代的通信变革【九】QPSK调制/解调

文章目录 2.8 QPSK 调制 / 解调简介QPSK 发射机的实现与原理QPSK 接收机的实现与原理QPSK 性能仿真QPSK 变体分析 本博客为系列博客&#xff0c;主要讲解各基带算法的原理与应用&#xff0c;包括&#xff1a;viterbi解码、Turbo编解码、Polar编解码、CORDIC算法、CRC校验、FFT/…

四、数据存储

在爬虫项目中&#xff0c;我们需要将目标站点数据进行持久化保存&#xff0c;一般数据保存的方式有两种&#xff1a; 文件保存数据库保存 在数据保存的过程中需要对数据完成去重操作&#xff0c;所有需要使用 redis 中的 set 数据类型完成去重。 1.CSV文件存储 1.1 什么是c…

C# OnnxRuntime部署DAMO-YOLO人头检测

目录 说明 效果 模型信息 项目 代码 下载 参考 说明 效果 模型信息 Model Properties ------------------------- --------------------------------------------------------------- Inputs ------------------------- name&#xff1a;input tensor&#xff1a;Floa…

Metal学习笔记七:片元函数

知道如何通过将顶点数据发送到 vertex 函数来渲染三角形、线条和点是一项非常巧妙的技能 — 尤其是因为您能够使用简单的单行片段函数为形状着色。但是&#xff0c;片段着色器能够执行更多操作。 ➤ 打开网站 https://shadertoy.com&#xff0c;在那里您会发现大量令人眼花缭乱…

【Mac】2025-MacOS系统下常用的开发环境配置

早期版本的一个环境搭建参考 1、brew Mac自带终端运行&#xff1a; /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" Installation successful!成功后运行三行命令后更新环境&#xff08;xxx是mac的username&a…

蓝桥杯web第三天

展开扇子题目&#xff0c; #box:hover #item1 { transform:rotate(-60deg); } 当悬浮在父盒子&#xff0c;子元素旋转 webkit display: -webkit-box&#xff1a;将元素设置为弹性伸缩盒子模型。-webkit-box-orient: vertical&#xff1a;设置伸缩盒子的子元素排列方…

Qt基础入门-详解

前言 qt之路正式开启 &#x1f493; 个人主页&#xff1a;普通young man-CSDN博客 ⏩ 文章专栏&#xff1a;C_普通young man的博客-CSDN博客 ⏩ 本人giee: 普通小青年 (pu-tong-young-man) - Gitee.com 若有问题 评论区见&#x1f4dd; &#x1f389;欢迎大家点赞&#x1f44…

FPGA开发,使用Deepseek V3还是R1(3):系统级与RTL级

以下都是Deepseek生成的答案 FPGA开发&#xff0c;使用Deepseek V3还是R1&#xff08;1&#xff09;&#xff1a;应用场景 FPGA开发&#xff0c;使用Deepseek V3还是R1&#xff08;2&#xff09;&#xff1a;V3和R1的区别 FPGA开发&#xff0c;使用Deepseek V3还是R1&#x…

移动端国际化翻译同步解决方案-V3

1.前言 因为软件出海&#xff0c;从在上上家公司就开始做翻译系统&#xff0c;到目前为止已经出了两个比较大的版本了&#xff0c;各个版本解决的痛点如下&#xff1a; V1版本&#xff1a; 主要针对的是AndroidiOS翻译不一致和翻译内容管理麻烦的问题&#xff0c;通过这个工具…

多空狙击线-新指标-图文教程,多空分界买点以及强弱操盘技术教程,通达信炒股软件指标

“多空狙击线”指标 “多空狙击线”特色指标是量能型技术指标&#xff0c;主要用于分析股票市场中机构做多/做空力量的强程度。该指标的构成、定义与原理如下: “多空狙击线”指标&#xff0c;又称机构做多/做空能量线&#xff0c;通过计算和分析股票市场中机构做多/做空力量…