diff --git a/customer_service.html b/cs_chart.html similarity index 100% rename from customer_service.html rename to cs_chart.html diff --git a/index.html b/index.html index 4e1999c..60d7ccd 100644 --- a/index.html +++ b/index.html @@ -85,16 +85,7 @@
-
- 客服完成率分布 - -
+
客服完成率分布
@@ -102,7 +93,17 @@
-
客服多指标对比
+
+ 客服多指标对比 + +
@@ -203,152 +204,29 @@ } /***************************************************************** - * 1. 原始数据明细 + 2025年1月-10月随机数据 + * 1. 初始化空数据 *****************************************************************/ -// 基础11月数据 - const baseNovData = [ - ["2025-11","陈航明",891,415,750,"84.18%",50,"5.61%",68,"7.63%",91,"10.21%",2,"0.22%",3906.60,"4.28%",4,"0.45%",0,"0%",793,"99.13%",101,"11.34%",274,"30.75%",36,"4.04%",24,"2.69%",35,"3.93%"], - ["2025-11","周汝琪",305,149,249,"81.64%",18,"5.9%",8,"2.62%",38,"12.46%",2,"0.66%",859.00,"2.91%",2,"0.66%",0,"0%",255,"95.51%",15,"4.92%",68,"22.3%",7,"2.3%",2,"0.66%",9,"2.95%"], - ["2025-11","卢紫嫣",705,353,579,"82.13%",37,"5.25%",31,"4.4%",89,"12.62%",0,"0%",3382.00,"5.3%",3,"0.43%",2,"0.28%",616,"100%",79,"11.21%",194,"27.52%",37,"5.25%",24,"3.4%",66,"9.36%"], - ["2025-11","徐婉茹",1010,466,840,"83.17%",52,"5.15%",62,"6.14%",118,"11.68%",1,"0.1%",2728.00,"2.66%",1,"0.1%",2,"0.2%",881,"98.77%",105,"10.4%",287,"28.42%",33,"3.27%",30,"2.97%",60,"5.94%"], - ["2025-11","刘春霞",986,493,813,"82.45%",53,"5.38%",63,"6.39%",120,"12.17%",0,"0%",5342.00,"5.34%",4,"0.41%",0,"0%",861,"99.42%",85,"8.62%",295,"29.92%",29,"2.94%",38,"3.85%",59,"5.98%"], - ["2025-11","王燕燕",336,160,275,"81.85%",21,"6.25%",15,"4.46%",40,"11.9%",1,"0.3%",1765.00,"4.97%",1,"0.3%",0,"0%",287,"96.96%",14,"4.17%",70,"20.83%",15,"4.46%",8,"2.38%",11,"3.27%"], - ["2025-11","何丹妮",39,21,32,"82.05%",5,"12.82%",0,"0%",2,"5.13%",0,"0%",0.00,"0%",0,"0%",0,"0%",35,"94.59%",4,"10.26%",6,"15.38%",2,"5.13%",1,"2.56%",3,"7.69%"], - ["2025-11","技术部测试",4,0,0,"0%",0,"0%",0,"0%",4,"100%",0,"0%",0.00,"0%",0,"0%",0,"0%",0,"0%",0,"0%",0,"0%",0,"0%",0,"0%",0,"0%",0,"0%"], - ["2025-11","蒋卡泽",174,63,128,"73.56%",9,"5.17%",17,"9.77%",37,"21.26%",8,"4.6%",1355.00,"8.05%",0,"0%",0,"0%",137,"100%",13,"7.47%",56,"32.18%",4,"2.3%",2,"1.15%",6,"3.45%"], - ["2025-11","冯雪",841,393,708,"84.19%",46,"5.47%",50,"5.95%",87,"10.34%",0,"0%",3697.00,"4.14%",2,"0.24%",1,"0.12%",690,"91.51%",68,"8.09%",233,"27.71%",26,"3.09%",18,"2.14%",53,"6.3%"], - ["2025-11","啾啾AI",5070,6,5056,"99.72%",0,"0%",1,"0.02%",14,"0.28%",0,"0%",0.00,"0%",8,"0.16%",1,"0.02%",0,"0%",1,"0.02%",454,"8.95%",0,"0%",111,"2.19%",5050,"99.61%"], - ["2025-11","王淑静",903,425,756,"83.72%",65,"7.2%",71,"7.86%",82,"9.08%",0,"0%",4089.00,"4.04%",6,"0.66%",1,"0.11%",780,"95.01%",94,"10.41%",269,"29.79%",22,"2.44%",14,"1.55%",60,"6.64%"], - ["2025-11","方文明",521,292,433,"83.11%",38,"7.29%",22,"4.22%",50,"9.6%",0,"0%",2158.00,"3.83%",5,"0.96%",0,"0%",442,"93.84%",39,"7.49%",123,"23.61%",17,"3.26%",9,"1.73%",27,"5.18%"], - ["2025-11","陈家南",11,3,9,"81.82%",0,"0%",2,"18.18%",2,"18.18%",0,"0%",850.00,"16.66%",0,"0%",0,"0%",2,"22.22%",1,"9.09%",2,"18.18%",0,"0%",0,"0%",8,"72.73%"], - ["2025-11","全部",11796,3239,10628,"90.1%",394,"3.34%",410,"3.48%",774,"6.56%",14,"0.12%",30131.60,"2.53%",36,"0.31%",7,"0.06%",5779,"52.43%",619,"5.25%",2331,"19.76%",228,"1.93%",281,"2.38%",5447,"46.18%"] - ]; - - // 生成2025年1月-10月随机数据 - function generateMonthlyData(baseData, months) { - const allData = [...baseData]; - - for (let month = 1; month <= 10; month++) { - const monthStr = month.toString().padStart(2, '0'); - const yearMonth = `2025-${monthStr}`; - - baseData.forEach(row => { - if (row[1] !== '全部') { // 跳过"全部"汇总行 - const newRow = [...row]; - newRow[0] = yearMonth; // 更新月份 - - // 为数值字段添加随机变化 (±20%) - for (let i = 2; i <= 30; i += 2) { - if (typeof newRow[i] === 'number') { - const variation = 0.8 + Math.random() * 0.4; // 0.8-1.2 - newRow[i] = Math.round(newRow[i] * variation); - } - } - - // 为百分比字段添加随机变化 (±5%) - for (let i = 3; i <= 31; i += 2) { - if (typeof newRow[i] === 'string' && newRow[i].includes('%')) { - const baseValue = parseFloat(newRow[i]); - const variation = 0.95 + Math.random() * 0.1; // 0.95-1.05 - const newValue = Math.max(0, Math.min(100, baseValue * variation)); - newRow[i] = newValue.toFixed(2) + '%'; - } - } - - allData.push(newRow); - } - }); - - // 添加月度汇总行 - const summaryRow = [...baseData.find(row => row[1] === '全部')]; - summaryRow[0] = yearMonth; - allData.push(summaryRow); - } - - return allData; - } - - const rawNov = generateMonthlyData(baseNovData, 10); - - /* 解析为统一对象 - 修复变量名问题,添加错误处理 */ - function parseRow(r){ - // 辅助函数:安全解析百分比或数值 - function safeParse(value, defaultValue = 0) { - if (value === null || value === undefined) return defaultValue; - if (typeof value === 'number') return value; - if (typeof value === 'string') { - // 移除 % 符号并解析 - const cleaned = value.replace('%', '').trim(); - // 检查是否包含无效字符 - if (cleaned === '' || cleaned === '极' || isNaN(cleaned)) { - return defaultValue; - } - const parsed = parseFloat(cleaned); - return isNaN(parsed) ? defaultValue : parsed; - } - return defaultValue; - } - - const [ - 月份,调度,订单总数,考核数,完成数,完成率, - 空驶数,空驶率,线下数,线下率,关闭数,关闭率, - 拒单数,拒单率,溢价金额,溢价率,不满数,不满率, - 投诉数,投诉率,审单数,审单率,预约数,预约率, - 未接50数,未接50率,超时数,超时率,不符数,不符率, - 未联系数,未联系率 - ] = r; - - return { - 月份,调度, - 订单总数: safeParse(订单总数, 0), - 考核数: safeParse(考核数, 0), - 完成数: safeParse(完成数, 0), - 完成率: safeParse(完成率, 0), - 空驶数: safeParse(空驶数, 0), - 空驶率: safeParse(空驶率, 0), - 线下数: safeParse(线下数, 0), - 线下率: safeParse(线下率, 0), - 关闭数: safeParse(关闭数, 0), - 关闭率: safeParse(关闭率, 0), - 拒单数: safeParse(拒单数, 0), - 拒单率: safeParse(拒单率, 0), - 溢价金额: safeParse(溢价金额, 0), - 溢价率: safeParse(溢价率, 0), - 不满数: safeParse(不满数, 0), - 不满率: safeParse(不满率, 0), - 投诉数: safeParse(投诉数, 0), - 投诉率: safeParse(投诉率, 0), - 审单数: safeParse(审单数, 0), - 审单率: safeParse(审单率, 0), - 预约数: safeParse(预约数, 0), - 预约率: safeParse(预约率, 0), - 未接50数: safeParse(未接50数, 0), - 未接50率: safeParse(未接50率, 0), - 超时数: safeParse(超时数, 0), - 超时率: safeParse(超时率, 0), - 不符数: safeParse(不符数, 0), - 不符率: safeParse(不符率, 0), - 未联系数: safeParse(未联系数, 0), - 未联系率: safeParse(未联系率, 0) - }; - } - // 解析数据,添加错误处理 + // 初始化为空数组,等待接口数据 let novData = []; - try { - novData = rawNov.map(parseRow); - console.log('数据解析成功,共', novData.length, '条记录'); - } catch (error) { - console.error('数据解析失败:', error); - // 如果解析失败,至少显示空数据,避免页面完全无法显示 - novData = []; - } - /* 生成 30 天趋势(Demo) */ + /* 生成趋势数据(基于真实月度数据模拟日度数据) */ function genTrend(base){ const arr=[]; + // 如果没有数据,返回空数组 + if (!base || base.length === 0) return arr; + + // 获取最新月份 + const months = [...new Set(base.map(b => b.月份))].sort(); + const latestMonth = months[months.length - 1]; + + if (!latestMonth) return arr; + + // 使用最新月份的数据生成30天趋势 + const latestData = base.filter(b => b.月份 === latestMonth); + for(let d=1;d<=30;d++){ - const date=dayjs('2025-11-01').add(d-1,'day'); - base.forEach(b=>{ + const date=dayjs(latestMonth + '-01').add(d-1,'day'); + latestData.forEach(b=>{ arr.push({ 日期:date.format('YYYY-MM-DD'), 客服:b.调度, @@ -363,18 +241,21 @@ } return arr; } - const trendData = genTrend(novData.filter(b=>b.调度!=='全部' && b.调度!=='技术部测试')); + + // 初始化为空数组 + let trendData = []; /* 全局状态 */ const state = { - month:'2025-11', - staff:[], - index:'完成率', - trendData, - novData, + month: '', + staff: [], + index: '完成率', + trendData: [], + novData: [], drillName: null, selectedStaffs: [], // 从柱状图点击选中的客服列表 - pieChartIndex: '完成率' // 饼图/柱状图切换的指标 + pieChartIndex: '完成率', // 饼图/柱状图切换的指标 + barChartIndex: '完成率' // 柱状图切换的指标 }; /* ECharts 实例 */ @@ -466,13 +347,13 @@ Object.keys(monthData).forEach(month => { const m = monthData[month]; if (staffs.length === 0 || staffs.length > 1) { - // 全部客服:动态计算百分比 + // 全部客服:动态计算百分比,保留2位小数 if (m.订单总数 > 0) { // 完成率 = (完成数 + 空驶数) / 订单总数 * 100 - m.完成率 = ((m.完成数 + m.空驶数) / m.订单总数 * 100); - m.投诉率 = (m.投诉数 / m.订单总数 * 100); - m.空驶率 = (m.空驶数 / m.订单总数 * 100); - m.关闭率 = (m.关闭数 / m.订单总数 * 100); + m.完成率 = parseFloat(((m.完成数 + m.空驶数) / m.订单总数 * 100).toFixed(2)); + m.投诉率 = parseFloat((m.投诉数 / m.订单总数 * 100).toFixed(2)); + m.空驶率 = parseFloat((m.空驶数 / m.订单总数 * 100).toFixed(2)); + m.关闭率 = parseFloat((m.关闭数 / m.订单总数 * 100).toFixed(2)); } else { m.完成率 = 0; m.投诉率 = 0; @@ -480,6 +361,8 @@ m.关闭率 = 0; } } + // 溢价金额也保留2位小数 + m.溢价金额 = parseFloat(m.溢价金额.toFixed(2)); }); return Object.values(monthData).sort((a, b) => a.月份.localeCompare(b.月份)); @@ -795,18 +678,48 @@ const months = [...new Set(state.novData.map(d => d.月份))].sort(); const latestMonth = months[months.length - 1] || months[0]; - // 无论是否选择客服,都显示所有客服的数据 + console.log('饼图数据 - 最新月份:', latestMonth, '指标:', pieIndex); + console.log('当前novData:', state.novData); + + // 无论是否选择客服,都显示所有客服的数据(排除"全部"和"技术部测试") const data = state.novData - .filter(d => d.调度 !== '全部' && d.调度 !== '技术部测试' && d.月份 === latestMonth) + .filter(d => { + const isValid = d.调度 !== '全部' && d.调度 !== '技术部测试' && d.月份 === latestMonth; + if (isValid) { + console.log('饼图客服:', d.调度, pieIndex + ':', d[pieIndex]); + } + return isValid; + }) .map(d => { const value = d[pieIndex]; + // 确保值是有效的数字 + const parsedValue = parseFloat(value); return { name: d.调度, - value: (value !== undefined && value !== null && !isNaN(value)) ? value : 0 + value: (!isNaN(parsedValue) && parsedValue > 0) ? parsedValue : 0 }; }) .filter(d => d.value > 0); // 过滤掉值为0的数据 + console.log('饼图最终数据:', data); + + // 如果没有有效数据,显示提示 + if (data.length === 0) { + console.warn('饼图没有有效数据'); + pie.setOption({ + title: { + text: '暂无数据', + left: 'center', + top: 'center', + textStyle: { + color: '#999', + fontSize: 16 + } + } + }, true); + return; + } + // 如果是完成率,显示饼图;其他指标显示柱状图 if (pieIndex === '完成率') { const option = { @@ -887,23 +800,27 @@ } } - /* 2. 折线 - 月度趋势(按日) */ + /* 2. 折线 - 月度趋势(使用真实月度数据) */ function updateLine(){ if (!line) { console.warn('折线图实例未初始化'); return; } - const {trendData, staff, index} = state; - if (!trendData || trendData.length === 0) { + const {novData, staff, index} = state; + + // 过滤掉 '全部' 和 '测试' 账号 + const validData = novData.filter(d => !d.调度.includes('全部') && !d.调度.includes('测试')); + + if (!validData || validData.length === 0) { console.warn('趋势数据为空'); return; } - const days = [...new Set(trendData.map(d => d.日期))].sort(); + const months = [...new Set(validData.map(d => d.月份))].sort(); // 如果没有选择客服,显示所有客服 - const staffsToShow = staff.length > 0 ? staff : [...new Set(trendData.map(d => d.客服))]; + const staffsToShow = staff.length > 0 ? staff : [...new Set(validData.map(d => d.调度))]; const series = staffsToShow.map((s, idx) => ({ name: s, @@ -912,8 +829,8 @@ symbol: 'circle', symbolSize: 6, lineStyle: { width: 3 }, - data: days.map(day => { - const item = trendData.find(d => d.日期 === day && d.客服 === s); + data: months.map(month => { + const item = validData.find(d => d.月份 === month && d.调度 === s); return item ? Math.max(0, item[index]) : 0; }) })); @@ -937,7 +854,7 @@ }, xAxis: { type: 'category', - data: days, + data: months, boundaryGap: false, axisLabel: { rotate: 45 @@ -960,14 +877,21 @@ return; } - const {novData, staff, index} = state; + const {novData, staff} = state; + const index = state.barChartIndex || '完成率'; + if (!novData || novData.length === 0) { console.warn('没有数据可显示'); return; } + // 获取最新月份 + const months = [...new Set(novData.map(d => d.月份))].sort(); + const latestMonth = months[months.length - 1]; + const filterData = novData.filter(d => - staff.length ? staff.includes(d.调度) : (d.调度 !== '全部' && d.调度 !== '技术部测试') + d.月份 === latestMonth && + d.调度 !== '全部' && !d.调度.includes('测试') ); const barData = filterData.map(d => { @@ -1001,10 +925,10 @@ }, yAxis: { type: 'value', - name: index + (state.index.includes('率') ? '(%)' : '') + name: index + (index.includes('率') ? '(%)' : '') }, series: [{ - name: state.index, + name: index, type: 'bar', data: barData.map(d => d.value), barMaxWidth: 40 @@ -1022,10 +946,10 @@ if (!state.selectedStaffs) { state.selectedStaffs = []; } - const index = state.selectedStaffs.indexOf(name); - if (index > -1) { + const idx = state.selectedStaffs.indexOf(name); + if (idx > -1) { // 已选中,取消选择 - state.selectedStaffs.splice(index, 1); + state.selectedStaffs.splice(idx, 1); } else { // 未选中,添加选择 state.selectedStaffs.push(name); @@ -1040,7 +964,7 @@ }); } - /* 4. 历史 - 单个或多个客服趋势 */ + /* 4. 历史 - 单个或多个客服趋势(使用真实月度数据) */ function updateHist(){ if (!hist) { console.warn('历史图表实例未初始化'); @@ -1064,24 +988,24 @@ } // 最后使用默认客服 else { - const defaultStaff = state.novData.find(d => d.调度 !== '全部' && d.调度 !== '技术部测试')?.调度 || '陈航明'; + const defaultStaff = state.novData.find(d => !d.调度.includes('全部') && !d.调度.includes('测试'))?.调度 || '陈航明'; staffsToShow = [defaultStaff]; } - // 获取所有选中客服的数据 - const allData = state.trendData.filter(d => staffsToShow.includes(d.客服)); + // 获取所有选中客服的数据(使用 novData) + const allData = state.novData.filter(d => staffsToShow.includes(d.调度)); if (!allData || allData.length === 0) { console.warn('没有找到客服数据:', staffsToShow); return; } - // 获取所有日期 - const days = [...new Set(allData.map(d => d.日期))].sort(); + // 获取所有月份 + const months = [...new Set(allData.map(d => d.月份))].sort(); // 为每个客服创建一条折线 const series = staffsToShow.map((staffName, idx) => { - const staffData = allData.filter(d => d.客服 === staffName); + const staffData = allData.filter(d => d.调度 === staffName); return { name: staffName, type: 'line', @@ -1089,8 +1013,8 @@ symbol: 'circle', symbolSize: 6, lineStyle: { width: 3 }, - data: days.map(day => { - const item = staffData.find(d => d.日期 === day); + data: months.map(month => { + const item = staffData.find(d => d.月份 === month); return item ? Math.max(0, item[state.index]) : null; }) }; @@ -1132,7 +1056,7 @@ }, xAxis: { type: 'category', - data: days, + data: months, boundaryGap: false, axisLabel: { rotate: 45 @@ -1181,11 +1105,14 @@ // 重新生成趋势数据 const trendBase = novData.filter(b => b.调度 !== '全部' && b.调度 !== '技术部测试'); - state.trendData = genTrend(trendBase); + trendData = genTrend(trendBase); + state.trendData = trendData; // 重新初始化客服列表 initCtrl(); renderAll(); + } else { + alert('获取数据失败,请检查网络连接或联系管理员'); } }); @@ -1229,7 +1156,8 @@ renderAll(); }); - // 饼图指标切换 + // 饼图指标切换(已移除,改为柱状图指标切换) + /* const pieChartIndexSel = document.getElementById('pieChartIndex'); if (pieChartIndexSel) { pieChartIndexSel.value = state.pieChartIndex || '完成率'; @@ -1238,6 +1166,17 @@ updatePie(); }); } + */ + + // 柱状图指标切换 + const barChartIndexSel = document.getElementById('barChartIndex'); + if (barChartIndexSel) { + barChartIndexSel.value = state.barChartIndex || '完成率'; + barChartIndexSel.addEventListener('change', e => { + state.barChartIndex = e.target.value; + updateBar(); + }); + } document.getElementById('export').addEventListener('click', () => { if (bar) { @@ -1323,11 +1262,14 @@ // 重新生成趋势数据 const trendBase = novData.filter(b => b.调度 !== '全部' && b.调度 !== '技术部测试'); - state.trendData = genTrend(trendBase); + trendData = genTrend(trendBase); + state.trendData = trendData; console.log('使用接口数据,共', novData.length, '条记录'); } else { - console.warn('接口数据异常,使用本地默认数据'); + console.warn('接口数据获取失败,请检查接口配置'); + // 显示友好提示 + alert('数据加载失败,请检查网络连接或联系管理员'); } initCtrl();