You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JS数组处理:提取嵌套属性到根层级 重组工时统计数据结构

动态月份工时表格数据结构调整方案

问题背景

我基于此前的问题 React displaying data in a table with dynamic columns 开展开发。
此前已实现用户选择月份后,表格按日展示对应客户工时的功能;现在需要扩展该模块:管理员选择用户、月份、年份后,表格表头自动填充所选月份的全部日期,每一行对应一个客户的工时数据。经过多步数据处理,得到的结果距离预期结构仅差一步,目前遇到阻塞。

现有代码与数据集

以下是简化后的数据集和当前实现逻辑:

const data = [
    {
    start: "2022-04-3",
    hours: 1,
    client: {
        id: "123",
      fullName: "Tom Tom"
    }
  },
  {
    start: "2022-04-10",
    hours: 2,
    client: {
        id: "123",
      fullName: "Tom Tom"
    }
  },
  {
    start: "2022-04-10",
    hours: 3,
    client: {
        id: "123",
      fullName: "Tom Tom"
    }
  },
  {
    start: "2022-04-10",
    hours: 1,
    client: {
        id: "123",
      fullName: "Tom Tom"
    }
  },
  {
    start: "2022-04-13",
    hours: 1,
    client: {
        id: "456",
      fullName: "Jack Jack"
    }
  },
  {
    start: "2022-04-13",
    hours: 2,
    client: {
        id: "123",
      fullName: "Tom Tom"
    }
  },
  {
    start: "2022-04-15",
    hours: 1,
    client: {
        id: "789",
      fullName: "Pete Pete"
    }
  },
  {
    start: "2022-04-15",
    hours: 1,
    client: {
        id: "789",
      fullName: "Pete Pete"
    }
  }
]

let collData = data.reduce((accumulator, currentValue) => { 
    let existing = accumulator.find((n) => n.client.id === currentValue.client.id && n.start === currentValue.start);
            if (existing) {
              existing.hours += currentValue.hours;
            } else {
              accumulator.push(currentValue);
            }
            return accumulator;
          }, []);
        console.log("step 1 ", collData);
        
        
let res = {};



collData.forEach((item) => {
    const splitDate = item.start.split("-");
    const day = splitDate[splitDate.length - 1];

    const newStat = { start: parseInt(day), hours: item.hours};
      
   if(res[item.client.id])
   {
       let stats = res[item.client.id].stats;
        stats = [...stats, newStat];
        res[item.client.id].stats = stats;
   }
   else{
      res[item.client.id] = {
        client: item.client,
        stats: [newStat]
      }
   }
    
  });


  let filtArr = [];
  
  const daysInMonth = 30;
 
    let dayInfo = [];
    for (let i = 1; i <= daysInMonth; i++) {
      dayInfo.push(i)
 
      // create an array and fill hours with 0
 var dayArr = dayInfo.map((day) => ({
        day: day,
        hours: 0
      }));
      } 
      
const displayDates = (id) => {
  const client = res[id];

    let temp = [];
      dayArr.map((i, idx) => {
        const getHours = client.stats.find((item) => item.start === i.day);
        let newStats = {};
        if (getHours) {
          newStats = {
          
            client: {fullName: client.client.fullName, id: client.client.id},
            stats: [{
                day: i.day,
                hours: getHours.hours,
            }]
            
            
          };
      
        }
        else{
          newStats = {
            
            client: {fullName: client.client.fullName, id: client.client.id},
            stats: [{
                day: i.day,
            hours: 0,
            
            }]
            
          };
        }
        temp[idx] = newStats;
      });
 return temp;
      
}

 Object.keys(res).forEach(id=>{
    filtArr = [...filtArr, res[id]];
        console.log(displayDates(id))
  })

预期结构说明

为了实现表格行和数据的映射渲染,需要将最终数据调整为指定结构,当前输出中每个日期都会重复生成携带client信息的对象:

当前错误输出

current output = 
[{
  client: {
    fullName: "Pete Pete",
    id: "789"
 },
  stats: [{
    day: 1,
    hours: 0
   }]
 }, {
  client: {
    fullName: "Pete Pete",
    id: "789"
 },
  stats: [{
    day: 2,
    hours: 0
  }]
 }, {
  client: {
    fullName: "Pete Pete",
    id: "789"
 },
  stats: [{
    day: 3,
    hours: 0
  }]
 },//and so on for all days of the month
]

理想输出结构

ideal output=
[
  {
    client: {name: "name name", id: "123"},
    stats: [
             {day: 1, hours: 0},
             {day: 2, hours: 1},
             {day: 3, hours: 0},
             {day: 4, hours: 2},
             //and so on for all the days in the month
            ]
  }
]

此前尝试使用map、forEach方法调整数据结构,但始终无法正确访问数组内对象属性,频繁出现undefined报错。猜测可以在现有处理逻辑的某一步直接生成目标结构,但受限于个人能力未能实现。在线运行示例:https://jsfiddle.net/joznox/xzd5y142/3/

修复方案

原代码有两个核心问题:

  1. 生成dayArr的逻辑错误,map操作写在了for循环内部,每次循环都会覆盖dayArr,最终只能得到长度为1的数组
  2. displayDates函数遍历每一天时都新建了携带client信息的独立对象,导致单个客户的数据被拆成了30条
    直接替换原有数据处理逻辑即可得到目标结构:
// 第一步:合并同客户同日期的重复工时记录
const collData = data.reduce((accumulator, currentValue) => { 
  const existing = accumulator.find((n) => n.client.id === currentValue.client.id && n.start === currentValue.start);
  if (existing) {
    existing.hours += currentValue.hours;
  } else {
    // 深拷贝避免修改原始数据
    accumulator.push(structuredClone(currentValue));
  }
  return accumulator;
}, []);

// 目标月份总天数,生产环境可通过new Date(选中年份, 选中月份, 0).getDate()动态计算
const daysInMonth = 30;
// 预生成整月默认工时为0的统计数组
const defaultMonthStats = Array.from({length: daysInMonth}, (_, idx) => ({
  day: idx + 1,
  hours: 0
}));

const clientMap = {};
collData.forEach(item => {
  const clientId = item.client.id;
  // 解析工时记录对应的日期(天)
  const recordDay = Number(item.start.split('-').at(-1));
  // 客户不存在时初始化数据,直接填充整月默认0工时
  if (!clientMap[clientId]) {
    clientMap[clientId] = {
      client: {
        id: clientId,
        name: item.client.fullName
      },
      stats: structuredClone(defaultMonthStats)
    }
  }
  // 找到对应日期的索引,更新实际工时
  const targetIndex = clientMap[clientId].stats.findIndex(stat => stat.day === recordDay);
  if (targetIndex !== -1) {
    clientMap[clientId].stats[targetIndex].hours = item.hours;
  }
});

// 最终输出符合要求的结构
const finalResult = Object.values(clientMap);
console.log(finalResult);

逻辑说明

  • 提前生成整月默认0工时的统计数组,不需要后续反复判断日期是否存在
  • 用对象做客户ID到客户数据的映射,查找效率远高于数组遍历find
  • 初始化客户数据时直接挂载整月默认统计,后续只需要替换有工时记录的日期对应的hours值即可
  • 最终直接取映射对象的value集合,每个客户对应一条数据,stats数组按顺序包含整月所有日期的工时,可直接用于表格渲染

内容的提问来源于stack exchange,提问作者Jowz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 23:48:39