坐席业绩数据分析

news2025/5/15 5:54:28

豆包提示词:

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

      <!-- 右侧图表区 -->
      <div
        class="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
        );
      });

      // 提取坐席名称,过滤掉空值和undefined
      const agents = [...new Set(validData.map(row => row['坐席姓名']))]
        .filter(agent => agent !== undefined && agent !== '')
        .sort();

      // 提取各级机构,过滤掉空值和undefined
      const 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>

效果:

​​​​​​​

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

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

相关文章

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

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

Arduino使用红外收发模块

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

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

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

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

今天给大家分享软件测试面试题入门篇&#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身…

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;还容易错过机会。作为一名…

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. 静态…

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\…

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…

【Redis 进阶】哨兵模式

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

CVE-2025-31258 macOS远程视图服务沙箱逃逸漏洞PoC已公开

苹果公司近日针对macOS系统中新披露的CVE-2025-31258漏洞发布补丁&#xff0c;该漏洞可能允许恶意应用程序突破沙箱限制&#xff0c;获取未授权的系统资源访问权限。在安全研究员Seo Hyun-gyu公开概念验证&#xff08;PoC&#xff09;利用代码后&#xff0c;该漏洞已在macOS Se…

武汉大学无人机视角下的多目标指代理解新基准!RefDrone:无人机场景指代表达理解数据集

作者&#xff1a;Zhichao Sun, Yepeng Liu, Huachao Zhu, Yuliang Gu, Yuda Zou, Zelong Liu, Gui-Song Xia, Bo Du, Yongchao Xu 单位&#xff1a;武汉大学计算机学院 论文标题&#xff1a;RefDrone: A Challenging Benchmark for Drone Scene Referring Expression Compreh…

【递归、搜索和回溯】二叉树中的深搜

个人主页 &#xff1a; zxctscl 专栏 【C】、 【C语言】、 【Linux】、 【数据结构】、 【算法】 如有转载请先通知 文章目录 前言1 2331. 计算布尔二叉树的值1.1 分析1.2 代码 2 129. 求根节点到叶节点数字之和2.1 分析2.2 代码 3 814. 二叉树剪枝3.1 分析3.2 代码 4 98. 验证…