当前位置: 首页 > article >正文

坐席业绩数据分析

豆包提示词:

使用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>

效果:

​​​​​​​

相关文章:

坐席业绩数据分析

豆包提示词&#xff1a; 使用papaparse.js&#xff0c;chart.js&#xff0c;tailwindcss和font-awesome&#xff0c;生成一个可以交互的简洁且可以运行的HTML代码&#xff0c;不要输出无关内容。 具体要求如下&#xff1a; 1、按坐席姓名输出业绩折线图。 2、系统导航区域&…...

国产大模型 “五强争霸”,决战 AGI

中国 AI 大模型市场正经历一场史无前例的洗牌&#xff01;曾经 “百模混战” 的局面已落幕&#xff0c;字节、阿里、阶跃星辰、智谱和 DeepSeek 五大巨头强势崛起&#xff0c;形成 “基模五强” 新格局。这场竞争不仅是技术实力的较量&#xff0c;更是资源、人才与生态的全面博…...

怎样将MM模块常用报表设置为ALV默认格式(MB52、MB5B、ME2M、ME1M等)

【SAP系统研究】 对SAP系统中的报表,最方便的格式就是ALV了,可排序、可导出,非常友好。 但有些常见报表却不是默认ALV界面的,譬如MB52: 是不是有点别扭?但其实是可以后台配置进行调整的。 现将一些常用报表修改为默认ALV的方法进行总结,便于大家使用。 一、MB52、MB5…...

Spark 集群配置、启动与监控指南

Spark 集群的配置和启动需要几个关键步骤。以下是完整的操作流程&#xff0c;包含配置修改、集群启动、任务提交和常见错误排查方法。 1. 修改 Spark 配置文件 首先需要编辑 Spark 配置文件&#xff0c;设置集群参数&#xff1a; bash # 进入 Spark 配置目录 cd $SPARK_HOM…...

前端面试每日三题 - Day 34

这是我为准备前端/全栈开发工程师面试整理的第34天每日三题练习&#xff1a; ✅ 题目1&#xff1a;WebGPU图形编程实战指南 核心概念 // WebGPU初始化流程 const adapter await navigator.gpu.requestAdapter(); const device await adapter.requestDevice();// 渲染管线配…...

比亚迪固态电池突破:王传福的技术哲学与产业重构|创客匠人热点评述

合肥某车间凌晨两点依然灯火通明&#xff0c;工程师正在调试的银白色设备&#xff0c;即将颠覆整个电动车行业 —— 比亚迪全固态电池产线的曝光&#xff0c;标志着中国新能源汽车产业正式迈入 “技术定义市场” 的新纪元。 一、技术突破的底层逻辑 比亚迪全固态电池的核心竞…...

Arduino使用红外收发模块

目录 Arduino UNO连接红外发射模块&#xff1a; Arduino D1连接红外接收模块&#xff1a; 有一个Arduini UNO板子和一个Arduino D1板子&#xff0c;我想通过红外发射模块和红外接收模块让他们进行通信。 先看结果&#xff1a; Arduino UNO连接红外发射模块&#xff1a; 发射模…...

【强化学习】强化学习算法 - 马尔可夫决策过程

马尔可夫决策过程 (Markov Decision Process, MDP) 1. MDP 原理介绍 马尔可夫决策过程 (MDP) 是强化学习 (Reinforcement Learning, RL) 中用于对序贯决策 (Sequential Decision Making) 问题进行数学建模的标准框架。它描述了一个智能体 (Agent) 与环境 (Environment) 交互的…...

机器学习 Day16 聚类算法 ,数据降维

聚类算法 1.简介 1.1 聚类概念 无监督学习&#xff1a;聚类是一种无监督学习算法&#xff0c;不需要预先标记的训练数据 相似性分组&#xff1a;根据样本之间的相似性自动将样本归到不同类别 相似度度量&#xff1a;常用欧式距离作为相似度计算方法 1.2 聚类vs分类 聚类&…...

开源Heygem本地跑AI数字人视频教程

图文教程&#xff1a; 点击跳转 视频教程 资料包下载 点击下载&#xff1a;...

软件测试——面试八股文(入门篇)

今天给大家分享软件测试面试题入门篇&#xff0c;看看大家能答对几题 一、 请你说一说测试用例的边界 参考回答&#xff1a; 边界值分析法就是对输入或输出的边界值进行测试的一种黑盒测试方法。通常边界值分析法是作为对等价类划分法的补充&#xff0c;这种情况下&#xff…...

Yolov8的详解与实战-深度学习目标检测

Yolov8的详解与实战- 文章目录 摘要 模型详解 C2F模块 Loss head部分 模型实战 训练COCO数据集 下载数据集 COCO转yolo格式数据集&#xff08;适用V4&#xff0c;V5&#xff0c;V6&#xff0c;V7&#xff0c;V8&#xff09; 配置yolov8环境 训练 测试 训练自定义数据集 Labelme…...

Python(1) 做一个随机数的游戏

有关变量的&#xff0c;其实就是 可以直接打印对应变量。 并且最后倒数第二行就是可以让两个数进行交换。 Py快捷键“ALTP 就是显示上一句的代码。 —————————————————————————————— 字符串 用 双引号或者单引号 。 然后 保证成双出现即可 要是…...

【Bootstrap V4系列】学习入门教程之 组件-导航条(Navbar)

Bootstrap V4系列 学习入门教程之 组件-导航条&#xff08;Navbar&#xff09; 导航条&#xff08;Navbar&#xff09;一、How it works二、Supported content 支持的内容2.1 Brand 品牌2.2 Nav 导航2.3 Forms 表格 三、Color schemes 配色方案四、Containers 容器五、Placemen…...

[Java实战]Spring Security 添加验证码(二十三)

[Java实战]Spring Security 添加验证码&#xff08;二十三&#xff09; 在现代的 Web 应用中&#xff0c;验证码是防止恶意攻击&#xff08;如暴力破解、自动注册等&#xff09;的重要手段之一。Spring Security 是一个功能强大的安全框架&#xff0c;提供了用户认证、授权等功…...

万文c++继承

1、继承的概念与定义 1.1继承的概念 继承&#xff1a;是c代码复用的手段&#xff0c;允许在原有的基础上扩展&#xff0c;在此之前都是函数层次的复用&#xff0c;继承是类设计层次的复用。 下面有两个类Student和Teacher都有姓名/地址/电话/年龄等成员变量。都有identity身…...

HTTP GET报文解读

考虑当浏览器发送一个HTTP GET报文时&#xff0c;通过Wireshark 俘获到下列ASCII字符串&#xff1a; GET /cs453/index.html HTTP/1.1 Host: gaia.cs.umass.edu User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.2) Gecko/20040804 Netscape/7.2 (ax) Acc…...

Linux grep -r 查找依赖包是否存在依赖类 Class

方法一&#xff1a;通过 Linux &#xff0c;grep -r ClassPath 命令 grep -f org.apache.kafka.connect.source.SourceRecord在 jar 包所在 lib 或者 lib/plugins 目录下执行&#xff0c;grep -r&#xff0c; flink-sql-connector-sqlserver-cdc-3.3.0.jar 中此 kafka Source…...

41:像素坐标与实际坐标转化

采用上面的算子 将像素坐标点转换为实际坐标 image_points_to_world_plane(CamParam, Worldpose, Row, Column, m, X, Y) 第一个参数&#xff1a;标定得到的内参--根据标定助手得到的 第二个参数&#xff1a;标定得到的外参--根据标定助手得到的 第三个参数&#xff1a;计算…...

大某麦演唱会门票如何自动抢

引言 仅供学习研究&#xff0c;欢迎交流 抢票难&#xff0c;难于上青天&#xff01;无论是演唱会、话剧还是体育赛事&#xff0c;大麦网的票总是秒光。大麦网是国内知名的票务平台&#xff0c;热门演出票往往一票难求。手动抢票不仅耗时&#xff0c;还容易错过机会。作为一名…...

人工智能外呼系统:重构智能交互的全维度进化

在数字化浪潮席卷全球的今天&#xff0c;人工智能外呼系统正以其颠覆性的技术革新&#xff0c;重新定义企业与客户的沟通范式。这一融合语音识别、自然语言处理与机器学习的智能系统&#xff0c;不仅实现了从 “机械应答” 到 “智慧交互” 的跨越&#xff0c;更在金融、医疗、…...

LVS负载均衡群集和keepalive

目录 一. 集群概述 1.1 集群的定义 1.2 集群的分类 1. 高可用集群 HA 2. 高性能运输群集 HPC 3.负载均衡群集 LB 4. 分布式存储集群 二. LVS概述 2.1 LVS的定义 2.2 LVS的工作原理 2.3 LVS 的三种工作模式 2.4 LVS 三种工作模式的对比 2.5 LVS 调度算法 1. 静态…...

在 Excel 中有效筛选重复元素

如果是新版excel UNIQUE(FILTER(D2:D19, COUNTIF(D2:D19, D2:D19)>1)) 旧版 Excel&#xff08;如 2019/2016/2013&#xff09; 使用方法&#xff1a;在 E2 单元格输入此公式。按 Ctrl Shift Enter&#xff08;数组公式&#xff09;。向下拖动填充至空白行为止。 IFERROR…...

Apache Pulsar 消息、流、存储的融合

Apache Pulsar 消息、流、存储的融合 消息队列在大层面有两种不同类型的应用&#xff0c;一种是在线系统的message queue&#xff0c;一种是流计算&#xff0c;data pipeline的streaming高throughout&#xff0c;一致性较低&#xff0c;延迟较差的过程。 存算分离 扩容和缩容快…...

最优化方法Python计算:有约束优化应用——线性可分问题支持向量机

设问题的数据样本点 ( x i , y i ) (\boldsymbol{x}_i,y_i) (xi​,yi​)&#xff0c; x i ∈ R n \boldsymbol{x}_i\in\text{R}^n xi​∈Rn&#xff0c; y i 1 y_i\pm1 yi​1&#xff0c; i 1 , 2 , ⋯ , m i1,2,\cdots,m i1,2,⋯,m。由于标签数据 y i ∈ { − 1 , 1 } y_i\…...

Linux 离线安装 Docker 和 Docker Compose 最新版 的完整指南

一、准备工作 1. 下载安装包​&#xff08;需在有网络的机器操作&#xff09;&#xff1a; Docker 引擎&#xff1a;从官方仓库下载最新二进制包 wget https://download.docker.com/linux/static/stable/x86_64/docker-24.0.6.tgz​Docker Compose&#xff1a;下载最新二进制…...

SpringBoot学习(上) , SpringBoot项目的创建(IDEA2024版本)

目录 1. SpringBoot介绍 SpringBoot特点 2. SpringBoot入门 2.1 创建SpringBoot项目 Spring Initialize 第一步: 选择创建项目 第二步: 选择起步依赖 第三步: 查看启动类 2.2 springboot父项目 2.3 测试案例 2.3.1 数据库 2.3.2 生成代码 1. SpringBoot介绍 Spring B…...

数据结构(四)——栈的应用—数制转换

利用栈进行数制转换&#xff1a; 十进制转换八进制&#xff1a;先将十进制数除以八得到余数&#xff0c;余数入栈&#xff0c;然后将得到的商继续除以八&#xff0c;直到商为零 #include <stdio.h> #include <stdlib.h>#define MAXSIZE 100//数制转换//定义链表节…...

多视角系统,视角之间的切换,输入操作。无人机Pawn视角的实现

一.创建自己的PlayerController。它相当于是灵魂&#xff0c;穿梭在不同Pawn之间。也即是切换视角。不同输入的响应也写在这里。这样即使&#xff0c;都有鼠标操作&#xff0c;也能区分。避免了代码的重复耦合。也可以叫做视角系统。 class LZJGAMEMODE_API ALZJPlayerControl…...

【Redis 进阶】哨兵模式

思维导图&#xff1a; 一、哨兵模式概述 &#xff08;一&#xff09;传统主从复制模式的局限性 在传统的Redis主从复制架构中&#xff0c;若主节点发生故障&#xff0c;运维人员需手动执行故障转移操作&#xff0c;将一个从节点提升为新主节点&#xff0c;并逐一通知所有客户…...