智能订单CP审核

根据多重条件自动审核大额订单

// ==UserScript==
// @name         智能订单CP审核
// @namespace    http://tampermonkey.net/
// @version      1.0.1
// @description  根据多重条件自动审核大额订单
// @author       Cisco
// @match        https://7777m.topcms.org/*
// @match        https://111bet22.topcms.org/*
// @match        https://888bet.topcms.org/*
// @match        https://hkgame.topcms.org/*
// @match        https://666bet.topcms.org/*
// @match        https://111bet.topcms.org/*
// @match        https://k9.topcms.org/*
// @match        https://34jogo.topcms.org/*
// @icon         https://7777m.topcms.org/favicon.ico
// @license      MIT
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addStyle
// ==/UserScript==
 
(function () {
    "use strict";
  
    const NS = "cpAuto"; // 唯一前缀,多个脚本用不同的值即可
    const gmGet = (k, def) => GM_getValue(`${NS}_${k}`, def);
    const gmSet = (k, v) => GM_setValue(`${NS}_${k}`, v);

    // 配置参数
    const rawConfigCp = {
        currentPage: 1, // 当前页码
        totalPages: 1, // 总页数
        pageSize: 200, // 每页订单数量
        isLastPage: false, // 是否是最后一页
        minSameIPUsers: 2, // 最大相同IP用户数
        minTotalRecharge: 10, // 总充值金额 >=
        maxTotalRecharge: 100, // 总充值金额 <=
        rechargeToProfitRatio: 10, // 总充值金额 > 游戏盈亏指定倍数
        depositWithdrawDiffRatio: 50,        // 设置的充提差比例
        actualDepositWithdrawDiffRatio: 0,  // 实际的充提差比例
        password: "", // 充值密码
        processedOrders: gmGet("processedOrders", {}), // 已处理订单记录
        payOutOrders: gmGet("payOutOrders", {}), // 已出款订单记录
        cancelledOrders: gmGet("cancelledOrders", {}), // 已取消出款订单记录
        currentOrderId: null, // 当前处理的订单ID
        isProcessing: false, // 是否正在处理中
        isReturning: false, // 是否正在返回订单页面
        panelCollapsed: false, // 面板是否收起
        completedOneRound: false, // 是否完成了一轮处理
        processingOrderId: null, // 正在处理的订单ID
        totalBetAmount: 0, // 当前订单的总投注额
        profitAmount: 0, // 当前订单的游戏盈亏
        startedProcessing: false,   // 新的一轮处理
    };
  
    // 使用 Proxy 监听 config 变化
    const config = new Proxy(rawConfigCp, {
      set(target, prop, value) {
        target[prop] = value;
        if (prop === "currentOrderId") {
          const el = document.getElementById(`${NS}_currentOrderId`);
          if (el) el.textContent = value || "";
        }
        return true;
      }
    });
  
    // 控制面板样式
    GM_addStyle(`
      .${NS}-monitor-panel {
        position: fixed;
        top: 20px;
        right: 140px;
        z-index: 9999;
        background: white;
        padding: 15px;
        border: 1px solid #ddd;
        border-radius: 5px;
        box-shadow: 0 2px 10px rgba(0,0,0,0.1);
        font-family: Arial, sans-serif;
        width: 400px;
        max-height: 90vh;
        overflow-y: auto;
        transition: all 0.3s ease;
      }
      .${NS}-monitor-panel.${NS}_collapsed {
        width: 40px;
        height: 40px;
        overflow: hidden;
        padding: 5px;
      }
      .${NS}-toggle-panel {
        position: absolute;
        top: 5px;
        right: 5px;
        width: 30px;
        height: 30px;
        border: none;
        background: #f0f0f0;
        border-radius: 50%;
        cursor: pointer;
        display: flex;
        align-items: center;
        justify-content: center;
        font-size: 16px;
        z-index: 10000;
      }
      .${NS}-toggle-panel:hover {
        background: #e0e0e0;
      }
      .${NS}_collapsed .${NS}-panel-content {
        display: none;
      }
      .monitor-header {
        margin: 0 0 15px 0;
        color: #409EFF;
        font-size: 16px;
        font-weight: bold;
        border-bottom: 1px solid #eee;
        padding-bottom: 10px;
      }
        
        .${NS}-monitor-panel .rule-block {
            border: 1px solid #ddd;
            border-radius: 5px;
            padding: 10px;
            margin-bottom: 12px;
            background: #fafafa;
        }

        .${NS}-monitor-panel .rule-title {
            font-weight: bold;
            font-size: 14px;
            margin-bottom: 8px;
            color: #409EFF;
            border-bottom: 1px solid #eee;
            padding-bottom: 4px;
        }
      .config-group {
        margin-bottom: 12px;
        position: relative;
      }

      .${NS}-config-label {
        display: block;
        margin-bottom: 5px;
        color: #666;
        font-size: 13px;
        font-weight: bold;
      }
      .${NS}-config-label.required:after {
        content: " *";
        color: #F56C6C;
      }

      .${NS}-config-input {
        width: 100%;
        padding: 8px;
        border: 1px solid #ddd;
        border-radius: 4px;
        box-sizing: border-box;
      }
      .${NS}-config-input.error {
        border-color: #F56C6C;
      }
        .unit-wrapper {
            display: flex;
            align-items: center;
            flex-wrap: nowrap;
        }
        .unit-text {
            margin-left: 4px;
            color: #999;
            font-size: 12px;
            white-space: nowrap;
        }

      .error-message {
        color: #F56C6C;
        font-size: 12px;
        margin-top: 3px;
        display: none;
      }
      .${NS}-monitor-button {
        width: 100%;
        padding: 10px;
        background: #409EFF;
        color: white;
        border: none;
        border-radius: 4px;
        font-weight: bold;
        cursor: pointer;
        transition: background 0.3s;
        margin-bottom: 10px;
      }
      .${NS}-monitor-button:disabled {
        background: #C0C4CC;
        cursor: not-allowed;
      }
      .${NS}-monitor-button.stop {
        background: #F56C6C;
      }
      .monitor-stats {
        margin-top: 15px;
        font-size: 12px;
        color: #666;
        border-top: 1px solid #eee;
        padding-top: 10px;
      }
      .monitor-stat-row {
        display: flex;
        justify-content: space-between;
        margin-bottom: 5px;
      }
      .monitor-progress-container {
        margin: 10px 0;
        height: 10px;
        background: #f0f0f0;
        border-radius: 5px;
        overflow: hidden;
      }
      .monitor-progress-bar {
        height: 100%;
        background: linear-gradient(to right, #67C23A, #409EFF);
        transition: width 0.3s;
      }
      #${NS}_statusText {
        font-weight: bold;
        color: #409EFF;
      }
      #${NS}_processedCount {
        font-weight: bold;
        color: #67C23A;
      }
      .button-container {
        display: flex;
        flex-direction: column;
      }
      .${NS}-monitor-button.hidden {
        display: none;
      }
    `);
  
    // ==================== 工具函数 ====================
  
    /**
     * 延迟执行
     * @param {number} ms 毫秒数
     * @returns {Promise<void>}
     */
    function delay(ms) {
      return new Promise(resolve => setTimeout(resolve, ms));
    }
  
    /**
     * 等待指定元素出现(可选检测其文本内容),超时返回 null 而不是抛异常
     * @param {string} selector - CSS 选择器
     * @param {number} [timeout=10000] - 等待的最长时间(毫秒)
     * @param {ParentNode} [parent=document] - 查找的父容器(默认 document)
     * @param {AbortSignal|null} [signal=null] - 可选的 AbortSignal,用于提前中断等待
     * @param {Object} [options={}] - 额外选项
     * @param {boolean} [options.requireText=false] - 是否要求元素有非空文本才算找到
     * @returns {Promise<Element|null>} - 找到则返回元素,没找到或超时返回 null
     */
    function waitForElement(
        selector,
        timeout = 10000,
        parent = document,
        signal = null,
        options = {}
    ) {
        // 如果 parent 为 null,则兜底使用 document
        if (!parent) parent = document;

        // 解析选项
        const { requireText = false } = options;

        return new Promise(resolve => {
            // 如果调用时就已经中断,直接返回 null
            if (signal?.aborted) return resolve(null);

            // 计算超时时间点
            const endTime = Date.now() + timeout;

            // 定时检测函数
            const check = () => {
                // 如果外部中断,直接返回 null
                if (signal?.aborted) return resolve(null);

                // 查找元素
                const element = parent.querySelector(selector);

                if (element) {
                    // 如果要求有文本内容,但当前元素文本为空,继续等待
                    if (requireText && !element.textContent.trim()) {
                        if (Date.now() < endTime) {
                            setTimeout(check, 200); // 每 200ms 再检测一次
                            return;
                        }
                        // 超时依然没内容
                        return resolve(null);
                    }

                    // 找到符合条件的元素
                    return resolve(element);
                }

                // 元素还没出现,检查是否超时
                if (Date.now() >= endTime) {
                    return resolve(null); // 超时返回 null
                }

                // 继续等待
                setTimeout(check, 200);
            };

            check(); // 启动首次检测
        });
    }

  
    /**
     * 等待页面跳转完成
     * @param {string} targetHash 目标页面hash
     * @param {number} timeout 超时时间(ms)
     * @returns {Promise<void>}
     */
    function waitForPageChange(targetHash, timeout = 10000) {
      return new Promise((resolve, reject) => {
        if (window.location.hash.includes(targetHash)) {
          return resolve();
        }
  
        const timer = setTimeout(() => {
          window.removeEventListener('hashchange', handler);
          reject(new Error(`Timeout waiting for page change to ${targetHash}`));
        }, timeout);
  
        const handler = () => {
          if (window.location.hash.includes(targetHash)) {
            clearTimeout(timer);
            window.removeEventListener('hashchange', handler);
            resolve();
          }
        };
  
        window.addEventListener('hashchange', handler);
      });
    }
  
    /**
     * 带重试的操作
     * @param {Function} operation 操作函数
     * @param {number} retries 重试次数
     * @param {number} delayMs 重试间隔(ms)
     * @param {AbortSignal} signal 取消信号
     * @returns {Promise<any>}
     */
    async function retryOperation(operation, retries = 3, delayMs = 1000, signal = null) {
      let lastError;
      for (let i = 0; i < retries; i++) {
        if (signal?.aborted) {
          throw new DOMException('Aborted', 'AbortError');
        }
        try {
          return await operation();
        } catch (err) {
          lastError = err;
          if (i < retries - 1) await delay(delayMs);
        }
      }
      throw lastError;
    }
  
    /**
     * 检查是否在订单页面
     * @returns {boolean}
     */
    function isOrderPage() {
      return window.location.hash.includes("#/order/unread-withdraw");
    }
  
    /**
     * 检查是否在代理页面
     * @returns {boolean}
     */
    function isAgentPage() {
      return window.location.hash.includes("#/agent/agent-list");
    }
  
    /**
     * 检查是否在提现记录页面
     * @returns {boolean}
     */
    function isWithdrawPage() {
      return window.location.hash.includes("#/order/order-withdraw");
    }
  
    /**
     * 检查是否在充值记录页面
     * @returns {boolean}
     */
    function isTopupPage() {
      return window.location.hash.includes("#/order/order-topup");
    }
  
    /**
     * 更新状态显示
     * @param {string} text 状态文本
     */
    function updateStatus(text) {
      const statusEl = document.getElementById(`${NS}_statusText`);
      if (statusEl) {
        statusEl.textContent = text;
      }
    }
  
    /**
     * 更新按钮显示状态
     */
    function updateButtonVisibility() {
      const startBtn = document.getElementById(`${NS}_startBtn`);
      const stopBtn = document.getElementById(`${NS}_stopBtn`);
  
      if (config.isProcessing) {
        startBtn?.classList.add("hidden");
        stopBtn?.classList.remove("hidden");
      } else {
        startBtn?.classList.remove("hidden");
        stopBtn?.classList.add("hidden");
      }
    }
  
    /**
     * 验证输入参数
     * @returns {boolean}
     */
    function validateInputs() {
        let isValid = true;
        const inputs = [
            { id: "minSameIPUsers", name: "大于相同IP用户数" },
            { id: "minTotalRecharge", name: "总充值金额 >=" },
            { id: "maxTotalRecharge", name: "总充值金额 <=" },
            { id: "rechargeToProfitRatio", name: "充值金额大于游戏盈亏倍数" },
            { id: "depositWithdrawDiffRatio", name: "充提差比例" },
            { id: "withdrawPassword", name: "充值密码" }
        ];

        inputs.forEach((input) => {
            const element = document.getElementById(`${NS}_${input.id}`);

            const errorElement =
            element.parentElement.querySelector(".error-message") ||
            createErrorElement(element.parentElement);

            // 密码单独验证
            if (input.id === `withdrawPassword`) {
                if (!element.value.trim()) {
                    element.classList.add("error");
                    errorElement.textContent = `${input.name}不能为空`;
                    errorElement.style.display = "block";
                    isValid = false;
                } else {
                    element.classList.remove("error");
                    errorElement.style.display = "none";
                }
                return; // 跳过后续数值验证
            }

            // 数值字段验证
            if (!element.value.trim()) {
                element.classList.add("error");
                errorElement.textContent = `${input.name}不能为空`;
                errorElement.style.display = "block";
                isValid = false;
            } else if (isNaN(element.value)) {
                element.classList.add("error");
                errorElement.textContent = `${input.name}必须是数字`;
                errorElement.style.display = "block";
                isValid = false;
            } else {
                element.classList.remove("error");
                errorElement.style.display = "none";
            }
        });

        return isValid;
        }
  
    /**
     * 创建错误提示元素
     * @param {HTMLElement} parent 父元素
     * @returns {HTMLElement}
     */
    function createErrorElement(parent) {
      const errorElement = document.createElement("div");
      errorElement.className = "error-message";
      parent.appendChild(errorElement);
      return errorElement;
    }
  
    /**
     * 更新配置
     */
    function updateConfig() {
        // 最大相同IP用户数
        config.minSameIPUsers = parseInt(
            document.getElementById(`${NS}_minSameIPUsers`).value
        );

        // 总充值金额 >=
        config.minTotalRecharge = parseFloat(
            document.getElementById(`${NS}_minTotalRecharge`).value
        ) || 0;

        // 总充值金额 <=
        config.maxTotalRecharge = parseFloat(
            document.getElementById(`${NS}_maxTotalRecharge`).value
        ) || 0;

        // 总充值金额大于游戏盈亏指定倍数
        config.rechargeToProfitRatio = parseFloat(
            document.getElementById(`${NS}_rechargeToProfitRatio`).value
        ) || 0;

        // 充提差比例
        config.depositWithdrawDiffRatio = parseFloat(
            document.getElementById(`${NS}_depositWithdrawDiffRatio`).value
        ) || 0;

        // 充值密码
        config.password = document.getElementById(`${NS}_withdrawPassword`).value || "";
    }

  
    /**
     * 标记订单为已处理
     * @param {string} orderId 订单ID
     * @param {string} reason 原因
     */
    function markOrderAsProcessed(orderId, reason = "") {
      if (!orderId) return;
  
      config.processedOrders[orderId] = {
        time: new Date().toISOString(),
        reason: reason || "已处理",
      };
      gmSet("processedOrders", config.processedOrders);
      document.getElementById(`${NS}_processedCount`).textContent = Object.keys(
        config.processedOrders
      ).length;
      console.log(`订单 ${orderId} 已标记为已处理`, reason ? `原因: ${reason}` : "");
      processor.closeAllDialogs(processor.abortController.signal);
    }
  
    /**
     * 从行中获取订单ID
     * @param {HTMLElement} row 表格行
     * @returns {string}
     */
    function getOrderIdFromRow(row) {
      // 1. 先找到包含"订单号"的表头单元格
      const headerCells = document.querySelectorAll(".el-table__header .cell");
      const orderHeader = Array.from(headerCells).find(
        (cell) => cell.textContent.trim() === "订单号"
      );
  
      if (orderHeader) {
        // 2. 获取列索引
        const colIndex = orderHeader.closest("th").getAttribute("aria-colindex");
        if (colIndex) {
          // 3. 使用列索引找到对应数据单元格
          const orderIdEl = row.querySelector(
            `td:nth-child(${colIndex}) .cell > span`
          );
          if (orderIdEl) {
            return orderIdEl.textContent.replace(/[\u200B-\u200D\uFEFF]/g, "").trim();
          }
        }
      }
  
      // 备用方案:通过内容特征匹配
      const cells = Array.from(row.querySelectorAll(".cell > span"));
      const idCell = cells.find((cell) => {
        const text = cell.textContent.trim();
        return /^tx_\d+_\d{8}\w+$/.test(text); // 匹配订单号格式
      });
  
      return idCell ? idCell.textContent.replace(/[\u200B-\u200D\uFEFF]/g, "").trim() : "";
    }
  
    /**
     * 更新分页信息
     */
    function updatePaginationInfo() {
      const pagination = document.querySelector(".el-pagination");
      if (!pagination) {
        console.error("无法找到分页元素");
        // 默认值
        config.currentPage = 1;
        config.totalPages = 1;
        config.isLastPage = true;
        return;
      }
  
      // 当前页
      const activePage = pagination.querySelector(".el-pager .number.active");
      config.currentPage = activePage ? parseInt(activePage.textContent) : 1;
  
      // 每页数量
      const pageSizeSelect = pagination.querySelector(
        ".el-select-dropdown__item.selected"
      );
      config.pageSize = pageSizeSelect
        ? parseInt(pageSizeSelect.textContent)
        : null;
  
      // 总条数
      let totalItems = null;
      const totalTextEl = pagination.querySelector(".el-pagination__total");
      if (totalTextEl) {
        const totalMatch = totalTextEl.textContent.match(/共\s*(\d+)\s*条/);
        if (totalMatch) {
          totalItems = parseInt(totalMatch[1]);
        }
      }
  
      // 计算总页数
      if (totalItems !== null && config.pageSize) {
        config.totalPages = Math.ceil(totalItems / config.pageSize);
      } else {
        config.totalPages = config.currentPage || 1;
      }
  
      // 是否最后一页
      const nextBtn = pagination.querySelector(".btn-next");
      if (config.totalPages === 1) {
        config.isLastPage = true;
      } else if (nextBtn) {
        config.isLastPage = nextBtn.disabled;
      } else {
        config.isLastPage = config.currentPage >= config.totalPages;
      }
  
      console.log(
        `当前页码: ${config.currentPage}/${config.totalPages}`,
        `每页: ${config.pageSize || "-"}条`,
        `是否末页: ${config.isLastPage}`
      );
    }
  
    /**
     * 清空所有搜索输入框
     */
    function clearAllSearchInputs() {
      // 清空订单号输入框
      const orderInput = document.querySelector(
        '.filter-container input[placeholder="请输入订单号"]'
      );
      if (orderInput) {
        orderInput.value = "";
        orderInput.dispatchEvent(new Event("input", { bubbles: true }));
      }
  
      // 清空用户ID输入框
      const userIdInput = document.querySelector(
        '.filter-container input[placeholder="请输入用户ID"]'
      );
      if (userIdInput) {
        userIdInput.value = "";
        userIdInput.dispatchEvent(new Event("input", { bubbles: true }));
      }
  
      // 重置第三方选择器
      const thirdPartySelect = document.querySelector(
        '.el-select input[placeholder="全部"]'
      );
      if (thirdPartySelect && thirdPartySelect.value !== "全部") {
        thirdPartySelect.value = "全部";
        thirdPartySelect.dispatchEvent(new Event("input", { bubbles: true }));
      }
  
      // 清空日期范围选择器
      const dateInputs = document.querySelectorAll(".el-range-input");
      dateInputs.forEach((input) => {
        if (input.value) {
          input.value = "";
          input.dispatchEvent(new Event("input", { bubbles: true }));
        }
      });
    }
  
    /**
     * 添加控制面板
     */
    function addControlPanel() {
      const panel = document.createElement("div");
      panel.className = `${NS}-monitor-panel`;
      panel.id = `${NS}_autoWithdrawPanel`;
  
      // 添加收起/展开按钮
      const toggleBtn = document.createElement("button");
      toggleBtn.className = `${NS}-toggle-panel`;
      toggleBtn.innerHTML = "×";
      toggleBtn.title = "收起/展开控制面板";
      toggleBtn.addEventListener("click", togglePanel);
  
      // 面板内容
      const panelContent = document.createElement("div");
      panelContent.className = `${NS}-panel-content`;
      panelContent.innerHTML = `
        <h3 class="monitor-header">智能CP出款系统</h3>
        
        <!-- 规则一模块 -->
        <div class="rule-block">
            <div class="rule-title">规则一</div>
            <div class="config-group">
                <label class="${NS}-config-label">提现金额区间</label>
                <div class="unit-wrapper" style="gap: 5px;">
                    <input type="number" class="${NS}-config-input" id="${NS}_minTotalRecharge" value="${config.minTotalRecharge}" step="0.01" style="flex: 1;">
                    <span style="color: #999;">—</span>
                    <input type="number" class="${NS}-config-input" id="${NS}_maxTotalRecharge" value="${config.maxTotalRecharge}" step="0.01" style="flex: 1;">
                    <span class="unit-text">元</span>
                </div>
            </div>

            <div class="config-group">
                <label class="${NS}-config-label">提现金额 ≥ 充值金额倍数</label>
                <div class="unit-wrapper">
                    <input type="number" class="${NS}-config-input" id="${NS}_rechargeToProfitRatio" value="${config.rechargeToProfitRatio}" step="0.1">
                    <span class="unit-text">倍</span>
                </div>
            </div>
        </div>

        <!-- 规则二模块 -->
        <div class="rule-block">
            <div class="rule-title">规则二</div>
            <div class="config-group">
                <label class="${NS}-config-label required">大于相同IP用户数</label>
                <input type="number" class="${NS}-config-input" id="${NS}_minSameIPUsers" value="${config.minSameIPUsers}" required>
                <div class="error-message"></div>
            </div>
        </div>

        <div class="config-group">
            <label class="${NS}-config-label">充提差比例</label>
            <div class="unit-wrapper">
                <input type="number" class="${NS}-config-input" id="${NS}_depositWithdrawDiffRatio" value="${config.depositWithdrawDiffRatio}" step="1">
                <span class="unit-text">%</span>
            </div>
        </div>

        <div class="config-group">
          <label class="${NS}-config-label required">充值密码</label>
          <input type="text" class="${NS}-config-input" id="${NS}_withdrawPassword" value="${config.password}" required>
          <div class="error-message"></div>
        </div>
        <div class="button-container">
          <button id="${NS}_startBtn" class="${NS}-monitor-button ${config.isProcessing ? "hidden" : ""}">开始处理</button>
          <button id="${NS}_stopBtn" class="${NS}-monitor-button stop ${!config.isProcessing ? "hidden" : ""}">停止处理</button>
        </div>
        <div class="button-container">
          <button id="${NS}_clearCacheBtn" class="${NS}-monitor-button">清理缓存</button>
        </div>
  
        <div class="monitor-stats">
          <div class="monitor-stat-row">
            <span>状态:</span>
            <span id="${NS}_statusText">待命</span>

          </div>
          <div class="monitor-stat-row">
            <span>当前处理:</span>
            <span id="${NS}_currentOrderId">${config.currentOrderId || ""}</span>
          </div>
          <div class="monitor-stat-row">
            <span>已处理:</span>
            <span id="${NS}_processedCount">${
              Object.keys(config.processedOrders).length
            }</span> 单
          </div>
          <div class="monitor-stat-row">
            <span>已出款:</span>
            <span id="${NS}_payOutOrders">${Object.keys(config.payOutOrders).join(", ")}</span>
          </div>
            <div class="monitor-stat-row">
                <span>已取消出款:</span>
                <span id="${NS}_cancelledOrders">${Object.keys(config.cancelledOrders).join(", ")}</span>
            </div>
        </div>
      `;
  
      panel.appendChild(toggleBtn);
      panel.appendChild(panelContent);
      document.body.appendChild(panel);
  
      // 恢复面板状态
      config.panelCollapsed = gmGet("panelCollapsed", false);
      if (config.panelCollapsed) {
        panel.classList.add(`${NS}_collapsed`);
        toggleBtn.innerHTML = "≡";
      }
  
      // 事件监听
      document.getElementById(`${NS}_startBtn`).addEventListener("click", function () {
        if (validateInputs()) {
          updateConfig();
          processor.start();
        }
      });
  
      document.getElementById(`${NS}_stopBtn`).addEventListener("click", function () {
        processor.stop();
      });
  
      // 为所有输入框添加实时验证
      document.querySelectorAll(`.${NS}-config-input`).forEach((input) => {
        input.addEventListener("input", function () {
          validateInputs();
        });
      });
  
      // 绑定清理缓存事件
      document.getElementById(`${NS}_clearCacheBtn`).addEventListener("click", function () {
        if (confirm("确定要清理缓存数据吗?这会清空已处理订单记录。")) {
          config.processedOrders = {};
          config.payOutOrders = {};
          config.cancelledOrders = {};
          config.currentOrderId = null;
  
          gmSet("processedOrders", {});
          gmSet("payOutOrders", {});
          gmSet("cancelledOrders", {});
          gmSet("currentOrderId", null);
  
          document.getElementById(`${NS}_processedCount`).textContent = "0";
          document.getElementById(`${NS}_payOutOrders`).textContent = "";
          document.getElementById(`${NS}_cancelledOrders`).textContent = "";
          document.getElementById(`${NS}_currentOrderId`).textContent = "";
  
          updateStatus("缓存已清理");
        }
      });
    }
  
    /**
     * 收起/展开面板
     */
    function togglePanel() {
      const panel = document.getElementById(`${NS}_autoWithdrawPanel`);
      config.panelCollapsed = !panel.classList.contains(`${NS}_collapsed`);
  
      if (config.panelCollapsed) {
        panel.classList.add(`${NS}_collapsed`);
        this.innerHTML = "≡";
      } else {
        panel.classList.remove(`${NS}_collapsed`);
        this.innerHTML = "×";
      }
  
      gmSet("panelCollapsed", config.panelCollapsed);
    }
  
    // ==================== 主流程控制器 ====================
  
    class OrderProcessor {
      constructor(config) {
        this.config = config;
        this.abortController = new AbortController();
        this.currentTask = null;
      }
  
      /**
       * 开始处理流程
       */
      async start() {
        if (this.currentTask) return;
        this.config.startedProcessing = true; // 开始新的轮次
        this.config.isProcessing = true;
        updateButtonVisibility();
        updateStatus("开始处理订单...");
  
        this.currentTask = this.runProcessingLoop()
          .catch(err => {
            console.error('Processing error:', err);
            updateStatus(`处理出错: ${err.message}`);
          })
          .finally(() => {
            this.currentTask = null;
            updateButtonVisibility();
          });
      }
  
      /**
       * 停止处理流程
       */
      stop() {
        if (!this.config.isProcessing) return;
        this.abortController.abort(); // 中止当前任务
        this.config.isProcessing = false;
        this.closeAllDialogs(this.abortController.signal); // 先执行需要旧 signal 的方法
        this.abortController = new AbortController(); // 再重置
        updateStatus("正在停止...");
    }
  
      /**
       * 主处理循环
       */
      async runProcessingLoop() {
        const { signal } = this.abortController;
  
        while (this.config.isProcessing && !signal.aborted) {
          try {
            if (isOrderPage()) {
                console.log('当前在订单页面');
                await this.processOrderPage(signal);
            } else {
                console.log('不在目标页面,跳转到订单页面');
                await this.navigateToOrderPage();
            }
          } catch (err) {
            if (err.name === 'AbortError') break;
            console.error('Loop error:', err);
            await delay(3000);
          }
        }
      }
  
      /**
       * 处理订单页面
       * @param {AbortSignal} signal 取消信号
       */
      async processOrderPage(signal) {
        if (!isOrderPage()) {
            // 如果当前不是订单页面就跳转过来
            processor.navigateToOrderPage();
            return;
        }
        if (signal.aborted) return;
        this.closeAllDialogs(signal);
        const emptyText = document.querySelector(".el-table__empty-text");
        if (emptyText?.textContent.includes("暂无数据")) {
            console.log('暂无数据');
            // 无数据隔5秒重开
            await delay(5000);
            await this.startNewRound(signal);
            return;
        }
        // 1. 确保页面完全加载
        const el = await waitForElement('.el-table--scrollable-x .el-table__body .el-table__row .cell > span', 10000, null, signal, { requireText: true });
        if (!el) {
            console.warn('没找到元素或超时');
            await this.processOrderPage(signal);
            return;
        }

        // 4. 设置订单状态为"已支付"
        // await this.selectDropdownOption("请选择", "200条/页", signal);
        // 2. 更新分页信息
        updatePaginationInfo();
  
        // 3. 如果不是最后一页,先跳转到最后一页
        // if (!this.config.isLastPage && this.config.currentPage < this.config.totalPages) {
        //   await this.goToLastPage(signal);
        //   return;
        // }
  
        // 5. 点击查询按钮
        // const queryBtn = await this.findQueryButton(signal);
        // queryBtn.click();
        
        // // 6. 等待查询结果
        // await delay(1500);

        // 4. 获取当前页订单行
        const rows = await this.getOrderRows(signal);
        if (rows.length === 0) {
          if (this.config.currentPage > 1) {
            await this.goToPrevPage(signal);
          } else {
            await this.startNewRound(signal);
          }
          return;
        }
        updateStatus(`开始处理订单列表`);
  
        // 5. 从最后一行开始处理
        for (let i = rows.length - 1; i >= 0; i--) {
          if (signal.aborted || !this.config.isProcessing) break;
  
          try {
            await this.processSingleOrder(rows[i], signal);
          } catch (err) {
            console.error(`处理订单失败:`, err);
            const orderId = getOrderIdFromRow(rows[i]);
            markOrderAsProcessed(orderId, `处理失败: ${err.message}`);
          }
        }
  
        // 6. 当前页处理完成后翻页或开始新一轮
        if (this.config.currentPage > 1) {
            await this.goToPrevPage(signal);
        } else {
            this.config.startedProcessing = false; // 重置标记
            await this.startNewRound(signal);
        }
      }

      /**
       * 加载已处理订单
       */
      async loadProcessedOrders() {
        const stored = await gmGet("processedOrders");
        if (stored && typeof stored === "object" && !Array.isArray(stored)) {
          // 如果是 Proxy 或特殊对象,用 Object.assign 复制到空对象
          config.processedOrders = Object.assign(Object.create(null), stored);
        } else {
          config.processedOrders = Object.create(null);
        }
        console.log("加载后的 processedOrders:", config.processedOrders);
      }

      /**
       * 处理单个订单
       * @param {HTMLElement} row 订单行
       * @param {AbortSignal} signal 取消信号
       */
      async processSingleOrder(row, signal) {
        const orderId = getOrderIdFromRow(row);
        console.log("当前订单ID", orderId);
        if (config.processedOrders[orderId]) {
            updateStatus(`订单 ${orderId} 已处理,跳过`);
            return;
        }

        this.config.currentOrderId = orderId;
        updateStatus(`处理订单: ${orderId}`);

        // 获取订单提现金额
        const amount = await this.getWithdrawAmount(row, signal);
        console.log("订单号:",orderId,"提现金额:",amount);
        // 2. 检查用户详情
        const userDetails = await this.getUserDetails(row, signal);
        if (!userDetails) {
          return markOrderAsProcessed(orderId, "获取用户详情失败");
        }

        const { count: successCount, failedRules } = this.checkFinancialConditions(userDetails, amount);
        // 3. 检查财务条件
        if (successCount == 1) {
            const reasonSoFar = failedRules.join(";");
            console.log("订单 ${orderId} 满足1个条件,计算充提差校验是否需要coinpay出款")
            // 4. 获取当天提现差
            const withdrawAmount = await this.getTotalWithdrawAmountRecords(userDetails.userId, signal);
            if (withdrawAmount < 1) {
                this.addCancelledOrder(orderId, `${reasonSoFar}`);
                return markOrderAsProcessed(orderId, "提现记录不满足");
            }
            // 5. 获取当天充值总额
            const rechargeAmount = await this.getTodayRecharge(userDetails.userId, signal);
            if (rechargeAmount < 1) {
                this.addCancelledOrder(orderId, `${reasonSoFar}`);
                return markOrderAsProcessed(orderId, "充值记录不满足");
            }
            // 计算实际充提差比率
            const actualDiffRatio = withdrawAmount / rechargeAmount;
            console.log("当天提现差:", withdrawAmount);
            console.log("当天充值总额:", rechargeAmount);
            console.log("计算实际充提差比率:", actualDiffRatio);

            if (actualDiffRatio > this.config.actualDepositWithdrawDiffRatio) {
                this.addCancelledOrder(orderId, `${reasonSoFar}`);
                return markOrderAsProcessed(orderId, "充提差比率超过设定,要求:"+this.config.actualDepositWithdrawDiffRatio+"%,实际:"+actualDiffRatio+"%");
            }
            // 6. 返回订单页面
            await this.goBackToOrderPage(signal);
            console.log("返回订单页面");

            // 重新查找订单行(不依赖旧的row)
            const freshRow = await this.findOrderRowById(orderId, signal);
            if (!freshRow) {
                return markOrderAsProcessed(orderId, "返回后找不到订单");
            }
            // 7. 批准订单
            await this.approveOrder(freshRow, signal);
    
            // 8. 标记为已处理
            markOrderAsProcessed(orderId, "已批准");
            this.config.payOutOrders[orderId] = true;
            gmSet("payOutOrders", this.config.payOutOrders);
            document.getElementById(`${NS}_payOutOrders`).textContent = Object.keys(
            this.config.payOutOrders
            ).join(", ");
        } else if (successCount == 2) {
            console.log("订单 ${orderId} 满足2个条件,直接coinpay出款")
            
            // 6. 返回订单页面
            await this.goBackToOrderPage(signal);
            console.log("返回订单页面");

            // 重新查找订单行(不依赖旧的row)
            const freshRow = await this.findOrderRowById(orderId, signal);
            if (!freshRow) {
                return markOrderAsProcessed(orderId, "返回后找不到订单");
            }
            // 7. 批准订单
            await this.approveOrder(freshRow, signal);
    
            // 8. 标记为已处理
            markOrderAsProcessed(orderId, "已批准");
            this.config.payOutOrders[orderId] = true;
            gmSet("payOutOrders", this.config.payOutOrders);
            document.getElementById(`${NS}_payOutOrders`).textContent = Object.keys(
            this.config.payOutOrders
            ).join(", ");
        } else {
            updateStatus(`处理订单: ${orderId} 两个规则都不满足`);
            // 8. 标记为已处理
            markOrderAsProcessed(orderId, "两个规则都不满足");
        }
        return;
      }
        async addCancelledOrder(orderId, reason) {
        this.config.cancelledOrders[orderId] = reason || "未提供理由";
        gmSet("cancelledOrders", this.config.cancelledOrders);

        // 显示成 “订单号(理由)” 的形式
        document.getElementById(`${NS}_cancelledOrders`).textContent =
            Object.entries(this.config.cancelledOrders)
            .map(([id, r]) => `${id}(${r})`)
            .join(", ");
        }

        /**
         * 返回到订单页面
         * @param {AbortSignal} signal 取消信号
         */
        async goBackToOrderPage(signal) {
            updateStatus("返回订单页面...");
            // 跳转到订单页面
            await this.navigateTo("#/order/unread-withdraw", signal);
            // 等待查询结果
            await delay(1500);
        }

        /**
         * 在订单列表页中根据 orderId 重新查找订单行
         * @param {string} orderId 订单ID
         * @param {AbortSignal} signal 取消信号
         * @returns {Promise<HTMLElement|null>} 返回找到的行,如果找不到则返回 null
         */
        async findOrderRowById(orderId, signal) {
            updateStatus(`正在查找订单 ${orderId}...`);
            
            const maxAttempts = 5;
            let attempts = 0;
            
            while (attempts < maxAttempts && !signal.aborted) {
                // 方法1:使用列位置(第7列的单元格)
                const cells = Array.from(document.querySelectorAll('.el-table__body tr td:nth-child(7) .cell'));
                const targetCell = cells.find(cell => cell.textContent.trim() === orderId);
                
                if (targetCell) {
                    const row = targetCell.closest('tr');
                    if (row) return row;
                }
                
                // 方法2:如果方法1失效,改用遍历所有行,检查第7列的文本
                const rows = document.querySelectorAll('.el-table__body tr');
                for (const row of rows) {
                    const orderCell = row.querySelector('td:nth-child(7) .cell');
                    if (orderCell?.textContent.trim() === orderId) {
                        return row;
                    }
                }
                
                // 如果没有找到,等待后重试
                attempts++;
                await delay(1000);
            }
            
            console.error(`找不到订单 ${orderId} 的行`);
            return null;
        }
        
        /**
         * 调整提现记录查询日期范围(GMT+0当天)
         */
        async adjustWithdrawDateRange(signal) {
            const dateInputs = await waitForElement(
            ".el-range-input",
            5000,
            null,
            signal
            );
            
            if (!dateInputs || dateInputs.length < 2) {
            throw new Error("找不到日期范围输入框");
            }
        
            const now = new Date();
            const startDate = new Date(now);
            startDate.setHours(0, 0, 0, 0);
            startDate.setHours(startDate.getHours() - 8); // 转为GMT+0
            
            const endDate = new Date(now);
            endDate.setHours(23, 59, 59, 999);
            endDate.setHours(endDate.getHours() - 8); // 转为GMT+0
        
            const formatDate = (date) => {
            const pad = (num) => num.toString().padStart(2, '0');
            return `${date.getFullYear()}-${pad(date.getMonth()+1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
            };
        
            const setter = Object.getOwnPropertyDescriptor(
            HTMLInputElement.prototype,
            "value"
            ).set;
            
            setter.call(dateInputs[0], formatDate(startDate));
            dateInputs[0].dispatchEvent(new Event("input", { bubbles: true }));
            
            setter.call(dateInputs[1], formatDate(endDate));
            dateInputs[1].dispatchEvent(new Event("input", { bubbles: true }));
        }

        /**
         * 获取今日充值总额
         * @param {AbortSignal} signal 取消信号
         */
        async getTodayRecharge(signal) {
            this.closeAllDialogs(signal);
            if (signal.aborted) return;
            // 跳转到充值记录页面
            await this.navigateTo("#/order/order-topup", signal);
            updateStatus(`检查用户 ${this.config.currentUserId} 的充值记录...`);
            // 等待网页加载
            await delay(1500);
            
            try {
                // 1. 确保页面完全加载
                await waitForElement('.el-table__body', 10000, null, signal);
                
                // 2. 设置用户ID搜索条件
                const idInput = await waitForElement(
                    'input[placeholder="请输入用户ID"].el-input__inner',
                    5000,
                    null,
                    signal
                );
                
                // 清空并设置用户ID
                await this.setInputValue(idInput, this.config.currentUserId, signal);
                
                // 3. 调整日期范围(最近7天)
                //await this.adjustTopupDateRange(signal);
                
                // 4. 设置订单状态为"已支付"
                await this.selectDropdownOption("全部状态", "已支付", signal);
                
                // 5. 点击查询按钮
                const queryBtn = await this.findQueryButton(signal);
                queryBtn.click();
                
                // 6. 等待查询结果
                await delay(2000);
                
                // 7. 获取最新充值记录
                const totalTopupAmount = await this.getTodayRechargeTotalAmount(signal);
                if (totalTopupAmount < 1) {
                    return 0;
                }
            } catch (err) {
                console.error('处理充值页面出错:', err);
                markOrderAsProcessed(this.config.currentOrderId, `充值记录检查失败: ${err.message}`);
                await this.navigateToOrderPage();
                throw err;
            }
        }
        
        /**
         * 调整充值记录查询日期范围(最近7天)
         */
        async adjustTopupDateRange(signal) {
            const dateInputs = await waitForElement(
            ".el-range-input",
            5000,
            null,
            signal
            );
            
            if (!dateInputs || dateInputs.length < 2) {
            throw new Error("找不到日期范围输入框");
            }
        
            const now = new Date();
            const endDate = new Date(now);
            endDate.setHours(23, 59, 59, 999);
            endDate.setHours(endDate.getHours() - 8); // 转为GMT+0
            
            const startDate = new Date(endDate);
            startDate.setDate(startDate.getDate() - 6); // 7天范围
            startDate.setHours(0, 0, 0, 0);
        
            const formatDate = (date) => {
            const pad = (num) => num.toString().padStart(2, '0');
            return `${date.getFullYear()}-${pad(date.getMonth()+1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
            };
        
            const setter = Object.getOwnPropertyDescriptor(
            HTMLInputElement.prototype,
            "value"
            ).set;
            
            setter.call(dateInputs[0], formatDate(startDate));
            dateInputs[0].dispatchEvent(new Event("input", { bubbles: true }));
            
            setter.call(dateInputs[1], formatDate(endDate));
            dateInputs[1].dispatchEvent(new Event("input", { bubbles: true }));
        }
        
        /**
         * 获取当天已支付的充值总金额
         * @param {AbortSignal} signal 取消信号
         * @returns {Promise<number>} 当天充值总金额
         */
        async getTodayRechargeTotalAmount(signal) {
            const rows = document.querySelectorAll(".el-table__body .el-table__row");
            if (rows.length <= 1) return 0; // 没有数据

            // 计算当天 GMT+0 日期
            const now = new Date();
            now.setHours(now.getHours() - 8);
            const todayYear = now.getUTCFullYear();
            const todayMonth = now.getUTCMonth() + 1;   // getUTCMonth()返回的是0-11,要得到正常的月份(1-12),需要加 1
            const todayDate = now.getUTCDate();

            let totalAmount = 0;

            for (let i = 1; i < rows.length; i++) {
                const cells = rows[i].querySelectorAll("td");

                // 假设时间在第 6 列(索引 5),金额在第 9 列(索引 8),状态在第 12 列(索引 11)
                const timeCell = cells[5]?.querySelector(".cell");
                const amountCell = cells[8]?.querySelector(".cell");
                const statusCell = cells[11]?.querySelector(".cell");

                if (!timeCell || !amountCell || !statusCell) continue;

                const timeText = timeCell.textContent.trim();
                const amountText = amountCell.textContent.trim();
                const statusText = statusCell.textContent.trim();

                if (!timeText || !amountText) continue;
                if (statusText !== "已支付") continue; // 只统计已支付充值

                const recordDate = new Date(timeText);
                if (
                    recordDate.getUTCFullYear() === todayYear &&
                    recordDate.getUTCMonth() + 1 === todayMonth &&
                    recordDate.getUTCDate() === todayDate
                ) {
                    const amount = parseFloat(amountText.replace(/[^\d.-]/g, ""));
                    if (!isNaN(amount)) {
                        totalAmount += amount;
                    }
                }
            }

            return totalAmount;
        }

 
      /**
       * 获取提现金额
       * @param {HTMLElement} row 订单行
       * @param {AbortSignal} signal 取消信号
       * @returns {Promise<number>}
       */
      async getWithdrawAmount(row, signal) {
        // 第8列通常是金额列
        console.log("当前订单row", row);
        const amountNode = row.querySelector("td:nth-child(8) .cell > span");
        // 数值解析函数
        const parseValue = (text) => {
          // 移除所有非数字字符(保留小数点和负号)
          const numStr = text.replace(/[^\d.-]/g, "");
          return parseFloat(numStr) || 0;
        };
        if (!amountNode) {
          // 如果第8列找不到,尝试其他可能的位置
          const amountNodes = row.querySelectorAll("td .cell > span");
          let foundNode = null;
  
          for (const node of amountNodes) {
            // 检查是否是金额(包含数字和小数点)
            if (/^\d+\.\d{2}$/.test(node.textContent.trim())) {
              foundNode = node;
              break;
            }
          }
  
          if (!foundNode) {
            throw new Error("提取提现金额失败");
          }
          return parseValue(foundNode.textContent);
        }
        return parseValue(amountNode.textContent);
      }
  
      /**
         * 查找查询按钮(带重试机制)
         */
        async findQueryButton(signal) {
            return await retryOperation(
            async () => {
                const buttons = document.querySelectorAll('.filter-container button.el-button');
                const btn = Array.from(buttons).find(b => 
                b.textContent.includes('查询') || 
                (b.querySelector('span')?.textContent.includes('查询'))
                );
                
                if (!btn) throw new Error('找不到查询按钮');
                if (btn.disabled) throw new Error('查询按钮不可用');
                return btn;
            },
            3, // 重试3次
            1000, // 间隔1秒
            signal
            );
        }

        /**
         * 查找 操作选中订单 (带重试机制)
         */
        async findOperateOrderButton(signal) {
            return await retryOperation(
                async () => {
                    const buttons = document.querySelectorAll('.filter-container button.el-button');
                    const btn = Array.from(buttons).find(b => 
                        b.textContent.includes('操作选中订单') || 
                        (b.querySelector('span')?.textContent.includes('操作选中订单'))
                    );
                    
                    if (!btn) throw new Error('找不到操作选中订单按钮');
                    if (btn.disabled) throw new Error('操作选中订单按钮不可用');
                    return btn;
                },
                3, // 重试3次
                1000, // 间隔1秒
                signal
            );
        }

      /**
       * 获取用户详情
       * @param {HTMLElement} row 订单行
       * @param {AbortSignal} signal 取消信号
       * @returns {Promise<Object>}
       */
      async getUserDetails(row, signal) {
        // 使用更稳定的选择器定位用户名元素
        let usernameEl = row.querySelector("td:nth-child(2) .el-tooltip"); // 用户名通常在第二列
        if (!usernameEl) {
          // 如果第二列找不到,尝试其他方式
          const usernameEls = row.querySelectorAll(".el-tooltip");
          if (usernameEls.length > 0) {
            usernameEl = usernameEls[0]; // 取第一个el-tooltip元素
          } else {
            // throw new Error("找不到用户名元素");
            return null;
          }
        }
  
        // 点击用户名打开详情弹窗
        usernameEl.click();
        
        // 等待弹窗出现
        const dialog = await waitForElement(
          '.el-dialog__wrapper:not([style*="display: none"])',
          5000,
          document,
          signal
        );
  
        if (!dialog) {
          // throw new Error("用户详情弹窗格式异常");
          return null;
        }
        // 等待财务表格渲染并有内容
        const el = await waitForElement(
            ".el-table__body .cell > span",
            5000,
            dialog,
            signal,
            { requireText: true }
        );
        if (!el) {
          // throw new Error("用户详情弹窗格式异常");
          return null;
        }

        // 获取弹窗中的所有表格
        const tables = dialog.querySelectorAll(".el-table");
        if (tables.length < 2) {
          //throw new Error("用户详情弹窗格式异常");
          return null;
        }
  
        // 第二个表格包含所需的财务数据
        const financialTable = tables[1];
        const cells = financialTable.querySelectorAll(".el-table__body .cell > span");
  
        // 按固定位置提取数值(索引从0开始)
        // 列顺序:余额, 误差, 订单充值, 订单提现, 提现手续费, 公积金购买, 通行证购买, 充提差, 游戏盈亏, 总投注额, 总打码量, 赠送总额
        const balanceText = cells[0]?.textContent || "0"; // 余额
        const totalRechargeText = cells[2]?.textContent || "0"; // 订单充值
        const profitText = cells[8]?.textContent || "0"; // 游戏盈亏
        const totalBetText = cells[9]?.textContent || "0"; // 总投注额
        const totalBonusText = cells[11]?.textContent || "0"; // 赠送总额
  
        // 第一个表格包含相同IP用户数
        const sameIPTable = tables[0];
        const sameIPUsersText = sameIPTable?.querySelector(".el-table__body tr td:nth-child(4) .cell > span")?.textContent || "0";

        // 数值解析函数
        const parseValue = (text) => {
          // 移除所有非数字字符(保留小数点和负号)
          const numStr = text.replace(/[^\d.-]/g, "");
          return parseFloat(numStr) || 0;
        };
  
        // 解析各数值
        const totalBet = parseValue(totalBetText); // 总投注额
        const totalBonus = parseValue(totalBonusText); // 赠送总额
        const totalRecharge = parseValue(totalRechargeText); // 订单充值
        const profit = parseValue(profitText); // 游戏盈亏
        const sameIPUsers = parseInt(sameIPUsersText) || 0; // 相同IP用户数
        const balance = parseValue(balanceText); // 余额
  
        this.config.totalBetAmount = totalBet; // 记录当前订单的总投注额
        this.config.profitAmount = profit; // 记录当前订单的游戏盈亏
  
        // 从用户信息区域提取用户ID
        const userIdEl = dialog.querySelector(
          ".el-table__row .cell div div:nth-child(3)"
        );
        const userId = userIdEl
          ? userIdEl.textContent.replace("ID:", "").trim()
          : null;
  
        console.log("订单号:",this.config.currentOrderId,"用户ID:",userId);
        console.log("订单号:",this.config.currentOrderId,"相同IP用户数:",sameIPUsers);
        console.log("订单号:",this.config.currentOrderId,"余额:",balance);
        console.log("订单号:",this.config.currentOrderId,"订单充值:",totalRecharge);
        console.log("订单号:",this.config.currentOrderId,"游戏盈亏:",profit);
        console.log("订单号:",this.config.currentOrderId,"总投注额:",totalBet);
        console.log("订单号:",this.config.currentOrderId,"赠送总额:",totalBonus);

        if (!userId) {
          // throw new Error("无法获取用户ID");
          return null;
        }
  
        // 关闭弹窗
        this.closeDialog(dialog);
  
        return {
          userId,
          totalBet,
          totalBonus,
          totalRecharge,
          profit,
          sameIPUsers,
          balance
        };
      }
  
      /**
       * 检查财务条件
       * @param {Object} details 用户详情
       * @returns {boolean}
       */
      checkFinancialConditions(details, amount) {
        const {
          totalBet,
          totalBonus,
          totalRecharge,
          profit,
          sameIPUsers,
          balance
        } = details;
  
        let count = 2;
        let failedRules = [];
        let ruleOne = 0;
        // 1.1 检查提现金额是否在指定范围内
        if (amount == 0 || this.config.minTotalRecharge > amount || amount > this.config.maxTotalRecharge) {
            updateStatus(`订单 ${this.config.currentOrderId} 提现金额不在指定范围内`);
            ruleOne += 1;
        }
        // 1.2 检查总充值金额 > 充值金额 × 指定倍数
        if (amount == 0 || amount < totalRecharge * this.config.rechargeToProfitRatio) {
          updateStatus(
            `订单 ${this.config.currentOrderId} 提现金额不满足大于充值金额指定倍数`
          );
            ruleOne += 1;
        }
        if (ruleOne > 0) {
          failedRules.push('不满足规则一'); // 规则1未满足
          count -= 1;
        }
  
        // 2. 检查相同IP用户数是否小于限制
        if (sameIPUsers <= this.config.minSameIPUsers) {
          updateStatus(`订单 ${this.config.currentOrderId} 相同IP用户数小于限制`);
          failedRules.push('不满足规则二'); // 规则2未满足
          count -= 1;
        }
        return { count, failedRules };
      }
  
      /**
       * 获取当日提现金额差
       * @description   当日总提现金额-当日coinPay提现金额
       * @param {string} userId 用户ID
       * @param {AbortSignal} signal 取消信号
       * @returns {Promise<boolean>}
       */
      async getTotalWithdrawAmountRecords(userId, signal) {
        this.closeAllDialogs(signal);
        // 跳转到提现记录页面
        await this.navigateTo("#/order/order-withdraw", signal);

        // 等待页面跳转成功
        await delay(1500);
        // 1. 查找用户ID输入框
        const idInput = await waitForElement(
          'input[placeholder="请输入用户ID"].el-input__inner',
          5000,
          null,
          signal
        );
  
        // 2. 设置用户ID值
        await this.setInputValue(idInput, userId, signal);
  
        // 3. 调整日期范围
        // await this.adjustDateTimeRange(signal);
  
        // 4. 设置订单状态为"已支付"
        await this.selectDropdownOption("全部状态", "已支付", signal);
  
        // 5. 点击查询按钮
        const queryBtn = await this.findQueryButton(signal);
        if (!queryBtn) {
            updateStatus(`用户 ${userId} 找不到查询按钮`);
            return 0;
        }
        queryBtn.click();
  
        // 6. 等待查询结果
        await delay(2000);
  
        // 7. 获取当日总提现金额
        const todayCount = await this.getTodayWithdrawTotalAmount(signal);
        if (todayCount < 1) {
          updateStatus(`用户 ${userId} 当天没有提现金额`);
          return 0;
        }
  
        // 8. 获取当日coinPay提现金额
        const coinPayCount = await this.getTodayCoinPayWithdrawAmount(signal);
        // 计算 当日总提现金额-当日coinPay提现金额
        const count  = todayCount - coinPayCount;
        return count;
      }
  
      /**
       * 批准coinpay订单
       * @param {HTMLElement} row 订单行
       * @param {AbortSignal} signal 取消信号
       */
      async approveOrder(row, signal) {
        // 点击"选择框"
        const cells = row.querySelectorAll("td");
        const statusCell = cells[0]; // 第1列单元格(选择框)
        
        if (!statusCell) {
          throw new Error("找不到订单选择框单元格");
        }
        // 点击勾选框
        const checkboxInner = statusCell.querySelector('.el-checkbox__inner');
        if (!checkboxInner) {
            throw new Error("找不到订单选择框");
        }
        checkboxInner.click();
        
        // 5. 点击操作订单按钮
        const operateOrderBtn = await this.findOperateOrderButton(signal);
        if (!operateOrderBtn) {
            updateStatus(`用户 ${userId} 找不到操作订单按钮`);
            return 0;
        }
        operateOrderBtn.click();
        // 等待弹窗出现
        const dialog = await waitForElement(
          '.el-dialog__wrapper:not([style*="display: none"])',
          5000,
          document,
          signal
        );
        if (!dialog) {
          throw new Error("操作订单出款弹窗格式异常");
        }

        // 选择操作类型
        await this.selectDropdownOptionInDialog(dialog, "操作类型", "指定三方出款", signal);
        // 等待第三方下拉渲染
        await delay(300); 
        // 选择第三方
        await this.selectDropdownOptionInDialog(dialog, "第三方", "coinpay", signal);
        // 查找密码输入框
        const passwordInput = await waitForElement(
            '.myDialog input.el-input__inner',
            3000,
            dialog,
            signal
        );
  
        // 设置密码
        await this.setInputValue(passwordInput, this.config.password, signal);
  
        // 点击批准按钮
        const approveBtn = await waitForElement(
            '.el-dialog__footer .el-button--primary',
            3000,
            dialog,
            signal
        );
        approveBtn.click();
  
        // 等待确认弹窗
        const confirmDialog = await waitForElement(
          '.el-message-box__wrapper:not([style*="display: none"])',
          5000,
          document,
          signal
        );
        if (!confirmDialog) throw new Error("确认弹窗未出现");
  
        // 点击确认按钮
        const confirmBtn = await waitForElement(
          '.el-button--primary',
          3000,
          confirmDialog,
          signal
        );
        confirmBtn.click();
  
        // 等待操作完成
        await delay(1000);
      }
  
      /**
       * 跳转到指定页面
       * @param {string} hash 页面hash
       * @param {AbortSignal} signal 取消信号
       */
      async navigateTo(hash, signal) {
        if (window.location.hash.includes(hash)) {
          await delay(1000);
          return;
        }
  
        window.location.hash = hash;
        await waitForPageChange(hash, 10000);
        await delay(1000); // 额外等待确保页面稳定
      }
  
      /**
       * 跳转到订单页面
       */
      async navigateToOrderPage() {
        await this.navigateTo("#/order/unread-withdraw");
      }
  
      /**
       * 跳转到最后一页
       * @param {AbortSignal} signal 取消信号
       */
      async goToLastPage(signal) {
        const pageInput = await waitForElement(
          ".el-pagination__editor input",
          5000,
          null,
          signal
        );
        const jumpBtn = await waitForElement(
          ".el-pagination__jump",
          5000,
          null,
          signal
        );
  
        // 设置页码为总页数
        const setter = Object.getOwnPropertyDescriptor(
          HTMLInputElement.prototype,
          "value"
        ).set;
        setter.call(pageInput, config.totalPages);
        pageInput.dispatchEvent(new Event("input", { bubbles: true }));
        pageInput.dispatchEvent(new Event("change", { bubbles: true }));
  
        // 模拟回车键触发跳转
        const enterEvent = new KeyboardEvent("keydown", {
          key: "Enter",
          code: "Enter",
          keyCode: 13,
          which: 13,
          bubbles: true,
        });
        pageInput.dispatchEvent(enterEvent);
  
        // 等待页面加载
        await delay(2000);
        await this.processOrderPage(signal);
      }
  
      /**
       * 跳转到上一页
       * @param {AbortSignal} signal 取消信号
       */
      async goToPrevPage(signal) {
        const prevBtn = await waitForElement(
          ".el-pagination .btn-prev:not([disabled])",
          5000,
          null,
          signal
        );
        prevBtn.click();
        config.currentPage--;
        await delay(2000);
        await this.processOrderPage(signal);
      }
  
      /**
       * 开始新一轮处理
       */
      async startNewRound(signal) {
        updateStatus("所有订单已处理完成");
        config.completedOneRound = true;
        gmSet("completedOneRound", true);
  
        // 点击查询按钮重新开始
        await this.clickSearchButton(signal);
      }
  
      /**
       * 点击查询按钮
       */
      async clickSearchButton(signal) {
        // 获取所有filter-container元素
        const filterContainers = document.querySelectorAll(".filter-container");
  
        // 第三个filter-container包含查询按钮
        if (filterContainers.length >= 3) {
          const searchContainer = filterContainers[2];
  
          // 查找查询按钮
          const searchBtn = Array.from(
            searchContainer.querySelectorAll("button.el-button")
          ).find((btn) => !btn.disabled && btn.textContent.includes("查询"));
  
          if (searchBtn) {
            updateStatus("点击查询按钮重新开始处理...");
  
            // 先清空所有搜索条件
            clearAllSearchInputs();
  
            // 点击查询按钮
            searchBtn.click();
  
            // 等待查询完成
            await delay(1500);
  
            config.completedOneRound = false;
            gmSet("completedOneRound", false);
  
            // 重新开始处理
            await delay(500);
            await this.processOrderPage(signal);
            return;
          }
        }
  
        // 如果没找到按钮,尝试其他方式
        updateStatus("未找到查询按钮,3秒后尝试重新开始");
        await delay(3000);
        await this.processOrderPage(signal);
      }
  
      /**
       * 获取订单行(根据表头内容动态定位)
       * @param {AbortSignal} signal 取消信号
       * @returns {Promise<NodeListOf<HTMLElement>>}
       */
      async getOrderRows(signal) {
        return await retryOperation(
            () => {
                // 找到包含“提现用户”表头的表格
                const orderTable = Array.from(document.querySelectorAll(".el-table--scrollable-x"))
                    .find(table => table.textContent.includes("提现用户"));

                if (!orderTable) {
                    console.log("未找到包含 '提现用户' 的订单表格");
                    return null;
                }

                const rows = orderTable.querySelectorAll(".el-table__body .el-table__row");
                if (rows.length === 0) {
                    console.log("订单表格没有行数据");
                    return null;
                }
                return rows;
            },
            3,
            1000,
            signal
        );
    }
  
      /**
       * 统计当天已支付提现总金额
       * @param {AbortSignal} signal 取消信号
       * @returns {Promise<number>}
       */
      async getTodayWithdrawTotalAmount(signal) {
        // 检查是否有"暂无数据"提示
        const emptyText = document.querySelector(".el-table__empty-text");
        if (emptyText?.textContent.includes("暂无数据")) {
            console.log("暂无提现数据")
            return 0;
        }

        // 获取表格的所有行
        const rows = await this.getOrderRows(signal);
        if (!rows || rows.length <= 1) {
            console.log("暂无提现数据")
            return 0; // 只有统计行,没有实际数据
        }

        // 计算当天的 GMT+0 日期(系统时间默认是 GMT+8,所以减去 8 小时得到 GMT+0)
        const now = new Date();
        now.setHours(now.getHours() - 8);
        const todayGMT0Year = now.getUTCFullYear();
        const todayGMT0Month = now.getUTCMonth() + 1;   // getUTCMonth()返回的是0-11,要得到正常的月份(1-12),需要加 1
        const todayGMT0Date = now.getUTCDate();

        let totalAmount = 0;

        // 遍历数据行(跳过第一行统计数据)
        for (let i = 1; i < rows.length; i++) {
            const cells = rows[i].querySelectorAll("td");

            // 固定位置第 6 列(索引5)为 "回调时间(GMT+0:00)"
            const timeCell = cells[5]?.querySelector(".cell");
            if (!timeCell) continue;
            const timeText = timeCell.textContent.trim();
            if (!timeText) continue;

            // 固定位置第 9 列(索引8)为提现金额
            const amountCell = cells[8]?.querySelector(".cell");
            if (!amountCell) continue;
            const amountText = amountCell.textContent.trim();
            if (!amountText) continue;

            // 固定位置第 12 列(索引11)为状态,需要判断是否已支付
            const statusCell = cells[11]?.querySelector(".cell");
            if (!statusCell) continue;
            const statusText = statusCell.textContent.trim();
            if (statusText !== "已支付") continue; // 只统计已支付的

            // 解析日期字符串
            const recordDate = new Date(timeText); // 假设 timeText 是 GMT+0 时间
            console.log(recordDate.getUTCFullYear())
            console.log(recordDate.getUTCMonth())
            console.log(recordDate.getUTCDate())
            if (
                recordDate.getUTCFullYear() === todayGMT0Year &&
                recordDate.getUTCMonth() + 1 === todayGMT0Month &&
                recordDate.getUTCDate() === todayGMT0Date
            ) {
                // 解析金额
                const amount = parseFloat(amountText);
                if (!isNaN(amount)) {
                    totalAmount += amount;
                }
            }
        }

        return totalAmount;
      }
  
      /**
       * 统计当天 CoinPay 已支付提现总金额
       * @param {AbortSignal} signal 取消信号
       * @returns {Promise<boolean>}
       */
      async getTodayCoinPayWithdrawAmount(signal) {
        await this.selectDropdownOption("全部", "CoinPay", signal);
  
        // 点击查询按钮
        const queryBtn = await this.findQueryButton(signal);
        if (!queryBtn) {
            return 0;
        }
        queryBtn.click();
  
        // 等待查询结果
        await delay(1500);
  
        // 检查是否有数据
        const emptyText = document.querySelector(".el-table__empty-text");
        if (emptyText?.textContent.includes("暂无数据")) {
            console.log("暂无CoinPay提现数据")
            return 0;
        }
  
        // 获取表格行
        const rows = document.querySelectorAll(".el-table__body .el-table__row");
        if (!rows || rows.length <= 1) {
            console.log("暂无CoinPay提现数据")
            return 0;
        }

        // 计算当天的 GMT+0 日期
        const now = new Date();
        now.setHours(now.getHours() - 8);
        const todayYear = now.getUTCFullYear();
        const todayMonth = now.getUTCMonth() + 1;   // getUTCMonth()返回的是0-11,要得到正常的月份(1-12),需要加 1
        const todayDate = now.getUTCDate();

        let totalAmount = 0;

        for (let i = 1; i < rows.length; i++) {
            const cells = rows[i].querySelectorAll("td");

            const timeCell = cells[5]?.querySelector(".cell");
            if (!timeCell) continue;
            const timeText = timeCell.textContent.trim();
            if (!timeText) continue;

            const amountCell = cells[8]?.querySelector(".cell");
            if (!amountCell) continue;
            const amountText = amountCell.textContent.trim();
            if (!amountText) continue;

            const statusCell = cells[11]?.querySelector(".cell");
            if (!statusCell) continue;
            const statusText = statusCell.textContent.trim();
            if (statusText !== "已支付") continue;

            const recordDate = new Date(timeText);
            if (
                recordDate.getUTCFullYear() === todayYear &&
                recordDate.getUTCMonth() + 1 === todayMonth &&
                recordDate.getUTCDate() === todayDate
            ) {
                const amount = parseFloat(amountText);
                if (!isNaN(amount)) {
                    totalAmount += amount;
                }
            }
        }

        return totalAmount;
      }
  
      /**
       * 调整日期时间范围(减8小时)
       * @param {AbortSignal} signal 取消信号
       */
      async adjustDateTimeRange(signal) {
        const dateRangeInputs = await waitForElement(
          ".el-range-input",
          5000,
          null,
          signal
        );
        if (!dateRangeInputs || dateRangeInputs.length < 2) {
          throw new Error("找不到日期范围输入框");
        }
  
        const startInput = dateRangeInputs[0];
        const endInput = dateRangeInputs[1];
  
        // 获取当前日期时间
        const now = new Date();
        const endDate = new Date(
          now.getFullYear(),
          now.getMonth(),
          now.getDate(),
          23,
          59,
          59
        );
        const startDate = new Date(
          now.getFullYear(),
          now.getMonth(),
          now.getDate(),
          0,
          0,
          0
        );
  
        // 减8小时
        startDate.setHours(startDate.getHours() - 8);
        endDate.setHours(endDate.getHours() - 8);
  
        // 格式化日期时间
        const formatDate = (date) => {
          const pad = (num) => num.toString().padStart(2, "0");
          return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(
            date.getDate()
          )} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(
            date.getSeconds()
          )}`;
        };
  
        // 设置日期时间
        const setter = Object.getOwnPropertyDescriptor(
          HTMLInputElement.prototype,
          "value"
        ).set;
        setter.call(startInput, formatDate(startDate));
        startInput.dispatchEvent(new Event("input", { bubbles: true }));
  
        setter.call(endInput, formatDate(endDate));
        endInput.dispatchEvent(new Event("input", { bubbles: true }));
      }
  
      /**
       * 设置输入框值
       * @param {HTMLInputElement} input 输入框
       * @param {string} value 值
       * @param {AbortSignal} signal 取消信号
       */
      async setInputValue(input, value, signal = null) {
        if (signal?.aborted) return;
  
        input.focus();
        input.value = "";
  
        const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
          HTMLInputElement.prototype,
          "value"
        ).set;
        nativeInputValueSetter.call(input, value);
  
        ["input", "change", "blur"].forEach((eventType) => {
          input.dispatchEvent(new Event(eventType, { bubbles: true }));
        });
  
        await delay(300); // 确保值已设置
      }
  
      /**
       * 选择下拉选项
       * @param {string} placeholder 下拉框placeholder
       * @param {string} optionText 选项文本
       * @param {AbortSignal} signal 取消信号
       */
      async selectDropdownOption(placeholder, optionText, signal) {
        const selectInput = await waitForElement(
          `.el-select input[placeholder="${placeholder}"]`,
          5000,
          null,
          signal
        );
        selectInput.click();
  
        const dropdown = await waitForElement(
          '.el-select-dropdown.el-popper:not([style*="display: none"])',
          5000,
          null,
          signal
        );
  
        const options = dropdown.querySelectorAll(".el-select-dropdown__item");
        const targetOption = Array.from(options).find(option => 
          option.textContent.trim().toLowerCase() === optionText.toLowerCase()
        );
  
        if (!targetOption) {
          throw new Error(`找不到选项: ${optionText}`);
        }
  
        targetOption.click();
        await delay(500); // 等待选择生效
      }

      /**
         * 在弹窗中选择下拉选项
         * @param {HTMLElement} dialog 弹窗容器
         * @param {string} label 下拉框左侧文本
         * @param {string} optionText 要选择的文本
         * @param {AbortSignal} signal 取消信号
         */
        async selectDropdownOptionInDialog(dialog, label, optionText, signal) {
            // 1. 找到 label 对应的容器(模糊匹配,只匹配直接文本节点)
            const labelDiv = Array.from(dialog.querySelectorAll('div'))
                .find(div => {
                    const text = Array.from(div.childNodes)
                        .filter(node => node.nodeType === Node.TEXT_NODE)
                        .map(node => node.textContent.trim())
                        .join('');
                    return text.replace(/\s|:/g, '').includes(label.replace(/\s|:/g, ''));
                });
            if (!labelDiv) throw new Error(`未找到 ${label} 标签`);

            // 2. 找到同一个父容器里的 .el-select
            const selectContainer = labelDiv.parentElement.querySelector('.el-select');
            if (!selectContainer) throw new Error(`未找到 ${label} 下拉框容器`);

            // 3. 再找到 input 并点击
            const input = selectContainer.querySelector('input.el-input__inner');
            if (!input) throw new Error(`未找到 ${label} 下拉框输入`);
            input.click();

            await delay(100); // 等待浮层渲染

            // 4. 等待至少一个浮层出现
            await waitForElement(
                '.el-select-dropdown.el-popper:not([style*="display: none"])',
                5000,
                document,
                signal
            );

            // 5. 精准匹配浮层位置
            const rect = input.getBoundingClientRect();
            const matchedDropdown = Array.from(document.querySelectorAll('.el-select-dropdown.el-popper:not([style*="display: none"])'))
                .find(d => {
                    const dr = d.getBoundingClientRect();
                    // 浮层 top 应在 input bottom 下方,left 与 input 左对齐
                    return dr.top > rect.bottom - 5 && Math.abs(dr.left - rect.left) < 5;
                });

            if (!matchedDropdown) throw new Error(`未找到 ${label} 下拉浮层`);

            // 6. 查找下拉选项并点击
            const options = matchedDropdown.querySelectorAll(".el-select-dropdown__item");
            const targetOption = Array.from(options)
                .find(opt => opt.textContent.trim().toLowerCase() === optionText.toLowerCase());

            if (!targetOption) throw new Error(`找不到 ${label} 下拉选项: ${optionText}`);

            targetOption.click();
            await delay(300); // 等待选择生效
        }



      /**
       * 关闭弹窗
       * @param {HTMLElement} dialog 弹窗元素
       */
      closeDialog(dialog) {
        const closeBtn = dialog.querySelector(".el-dialog__headerbtn");
        if (closeBtn) closeBtn.click();
      }
  
      /**
       * 关闭所有弹窗
       */
      async closeAllDialogs(signal) {
        const dialogs = document.querySelectorAll('.el-dialog__wrapper:not([style*="display: none"])');
        if (!dialogs) {
          processor.processOrderPage(signal);
          return;
        }
        for (const dialog of dialogs) {
          const closeBtn = dialog.querySelector('.el-dialog__headerbtn');
          if (closeBtn) closeBtn.click();
          await delay(300);
        }
      }
    }
  
    // 创建处理器实例
    const processor = new OrderProcessor(config);
  
    // 初始化
    function init() {
        // 初始化控制面板
        addControlPanel();

        // 检查是否已完成一轮处理
        config.completedOneRound = gmGet("completedOneRound", false);
        if (config.completedOneRound) {
            updateStatus("检测到已完成一轮处理,开始新一轮处理");
            gmSet("completedOneRound", false);
            config.processedOrders = {};
            gmSet("processedOrders", {});
            document.getElementById(`${NS}_processedCount`).textContent = "0";
        }

        updateStatus("准备就绪");
  
        // 监听hash变化
        // window.addEventListener("hashchange", () => {
        //     if (!config.isProcessing) return;
        //     if (isOrderPage()) {
        //         processor.processOrderPage(processor.abortController.signal);
        //     } else {
        //         // 其他所有页面都返回订单页
        //         processor.navigateToOrderPage();
        //     }
        // });
  
        // 如果当前已经在订单页面且正在处理中,直接开始处理
        if (config.isProcessing) {
            updateStatus("开始处理中...");
            processor.processOrderPage(processor.abortController.signal);
        }
    }
  
    // 页面加载完成后执行
    if (document.readyState === "complete") {
      init();
    } else {
      window.addEventListener("load", init);
    }
  })();