欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 汽车 > 新车 > 坐席业绩数据分析

坐席业绩数据分析

2025/5/15 11:27:41 来源:https://blog.csdn.net/wxl781227/article/details/147838892  浏览:    关键词:坐席业绩数据分析

豆包提示词:

使用papaparse.js,chart.js,tailwindcss和font-awesome,生成一个可以交互的简洁且可以运行的HTML代码,不要输出无关内容。

具体要求如下:

1、按坐席姓名输出业绩折线图。

2、系统导航区域:放置上传csv文件的按钮,需要正确解析日期、坐席姓名、一级机构至五级机构的机构名称、各业绩的中位值,(数据格式:日期,坐席姓名,一级机构,二级机构,三级机构,四级机构,五级机构,业务等级,在线时长,外呼时长,接通时长,外呼次数,接通次数,有效通次,接通率,违规次数,推荐次数),时长的单位均为分钟。

3、顶部区域:筛选X轴日期(日/周/月,使用按钮组件选择),Y轴业绩指标(通话时长/外呼次数/接通次数/..,使用按钮组件选择),可以根据业绩指标设置目标值,默认设置为对应业绩指标的中位值,允许修改。左侧区域选择坐席名称和机构名称,右侧区域显示图表。

4、坐席选择:勾选合法的坐席名称(坐席姓名的取值)

5、机构选择:先使用按钮组件的形式显示一级机构至五级机构,点击某级机构时,使用checkbox显示某级机构下唯一的合法的机构名称,默认选中五级机构并勾选中五级机构下的机构名称,被勾选的机构名称需要计算机构均值,目标值和机构均值都使用不同颜色虚线显示在折线图上。

6、坐席名称/每级机构的名称都可以全选/取消全选。

7、所有操作都会直接更新图表。

通过调整日/周/月的焦点、过滤掉undefined项、按所有坐席计算机构均值得到的最终代码如下:

<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>坐席业绩数据分析</title><script src="https://cdn.tailwindcss.com"></script><link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css" rel="stylesheet"><script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.8/dist/chart.umd.min.js"></script><script src="https://cdn.jsdelivr.net/npm/papaparse@5.4.1/papaparse.min.js"></script><script>tailwind.config = {theme: {extend: {colors: {primary: '#3B82F6',secondary: '#10B981',accent: '#6366F1',neutral: '#6B7280',success: '#10B981',warning: '#F59E0B',danger: '#EF4444',info: '#06B6D4',},fontFamily: {sans: ['Inter', 'system-ui', 'sans-serif'],},boxShadow: {'card': '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)','card-hover': '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)',}},}}</script><style type="text/tailwindcss">@layer utilities {.content-auto {content-visibility: auto;}.scrollbar-hide {-ms-overflow-style: none;scrollbar-width: none;}.scrollbar-hide::-webkit-scrollbar {display: none;}.chart-container {position: relative;height: 100%;width: 100%;}.btn-toggle.active {@apply bg-primary text-white;}.btn-toggle:not(.active) {@apply bg-gray-100 text-gray-700 hover:bg-gray-200;}.checkbox-container {max-height: 150px;overflow-y: auto;scrollbar-width: thin;}.checkbox-container::-webkit-scrollbar {width: 4px;}.checkbox-container::-webkit-scrollbar-track {background: #f1f1f1;}.checkbox-container::-webkit-scrollbar-thumb {background: #c1c1c1;border-radius: 4px;}.checkbox-container::-webkit-scrollbar-thumb:hover {background: #a1a1a1;}.loading-spinner {border-top-color: theme('colors.primary');animation: spinner 0.6s linear infinite;}@keyframes spinner {to {transform: rotate(360deg);}}}</style>
</head><body class="bg-gray-50 font-sans text-gray-800 min-h-screen flex flex-col"><!-- 导航栏 --><header class="bg-white shadow-sm sticky top-0 z-50"><div class="container mx-auto px-4 py-3 flex flex-col md:flex-row md:items-center justify-between"><div class="flex items-center mb-3 md:mb-0"><h1 class="text-xl md:text-2xl font-bold text-primary flex items-center"><i class="fa fa-bar-chart mr-2"></i><span>坐席业绩数据分析</span></h1></div><div class="flex items-center"><label for="file-upload"class="cursor-pointer bg-primary hover:bg-primary/90 text-white font-medium py-2 px-4 rounded-md transition-all duration-200 flex items-center"><i class="fa fa-upload mr-2"></i><span>上传CSV文件</span></label><input id="file-upload" type="file" accept=".csv" class="hidden" /><span id="file-name" class="ml-3 text-sm text-gray-500"></span></div></div></header><!-- 主要内容区 --><main class="flex-grow container mx-auto px-4 py-6"><!-- 顶部筛选区 --><div class="bg-white rounded-lg shadow-card p-4 mb-6 transform transition-all duration-300 hover:shadow-card-hover"><div class="grid grid-cols-1 lg:grid-cols-3 gap-6"><!-- X轴日期筛选 --><div class="space-y-2"><h3 class="font-semibold text-gray-700 flex items-center"><i class="fa fa-calendar-alt mr-2 text-primary"></i><span>X轴日期筛选</span></h3><div class="flex flex-wrap gap-2"><button id="date-day"class="btn-toggle active px-3 py-1 rounded-md text-sm font-medium transition-all duration-200">日</button><button id="date-week"class="btn-toggle px-3 py-1 rounded-md text-sm font-medium transition-all duration-200">周</button><button id="date-month"class="btn-toggle px-3 py-1 rounded-md text-sm font-medium transition-all duration-200">月</button></div></div><!-- Y轴业绩指标筛选 --><div class="space-y-2"><h3 class="font-semibold text-gray-700 flex items-center"><i class="fa fa-line-chart mr-2 text-primary"></i><span>Y轴业绩指标</span></h3><div class="flex flex-wrap gap-2"><button id="metric-duration"class="btn-toggle active px-3 py-1 rounded-md text-sm font-medium transition-all duration-200">通话时长</button><button id="metric-call"class="btn-toggle px-3 py-1 rounded-md text-sm font-medium transition-all duration-200">外呼次数</button><button id="metric-connect"class="btn-toggle px-3 py-1 rounded-md text-sm font-medium transition-all duration-200">接通次数</button><button id="metric-effective"class="btn-toggle px-3 py-1 rounded-md text-sm font-medium transition-all duration-200">有效通次</button></div></div><!-- 目标值设置 --><div class="space-y-2"><h3 class="font-semibold text-gray-700 flex items-center"><i class="fa fa-bullseye mr-2 text-primary"></i><span>目标值设置</span></h3><div class="flex items-center"><input type="number" id="target-value"class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary transition-all duration-200"placeholder="输入目标值"><button id="set-target"class="ml-2 bg-primary hover:bg-primary/90 text-white px-4 py-2 rounded-md transition-all duration-200">设置</button></div><p id="current-median" class="text-sm text-gray-500 mt-1">当前中位值: <span class="font-medium">-</span></p></div></div></div><!-- 筛选和图表区域 --><div class="grid grid-cols-1 lg:grid-cols-12 gap-6"><!-- 左侧筛选区 --><div class="lg:col-span-4 space-y-6"><!-- 坐席选择 --><div class="bg-white rounded-lg shadow-card p-4 transform transition-all duration-300 hover:shadow-card-hover"><div class="flex justify-between items-center mb-3"><h3 class="font-semibold text-gray-700 flex items-center"><i class="fa fa-users mr-2 text-primary"></i><span>坐席选择</span></h3><div class="flex space-x-2"><button id="select-all-agents"class="text-xs px-2 py-1 bg-gray-100 hover:bg-gray-200 rounded transition-all duration-200">全选</button><button id="deselect-all-agents"class="text-xs px-2 py-1 bg-gray-100 hover:bg-gray-200 rounded transition-all duration-200">取消全选</button></div></div><div id="agent-container" class="checkbox-container p-2 border border-gray-200 rounded-md"><!-- 坐席选项将通过JS动态生成 --><div class="flex items-center justify-center h-20 text-gray-400"><i class="fa fa-file-csv text-2xl mr-2"></i><span>请先上传CSV文件</span></div></div></div><!-- 机构选择 --><div class="bg-white rounded-lg shadow-card p-4 transform transition-all duration-300 hover:shadow-card-hover"><h3 class="font-semibold text-gray-700 flex items-center mb-3"><i class="fa fa-sitemap mr-2 text-primary"></i><span>机构选择</span></h3><!-- 机构级别选择 --><div class="flex flex-wrap gap-2 mb-3"><button data-level="1"class="org-level-btn px-3 py-1 rounded-md text-sm font-medium bg-gray-100 hover:bg-gray-200 transition-all duration-200">一级机构</button><button data-level="2"class="org-level-btn px-3 py-1 rounded-md text-sm font-medium bg-gray-100 hover:bg-gray-200 transition-all duration-200">二级机构</button><button data-level="3"class="org-level-btn px-3 py-1 rounded-md text-sm font-medium bg-gray-100 hover:bg-gray-200 transition-all duration-200">三级机构</button><button data-level="4"class="org-level-btn px-3 py-1 rounded-md text-sm font-medium bg-gray-100 hover:bg-gray-200 transition-all duration-200">四级机构</button><button data-level="5"class="org-level-btn active px-3 py-1 rounded-md text-sm font-medium bg-primary text-white transition-all duration-200">五级机构</button></div><!-- 机构列表 --><div class="mb-3"><div id="current-org-level" class="text-sm text-gray-500 mb-2">当前显示: 五级机构</div><div class="flex space-x-2 mb-2"><button id="select-all-orgs"class="text-xs px-2 py-1 bg-gray-100 hover:bg-gray-200 rounded transition-all duration-200">全选</button><button id="deselect-all-orgs"class="text-xs px-2 py-1 bg-gray-100 hover:bg-gray-200 rounded transition-all duration-200">取消全选</button></div><div id="org-container" class="checkbox-container p-2 border border-gray-200 rounded-md"><!-- 机构选项将通过JS动态生成 --><div class="flex items-center justify-center h-20 text-gray-400"><i class="fa fa-file-csv text-2xl mr-2"></i><span>请先上传CSV文件</span></div></div></div><!-- 机构均值显示 --><div id="org-average-container" class="p-3 bg-gray-50 rounded-md border border-gray-200"><h4 class="font-medium text-sm mb-1">机构均值: <span id="current-org-average"class="text-primary font-semibold">-</span></h4><div class="w-full bg-gray-200 rounded-full h-2"><div id="org-average-bar" class="bg-primary h-2 rounded-full" style="width: 0%"></div></div></div></div></div><!-- 右侧图表区 --><divclass="lg:col-span-8 bg-white rounded-lg shadow-card p-4 transform transition-all duration-300 hover:shadow-card-hover"><div class="flex justify-between items-center mb-4"><h3 class="font-semibold text-gray-700 flex items-center"><i class="fa fa-chart-line mr-2 text-primary"></i><span id="chart-title">坐席业绩趋势分析</span></h3><div class="flex space-x-2"><button id="download-png"class="text-sm px-3 py-1 bg-gray-100 hover:bg-gray-200 rounded transition-all duration-200 flex items-center"><i class="fa fa-download mr-1"></i> PNG</button><button id="download-svg"class="text-sm px-3 py-1 bg-gray-100 hover:bg-gray-200 rounded transition-all duration-200 flex items-center"><i class="fa fa-download mr-1"></i> SVG</button></div></div><!-- 修改图表容器 --><div class="h-[400px] w-full chart-container overflow-hidden relative"><canvas id="performance-chart" class="absolute top-0 left-0 w-full h-full"></canvas></div><div id="chart-loading" class="hidden absolute inset-0 flex items-center justify-center bg-white/80"><div class="flex flex-col items-center"><div class="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-primary"></div><p class="mt-2 text-gray-500">正在加载图表...</p></div></div></div></div></main><!-- 页脚 --><footer class="bg-white border-t border-gray-200 py-4 mt-8"><div class="container mx-auto px-4 text-center text-gray-500 text-sm"><p>© 2025 坐席业绩数据分析系统 | 设计与开发</p></div></footer><!-- 通知组件 --><div id="notification"class="fixed bottom-4 right-4 bg-white shadow-lg rounded-lg p-4 transform transition-all duration-300 translate-y-20 opacity-0 z-50 flex items-center max-w-xs"><div id="notification-icon" class="mr-3 text-primary"><i class="fa fa-info-circle"></i></div><div><h4 id="notification-title" class="font-medium text-gray-800">通知标题</h4><p id="notification-message" class="text-sm text-gray-600">通知内容将显示在这里...</p></div><button id="close-notification" class="ml-4 text-gray-400 hover:text-gray-600"><i class="fa fa-times"></i></button></div><script>// 全局变量let csvData = null;let chart = null;let selectedDateType = 'day';let selectedMetric = 'duration';let targetValue = null;let medians = {};let currentOrgLevel = 5;let chartData = {};let isChartUpdating = false;let loadingStartTime = 0;const MIN_LOADING_TIME = 500; // 最小加载时间(毫秒)// DOM 元素const fileUpload = document.getElementById('file-upload');const fileName = document.getElementById('file-name');const agentContainer = document.getElementById('agent-container');const orgContainer = document.getElementById('org-container');const currentOrgLevelEl = document.getElementById('current-org-level');const currentMedianEl = document.getElementById('current-median').querySelector('span');const currentOrgAverageEl = document.getElementById('current-org-average');const orgAverageBar = document.getElementById('org-average-bar');const chartTitle = document.getElementById('chart-title');const chartLoading = document.getElementById('chart-loading');const notification = document.getElementById('notification');const notificationTitle = document.getElementById('notification-title');const notificationMessage = document.getElementById('notification-message');const notificationIcon = document.getElementById('notification-icon');const closeNotification = document.getElementById('close-notification');const targetValueInput = document.getElementById('target-value');const setTargetBtn = document.getElementById('set-target');const downloadPngBtn = document.getElementById('download-png');const downloadSvgBtn = document.getElementById('download-svg');// 日期类型按钮const dateDayBtn = document.getElementById('date-day');const dateWeekBtn = document.getElementById('date-week');const dateMonthBtn = document.getElementById('date-month');// 指标类型按钮const metricDurationBtn = document.getElementById('metric-duration');const metricCallBtn = document.getElementById('metric-call');const metricConnectBtn = document.getElementById('metric-connect');const metricEffectiveBtn = document.getElementById('metric-effective');// 机构级别按钮const orgLevelBtns = document.querySelectorAll('.org-level-btn');// 全选/取消全选按钮const selectAllAgentsBtn = document.getElementById('select-all-agents');const deselectAllAgentsBtn = document.getElementById('deselect-all-agents');const selectAllOrgsBtn = document.getElementById('select-all-orgs');const deselectAllOrgsBtn = document.getElementById('deselect-all-orgs');// 初始化图表function initChart() {const ctx = document.getElementById('performance-chart').getContext('2d');// 销毁已存在的图表if (chart) {chart.destroy();}// 创建新图表chart = new Chart(ctx, {type: 'line',data: {labels: [],datasets: []},options: {responsive: true,maintainAspectRatio: false,interaction: {mode: 'index',intersect: false,},plugins: {legend: {position: 'top',labels: {usePointStyle: true,boxWidth: 6}},tooltip: {backgroundColor: 'rgba(255, 255, 255, 0.9)',titleColor: '#333',bodyColor: '#666',borderColor: '#ddd',borderWidth: 1,padding: 12,boxPadding: 6,usePointStyle: true,callbacks: {label: function (context) {let label = context.dataset.label || '';if (label) {label += ': ';}if (context.parsed.y !== null) {const value = context.parsed.y;label += selectedMetric === 'duration' ?value.toFixed(1) + ' 分钟' :value.toFixed(0);}return label;}}}},scales: {x: {grid: {display: false}},y: {beginAtZero: true,grid: {color: 'rgba(0, 0, 0, 0.05)'}}},animations: {tension: {duration: 1000,easing: 'linear'}}}});}// 解析CSV文件function parseCSV(file) {showLoading(true);Papa.parse(file, {header: true,dynamicTyping: true,complete: function (results) {csvData = results.data;fileName.textContent = file.name;// 处理数据processData();// 初始化图表initChart();// 更新图表updateChart();showLoading(false);showNotification('成功', 'CSV文件已成功导入', 'success');},error: function (error) {showLoading(false);showNotification('错误', 'CSV文件解析失败: ' + error.message, 'error');}});}// 处理数据function processData() {if (!csvData || csvData.length === 0) return;// 过滤掉包含undefined或无效值的行const validData = csvData.filter(row => {return (row['坐席姓名'] !== undefined &&row['一级机构'] !== undefined &&row['二级机构'] !== undefined &&row['三级机构'] !== undefined &&row['四级机构'] !== undefined &&row['五级机构'] !== undefined);});// 提取坐席名称,过滤掉空值和undefinedconst agents = [...new Set(validData.map(row => row['坐席姓名']))].filter(agent => agent !== undefined && agent !== '').sort();// 提取各级机构,过滤掉空值和undefinedconst organizations = {1: [...new Set(validData.map(row => row['一级机构']))].filter(org => org !== undefined && org !== '').sort(),2: [...new Set(validData.map(row => row['二级机构']))].filter(org => org !== undefined && org !== '').sort(),3: [...new Set(validData.map(row => row['三级机构']))].filter(org => org !== undefined && org !== '').sort(),4: [...new Set(validData.map(row => row['四级机构']))].filter(org => org !== undefined && org !== '').sort(),5: [...new Set(validData.map(row => row['五级机构']))].filter(org => org !== undefined && org !== '').sort()};// 计算各业绩的中位值calculateMedians();// 更新坐席选择updateAgentSelection(agents);// 更新机构选择updateOrgSelection(organizations[currentOrgLevel], currentOrgLevel);// 设置默认目标值targetValue = medians[selectedMetric];targetValueInput.value = targetValue;currentMedianEl.textContent = targetValue;}// 计算各业绩的中位值function calculateMedians() {if (!csvData || csvData.length === 0) return;// 提取需要计算中位值的字段const fields = ['在线时长', '外呼时长', '接通时长', '外呼次数', '接通次数', '有效通次', '接通率', '违规次数', '推荐次数'];fields.forEach(field => {// 过滤掉无效值并排序const values = csvData.map(row => row[field]).filter(value => typeof value === 'number' && !isNaN(value)).sort((a, b) => a - b);if (values.length > 0) {// 计算中位值const middle = Math.floor(values.length / 2);medians[field] = values.length % 2 === 0 ?(values[middle - 1] + values[middle]) / 2 :values[middle];} else {medians[field] = 0;}});// 映射指标到中文名称medians['duration'] = medians['接通时长'];medians['call'] = medians['外呼次数'];medians['connect'] = medians['接通次数'];medians['effective'] = medians['有效通次'];}// 更新坐席选择function updateAgentSelection(agents) {agentContainer.innerHTML = '';if (!agents || agents.length === 0) {agentContainer.innerHTML = `<div class="flex items-center justify-center h-20 text-gray-400"><i class="fa fa-exclamation-circle text-2xl mr-2"></i><span>未找到坐席数据</span></div>`;return;}agents.forEach(agent => {const checkbox = document.createElement('div');checkbox.className = 'flex items-center mb-2';checkbox.innerHTML = `<input type="checkbox" id="agent-${agent}" name="agent" value="${agent}" class="agent-checkbox rounded text-primary focus:ring-primary h-4 w-4"><label for="agent-${agent}" class="ml-2 text-sm text-gray-700">${agent}</label>`;agentContainer.appendChild(checkbox);// 添加事件监听器const input = checkbox.querySelector('input');input.addEventListener('change', updateChartDebounced);});// 默认全选document.querySelectorAll('.agent-checkbox').forEach(cb => {cb.checked = true;});}// 更新机构选择function updateOrgSelection(orgs, level) {orgContainer.innerHTML = '';// 过滤掉undefined和空字符串const validOrgs = orgs.filter(org => org !== undefined && org !== '');if (validOrgs.length === 0) {orgContainer.innerHTML = `<div class="flex items-center justify-center h-20 text-gray-400"><i class="fa fa-exclamation-circle text-2xl mr-2"></i><span>未找到机构数据</span></div>`;return;}// 创建机构复选框validOrgs.forEach(org => {const checkbox = document.createElement('div');checkbox.className = 'flex items-center mb-2';checkbox.innerHTML = `<input type="checkbox" id="org-${level}-${org}" name="org" value="${org}" class="org-checkbox rounded text-primary focus:ring-primary h-4 w-4"><label for="org-${level}-${org}" class="ml-2 text-sm text-gray-700">${org}</label>`;orgContainer.appendChild(checkbox);// 添加事件监听器const input = checkbox.querySelector('input');input.addEventListener('change', updateChartDebounced);});// 更新当前机构级别显示currentOrgLevelEl.textContent = `当前显示: ${['一级', '二级', '三级', '四级', '五级'][level - 1]}机构`;// 默认全选document.querySelectorAll('.org-checkbox').forEach(cb => {cb.checked = true;});}// 准备图表数据function prepareChartData() {if (!csvData || csvData.length === 0) return;// 过滤掉包含undefined或无效值的行const validData = csvData.filter(row => {return (row['坐席姓名'] !== undefined &&row['一级机构'] !== undefined &&row['二级机构'] !== undefined &&row['三级机构'] !== undefined &&row['四级机构'] !== undefined &&row['五级机构'] !== undefined &&row['日期'] !== undefined);});// 获取选中的坐席和机构const selectedAgents = Array.from(document.querySelectorAll('.agent-checkbox:checked')).map(cb => cb.value);const selectedOrgs = Array.from(document.querySelectorAll('.org-checkbox:checked')).map(cb => cb.value);// 检查是否有选中的坐席和机构if (selectedAgents.length === 0) {showNotification('提示', '请至少选择一个坐席', 'warning');return null;}if (selectedOrgs.length === 0) {showNotification('提示', '请至少选择一个机构', 'warning');return null;}// 确定使用的日期字段let dateField = '日期';// 根据日期类型分组const groupedData = {};const allAgentsGroupedData = {}; // 存储所有坐席的数据,用于计算机构均值// 确定要显示的指标let metricField = '';let metricName = '';switch (selectedMetric) {case 'duration':metricField = '接通时长';metricName = '通话时长(分钟)';break;case 'call':metricField = '外呼次数';metricName = '外呼次数';break;case 'connect':metricField = '接通次数';metricName = '接通次数';break;case 'effective':metricField = '有效通次';metricName = '有效通次';break;}// 更新图表标题chartTitle.textContent = `坐席${metricName}趋势分析`;// 处理数据 - 计算所有坐席的机构数据validData.forEach(row => {// 只过滤未选中的机构(保留所有坐席)if (!selectedOrgs.includes(row[`${['一', '二', '三', '四', '五'][currentOrgLevel - 1]}级机构`])) return;// 格式化日期let dateKey = row[dateField];// 根据日期类型调整if (selectedDateType === 'week') {const date = new Date(dateKey);const weekNum = Math.ceil((date.getDate() + 6 - date.getDay()) / 7);dateKey = `${date.getFullYear()}-W${weekNum}`;} else if (selectedDateType === 'month') {const date = new Date(dateKey);dateKey = `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}`;}// 初始化日期组if (!allAgentsGroupedData[dateKey]) {allAgentsGroupedData[dateKey] = {orgTotal: 0,orgCount: 0};}// 机构数据 (包含所有坐席)allAgentsGroupedData[dateKey].orgTotal += row[metricField];allAgentsGroupedData[dateKey].orgCount += 1;});// 处理数据 - 计算选中坐席的数据validData.forEach(row => {// 过滤未选中的坐席和机构if (!selectedAgents.includes(row['坐席姓名'])) return;if (!selectedOrgs.includes(row[`${['一', '二', '三', '四', '五'][currentOrgLevel - 1]}级机构`])) return;// 格式化日期 (与上面相同)let dateKey = row[dateField];if (selectedDateType === 'week') {const date = new Date(dateKey);const weekNum = Math.ceil((date.getDate() + 6 - date.getDay()) / 7);dateKey = `${date.getFullYear()}-W${weekNum}`;} else if (selectedDateType === 'month') {const date = new Date(dateKey);dateKey = `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}`;}// 初始化日期组if (!groupedData[dateKey]) {groupedData[dateKey] = {agents: {},orgTotal: 0,orgCount: 0};}// 坐席数据if (!groupedData[dateKey].agents[row['坐席姓名']]) {groupedData[dateKey].agents[row['坐席姓名']] = {value: 0,count: 0};}groupedData[dateKey].agents[row['坐席姓名']].value += row[metricField];groupedData[dateKey].agents[row['坐席姓名']].count += 1;// 机构数据 (仅选中坐席)groupedData[dateKey].orgTotal += row[metricField];groupedData[dateKey].orgCount += 1;});// 转换为图表可用的数据格式const sortedDates = Object.keys(groupedData).sort();const agentData = {};const orgAverageData = [];// 初始化坐席数据selectedAgents.forEach(agent => {agentData[agent] = [];});// 填充数据sortedDates.forEach(date => {const group = groupedData[date];const allAgentsGroup = allAgentsGroupedData[date] || { orgTotal: 0, orgCount: 0 };// 坐席数据selectedAgents.forEach(agent => {if (group.agents[agent]) {agentData[agent].push(group.agents[agent].value / group.agents[agent].count);} else {agentData[agent].push(null);}});// 机构平均数据 - 使用所有坐席的数据orgAverageData.push(allAgentsGroup.orgCount > 0 ?allAgentsGroup.orgTotal / allAgentsGroup.orgCount : null);});// 计算总体机构平均值 - 使用所有坐席的数据const allValidOrgValues = Object.values(allAgentsGroupedData).map(g => g.orgCount > 0 ? g.orgTotal / g.orgCount : null).filter(value => value !== null);const overallOrgAverage = allValidOrgValues.length > 0 ?allValidOrgValues.reduce((sum, value) => sum + value, 0) / allValidOrgValues.length :0;// 更新机构平均值显示currentOrgAverageEl.textContent = selectedMetric === 'duration' ?overallOrgAverage.toFixed(1) + ' 分钟' :overallOrgAverage.toFixed(0);// 更新进度条const maxValue = Math.max(overallOrgAverage,targetValue,...Object.values(agentData).flat().filter(value => value !== null));orgAverageBar.style.width = `${(overallOrgAverage / maxValue) * 100}%`;// 构建图表数据chartData = {labels: sortedDates,datasets: [],orgAverage: orgAverageData,overallOrgAverage: overallOrgAverage,maxValue: maxValue};// 为每个坐席创建数据集const colors = ['#3B82F6', '#10B981', '#6366F1', '#F59E0B', '#EF4444','#06B6D4', '#8B5CF6', '#EC4899', '#14B8A6', '#F97316'];selectedAgents.forEach((agent, index) => {chartData.datasets.push({label: agent,data: agentData[agent],borderColor: colors[index % colors.length],backgroundColor: `${colors[index % colors.length]}20`,borderWidth: 2,pointRadius: 3,pointHoverRadius: 5,tension: 0.1,fill: false});});// 添加机构平均线chartData.datasets.push({label: '机构平均',data: orgAverageData,borderColor: '#6B7280',borderWidth: 2,borderDash: [5, 5],pointRadius: 0,fill: false,order: 2});// 添加目标线if (targetValue !== null) {chartData.datasets.push({label: '目标值',data: sortedDates.map(() => targetValue),borderColor: '#F59E0B',borderWidth: 2,borderDash: [10, 5],pointRadius: 0,fill: false,order: 1});}return chartData;}// 更新图表function updateChart() {if (isChartUpdating) return;const chartData = prepareChartData();if (!chartData) {// 数据准备失败,不更新图表return;}isChartUpdating = true;showLoading(true);// 延迟更新图表,确保加载动画至少显示 MIN_LOADING_TIME 毫秒const loadingDuration = Date.now() - loadingStartTime;const delay = Math.max(0, MIN_LOADING_TIME - loadingDuration);setTimeout(() => {// 更新图表if (chart) {chart.data.labels = chartData.labels;chart.data.datasets = chartData.datasets;// 更新Y轴最大值,留出一些空间chart.options.scales.y.suggestedMax = chartData.maxValue * 1.1;// 更新标题chart.options.plugins.title = {display: true,text: chartTitle.textContent,font: {size: 16,weight: 'bold'}};chart.update();}isChartUpdating = false;showLoading(false);}, delay);}// 防抖处理更新图表let updateChartTimeout;function updateChartDebounced() {clearTimeout(updateChartTimeout);updateChartTimeout = setTimeout(updateChart, 300);}// 显示/隐藏加载状态function showLoading(show) {if (show) {loadingStartTime = Date.now();chartLoading.classList.remove('hidden');} else {chartLoading.classList.add('hidden');}}// 显示通知function showNotification(title, message, type = 'info') {notificationTitle.textContent = title;notificationMessage.textContent = message;// 设置图标notificationIcon.innerHTML = '';let iconClass = 'fa-info-circle';switch (type) {case 'success':iconClass = 'fa-check-circle';notificationIcon.className = 'mr-3 text-success';break;case 'error':iconClass = 'fa-exclamation-circle';notificationIcon.className = 'mr-3 text-danger';break;case 'warning':iconClass = 'fa-exclamation-triangle';notificationIcon.className = 'mr-3 text-warning';break;default:iconClass = 'fa-info-circle';notificationIcon.className = 'mr-3 text-primary';}notificationIcon.innerHTML = `<i class="fa ${iconClass}"></i>`;// 显示通知notification.classList.remove('translate-y-20', 'opacity-0');// 自动关闭setTimeout(() => {closeNotificationHandler();}, 5000);}// 关闭通知function closeNotificationHandler() {notification.classList.add('translate-y-20', 'opacity-0');}// 初始化function init() {// 初始化图表initChart();// 文件上传事件fileUpload.addEventListener('change', function (e) {const file = e.target.files[0];if (file) {parseCSV(file);}});// 修改日期筛选按钮事件处理函数dateDayBtn.addEventListener('click', function () {// 只清除日期组按钮的active状态document.querySelectorAll('#date-day, #date-week, #date-month').forEach(btn => {btn.classList.remove('active');});this.classList.add('active');selectedDateType = 'day';updateChartDebounced();});dateWeekBtn.addEventListener('click', function () {// 只清除日期组按钮的active状态document.querySelectorAll('#date-day, #date-week, #date-month').forEach(btn => {btn.classList.remove('active');});this.classList.add('active');selectedDateType = 'week';updateChartDebounced();});dateMonthBtn.addEventListener('click', function () {// 只清除日期组按钮的active状态document.querySelectorAll('#date-day, #date-week, #date-month').forEach(btn => {btn.classList.remove('active');});this.classList.add('active');selectedDateType = 'month';updateChartDebounced();});// 修改业绩指标按钮事件处理函数metricDurationBtn.addEventListener('click', function () {// 只清除指标组按钮的active状态document.querySelectorAll('#metric-duration, #metric-call, #metric-connect, #metric-effective').forEach(btn => {btn.classList.remove('active');});this.classList.add('active');selectedMetric = 'duration';targetValue = medians[selectedMetric];targetValueInput.value = targetValue;currentMedianEl.textContent = targetValue;updateChartDebounced();});metricCallBtn.addEventListener('click', function () {// 只清除指标组按钮的active状态document.querySelectorAll('#metric-duration, #metric-call, #metric-connect, #metric-effective').forEach(btn => {btn.classList.remove('active');});this.classList.add('active');selectedMetric = 'call';targetValue = medians[selectedMetric];targetValueInput.value = targetValue;currentMedianEl.textContent = targetValue;updateChartDebounced();});metricConnectBtn.addEventListener('click', function () {// 只清除指标组按钮的active状态document.querySelectorAll('#metric-duration, #metric-call, #metric-connect, #metric-effective').forEach(btn => {btn.classList.remove('active');});this.classList.add('active');selectedMetric = 'connect';targetValue = medians[selectedMetric];targetValueInput.value = targetValue;currentMedianEl.textContent = targetValue;updateChartDebounced();});metricEffectiveBtn.addEventListener('click', function () {// 只清除指标组按钮的active状态document.querySelectorAll('#metric-duration, #metric-call, #metric-connect, #metric-effective').forEach(btn => {btn.classList.remove('active');});this.classList.add('active');selectedMetric = 'effective';targetValue = medians[selectedMetric];targetValueInput.value = targetValue;currentMedianEl.textContent = targetValue;updateChartDebounced();});// 机构级别选择事件orgLevelBtns.forEach(btn => {btn.addEventListener('click', function () {currentOrgLevel = parseInt(this.dataset.level);orgLevelBtns.forEach(b => b.classList.remove('active', 'bg-primary', 'text-white'));orgLevelBtns.forEach(b => b.classList.add('bg-gray-100', 'hover:bg-gray-200'));this.classList.add('active', 'bg-primary', 'text-white');this.classList.remove('bg-gray-100', 'hover:bg-gray-200');// 更新机构选择if (csvData && csvData.length > 0) {const orgs = [...new Set(csvData.map(row => row[`${['一', '二', '三', '四', '五'][currentOrgLevel - 1]}级机构`]))].sort();updateOrgSelection(orgs, currentOrgLevel);updateChartDebounced();}});});// 全选/取消全选坐席selectAllAgentsBtn.addEventListener('click', function () {document.querySelectorAll('.agent-checkbox').forEach(cb => {cb.checked = true;});updateChartDebounced();});deselectAllAgentsBtn.addEventListener('click', function () {document.querySelectorAll('.agent-checkbox').forEach(cb => {cb.checked = false;});updateChartDebounced();});// 全选/取消全选机构selectAllOrgsBtn.addEventListener('click', function () {document.querySelectorAll('.org-checkbox').forEach(cb => {cb.checked = true;});updateChartDebounced();});deselectAllOrgsBtn.addEventListener('click', function () {document.querySelectorAll('.org-checkbox').forEach(cb => {cb.checked = false;});updateChartDebounced();});// 设置目标值setTargetBtn.addEventListener('click', function () {const value = parseFloat(targetValueInput.value);if (!isNaN(value)) {targetValue = value;updateChartDebounced();showNotification('成功', '目标值已更新', 'success');} else {showNotification('错误', '请输入有效的数值', 'error');}});// 按Enter键设置目标值targetValueInput.addEventListener('keypress', function (e) {if (e.key === 'Enter') {setTargetBtn.click();}});// 下载图表downloadPngBtn.addEventListener('click', function () {if (chart) {const link = document.createElement('a');link.download = '坐席业绩分析.png';link.href = chart.toBase64Image('image/png', 1.0);link.click();}});downloadSvgBtn.addEventListener('click', function () {if (chart) {// 注意:Chart.js默认不支持直接导出SVG,但可以通过一些库实现showNotification('提示', 'SVG导出功能需要额外的库支持', 'info');}});// 关闭通知closeNotification.addEventListener('click', closeNotificationHandler);// 初始提示showNotification('提示', '请上传CSV格式的业绩数据文件', 'info');}// 页面加载完成后初始化document.addEventListener('DOMContentLoaded', init);</script>
</body></html>

效果:

​​​​​​​

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词