cuipengcheng / 信息统计

// ==UserScript==
// @name         信息统计
// @namespace    http://tampermonkey.net/
// @version      2025-10-23
// @description  try to take over the world!
// @author       You
// @match        https://qlabel.tencent.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=tencent.com
// @updateURL https://openuserjs.org/meta/cuipengcheng/信息统计.meta.js
// @downloadURL https://openuserjs.org/install/cuipengcheng/信息统计.user.js
// @copyright 2025, cuipengcheng (https://openuserjs.org/users/cuipengcheng)
// @license MIT
// @grant        none
// ==/UserScript==

(function () {
  'use strict';

  // 解析大整数JSON
function parseJSONWithBigInt(text) {
  const bigIntFields = ['detail_id', 'task_id', 'check_id', 'id', 'deliver_id', 'dataset_item_id'];
  
  let processedText = text;
  bigIntFields.forEach(field => {
    const regex = new RegExp(`"${field}"\\s*:\\s*(\\d{15,})`, 'g');
    processedText = processedText.replace(regex, `"${field}": "$1"`);
  });
  
  return JSON.parse(processedText);
}

// 自定义 fetch 响应解析函数
async function parseResponse(response) {
  const text = await response.text();
  try {
    return parseJSONWithBigInt(text);
  } catch (error) {
    console.error('JSON解析失败,尝试使用标准解析:', error);
    return JSON.parse(text);
  }
}

// 全局状态管理
window.statsDownloadState = {
  isRunning: false,
  isPaused: false,
  currentTaskName: null,
  searchType: 'name', // 搜索类型:'name' 或 'id'
  selectedStatuses: ['验收通过'], // 选择的批次状态
  statsData: [], // 存储所有统计数据(旧格式,保留兼容)
  taskDataMap: {} // 按任务名称分组的数据:{ 任务名称: [数据行...] }
};

// 请求延迟配置(避免请求过于频繁)
window.requestDelay = {
  // 随机延迟函数
  random: (min, max) => {
    return Math.floor(Math.random() * (max - min + 1)) + min;
  },
  
  // 任务查询延迟(1-2秒)
  taskQuery: () => new Promise(resolve => 
    setTimeout(resolve, window.requestDelay.random(1000, 2000))
  ),
  
  // 批次列表请求延迟(800-1500ms)
  deliverList: () => new Promise(resolve => 
    setTimeout(resolve, window.requestDelay.random(800, 1500))
  ),
  
  // 题目详情请求延迟(500-1000ms)
  detailList: () => new Promise(resolve => 
    setTimeout(resolve, window.requestDelay.random(500, 1000))
  ),
  
  // 批次处理间延迟(1-2秒)
  betweenDelivers: () => new Promise(resolve => 
    setTimeout(resolve, window.requestDelay.random(1000, 2000))
  ),
  
  // 任务处理间延迟(2-4秒)
  betweenTasks: () => new Promise(resolve => 
    setTimeout(resolve, window.requestDelay.random(2000, 4000))
  )
};

// 查询任务信息(支持按任务名称或任务编号)
async function fetchTask(searchValue, searchType = 'name') {
  const params = {
    "page": {
      "start": 0,
      "size": 10,
      "return_total": 1
    },
    "task_type": 0,
    "need_stat_task": true,
    "need_stat_deliver": true
  };
  
  // 根据搜索类型添加不同的参数
  if (searchType === 'id') {
    params.task_id = searchValue;
  } else {
    params.task_name = searchValue;
  }
  
  const requestBody = {
    "jsonrpc": "2.0",
    "method": "listFormalManagerTasks",
    "id": Date.now(),
    "params": params
  };
  
  try {
    const response = await fetch("https://qlabel.tencent.com/api/workbench/listFormalManagerTasks", {
      "headers": {
        "accept": "application/json, text/plain, */*",
        "accept-language": "zh-CN,zh;q=0.9,en;q=0.8",
        "content-type": "application/json",
        "sec-ch-ua": "\"Not;A=Brand\";v=\"99\", \"Google Chrome\";v=\"139\", \"Chromium\";v=\"139\"",
        "sec-ch-ua-mobile": "?0",
        "sec-ch-ua-platform": "\"macOS\"",
        "sec-fetch-dest": "empty",
        "sec-fetch-mode": "cors",
        "sec-fetch-site": "same-origin",
        "x-requested-with": "XMLHttpRequest"
      },
      "referrer": "https://qlabel.tencent.com/workbench/formal-tasks",
      "body": JSON.stringify(requestBody),
      "method": "POST",
      "mode": "cors",
      "credentials": "include"
    });
    
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    
    const data = await parseResponse(response);
    return data;
  } catch (error) {
    console.error('获取任务信息失败:', error);
    throw error;
  }
}

// 获取批次列表
async function fetchDeliverTasks(taskId) {
  let allDelivers = [];
  let start = 0;
  const size = 10;
  
  try {
    while (true) {
      const requestBody = {
        "jsonrpc": "2.0",
        "method": "listDeliverTasks",
        "id": Date.now(),
        "params": {
          "task_id": taskId,
          "page": {
            "start": start,
            "size": size,
            "return_total": 1
          },
          "from": 0
        }
      };
      
      const response = await fetch("https://qlabel.tencent.com/api/workbench/listDeliverTasks", {
        "headers": {
          "accept": "application/json, text/plain, */*",
          "accept-language": "zh-CN,zh;q=0.9,en;q=0.8",
          "content-type": "application/json",
          "sec-ch-ua": "\"Not;A=Brand\";v=\"99\", \"Google Chrome\";v=\"139\", \"Chromium\";v=\"139\"",
          "sec-ch-ua-mobile": "?0",
          "sec-ch-ua-platform": "\"macOS\"",
          "sec-fetch-dest": "empty",
          "sec-fetch-mode": "cors",
          "sec-fetch-site": "same-origin",
          "x-requested-with": "XMLHttpRequest"
        },
        "referrer": `https://qlabel.tencent.com/workbench/formal-tasks/${taskId}?tab=delivers`,
        "body": JSON.stringify(requestBody),
        "method": "POST",
        "mode": "cors",
        "credentials": "include"
      });
      
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }
      
      const data = await parseResponse(response);
      
      if (!data.result || !data.result.data || data.result.data.length === 0) {
        break;
      }
      
      allDelivers.push(...data.result.data);
      
      if (allDelivers.length >= data.result.total) {
        break;
      }
      
      start += size;
      // 批次列表请求间延迟(800-1500ms)
      await window.requestDelay.deliverList();
    }
    
    // 根据选择的状态过滤批次
    const selectedStatuses = window.statsDownloadState.selectedStatuses || ['验收通过'];
    const filteredDelivers = allDelivers.filter(deliver => 
      selectedStatuses.includes(deliver.status_name)
    );
    console.log(`任务 ${taskId} 共 ${allDelivers.length} 个批次,筛选状态 [${selectedStatuses.join(', ')}] 共 ${filteredDelivers.length} 个`);
    
    return filteredDelivers;
  } catch (error) {
    console.error('获取批次列表失败:', error);
    throw error;
  }
}

// 获取批次的题目详情列表
async function fetchLabelDeliverDetails(taskId, deliverId, cycleStep = 0) {
  let allDetails = [];
  let start = 0;
  const size = 100; // 增大每次请求的数量
  
  try {
    while (true) {
      const requestBody = {
        "jsonrpc": "2.0",
        "method": "listLabelDeliverDetails",
        "id": Date.now(),
        "params": {
          "task_id": taskId,
          "deliver_id": deliverId,
          "page": {
            "start": start,
            "size": size,
            "return_total": 1
          },
          "cycle_step": cycleStep
        }
      };
      
      const response = await fetch("https://qlabel.tencent.com/api/workbench/listLabelDeliverDetails", {
        "headers": {
          "accept": "application/json, text/plain, */*",
          "accept-language": "zh-CN,zh;q=0.9,en;q=0.8",
          "content-type": "application/json",
          "sec-ch-ua": "\"Not;A=Brand\";v=\"99\", \"Google Chrome\";v=\"139\", \"Chromium\";v=\"139\"",
          "sec-ch-ua-mobile": "?0",
          "sec-ch-ua-platform": "\"macOS\"",
          "sec-fetch-dest": "empty",
          "sec-fetch-mode": "cors",
          "sec-fetch-site": "same-origin",
          "x-requested-with": "XMLHttpRequest"
        },
        "referrer": `https://qlabel.tencent.com/workbench/formal-tasks/${taskId}?tab=delivers&deliver_id=${deliverId}&type=detail`,
        "body": JSON.stringify(requestBody),
        "method": "POST",
        "mode": "cors",
        "credentials": "include"
      });
      
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }
      
      const data = await parseResponse(response);
      
      if (!data.result || !data.result.data || data.result.data.length === 0) {
        break;
      }
      
      allDetails.push(...data.result.data);
      
      if (allDetails.length >= data.result.total) {
        break;
      }
      
      start += size;
      // 题目详情请求间延迟(500-1000ms)
      await window.requestDelay.detailList();
    }
    
    return allDetails;
  } catch (error) {
    console.error('获取题目详情列表失败:', error);
    throw error;
  }
}

// 处理单个任务的统计
async function processTaskStats(searchValue, statusElement, searchType = 'name') {
  try {
    const searchLabel = searchType === 'id' ? '任务编号' : '任务名称';
    statusElement.textContent = `正在查询任务 (${searchLabel}): ${searchValue}...`;
    
    // 1. 查询任务信息(添加延迟)
    await window.requestDelay.taskQuery();
    const taskResponse = await fetchTask(searchValue, searchType);
    
    if (!taskResponse.result || !taskResponse.result.data || taskResponse.result.data.length === 0) {
      console.warn(`未找到任务 (${searchLabel}): ${searchValue}`);
      statusElement.textContent = `未找到任务 (${searchLabel}): ${searchValue}`;
      return;
    }
    
    const task = taskResponse.result.data[0];
    const taskId = task.task_id;
    const fullTaskName = task.task_name;
    
    console.log(`找到任务: ${fullTaskName} (ID: ${taskId})`);
    statusElement.textContent = `正在获取批次列表: ${fullTaskName}...`;
    
    // 2. 获取批次列表(根据选择的状态筛选)
    const delivers = await fetchDeliverTasks(taskId);
    
    if (delivers.length === 0) {
      const statusText = window.statsDownloadState.selectedStatuses.join('、');
      console.warn(`任务 ${fullTaskName} 没有符合状态 [${statusText}] 的批次`);
      statusElement.textContent = `任务 ${fullTaskName} 没有符合状态 [${statusText}] 的批次`;
      return;
    }
    
    const statusText = window.statsDownloadState.selectedStatuses.join('、');
    console.log(`任务 ${fullTaskName} 共 ${delivers.length} 个符合状态 [${statusText}] 的批次`);
    
    // 3. 遍历每个批次,获取题目详情
    for (let i = 0; i < delivers.length; i++) {
      if (!window.statsDownloadState.isRunning) {
        break;
      }
      
      // 检查暂停状态
      while (window.statsDownloadState.isPaused && window.statsDownloadState.isRunning) {
        statusElement.textContent = `已暂停 - ${fullTaskName} (${i + 1}/${delivers.length})`;
        await new Promise(resolve => setTimeout(resolve, 1000));
      }
      
      if (!window.statsDownloadState.isRunning) {
        break;
      }
      
      const deliver = delivers[i];
      const deliverId = deliver.deliver_id;
      const deliverName = deliver.deliver_name;
      // 获取批次的质检环节(从 deliver_task_stat.cycle_step_name)
      const cycleStepName = deliver.deliver_task_stat?.cycle_step_name || '';
      
      statusElement.textContent = `处理批次 ${i + 1}/${delivers.length}: ${deliverName}...`;
      console.log(`正在处理批次: ${deliverName} (${i + 1}/${delivers.length})`);
      
      // 获取题目详情列表
      const details = await fetchLabelDeliverDetails(taskId, deliverId);
      console.log(`批次 ${deliverName} 共 ${details.length} 个题目`);
      
      // 4. 统计数据
      for (const detail of details) {
        // 新格式:不包含任务名称列
        const statsRow = {
          批次名称: deliverName,
          任务明细ID: detail.detail_id ? detail.detail_id.toString() : '',
          题目ID: detail.dataset_item_id ? detail.dataset_item_id.toString() : '',
          题包Key: detail.pack_key || '',
          是否有效: detail.detail_is_valid === 1 ? '有效' : (detail.detail_is_valid || ''),
          标注员: detail.labeler_nick_name || '',
          质检环节: cycleStepName,  // 使用批次的 cycle_step_name
          质检员: detail.reviewer_name || '',
          质检结果: detail.check_status === 50 ? '质检通过' : (detail.check_status === 60 ? '修改通过' : (detail.check_status || ''))
        };
        
        // 按任务名称分组存储
        if (!window.statsDownloadState.taskDataMap[fullTaskName]) {
          window.statsDownloadState.taskDataMap[fullTaskName] = [];
        }
        window.statsDownloadState.taskDataMap[fullTaskName].push(statsRow);
        
        // 同时保留旧格式(包含任务名称列)用于兼容
        window.statsDownloadState.statsData.push({
          任务名称: fullTaskName,
          ...statsRow
        });
      }
      
      statusElement.textContent = `已处理 ${i + 1}/${delivers.length} 个批次,共 ${window.statsDownloadState.statsData.length} 条数据`;
      
      // 批次处理间延迟(1-2秒)
      if (i < delivers.length - 1) {
        await window.requestDelay.betweenDelivers();
      }
    }
    
    console.log(`任务 ${fullTaskName} 统计完成,共 ${window.statsDownloadState.statsData.length} 条数据`);
    
  } catch (error) {
    const searchLabel = searchType === 'id' ? '任务编号' : '任务名称';
    console.error(`处理任务失败 (${searchLabel}: ${searchValue}):`, error);
    statusElement.textContent = `处理任务失败 (${searchLabel}: ${searchValue}): ${error.message}`;
    throw error;
  }
}

// 开始统计
async function startStatsDownload(searchValues, searchType = 'name') {
  window.statsDownloadState.isRunning = true;
  window.statsDownloadState.isPaused = false;
  window.statsDownloadState.statsData = [];
  window.statsDownloadState.taskDataMap = {}; // 重置任务数据映射
  window.statsDownloadState.searchType = searchType;
  
  const statusElement = document.getElementById('stats-status');
  const startBtn = document.getElementById('stats-start-btn');
  const pauseBtn = document.getElementById('stats-pause-btn');
  const stopBtn = document.getElementById('stats-stop-btn');
  const exportBtn = document.getElementById('stats-export-btn');
  
  startBtn.disabled = true;
  exportBtn.disabled = true;
  
  const searchLabel = searchType === 'id' ? '任务编号' : '任务名称';
  
  try {
    for (let i = 0; i < searchValues.length; i++) {
      if (!window.statsDownloadState.isRunning) {
        break;
      }
      
      const searchValue = searchValues[i].trim();
      if (!searchValue) continue;
      
      console.log(`开始处理 (${searchLabel}) ${i + 1}/${searchValues.length}: ${searchValue}`);
      window.statsDownloadState.currentTaskName = searchValue;
      
      await processTaskStats(searchValue, statusElement, searchType);
      
      // 任务处理间延迟(2-4秒)
      if (i < searchValues.length - 1 && window.statsDownloadState.isRunning) {
        await window.requestDelay.betweenTasks();
      }
    }
    
    if (window.statsDownloadState.isRunning) {
      statusElement.textContent = `✅ 统计完成!共 ${window.statsDownloadState.statsData.length} 条数据`;
      console.log(`所有任务统计完成,共 ${window.statsDownloadState.statsData.length} 条数据`);
      exportBtn.disabled = false;
    } else {
      statusElement.textContent = `已停止 - 共 ${window.statsDownloadState.statsData.length} 条数据`;
    }
    
  } catch (error) {
    console.error('统计过程中发生错误:', error);
    statusElement.textContent = `❌ 统计失败: ${error.message}`;
  } finally {
    window.statsDownloadState.isRunning = false;
    window.statsDownloadState.isPaused = false;
    startBtn.disabled = false;
    pauseBtn.textContent = '暂停';
    if (window.statsDownloadState.statsData.length > 0) {
      exportBtn.disabled = false;
    }
  }
}

// 导出为Excel
async function exportToExcel() {
  if (Object.keys(window.statsDownloadState.taskDataMap).length === 0) {
    alert('没有数据可导出');
    return;
  }
  
  try {
    // 动态加载 SheetJS 库
    if (!window.XLSX) {
      const statusElement = document.getElementById('stats-status');
      statusElement.textContent = '正在加载 Excel 库...';
      
      await loadSheetJSLibrary();
      
      statusElement.textContent = 'Excel 库加载完成,正在生成文件...';
    }
    
    // 创建工作簿
    const wb = window.XLSX.utils.book_new();
    
    // 设置列宽(不包含任务名称列)
    const colWidths = [
      { wch: 40 }, // 批次名称
      { wch: 20 }, // 任务明细ID
      { wch: 20 }, // 题目ID
      { wch: 35 }, // 题包Key
      { wch: 10 }, // 是否有效
      { wch: 15 }, // 标注员
      { wch: 20 }, // 质检环节
      { wch: 15 }, // 质检员
      { wch: 12 }  // 质检结果
    ];
    
    // 为每个任务创建一个sheet
    let sheetIndex = 1;
    for (const [taskName, taskData] of Object.entries(window.statsDownloadState.taskDataMap)) {
      if (taskData.length === 0) continue;
      
      // 将数据转换为工作表
      const ws = window.XLSX.utils.json_to_sheet(taskData);
      ws['!cols'] = colWidths;
      
      // 生成sheet名称(Excel sheet名称限制:最长31个字符,不能包含特殊字符)
      let sheetName = taskName;
      // 替换Excel不支持的字符
      sheetName = sheetName.replace(/[\[\]:*?\/\\]/g, '_');
      // 限制长度
      if (sheetName.length > 31) {
        sheetName = sheetName.substring(0, 28) + '...';
      }
      // 如果sheet名称重复,添加序号
      let finalSheetName = sheetName;
      let suffix = 1;
      while (wb.SheetNames.includes(finalSheetName)) {
        finalSheetName = sheetName.substring(0, 28) + `_${suffix}`;
        suffix++;
      }
      
      // 添加工作表到工作簿
      window.XLSX.utils.book_append_sheet(wb, ws, finalSheetName);
      console.log(`添加Sheet: ${finalSheetName}, 数据行数: ${taskData.length}`);
      sheetIndex++;
    }
    
    // 生成文件名
    const timestamp = new Date().toISOString().slice(0, 19).replace(/:/g, '-');
    const fileName = `标注统计数据_${timestamp}.xlsx`;
    
    // 导出文件
    window.XLSX.writeFile(wb, fileName);
    
    const statusElement = document.getElementById('stats-status');
    const totalSheets = Object.keys(window.statsDownloadState.taskDataMap).length;
    const totalRows = Object.values(window.statsDownloadState.taskDataMap).reduce((sum, data) => sum + data.length, 0);
    statusElement.textContent = `✅ 导出成功!${totalSheets}个任务,共${totalRows}条数据`;
    console.log(`Excel文件已导出: ${fileName}, ${totalSheets}个Sheet, 共${totalRows}条数据`);
    
  } catch (error) {
    console.error('导出Excel失败:', error);
    alert('导出Excel失败: ' + error.message);
  }
}

// 加载 SheetJS 库
function loadSheetJSLibrary() {
  return new Promise((resolve, reject) => {
    if (window.XLSX) {
      resolve();
      return;
    }
    
    const script = document.createElement('script');
    script.src = 'https://cdn.sheetjs.com/xlsx-0.20.0/package/dist/xlsx.full.min.js';
    script.onload = () => {
      console.log('SheetJS 库加载成功');
      resolve();
    };
    script.onerror = () => {
      reject(new Error('SheetJS 库加载失败'));
    };
    document.head.appendChild(script);
  });
}

// 创建控制面板
function createControlPanel() {
  // 如果已存在,先移除
  const existing = document.getElementById('stats-control-panel');
  if (existing) {
    existing.remove();
  }
  
  // 创建控制球
  const controlBall = document.createElement('div');
  controlBall.id = 'stats-control-ball';
  controlBall.textContent = '📊';
  controlBall.style.cssText = `
    position: fixed;
    bottom: 80px;
    right: 30px;
    width: 60px;
    height: 60px;
    border-radius: 50%;
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    color: white;
    font-size: 28px;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
    z-index: 10000;
    transition: all 0.3s ease;
  `;
  
  controlBall.addEventListener('mouseenter', () => {
    controlBall.style.transform = 'scale(1.1)';
  });
  
  controlBall.addEventListener('mouseleave', () => {
    controlBall.style.transform = 'scale(1)';
  });
  
  // 创建功能窗口
  const panel = document.createElement('div');
  panel.id = 'stats-control-panel';
  panel.style.cssText = `
    position: fixed;
    bottom: 150px;
    right: 30px;
    width: 400px;
    background: white;
    border: 1px solid #ddd;
    border-radius: 12px;
    box-shadow: 0 6px 20px rgba(0, 0, 0, 0.2);
    z-index: 10001;
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    font-size: 14px;
    display: none;
  `;
  
  panel.innerHTML = `
    <div style="padding: 16px; border-bottom: 1px solid #eee;">
      <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
        <h3 style="margin: 0; font-size: 18px; color: #333;">📊 统计信息下载</h3>
        <button id="stats-close-panel" style="
          background: none;
          border: none;
          font-size: 20px;
          cursor: pointer;
          color: #999;
          padding: 0;
          width: 24px;
          height: 24px;
          line-height: 24px;
        ">×</button>
      </div>
      
      <div style="margin-bottom: 12px;">
        <label style="display: block; margin-bottom: 6px; color: #666; font-size: 13px;">
          搜索方式:
        </label>
        <div style="display: flex; gap: 16px; padding: 8px 0;">
          <label style="display: flex; align-items: center; cursor: pointer; font-size: 13px;">
            <input type="radio" name="search-type" value="name" checked style="margin-right: 4px;">
            <span>任务名称</span>
          </label>
          <label style="display: flex; align-items: center; cursor: pointer; font-size: 13px;">
            <input type="radio" name="search-type" value="id" style="margin-right: 4px;">
            <span>任务编号</span>
          </label>
        </div>
      </div>
      
      <div style="margin-bottom: 12px;">
        <label style="display: block; margin-bottom: 6px; color: #666; font-size: 13px;">
          批次状态筛选:
        </label>
        <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px; padding: 8px; background: #f9fafb; border-radius: 6px;">
          <label style="display: flex; align-items: center; cursor: pointer; font-size: 12px;">
            <input type="checkbox" name="status-filter" value="验收通过" checked style="margin-right: 4px;">
            <span>验收通过</span>
          </label>
          <label style="display: flex; align-items: center; cursor: pointer; font-size: 12px;">
            <input type="checkbox" name="status-filter" value="待验收" style="margin-right: 4px;">
            <span>待验收</span>
          </label>
          <label style="display: flex; align-items: center; cursor: pointer; font-size: 12px;">
            <input type="checkbox" name="status-filter" value="待交付" style="margin-right: 4px;">
            <span>待交付</span>
          </label>
          <label style="display: flex; align-items: center; cursor: pointer; font-size: 12px;">
            <input type="checkbox" name="status-filter" value="验收驳回" style="margin-right: 4px;">
            <span>验收驳回</span>
          </label>
          <label style="display: flex; align-items: center; cursor: pointer; font-size: 12px;">
            <input type="checkbox" name="status-filter" value="待审核" style="margin-right: 4px;">
            <span>待审核</span>
          </label>
        </div>
      </div>
      
      <div style="margin-bottom: 12px;">
        <label style="display: block; margin-bottom: 6px; color: #666; font-size: 13px;">
          <span id="stats-input-label">任务名称</span>(多个任务可用逗号、分号、空格或换行分隔):
        </label>
        <textarea id="stats-task-names" style="
          width: 100%;
          height: 80px;
          padding: 8px;
          border: 1px solid #ddd;
          border-radius: 6px;
          font-size: 13px;
          resize: vertical;
          box-sizing: border-box;
        " placeholder="支持多种分隔:任务1,任务2 或 任务1;任务2 或换行输入"></textarea>
      </div>
      
      <div style="margin-bottom: 12px;">
        <div id="stats-status" style="
          padding: 8px 12px;
          background: #f5f5f5;
          border-radius: 6px;
          font-size: 12px;
          color: #666;
          min-height: 20px;
        ">待开始...</div>
      </div>
      
      <div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 8px; margin-bottom: 12px;">
        <button id="stats-start-btn" style="
          padding: 10px;
          background: #667eea;
          color: white;
          border: none;
          border-radius: 6px;
          cursor: pointer;
          font-size: 13px;
          font-weight: 500;
        ">开始统计</button>
        
        <button id="stats-pause-btn" style="
          padding: 10px;
          background: #f59e0b;
          color: white;
          border: none;
          border-radius: 6px;
          cursor: pointer;
          font-size: 13px;
          font-weight: 500;
        ">暂停</button>
        
        <button id="stats-stop-btn" style="
          padding: 10px;
          background: #ef4444;
          color: white;
          border: none;
          border-radius: 6px;
          cursor: pointer;
          font-size: 13px;
          font-weight: 500;
        ">停止</button>
      </div>
      
      <button id="stats-export-btn" disabled style="
        width: 100%;
        padding: 12px;
        background: #10b981;
        color: white;
        border: none;
        border-radius: 6px;
        cursor: pointer;
        font-size: 14px;
        font-weight: 500;
      ">导出Excel</button>
      
      <div style="margin-top: 12px; padding: 8px; background: #fef3c7; border-radius: 6px; font-size: 11px; color: #92400e; line-height: 1.4;">
        💡 提示:支持按任务名称或任务编号搜索,可多选批次状态(验收通过、待验收、待交付、验收驳回、待审核)。统计数据包括任务明细ID、题目ID、题包Key、是否有效、标注员、质检环节、质检员、质检结果等信息。
      </div>
    </div>
  `;
  
  document.body.appendChild(controlBall);
  document.body.appendChild(panel);
  
  // 绑定事件
  controlBall.addEventListener('click', () => {
    panel.style.display = panel.style.display === 'none' ? 'block' : 'none';
  });
  
  document.getElementById('stats-close-panel').addEventListener('click', () => {
    panel.style.display = 'none';
  });
  
  // 搜索类型切换事件
  const searchTypeRadios = document.querySelectorAll('input[name="search-type"]');
  searchTypeRadios.forEach(radio => {
    radio.addEventListener('change', (e) => {
      const searchType = e.target.value;
      const inputLabel = document.getElementById('stats-input-label');
      const textarea = document.getElementById('stats-task-names');
      
      if (searchType === 'id') {
        inputLabel.textContent = '任务编号';
        textarea.placeholder = '支持多种分隔:1234567890,0987654321 或换行输入';
      } else {
        inputLabel.textContent = '任务名称';
        textarea.placeholder = '支持多种分隔:任务1,任务2 或 任务1;任务2 或换行输入';
      }
    });
  });
  
  document.getElementById('stats-start-btn').addEventListener('click', () => {
    const searchValuesText = document.getElementById('stats-task-names').value.trim();
    const searchType = document.querySelector('input[name="search-type"]:checked').value;
    const searchLabel = searchType === 'id' ? '任务编号' : '任务名称';
    
    if (!searchValuesText) {
      alert(`请输入${searchLabel}`);
      return;
    }
    
    // 获取选中的状态
    const selectedStatuses = Array.from(document.querySelectorAll('input[name="status-filter"]:checked'))
      .map(checkbox => checkbox.value);
    
    if (selectedStatuses.length === 0) {
      alert('请至少选择一个批次状态');
      return;
    }
    
    // 更新全局状态
    window.statsDownloadState.selectedStatuses = selectedStatuses;
    
    // 支持多种分隔符:中英文逗号、中英文分号、空格、换行等
    const searchValues = searchValuesText
      .split(/[,,;;\s]+/)  // 匹配逗号、分号、空格、换行等
      .map(value => value.trim())
      .filter(value => value);
    
    if (searchValues.length === 0) {
      alert(`请输入有效的${searchLabel}`);
      return;
    }
    
    console.log(`开始统计 ${searchValues.length} 个任务 (${searchLabel}):`, searchValues);
    console.log(`筛选状态: [${selectedStatuses.join(', ')}]`);
    startStatsDownload(searchValues, searchType);
  });
  
  document.getElementById('stats-pause-btn').addEventListener('click', () => {
    const btn = document.getElementById('stats-pause-btn');
    window.statsDownloadState.isPaused = !window.statsDownloadState.isPaused;
    btn.textContent = window.statsDownloadState.isPaused ? '恢复' : '暂停';
    btn.style.background = window.statsDownloadState.isPaused ? '#10b981' : '#f59e0b';
  });
  
  document.getElementById('stats-stop-btn').addEventListener('click', () => {
    window.statsDownloadState.isRunning = false;
    window.statsDownloadState.isPaused = false;
    const pauseBtn = document.getElementById('stats-pause-btn');
    pauseBtn.textContent = '暂停';
    pauseBtn.style.background = '#f59e0b';
    console.log('统计已停止');
  });
  
  document.getElementById('stats-export-btn').addEventListener('click', () => {
    exportToExcel();
  });
  
  // 禁用导出按钮样式
  const exportBtn = document.getElementById('stats-export-btn');
  const updateExportBtnStyle = () => {
    if (exportBtn.disabled) {
      exportBtn.style.background = '#d1d5db';
      exportBtn.style.cursor = 'not-allowed';
    } else {
      exportBtn.style.background = '#10b981';
      exportBtn.style.cursor = 'pointer';
    }
  };
  
  const observer = new MutationObserver(updateExportBtnStyle);
  observer.observe(exportBtn, { attributes: true, attributeFilter: ['disabled'] });
  
  console.log('✅ 统计信息下载控制面板已创建');
}

// 初始化
function initStatsDownload() {
  console.log('🚀 初始化统计信息下载功能...');
  
  // 等待页面完全加载
  setTimeout(() => {
    createControlPanel();
    console.log('✅ 统计信息下载功能初始化完成');
  }, 2000);
}

// 页面加载完成后初始化
if (document.readyState === 'loading') {
  document.addEventListener('DOMContentLoaded', initStatsDownload);
} else {
  initStatsDownload();
}
})();