auto CH_TK

Mass-scale automated ad delivery

Dieses Skript sollte nicht direkt installiert werden. Es handelt sich hier um eine Bibliothek für andere Skripte, welche über folgenden Befehl in den Metadaten eines Skriptes eingebunden wird // @require https://update.greasyfork.org/scripts/582137/1848327/auto%20CH_TK.js

Du musst eine Erweiterung wie Tampermonkey, Greasemonkey oder Violentmonkey installieren, um dieses Skript zu installieren.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

Sie müssten eine Skript Manager Erweiterung installieren damit sie dieses Skript installieren können

(Ich habe schon ein Skript Manager, Lass mich es installieren!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         巨量引擎投放自动化助手(sheng)
// @namespace    http://tampermonkey.net/
// @version      5.27
// @description  巨量引擎投放智能辅助脚本,支持Excel数据导入持久化、抖音号增删改查、全域ROI系数配比与安全限额、双击悬浮球快速运行及悬浮球最小化控制台、自动采集管理页已有项目限制500最大数量上限。修复其他电脑上因国内 CDN 拦截导致 XLSX.read is not a function 的报错,加入国内多线路动态修补。完美修复抖音号保存失效、下拉切换不生效的遗留问题。修复管理页 getManageTotalProjects 缺失导致无法自动循环的致命错误。完美重装已搭/未搭进度上下各5行动态滚动对焦功能。
// @author       327ohnson
// @match        *://ad.oceanengine.com/ad/web/manage*
// @match        *://ad.oceanengine.com/ad/web/roi/ad/create*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @require      https://registry.npmmirror.com/xlsx/0.18.5/files/dist/xlsx.full.min.js
// @run-at       document-end
// ==/UserScript==

(function() {
    'use strict';

    // 1. 初始化油猴原生专属存储空间 (安全隔离,不会被网页的 localStorage 清理机制抹除)
    const STORAGE_KEY = 'oceanengine_excel_data_secure';
    const AUTO_ACTIVE_KEY = 'oceanengine_auto_active_secure';   // 跨页面自动化标记
    const DOUYIN_STORAGE_KEY = 'oceanengine_douyin_secure';      // 抖音号持久化键
    const ACTIVE_DOUYIN_KEY = 'oceanengine_active_douyin_id';    // 当前选定抖音ID持久化键
    const PROGRESS_INDEX_KEY = 'oceanengine_progress_index';     // 自动填表行进度键

    // v3.2 核心:开辟独立于Excel文件的“已搭”防丢哈希映射数据库
    const YIDA_RECORDS_KEY = 'oceanengine_yida_records_secure';  // 已搭状态映射表

    // ROI 专属安全沙盒持久化键
    const ROI_COEFFICIENT_KEY = 'oceanengine_roi_coefficient';   // 投放ROI值
    const ROI_LOCKED_KEY = 'oceanengine_roi_locked';             // 投放ROI锁定状态
    const ROI_MIN_LIMIT_KEY = 'oceanengine_roi_min_limit';       // ROI防错最低限度
    const ROI_MAX_LIMIT_KEY = 'oceanengine_roi_max_limit';       // ROI防错最高限度

    // 标题管理专属持久化键
    const TITLES_STORAGE_KEY = 'oceanengine_titles_secure';      // 标题库存储
    const TITLES_LOCKED_KEY = 'oceanengine_titles_locked';        // 标题库锁定状态

    // 产品卖点管理专属持久化键
    const SELLING_POINTS_STORAGE_KEY = 'oceanengine_selling_points_secure'; // 卖点库存储
    const SELLING_POINTS_LOCKED_KEY = 'oceanengine_selling_points_locked';   // 卖点锁定状态

    // 项目名模板管理专属持久化键
    const PROJECT_NAME_STORAGE_KEY = 'oceanengine_project_name_secure';    // 项目名模板库存储
    const PROJECT_NAME_LOCKED_KEY = 'oceanengine_project_name_locked';      // 项目名锁定状态

    // 剩下可搭项目数据管理持久化键
    const TOTAL_CREATED_PROJECTS_KEY = 'oceanengine_total_created_projects'; // 已创建项目总数
    const REMAINING_PROJECTS_KEY = 'oceanengine_remaining_projects';         // 剩下可搭项目数

    let excelData = null;
    let douyinAccounts = []; // 存放抖音号列表:[{id: '', name: '', remark: ''}]
    let editIndex = -1;      // 当前正在编辑的抖音号索引 (-1代表普通新增状态)

    // 自动化状态控制机
    let isRunning = false;
    let isPaused = false;

    // 从油猴专属沙盒获取已存在的数据
    try {
        const savedData = GM_getValue(STORAGE_KEY, null);
        if (savedData) excelData = JSON.parse(savedData);

        const savedDouyin = GM_getValue(DOUYIN_STORAGE_KEY, null);
        if (savedDouyin) douyinAccounts = JSON.parse(savedDouyin);
    } catch (e) {
        console.error('读取油猴专属沙盒初始化数据失败:', e);
    }

    // 动态评估页面类型的变量
    let isManagePage = location.href.includes('/ad/web/manage');
    let isCreatePage = location.href.includes('/ad/web/roi/ad/create');

    // 2. 注入全局 iOS 风格 CSS 样式
    const style = document.createElement('style');
    style.innerHTML = `
        :root {
            --ios-blue: #007AFF;
            --ios-blue-active: #0056B3;
            --ios-orange: #FF9500;
            --ios-orange-active: #D57C00;
            --ios-red: #FF3B30;
            --ios-red-active: #C62820;
            --ios-gray: #8E8E93;
            --ios-bg: rgba(255, 255, 255, 0.85);
            --ios-card-bg: rgba(242, 242, 247, 0.8);
            --ios-text: #000000;
            --ios-text-sec: #8E8E93;
            --ios-border: rgba(60, 60, 67, 0.18);
        }

        #ios-helper-panel {
            position: fixed;
            top: 100px;
            right: 40px;
            width: 340px;
            max-height: 85vh;
            background: var(--ios-bg);
            backdrop-filter: blur(20px) saturate(190%);
            -webkit-backdrop-filter: blur(20px) saturate(190%);
            border-radius: 20px;
            box-shadow: 0 12px 38px rgba(0, 0, 0, 0.15), 0 2px 8px rgba(0, 0, 0, 0.05);
            border: 1px solid var(--ios-border);
            z-index: 999999;
            font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "SF Pro Icons", "Helvetica Neue", Helvetica, Arial, sans-serif;
            color: var(--ios-text);
            display: flex;
            flex-direction: column;
            overflow: hidden;
            user-select: none;
            transition: opacity 0.2s ease, transform 0.2s ease;
        }

        /* 顶部拖动导航条 */
        .ios-header {
            padding: 14px 18px;
            background: rgba(255, 255, 255, 0.5);
            border-bottom: 0.5px solid var(--ios-border);
            cursor: move;
            display: flex;
            align-items: center;
            justify-content: space-between;
        }

        .ios-header-title {
            font-size: 17px;
            font-weight: 600;
            letter-spacing: -0.4px;
        }

        .ios-header-subtitle {
            font-size: 11px;
            color: var(--ios-text-sec);
            background: rgba(0,0,0,0.05);
            padding: 2px 8px;
            border-radius: 10px;
        }

        /* iOS 经典悬浮球样式 (AssistiveTouch) */
        #ios-assistive-touch {
            position: fixed;
            width: 54px;
            height: 54px;
            border-radius: 50%;
            background: rgba(0, 0, 0, 0.5);
            backdrop-filter: blur(15px) saturate(190%);
            -webkit-backdrop-filter: blur(15px) saturate(190%);
            border: 2px solid rgba(255, 255, 255, 0.25);
            box-shadow: 0 4px 24px rgba(0, 0, 0, 0.35), inset 0 0 12px rgba(255, 255, 255, 0.15);
            z-index: 999999;
            cursor: pointer;
            display: none;
            align-items: center;
            justify-content: center;
            box-sizing: border-box;
            user-select: none;
            transition: transform 0.1s;
        }

        #ios-assistive-touch:active {
            transform: scale(0.9);
        }

        #ios-assistive-touch::before {
            content: '';
            width: 32px;
            height: 32px;
            border-radius: 50%;
            background: rgba(255, 255, 255, 0.2);
            border: 1px solid rgba(255, 255, 255, 0.35);
            display: flex;
            align-items: center;
            justify-content: center;
            box-sizing: border-box;
        }

        #ios-assistive-touch::after {
            content: '';
            position: absolute;
            width: 18px;
            height: 18px;
            border-radius: 50%;
            /* CSS 属性配色自适应。运行中显示绿色(#21a675),暂停或待命显示深红色(#c91f37) */
            background: var(--ball-color, #c91f37);
            box-shadow: 0 0 10px var(--ball-shadow, rgba(201, 31, 55, 0.6));
            box-sizing: border-box;
            transition: background 0.25s ease, box-shadow 0.25s ease;
        }

        /* iOS 经典分段切换控制 (Segmented Control) */
        .ios-segmented-control {
            display: flex;
            background: rgba(120, 120, 128, 0.12);
            border-radius: 9px;
            padding: 2px;
            margin: 0 4px;
        }

        .ios-segment-btn {
            flex: 1;
            border: none;
            background: transparent;
            padding: 6px 0;
            font-size: 13px;
            font-weight: 500;
            border-radius: 7px;
            cursor: pointer;
            color: var(--ios-text-sec);
            transition: all 0.2s;
            outline: none;
        }

        .ios-segment-btn.active {
            background: #ffffff;
            box-shadow: 0 1px 3px rgba(0,0,0,0.1), 0 1px 1px rgba(0,0,0,0.05);
            color: var(--ios-text);
            font-weight: 600;
        }

        /* 内容承载区域与标签页面 */
        .ios-content {
            padding: 18px;
            overflow-y: auto;
            max-height: calc(85vh - 60px);
            display: flex;
            flex-direction: column;
            gap: 12px;
        }

        .ios-tab-pane {
            display: none;
            flex-direction: column;
            gap: 12px;
        }

        .ios-tab-pane.active-pane {
            display: flex;
        }

        /* 输入框 */
        .ios-input-text {
            width: 100%;
            height: 38px;
            border-radius: 8px;
            border: 0.5px solid var(--ios-border);
            padding: 0 10px;
            font-size: 14px;
            background: #ffffff;
            color: #000000;
            box-sizing: border-box;
            outline: none;
            transition: border-color 0.2s;
        }

        .ios-input-text:focus {
            border-color: var(--ios-blue);
        }

        .ios-input-text:disabled {
            background-color: #E5E5EA !important;
            color: #8E8E93 !important;
            cursor: not-allowed;
        }

        /* iOS风格文本域 */
        .ios-textarea {
            width: 100%;
            height: 100px;
            border-radius: 8px;
            border: 0.5px solid var(--ios-border);
            padding: 8px 10px;
            font-size: 13px;
            background: #ffffff;
            color: #000000;
            box-sizing: border-box;
            outline: none;
            transition: border-color 0.2s;
            resize: vertical;
            line-height: 1.5;
            font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", Arial, sans-serif;
        }

        .ios-textarea:focus {
            border-color: var(--ios-blue);
        }

        .ios-textarea:disabled {
            background-color: #E5E5EA !important;
            color: #8E8E93 !important;
            cursor: not-allowed;
        }

        /* 已搭重置按钮(用于局部清除) */
        .yida-badge-btn {
            color: #34C759;
            font-weight: 600;
            background: rgba(52, 199, 89, 0.15);
            padding: 2px 6px;
            border-radius: 4px;
            font-size: 10px;
            cursor: pointer;
            transition: all 0.2s;
            display: inline-block;
            user-select: none;
        }
        .yida-badge-btn:hover {
            background: var(--ios-red) !important;
            color: #ffffff !important;
        }

        /* 下拉选择框 */
        #active-douyin-select {
            width: 100%;
            height: 38px;
            border-radius: 8px;
            border: 0.5px solid var(--ios-border);
            padding: 0 10px;
            font-size: 14px;
            background-color: #ffffff;
            color: #000000;
            box-sizing: border-box;
            outline: none;
            cursor: pointer;
            transition: border-color 0.2s;
            appearance: none;
            -webkit-appearance: none;
            background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2212%22%20height%3D%2212%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22none%22%20stroke%3D%22%238E8E93%22%20stroke-width%3D%223%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Cpolyline%20points%3D%226%209%2012%2015%2018%209%22%3E%3C%2Fpolyline%3E%3C%2Fsvg%3E");
            background-repeat: no-repeat;
            background-position: right 12px center;
            background-size: 12px;
            padding-right: 30px;
        }

        #active-douyin-select:focus {
            border-color: var(--ios-blue);
        }

        /* 抖音账号单条样式 */
        .dy-item {
            background: rgba(255, 255, 255, 0.7);
            border-radius: 10px;
            padding: 10px;
            border: 0.5px solid var(--ios-border);
            display: flex;
            justify-content: space-between;
            align-items: center;
        }

        .dy-item-info {
            display: flex;
            flex-direction: column;
            gap: 2px;
            overflow: hidden;
            flex: 1;
            padding-right: 8px;
        }

        .dy-item-name {
            font-size: 14px;
            font-weight: 600;
            color: var(--ios-text);
            text-overflow: ellipsis;
            white-space: nowrap;
            overflow: hidden;
        }

        .dy-item-details {
            font-size: 11px;
            color: var(--ios-text-sec);
            text-overflow: ellipsis;
            white-space: nowrap;
            overflow: hidden;
        }

        .dy-item-actions {
            display: flex;
            gap: 6px;
        }

        .dy-action-btn {
            border: none;
            background: transparent;
            padding: 5px 8px;
            cursor: pointer;
            font-size: 12px;
            font-weight: 600;
            border-radius: 6px;
            transition: all 0.2s;
            outline: none;
        }

        .dy-btn-edit {
            color: var(--ios-blue);
            background: rgba(0, 122, 255, 0.1);
        }
        .dy-btn-edit:active {
            background: rgba(0, 122, 255, 0.2);
        }

        .dy-btn-delete {
            color: var(--ios-red);
            background: rgba(255, 59, 48, 0.1);
        }
        .dy-btn-delete:active {
            background: rgba(255, 59, 48, 0.2);
        }

        /* 控制栏并排组 */
        .ios-btn-group-primary {
            display: flex;
            gap: 6px;
            width: 100%;
        }

        /* iOS 按钮系统 */
        .ios-btn {
            height: 44px;
            border-radius: 12px;
            border: none;
            font-size: 15px;
            font-weight: 600;
            cursor: pointer;
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 4px;
            transition: all 0.2s cubic-bezier(0.25, 0.8, 0.25, 1);
            outline: none;
            min-width: 0;
        }

        .ios-btn-primary {
            background-color: var(--ios-blue);
            color: #ffffff;
        }
        .ios-btn-primary:active {
            background-color: var(--ios-blue-active);
            transform: scale(0.97);
        }

        .ios-btn-orange {
            background-color: var(--ios-orange);
            color: #ffffff;
        }
        .ios-btn-orange:active {
            background-color: var(--ios-orange-active);
            transform: scale(0.97);
        }

        .ios-btn-secondary {
            background-color: #ffffff;
            color: var(--ios-blue);
            border: 1px solid var(--ios-border);
        }
        .ios-btn-secondary:active {
            background-color: rgba(0, 122, 255, 0.08);
            transform: scale(0.97);
        }

        .ios-btn-danger {
            background-color: var(--ios-red);
            color: #ffffff;
        }
        .ios-btn-danger:active {
            background-color: var(--ios-red-active);
            transform: scale(0.97);
        }

        .ios-btn-disabled {
            background-color: #E5E5EA !important;
            color: #AEAEB2 !important;
            cursor: not-allowed !important;
            transform: none !important;
        }

        /* 底部双按钮对齐结构 */
        .ios-btn-group {
            display: grid;
            grid-template-columns: 2fr 1fr;
            gap: 8px;
            width: 100%;
        }

        /* iOS风格卡片包装器 */
        .ios-card {
            background: var(--ios-card-bg);
            border-radius: 12px;
            padding: 12px;
            border: 0.5px solid var(--ios-border);
        }

        .ios-card-title {
            font-size: 13px;
            font-weight: 500;
            color: var(--ios-text-sec);
            text-transform: uppercase;
            margin-bottom: 8px;
        }

        /* Excel预览表格样式 */
        .excel-preview-container {
            max-height: 180px;
            overflow: auto;
            border-radius: 8px;
            border: 0.5px solid var(--ios-border);
            background: #ffffff;
        }

        .excel-table {
            width: 100%;
            border-collapse: collapse;
            font-size: 11px;
            text-align: left;
        }

        .excel-table th {
            background: #F2F2F7;
            position: sticky;
            top: 0;
            padding: 6px 8px;
            font-weight: 600;
            border-bottom: 0.5px solid var(--ios-border);
            white-space: nowrap;
            z-index: 10;
        }

        .excel-table td {
            padding: 6px 8px;
            border-bottom: 0.5px solid rgba(60, 60, 67, 0.08);
            max-width: 120px;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
        }

        /* 日志窗口 */
        .log-console {
            background: #000000;
            color: #00FF00;
            font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
            font-size: 11px;
            padding: 10px;
            border-radius: 8px;
            max-height: 100px;
            overflow-y: auto;
            box-shadow: inset 0 2px 8px rgba(0,0,0,0.8);
        }

        .log-item {
            margin-bottom: 4px;
            line-height: 1.3;
        }

        /* 隐藏的原生文件输入框 */
        #excel-file-picker {
            display: none;
        }
    `;
    document.head.appendChild(style);

    // 根据页面类型设置主按钮文本
    const primaryBtnText = isManagePage ? '开始搭建 (跳转)' : '开始自动运行';

    // 3. 构建 UI Dom 结构
    const panel = document.createElement('div');
    panel.id = 'ios-helper-panel';
    panel.innerHTML = `
        <div class="ios-header" id="ios-drag-handle">
            <span class="ios-header-title">投放自动化助手</span>
            <div style="display: flex; align-items: center; gap: 8px;">
                <span class="ios-header-subtitle">Johnson 阿盛</span>
                <!-- iOS 最小化缩放按钮 -->
                <button id="btn-minimize-panel" style="width: 24px; height: 24px; border: none; background: transparent; cursor: pointer; display: flex; align-items: center; justify-content: center; color: var(--ios-text-sec); outline: none;" title="最小化为悬浮球">
                    <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><line x1="5" y1="12" x2="19" y2="12"></line></svg>
                </button>
            </div>
        </div>
        <div class="ios-content">
            <!-- iOS 分段式 Tab 切换器 -->
            <div class="ios-segmented-control">
                <button class="ios-segment-btn active" id="segment-auto">自动化投放</button>
                <button class="ios-segment-btn" id="segment-douyin">抖音号管理</button>
                <button class="ios-segment-btn" id="segment-admin">安全管理</button>
            </div>

            <!-- TAB 1: 自动化投放 -->
            <div id="tab-auto-pane" class="ios-tab-pane active-pane">
                <!-- 抖音号选择下拉卡片 -->
                <div class="ios-card">
                    <div class="ios-card-title">当前投放抖音号</div>
                    <select id="active-douyin-select">
                        <option value="">-- 请选择要投放的抖音号 --</option>
                    </select>
                </div>

                <!-- 全域ROI系数配置卡片 -->
                <div class="ios-card" style="margin-bottom: 2px;">
                    <div class="ios-card-title">全域ROI系数配置</div>
                    <div style="display: flex; gap: 8px; align-items: center;">
                        <input type="number" step="0.01" id="roi-coefficient-input" class="ios-input-text" placeholder="输入投放ROI系数" style="flex: 1;">
                        <button class="ios-btn ios-btn-primary" id="btn-save-roi" style="width: 70px; height: 38px; font-size: 14px; flex-shrink: 0; margin: 0;">确定</button>
                        <button class="ios-btn ios-btn-secondary" id="btn-edit-roi" style="width: 70px; height: 38px; font-size: 14px; display: none; flex-shrink: 0; margin: 0;">修改</button>
                    </div>
                </div>

                <!-- 剩下可搭项目额度卡片(第一页中间位置) -->
                <div class="ios-card" style="margin-bottom: 2px; background: rgba(0, 122, 255, 0.05); border-left: 4px solid var(--ios-blue);">
                    <div style="display: flex; align-items: center; justify-content: space-between; width: 100%;">
                        <span style="font-size: 13px; font-weight: 600; color: var(--ios-text-sec);">剩下可搭项目:</span>
                        <span id="remaining-projects-count" style="font-size: 16px; color: var(--ios-blue); font-weight: 700; transition: all 0.3s;">-- 个</span>
                    </div>
                    <div style="font-size: 10px; color: var(--ios-text-sec); margin-top: 4px; text-align: left;" id="remaining-projects-tip">
                        (正在检测巨量后台已有项目数...)
                    </div>
                </div>

                <!-- 核心控制动作栏:开始、暂停、重新运行 -->
                <div class="ios-btn-group-primary">
                    <button class="ios-btn ios-btn-primary" id="btn-start-build" style="flex: 2; min-width: 0;">
                        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polygon points="5 3 19 12 5 21 5 3"></polygon></svg>
                        <span id="btn-text-label" style="font-size: 13px; text-overflow: ellipsis; white-space: nowrap; overflow: hidden;">${primaryBtnText}</span>
                    </button>
                    <button class="ios-btn ios-btn-disabled" id="btn-pause-build" style="width: 60px; flex-shrink: 0; font-size: 14px;">
                        <span id="btn-pause-label">暂停</span>
                    </button>
                    <button class="ios-btn ios-btn-secondary" id="btn-rerun-build" style="width: 80px; flex-shrink: 0; font-size: 13px; font-weight: 700; color: var(--ios-orange); border-color: rgba(255,149,0,0.4);" title="重置当前流程,重新扫描已搭数据库并从第一个未搭剧目重试运行">
                        重新运行
                    </button>
                </div>

                <!-- 导入与清空 -->
                <div class="ios-btn-group">
                    <button class="ios-btn ios-btn-secondary" id="btn-import-excel">
                        <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="17 8 12 3 7 8"></polyline><line x1="12" y1="3" x2="12" y2="15"></line></svg>
                        导入数据/Excel
                    </button>
                    <button class="ios-btn ios-btn-danger" id="btn-clear-data">清空</button>
                </div>

                <input type="file" id="excel-file-picker" accept=".xlsx, .xls" />

                <!-- 当前搭建剧目看板与手动跳过控制 -->
                <div class="ios-card" style="padding: 10px 14px; background: rgba(255, 149, 0, 0.1); border-left: 4px solid var(--ios-orange); border-radius: 12px; margin-bottom: 2px;">
                    <div style="display: flex; align-items: center; justify-content: space-between; width: 100%; box-sizing: border-box; margin-bottom: 6px;">
                        <span style="font-size: 13px; font-weight: 600; color: var(--ios-text-sec);">正搭建的剧目为:</span>
                        <span id="current-building-drama-name" style="font-size: 14px; color: var(--ios-orange); font-weight: 700; text-overflow: ellipsis; white-space: nowrap; overflow: hidden; max-width: 160px; text-align: right;">--</span>
                    </div>
                    <div style="display: flex; justify-content: flex-end; gap: 8px;">
                        <button class="ios-btn ios-btn-secondary" id="btn-manual-yida" style="height: 26px; font-size: 11px; padding: 0 10px; border-radius: 6px; margin: 0; background: #ffffff; color: var(--ios-orange); border: 0.5px solid var(--ios-orange);" title="手动将当前显示剧目在安全库与Excel中标记为‘已搭’,防止重复配置">
                            手动标记“已搭”
                        </button>
                    </div>
                </div>

                <!-- 数据载入状态 -->
                <div class="ios-card">
                    <div class="ios-card-title" id="data-status-title">数据载入状态</div>
                    <div id="data-preview-box">
                        <div style="font-size: 13px; color: var(--ios-text-sec); text-align: center; padding: 20px 0;">
                            暂无导入数据,请先导入Excel
                        </div>
                    </div>
                </div>

                <!-- 控制台 -->
                <div class="ios-card">
                    <div class="ios-card-title">系统控制台日志</div>
                    <div class="log-console" id="log-console-box">
                        <div class="log-item" style="color: #8E8E93;">[系统] 助手加载完毕,等待操作...</div>
                    </div>
                </div>
            </div>

            <!-- TAB 2: 抖音号管理 -->
            <div id="tab-douyin-pane" class="ios-tab-pane">
                <!-- 抖音号编辑/添加卡片 -->
                <div class="ios-card">
                    <div class="ios-card-title" id="douyin-form-title">添加抖音号</div>
                    <div style="display: flex; flex-direction: column; gap: 8px;">
                        <input type="text" id="dy-input-id" class="ios-input-text" placeholder="请输入抖音ID (唯一标识)">
                        <input type="text" id="dy-input-name" class="ios-input-text" placeholder="请输入抖音名称">
                        <input type="text" id="dy-input-remark" class="ios-input-text" placeholder="备注信息 (可选)">
                        <div style="display: flex; gap: 8px; margin-top: 4px;">
                            <button class="ios-btn ios-btn-primary" id="btn-save-douyin" style="flex: 1; height: 38px; font-size: 14px;">保存抖音号</button>
                            <button class="ios-btn ios-btn-secondary" id="btn-cancel-douyin" style="flex: 1; height: 38px; font-size: 14px; display: none; flex-shrink: 0; margin: 0;">取消修改</button>
                        </div>
                    </div>
                </div>

                <!-- 已保存的抖音号卡片 -->
                <div class="ios-card" style="flex-grow: 1; display: flex; flex-direction: column;">
                    <div class="ios-card-title">已存抖音号库</div>
                    <div id="douyin-list-box" style="display: flex; flex-direction: column; gap: 8px; max-height: 280px; overflow-y: auto; padding-right: 2px;">
                        <div style="font-size: 13px; color: var(--ios-text-sec); text-align: center; padding: 25px 0;">
                            抖音库暂无配置,请在上方录入
                        </div>
                    </div>
                </div>
            </div>

            <!-- TAB 3: 安全管理 -->
            <div id="tab-admin-pane" class="ios-tab-pane">
                <!-- 项目名配置卡片 -->
                <div class="ios-card">
                    <div class="ios-card-title">项目名配置</div>
                    <div style="display: flex; flex-direction: column; gap: 8px;">
                        <input type="text" id="project-name-template-input" class="ios-input-text" placeholder="例: <剧目>_全域投放_<日期>_ROI:<ROI>">
                        <div style="display: flex; gap: 4px; flex-wrap: wrap;">
                            <button class="ios-btn ios-btn-secondary" id="btn-insert-date-tag" style="height: 28px; font-size: 11px; padding: 0 6px; border-radius: 6px; margin: 0; flex: 1;" title="点击插入日期占位符 (打包输出时转化为 %m%d 格式)">插入 &lt;日期&gt;</button>
                            <button class="ios-btn ios-btn-secondary" id="btn-insert-drama-tag" style="height: 28px; font-size: 11px; padding: 0 6px; border-radius: 6px; margin: 0; flex: 1;" title="点击插入剧目名占位符 (自动提取当前搭建剧目名称)">插入 &lt;剧目&gt;</button>
                            <button class="ios-btn ios-btn-secondary" id="btn-insert-roi-tag" style="height: 28px; font-size: 11px; padding: 0 6px; border-radius: 6px; margin: 0; flex: 1;" title="点击插入ROI占位符 (自动提取第一页配置的全域ROI)">插入 &lt;ROI&gt;</button>
                        </div>
                        <div style="display: flex; gap: 6px; width: 100%;">
                            <button class="ios-btn ios-btn-primary" id="btn-lock-project-name" style="flex: 1; height: 38px; font-size: 13px;">锁定</button>
                            <button class="ios-btn ios-btn-secondary" id="btn-modify-project-name" style="flex: 1; height: 38px; font-size: 13px; display: none;">修改</button>
                            <button class="ios-btn ios-btn-danger" id="btn-clear-project-name" style="width: 70px; height: 38px; font-size: 13px;">清空</button>
                        </div>
                    </div>
                </div>

                <div class="ios-card">
                    <div class="ios-card-title">ROI 防错上下限配置</div>
                    <div style="display: flex; flex-direction: column; gap: 8px;">
                        <div style="display: flex; gap: 8px; align-items: center; justify-content: space-between;">
                            <span style="font-size: 13px; font-weight: 600; color: var(--ios-text-sec); width: 80px;">最小值限制:</span>
                            <input type="number" step="0.01" id="roi-min-input" class="ios-input-text" placeholder="设置ROI下限(防错)" style="flex: 1;">
                        </div>
                        <div style="display: flex; gap: 8px; align-items: center; justify-content: space-between;">
                            <span style="font-size: 13px; font-weight: 600; color: var(--ios-text-sec); width: 80px;">最大值限制:</span>
                            <input type="number" step="0.01" id="roi-max-input" class="ios-input-text" placeholder="设置ROI上限(防错)" style="flex: 1;">
                        </div>
                        <button class="ios-btn ios-btn-primary" id="btn-save-admin-limits" style="height: 38px; font-size: 14px; margin-top: 4px; width: 100%;">保存防错配置</button>
                    </div>
                </div>

                <!-- 标题管理卡片 -->
                <div class="ios-card">
                    <div class="ios-card-title">标题管理 (每行一个标题)</div>
                    <div style="display: flex; flex-direction: column; gap: 8px;">
                        <textarea id="title-management-input" class="ios-textarea" placeholder="在此输入自定义创意标题(支持多行输入)&#10;每行代表一个标题"></textarea>
                        <div style="display: flex; gap: 6px; width: 100%;">
                            <button class="ios-btn ios-btn-primary" id="btn-lock-titles" style="flex: 1; height: 38px; font-size: 13px;">锁定</button>
                            <button class="ios-btn ios-btn-secondary" id="btn-modify-titles" style="flex: 1; height: 38px; font-size: 13px; display: none;">修改</button>
                            <button class="ios-btn ios-btn-danger" id="btn-clear-titles" style="width: 70px; height: 38px; font-size: 13px;">清空</button>
                        </div>
                    </div>
                </div>

                <!-- 产品卖点管理卡片 -->
                <div class="ios-card">
                    <div class="ios-card-title">产品卖点管理 (每行一个卖点,最多10个)</div>
                    <div style="display: flex; flex-direction: column; gap: 8px;">
                        <textarea id="selling-points-input" class="ios-textarea" placeholder="在此输入自定义产品卖点(支持多行输入)&#10;每行代表一个产品卖点"></textarea>
                        <div style="display: flex; gap: 6px; width: 100%;">
                            <button class="ios-btn ios-btn-primary" id="btn-lock-points" style="flex: 1; height: 38px; font-size: 13px;">锁定</button>
                            <button class="ios-btn ios-btn-secondary" id="btn-modify-points" style="flex: 1; height: 38px; font-size: 13px; display: none;">修改</button>
                            <button class="ios-btn ios-btn-danger" id="btn-clear-points" style="width: 70px; height: 38px; font-size: 13px;">清空</button>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    `;
    document.body.appendChild(panel);

    // 4. 获取 DOM 元素引用
    const dragHandle = document.getElementById('ios-drag-handle');
    const btnStartBuild = document.getElementById('btn-start-build');
    const btnPauseBuild = document.getElementById('btn-pause-build');
    const btnPauseLabel = document.getElementById('btn-pause-label');
    const btnRerunBuild = document.getElementById('btn-rerun-build');
    const btnManualYida = document.getElementById('btn-manual-yida');
    const btnImportExcel = document.getElementById('btn-import-excel');
    const btnClearData = document.getElementById('btn-clear-data');
    const filePicker = document.getElementById('excel-file-picker');
    const dataStatusTitle = document.getElementById('data-status-title');
    const dataPreviewBox = document.getElementById('data-preview-box');
    const logConsoleBox = document.getElementById('log-console-box');

    // 抖音管理与多Tab专属 DOM
    const tabAuto = document.getElementById('segment-auto');
    const tabDouyin = document.getElementById('segment-douyin');
    const tabAdmin = document.getElementById('segment-admin');
    const paneAuto = document.getElementById('tab-auto-pane');
    const paneDouyin = document.getElementById('tab-douyin-pane');
    const paneAdmin = document.getElementById('tab-admin-pane');

    const dyFormTitle = document.getElementById('douyin-form-title');
    const dyInputId = document.getElementById('dy-input-id');
    const dyInputName = document.getElementById('dy-input-name');
    const dyInputRemark = document.getElementById('dy-input-remark');
    const btnSaveDouyin = document.getElementById('btn-save-douyin');
    const btnCancelDouyin = document.getElementById('btn-cancel-douyin');
    const douyinListBox = document.getElementById('douyin-list-box');
    const activeDouyinSelect = document.getElementById('active-douyin-select');

    // ROI 投放与极值 DOM 引用
    const roiInput = document.getElementById('roi-coefficient-input');
    const btnSaveRoi = document.getElementById('btn-save-roi');
    const btnEditRoi = document.getElementById('btn-edit-roi');
    const minLimitInput = document.getElementById('roi-min-input');
    const maxLimitInput = document.getElementById('roi-max-input');
    const btnSaveAdminLimits = document.getElementById('btn-save-admin-limits');

    // 标题管理 DOM 引用
    const titleInput = document.getElementById('title-management-input');
    const btnLockTitles = document.getElementById('btn-lock-titles');
    const btnModifyTitles = document.getElementById('btn-modify-titles');
    const btnClearTitles = document.getElementById('btn-clear-titles');

    // 产品卖点 DOM 引用
    const pointsInput = document.getElementById('selling-points-input');
    const btnLockPoints = document.getElementById('btn-lock-points');
    const btnModifyPoints = document.getElementById('btn-modify-points');
    const btnClearPoints = document.getElementById('btn-clear-points');

    // 项目名模板 DOM 引用
    const projectNameInput = document.getElementById('project-name-template-input');
    const btnLockProjectName = document.getElementById('btn-lock-project-name');
    const btnModifyProjectName = document.getElementById('btn-modify-project-name');
    const btnClearProjectName = document.getElementById('btn-clear-project-name');
    const btnInsertDateTag = document.getElementById('btn-insert-date-tag');
    const btnInsertDramaTag = document.getElementById('btn-insert-drama-tag');
    const btnInsertRoiTag = document.getElementById('btn-insert-roi-tag');

    // 创建 iOS 物理悬浮球 (AssistiveTouch)
    const floatBall = document.createElement('div');
    floatBall.id = 'ios-assistive-touch';
    floatBall.title = '投放自动化助手 - 双击快速开始投放 | 点击还原';
    document.body.appendChild(floatBall);

    // 5. 工具方法:打印日志
    function log(message, type = 'info') {
        const time = new Date().toLocaleTimeString();
        const logItem = document.createElement('div');
        logItem.className = 'log-item';

        let color = '#00FF00'; // 默认绿
        if (type === 'error') color = '#FF3B30';
        if (type === 'warning') color = '#FF9500';
        if (type === 'system') color = '#8E8E93';

        logItem.style.color = color;
        logItem.innerText = `[${time}] ${message}`;
        logConsoleBox.appendChild(logItem);
        logConsoleBox.scrollTop = logConsoleBox.scrollHeight;
    }

    // 确保 XLSX 解析库完美加载的国内动态多线路双重保险修补逻辑
    async function ensureXLSXLoaded() {
        let activeXLSX = typeof XLSX !== 'undefined' ? XLSX : (typeof window.XLSX !== 'undefined' ? window.XLSX : null);
        if (activeXLSX && typeof activeXLSX.read === 'function') {
            return true;
        }

        log("⚠️ 检测到本地 Excel 解析库(XLSX)加载异常,正在自动启动国内高速多线路动态修补...", "warning");

        // 整理国内极其稳定且高速的知名 CDN 镜像(依次降级读取)
        const cdns = [
            'https://lib.baomitu.com/xlsx/0.18.5/xlsx.full.min.js',             // 360 Baomitu 源 (国内极速)
            'https://cdn.staticfile.org/xlsx/0.18.5/xlsx.full.min.js',          // Staticfile 国内联合源
            'https://registry.npmmirror.com/xlsx/0.18.5/files/dist/xlsx.full.min.js' // 阿里淘宝原厂源
        ];

        for (const url of cdns) {
            try {
                log(`- 正在尝试通过备用高速线路注入加载: ${url}`, 'system');
                const loaded = await new Promise((resolve) => {
                    const script = document.createElement('script');
                    script.src = url;
                    script.onload = () => {
                        const testXLSX = typeof window.XLSX !== 'undefined' ? window.XLSX : null;
                        if (testXLSX && typeof testXLSX.read === 'function') {
                            resolve(true);
                        } else {
                            resolve(false);
                        }
                    };
                    script.onerror = () => resolve(false);
                    document.head.appendChild(script);
                });

                if (loaded) {
                    log("✅ 备用线路动态修补成功!Excel 解析库已修复并重新激活。", "info");
                    return true;
                }
            } catch (e) {
                console.error('动态注入 Excel CDN 线路时发生捕获异常:', e);
            }
        }
        return false;
    }

    // UI 正搭建剧目显示更新
    function updateCurrentBuildingDramaName(name) {
        const dramaEl = document.getElementById('current-building-drama-name');
        if (dramaEl) {
            dramaEl.innerText = name || '--';
        }
    }

    // 呼吸指示环配色动态管理
    function updateBallStyle() {
        if (isRunning && !isPaused) {
            floatBall.style.setProperty('--ball-color', '#21a675');
            floatBall.style.setProperty('--ball-shadow', 'rgba(33, 166, 117, 0.7)');
        } else {
            floatBall.style.setProperty('--ball-color', '#c91f37');
            floatBall.style.setProperty('--ball-shadow', 'rgba(201, 31, 55, 0.7)');
        }
    }

    // 智能元素真实可见度核对方法 (屏蔽 SPA 骨架屏、加载遮罩、不可见占位符)
    function isVisible(el) {
        if (!el) return false;

        // 1. 物理宽高基本过滤
        if (el.offsetWidth === 0 || el.offsetHeight === 0) {
            return false;
        }

        // 2. CSS 隐形与骨架屏排查
        try {
            const style = window.getComputedStyle(el);
            if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') {
                return false;
            }

            // 匹配剔除骨架屏常见样式
            const className = String(el.className || '').toLowerCase();
            if (className.includes('skeleton') || className.includes('loading') || className.includes('placeholder')) {
                return false;
            }

            const rect = el.getBoundingClientRect();
            if (rect.width === 0 || rect.height === 0) {
                return false;
            }
        } catch (e) {}

        return true;
    }

    // 字符串规范化归一化工具,移除所有空格、特殊标点符号,统一转小写,防止中英文符号差异导致比对失败
    function normalizeString(str) {
        if (!str) return '';
        return String(str)
            .toLowerCase()
            .replace(/[\s\p{P}\p{S}\p{Z}]/gu, ''); // 完美清洗标点与空格
    }

    // 6. 实现拖拽与悬浮球、面板位置共享同步逻辑
    let isDragging = false;
    let startX, startY;
    let initialLeft, initialTop;

    const storedX = GM_getValue('ios_helper_x', null);
    const storedY = GM_getValue('ios_helper_y', null);

    // 同步刷新或初加载时的坐标
    function syncConsolePositions(x, y) {
        panel.style.left = x + 'px';
        panel.style.top = y + 'px';
        panel.style.right = 'auto';

        floatBall.style.left = x + 'px';
        floatBall.style.top = y + 'px';
    }

    if (storedX !== null && storedY !== null) {
        syncConsolePositions(storedX, storedY);
    }

    // 拖动主面板
    dragHandle.addEventListener('mousedown', (e) => {
        if (e.target.closest('#btn-minimize-panel')) return;
        isDragging = true;
        startX = e.clientX;
        startY = e.clientY;

        const rect = panel.getBoundingClientRect();
        initialLeft = rect.left;
        initialTop = rect.top;

        panel.style.right = 'auto';
        panel.style.bottom = 'auto';

        document.addEventListener('mousemove', onMouseMove);
        document.addEventListener('mouseup', onMouseUp);

        e.preventDefault();
    });

    function onMouseMove(e) {
        if (!isDragging) return;
        const dx = e.clientX - startX;
        const dy = e.clientY - startY;

        let newLeft = initialLeft + dx;
        let newTop = initialTop + dy;

        const maxX = window.innerWidth - panel.offsetWidth;
        const maxY = window.innerHeight - panel.offsetHeight;
        newLeft = Math.max(0, Math.min(newLeft, maxX));
        newTop = Math.max(0, Math.min(newTop, maxY));

        syncConsolePositions(newLeft, newTop);
    }

    // 拖动释放
    function onMouseUp() {
        if (isDragging) {
            isDragging = false;
            GM_setValue('ios_helper_x', parseInt(panel.style.left));
            GM_setValue('ios_helper_y', parseInt(panel.style.top));
            document.removeEventListener('mousemove', onMouseMove);
            document.removeEventListener('mouseup', onMouseUp);
        }
    }

    // iOS 经典悬浮球 independent 仿真拖动与双击
    let isDraggingBall = false;
    let ballStartX, ballStartY;
    let ballInitialLeft, ballInitialTop;
    let lastClickTime = 0;
    let clickTimeout = null;

    floatBall.addEventListener('mousedown', (e) => {
        isDraggingBall = false;
        ballStartX = e.clientX;
        ballStartY = e.clientY;

        const rect = floatBall.getBoundingClientRect();
        ballInitialLeft = rect.left;
        ballInitialTop = rect.top;

        document.addEventListener('mousemove', onMouseMoveBall);
        document.addEventListener('mouseup', onMouseUpBall);

        e.preventDefault();
    });

    function onMouseMoveBall(e) {
        const dx = e.clientX - ballStartX;
        const dy = e.clientY - ballStartY;
        if (Math.abs(dx) > 4 || Math.abs(dy) > 4) {
            isDraggingBall = true;
        }

        let newLeft = ballInitialLeft + dx;
        let newTop = ballInitialTop + dy;

        const maxX = window.innerWidth - floatBall.offsetWidth;
        const maxY = window.innerHeight - floatBall.offsetHeight;
        newLeft = Math.max(0, Math.min(newLeft, maxX));
        newTop = Math.max(0, Math.min(newTop, maxY));

        syncConsolePositions(newLeft, newTop);
    }

    // 悬浮球释放
    function onMouseUpBall() {
        document.removeEventListener('mousemove', onMouseMoveBall);
        document.removeEventListener('mouseup', onMouseUpBall);

        if (isDraggingBall) {
            GM_setValue('ios_helper_x', parseInt(floatBall.style.left));
            GM_setValue('ios_helper_y', parseInt(floatBall.style.top));
        } else {
            // 双击逻辑
            const currentTime = Date.now();
            if (currentTime - lastClickTime < 300) {
                clearTimeout(clickTimeout);
                if (isRunning) {
                    log("⏸ [悬浮球双击] 流程运行中,触发暂停/继续状态切换", "system");
                    btnPauseBuild.click();
                } else {
                    log("▶ [悬浮球双击] 触发一键极速启动自动化投放流程...", "info");
                    if (btnStartBuild.classList.contains('ios-btn-disabled')) {
                        log("❌ 极速启动失败:请先导入 Excel 数据并选定抖音账号!", "error");
                    } else {
                        btnStartBuild.click();
                    }
                }
            } else {
                // 单击候选
                clickTimeout = setTimeout(() => {
                    togglePanelMinimizeState(false);
                }, 300);
            }
            lastClickTime = currentTime;
        }
    }

    // 控制台窗口大小切换处理器
    function togglePanelMinimizeState(minimize) {
        if (minimize) {
            panel.style.display = 'none';
            floatBall.style.display = 'flex';
            GM_setValue('ios_helper_minimized', 'true');
        } else {
            panel.style.display = 'flex';
            floatBall.style.display = 'none';
            GM_setValue('ios_helper_minimized', 'false');
        }
    }

    // 最小化事件
    document.getElementById('btn-minimize-panel').addEventListener('click', (e) => {
        e.stopPropagation();
        togglePanelMinimizeState(true);
    });

    // 7. 三页分段式 Tab 切换机制
    tabAuto.addEventListener('click', () => {
        tabAuto.classList.add('active');
        tabDouyin.classList.remove('active');
        tabAdmin.classList.remove('active');
        paneAuto.classList.add('active-pane');
        paneDouyin.classList.remove('active-pane');
        paneAdmin.classList.remove('active-pane');
    });

    tabDouyin.addEventListener('click', () => {
        tabDouyin.classList.add('active');
        tabAuto.classList.remove('active');
        tabAdmin.classList.remove('active');
        paneDouyin.classList.add('active-pane');
        paneAuto.classList.remove('active-pane');
        paneAdmin.classList.remove('active-pane');
        renderDouyinList(); // 渲染抖音列表
    });

    tabAdmin.addEventListener('click', () => {
        tabAdmin.classList.add('active');
        tabAuto.classList.remove('active');
        tabDouyin.classList.remove('active');
        paneAdmin.classList.add('active-pane');
        paneAuto.classList.remove('active-pane');
        paneDouyin.classList.remove('active-pane');
    });

    // 全域 ROI 系数配置持久化载入
    const storedRoi = GM_getValue(ROI_COEFFICIENT_KEY, '');
    const isRoiLocked = GM_getValue(ROI_LOCKED_KEY, 'false');

    if (storedRoi) {
        roiInput.value = storedRoi;
    }

    if (isRoiLocked === 'true') {
        roiInput.disabled = true;
        btnSaveRoi.style.display = 'none';
        btnEditRoi.style.display = 'block';
    } else {
        roiInput.disabled = false;
        btnSaveRoi.style.display = 'block';
        btnEditRoi.style.display = 'none';
    }

    // 点击确定保存 ROI
    btnSaveRoi.addEventListener('click', () => {
        const val = parseFloat(roiInput.value);
        if (isNaN(val)) {
            log("❌ 保存失败:请输入有效的ROI数值!", "error");
            return;
        }

        const minLimit = GM_getValue(ROI_MIN_LIMIT_KEY, '');
        const maxLimit = GM_getValue(ROI_MAX_LIMIT_KEY, '');

        if (minLimit !== '' && val < parseFloat(minLimit)) {
            log(`❌ 保存失败:当前设定的系数 [${val}] 低于管理设定的防错下限 [${minLimit}]!`, "error");
            return;
        }
        if (maxLimit !== '' && val > parseFloat(maxLimit)) {
            log(`❌ 保存失败:当前设定的系数 [${val}] 高于管理设定的防错上限 [${maxLimit}]!`, "error");
            return;
        }

        GM_setValue(ROI_COEFFICIENT_KEY, val);
        GM_setValue(ROI_LOCKED_KEY, 'true');
        roiInput.disabled = true;
        btnSaveRoi.style.display = 'none';
        btnEditRoi.style.display = 'block';
        log(`✨ ROI 系数已成功配置并锁定为:【${val}】`, "info");
    });

    // ROI 修改解锁
    btnEditRoi.addEventListener('click', () => {
        GM_setValue(ROI_LOCKED_KEY, 'false');
        roiInput.disabled = false;
        btnSaveRoi.style.display = 'block';
        btnEditRoi.style.display = 'none';
        roiInput.focus();
        log("📝 ROI 系数已解锁,已进入可修改编辑模式。", "system");
    });

    // TAB 3: 管理页面上下限载入与保存
    minLimitInput.value = GM_getValue(ROI_MIN_LIMIT_KEY, '');
    maxLimitInput.value = GM_getValue(ROI_MAX_LIMIT_KEY, '');

    btnSaveAdminLimits.addEventListener('click', () => {
        const minVal = minLimitInput.value.trim();
        const maxVal = maxLimitInput.value.trim();

        if (minVal !== '' && isNaN(parseFloat(minVal))) {
            log("❌ 保存失败:防错最小值必须为有效数字!", "error");
            return;
        }
        if (maxVal !== '' && isNaN(parseFloat(maxVal))) {
            log("❌ 保存失败:防错最大值必须为有效数字!", "error");
            return;
        }

        if (minVal !== '' && maxVal !== '' && parseFloat(minVal) > parseFloat(maxVal)) {
            log("❌ 保存失败:安全防错下限不可大于防错上限!", "error");
            return;
        }

        GM_setValue(ROI_MIN_LIMIT_KEY, minVal);
        GM_setValue(ROI_MAX_LIMIT_KEY, maxVal);
        log("🔒 ROI 安全管理上下限已保存成功。", "info");
    });

    // ==========================================
    // TAB 3 创意标题管理控制逻辑极其数据持久化
    // ==========================================
    if (titleInput) {
        titleInput.value = GM_getValue(TITLES_STORAGE_KEY, '');
    }

    function updateTitlesUIState(locked) {
        if (!titleInput || !btnLockTitles || !btnModifyTitles) return;
        if (locked === 'true' || locked === true) {
            titleInput.disabled = true;
            btnLockTitles.style.display = 'none';
            btnModifyTitles.style.display = 'block';
        } else {
            titleInput.disabled = false;
            btnLockTitles.style.display = 'block';
            btnModifyTitles.style.display = 'none';
        }
    }

    // 加载时自检创意标题锁定状态
    const isTitlesLocked = GM_getValue(TITLES_LOCKED_KEY, 'false');
    updateTitlesUIState(isTitlesLocked);

    // 【锁定】按钮绑定事件
    if (btnLockTitles) {
        btnLockTitles.addEventListener('click', () => {
            const rawValue = titleInput.value; // 保留换行符 \n
            GM_setValue(TITLES_STORAGE_KEY, rawValue);
            GM_setValue(TITLES_LOCKED_KEY, 'true');
            updateTitlesUIState(true);

            // 计算有效行数并日志反馈
            const lineCount = rawValue.split('\n').filter(line => line.trim() !== '').length;
            log(`🔒 标题库已保存并锁定!(共 ${lineCount} 行有效标题)`, "info");
        });
    }

    // 【修改】按钮绑定事件
    if (btnModifyTitles) {
        btnModifyTitles.addEventListener('click', () => {
            GM_setValue(TITLES_LOCKED_KEY, 'false');
            updateTitlesUIState(false);
            titleInput.focus();
            log("📝 标题库已解锁,进入编辑修改模式。", "system");
        });
    }

    // 【清空】按钮绑定事件
    if (btnClearTitles) {
        btnClearTitles.addEventListener('click', () => {
            titleInput.value = '';
            GM_deleteValue(TITLES_STORAGE_KEY);
            GM_setValue(TITLES_LOCKED_KEY, 'false');
            updateTitlesUIState(false);
            log("🧹 标题库已彻底清空,并重置为未锁定编辑状态。", "system");
        });
    }

    // ==========================================
    // TAB 3 产品卖点管理控制逻辑极其数据持久化
    // ==========================================
    if (pointsInput) {
        pointsInput.value = GM_getValue(SELLING_POINTS_STORAGE_KEY, '');
    }

    function updatePointsUIState(locked) {
        if (!pointsInput || !btnLockPoints || !btnModifyPoints) return;
        if (locked === 'true' || locked === true) {
            pointsInput.disabled = true;
            btnLockPoints.style.display = 'none';
            btnModifyPoints.style.display = 'block';
        } else {
            pointsInput.disabled = false;
            btnLockPoints.style.display = 'block';
            btnModifyPoints.style.display = 'none';
        }
    }

    // 加载时自检产品的锁定状态
    const isPointsLocked = GM_getValue(SELLING_POINTS_LOCKED_KEY, 'false');
    updatePointsUIState(isPointsLocked);

    // 产品卖点 【锁定】 按钮绑定事件
    if (btnLockPoints) {
        btnLockPoints.addEventListener('click', () => {
            const rawValue = pointsInput.value; // 保留换行 \n
            GM_setValue(SELLING_POINTS_STORAGE_KEY, rawValue);
            GM_setValue(SELLING_POINTS_LOCKED_KEY, 'true');
            updatePointsUIState(true);

            const lineCount = rawValue.split('\n').filter(line => line.trim() !== '').length;
            log(`🔒 产品卖点库已保存并锁定!(共 ${lineCount} 条有效产品卖点)`, "info");
        });
    }

    // 产品卖点 【修改】 按钮绑定事件
    if (btnModifyPoints) {
        btnModifyPoints.addEventListener('click', () => {
            GM_setValue(SELLING_POINTS_LOCKED_KEY, 'false');
            updatePointsUIState(false);
            pointsInput.focus();
            log("📝 产品卖点库已解锁,进入编辑修改模式。", "system");
        });
    }

    // 产品卖点 【清空】 按钮绑定事件
    if (btnClearPoints) {
        btnClearPoints.addEventListener('click', () => {
            pointsInput.value = '';
            GM_deleteValue(SELLING_POINTS_STORAGE_KEY);
            GM_setValue(SELLING_POINTS_LOCKED_KEY, 'false');
            updatePointsUIState(false);
            log("🧹 产品卖点库已彻底清空,并重置为未锁定编辑状态。", "system");
        });
    }

    // ==========================================
    // TAB 3 项目名模板管理控制逻辑极其数据持久化
    // ==========================================
    if (projectNameInput) {
        projectNameInput.value = GM_getValue(PROJECT_NAME_STORAGE_KEY, '');
    }

    function updateProjectNameUIState(locked) {
        if (!projectNameInput || !btnLockProjectName || !btnModifyProjectName) return;
        if (locked === 'true' || locked === true) {
            projectNameInput.disabled = true;
            btnLockProjectName.style.display = 'none';
            btnModifyProjectName.style.display = 'block';
            if (btnInsertDateTag) btnInsertDateTag.classList.add('ios-btn-disabled');
            if (btnInsertDramaTag) btnInsertDramaTag.classList.add('ios-btn-disabled');
            if (btnInsertRoiTag) btnInsertRoiTag.classList.add('ios-btn-disabled');
        } else {
            projectNameInput.disabled = false;
            btnLockProjectName.style.display = 'block';
            btnModifyProjectName.style.display = 'none';
            if (btnInsertDateTag) btnInsertDateTag.classList.remove('ios-btn-disabled');
            if (btnInsertDramaTag) btnInsertDramaTag.classList.remove('ios-btn-disabled');
            if (btnInsertRoiTag) btnInsertRoiTag.classList.remove('ios-btn-disabled');
        }
    }

    const isProjectNameLocked = GM_getValue(PROJECT_NAME_LOCKED_KEY, 'false');
    updateProjectNameUIState(isProjectNameLocked);

    if (btnInsertDateTag) {
        btnInsertDateTag.addEventListener('click', () => {
            if (projectNameInput.disabled) return;
            insertAtCursor(projectNameInput, '<日期>');
        });
    }

    if (btnInsertDramaTag) {
        btnInsertDramaTag.addEventListener('click', () => {
            if (projectNameInput.disabled) return;
            insertAtCursor(projectNameInput, '<剧目>');
        });
    }

    if (btnInsertRoiTag) {
        btnInsertRoiTag.addEventListener('click', () => {
            if (projectNameInput.disabled) return;
            insertAtCursor(projectNameInput, '<ROI>');
        });
    }

    if (btnLockProjectName) {
        btnLockProjectName.addEventListener('click', () => {
            const rawValue = projectNameInput.value;
            GM_setValue(PROJECT_NAME_STORAGE_KEY, rawValue);
            GM_setValue(PROJECT_NAME_LOCKED_KEY, 'true');
            updateProjectNameUIState(true);
            log(`🔒 项目名模板已保存并锁定!`, "info");
        });
    }

    if (btnModifyProjectName) {
        btnModifyProjectName.addEventListener('click', () => {
            GM_setValue(PROJECT_NAME_LOCKED_KEY, 'false');
            updateProjectNameUIState(false);
            projectNameInput.focus();
            log("📝 项目名模板已解锁,进入编辑修改模式。", "system");
        });
    }

    if (btnClearProjectName) {
        btnClearProjectName.addEventListener('click', () => {
            projectNameInput.value = '';
            GM_deleteValue(PROJECT_NAME_STORAGE_KEY);
            GM_setValue(PROJECT_NAME_LOCKED_KEY, 'false');
            updateProjectNameUIState(false);
            log("🧹 项目名模板已彻底清空,并重置为未锁定编辑状态。", "system");
        });
    }

    // 局部智能清除“已搭”记录逻辑
    function removeSpecificYida(drama, dyid) {
        let yidaRecords = {};
        try {
            const savedYida = GM_getValue(YIDA_RECORDS_KEY, '{}');
            yidaRecords = JSON.parse(savedYida);
        } catch (e) {
            console.error(e);
        }

        const cleanDrama = String(drama).trim().replace(/\s+/g, '');
        const cleanDyid = String(dyid).trim();
        const key = `${cleanDrama}_${cleanDyid}`;

        if (yidaRecords[key]) {
            delete yidaRecords[key];
            GM_setValue(YIDA_RECORDS_KEY, JSON.stringify(yidaRecords));
            log(`✨ 已成功解除剧目 【${drama}】 的已搭标记!`, 'info');
        }

        // 同步本地Excel内存缓存数据
        if (excelData && excelData.length > 1) {
            const activeAccount = douyinAccounts.find(acc => acc.id === cleanDyid);
            let colIdx = -1;
            if (activeAccount) {
                colIdx = getDouyinColumnIndex(excelData, activeAccount.id, activeAccount.name);
            }
            if (colIdx !== -1) {
                const rows = excelData.slice(1);
                const rIdx = rows.findIndex(r => String(r[0]).trim().replace(/\s+/g, '') === cleanDrama);
                if (rIdx !== -1) {
                    if (excelData[rIdx + 1] && excelData[rIdx + 1][colIdx] === "已搭") {
                        excelData[rIdx + 1][colIdx] = ""; // 还原为空白
                        GM_setValue(STORAGE_KEY, JSON.stringify(excelData));
                    }
                }
            }
        }

        // 重新检索最新的未搭建进度索引并安全覆盖
        const nextIdx = getNextUncompletedIndex();
        GM_setValue(PROGRESS_INDEX_KEY, nextIdx);
        const rows = excelData ? excelData.slice(1) : [];
        if (rows[nextIdx]) {
            updateCurrentBuildingDramaName(rows[nextIdx][0]);
        } else {
            updateCurrentBuildingDramaName('--');
        }

        renderPreviewTable();
    }

    // 对预览表的数据容器进行事件委托绑定
    if (dataPreviewBox) {
        dataPreviewBox.addEventListener('click', (e) => {
            const targetBadge = e.target.closest('.yida-badge-btn');
            if (targetBadge) {
                e.stopPropagation();
                const drama = targetBadge.getAttribute('data-drama');
                const dyid = targetBadge.getAttribute('data-dyid');
                if (drama && dyid) {
                    removeSpecificYida(drama, dyid);
                }
            }
        });
    }

    // 8. 渲染抖音列表 (CRUD)
    function renderDouyinList() {
        if (!douyinAccounts || douyinAccounts.length === 0) {
            douyinListBox.innerHTML = `
                <div style="font-size: 13px; color: var(--ios-text-sec); text-align: center; padding: 25px 0;">
                    抖音库暂无配置,请在上方录入
                </div>
            `;
            updateActiveDouyinDropdown();
            return;
        }

        let html = '';
        douyinAccounts.forEach((acc, idx) => {
            html += `
                <div class="dy-item">
                    <div class="dy-item-info">
                        <div class="dy-item-name">${acc.name}</div>
                        <div class="dy-item-details">ID: ${acc.id} ${acc.remark ? `| 备注: ${acc.remark}` : ''}</div>
                    </div>
                    <div class="dy-item-actions">
                        <button class="dy-action-btn dy-btn-edit" data-idx="${idx}">编辑</button>
                        <button class="dy-action-btn dy-btn-delete" data-idx="${idx}">删除</button>
                    </div>
                </div>
            `;
        });
        douyinListBox.innerHTML = html;

        // 绑定编辑 and 删除按钮
        douyinListBox.querySelectorAll('.dy-btn-edit').forEach(btn => {
            btn.addEventListener('click', (e) => {
                const idx = parseInt(e.target.getAttribute('data-idx'));
                enterEditMode(idx);
            });
        });

        douyinListBox.querySelectorAll('.dy-btn-delete').forEach(btn => {
            btn.addEventListener('click', (e) => {
                const idx = parseInt(e.target.getAttribute('data-idx'));
                deleteDouyinAccount(idx);
            });
        });

        updateActiveDouyinDropdown();
    }

    // 同步 Tab 1 下拉选择框
    function updateActiveDouyinDropdown() {
        if (!activeDouyinSelect) return;

        const currentActiveId = GM_getValue(ACTIVE_DOUYIN_KEY, '');
        let html = '<option value="">-- 请选择要投放的抖音号 --</option>';

        if (douyinAccounts && douyinAccounts.length > 0) {
            douyinAccounts.forEach(acc => {
                const isSelected = acc.id === currentActiveId ? 'selected' : '';
                html += `<option value="${acc.id}" ${isSelected}>${acc.name} (ID: ${acc.id})</option>`;
            });
        } else {
            html = '<option value="">-- 请先在“抖音号管理”中录入账号 --</option>';
        }

        activeDouyinSelect.innerHTML = html;
    }

    // 进入编辑模式
    function enterEditMode(index) {
        editIndex = index;
        const acc = douyinAccounts[index];
        dyInputId.value = acc.id;
        dyInputName.value = acc.name;
        dyInputRemark.value = acc.remark || '';

        dyFormTitle.innerText = "修改抖音号配置";
        btnSaveDouyin.innerText = "保存修改";
        btnCancelDouyin.style.display = "block";
    }

    // 退出编辑模式
    function exitEditMode() {
        editIndex = -1;
        dyInputId.value = '';
        dyInputName.value = '';
        dyInputRemark.value = '';

        dyFormTitle.innerText = "添加抖音号";
        btnSaveDouyin.innerText = "保存抖音号";
        btnCancelDouyin.style.display = "none";
    }

    // 删除抖音号
    function deleteDouyinAccount(index) {
        const deleted = douyinAccounts[index];
        douyinAccounts.splice(index, 1);
        GM_setValue(DOUYIN_STORAGE_KEY, JSON.stringify(douyinAccounts));

        const currentActiveId = GM_getValue(ACTIVE_DOUYIN_KEY, '');
        if (currentActiveId === deleted.id) {
            GM_deleteValue(ACTIVE_DOUYIN_KEY);
        }

        if (editIndex === index) {
            exitEditMode();
        } else if (editIndex > index) {
            editIndex--;
        }
        renderDouyinList();
    }

    // ==========================================
    // 🌟 补全抖音号管理页的核心交互事件监听 🌟
    // ==========================================
    if (btnSaveDouyin) {
        btnSaveDouyin.addEventListener('click', () => {
            const id = dyInputId.value.trim();
            const name = dyInputName.value.trim();
            const remark = dyInputRemark.value.trim();

            if (!id || !name) {
                log("❌ 保存失败:抖音ID和抖音名称不能为空!", "error");
                return;
            }

            if (editIndex === -1) {
                // 新增状态下,校验 ID 的唯一性
                const exists = douyinAccounts.some(acc => acc.id === id);
                if (exists) {
                    log(`❌ 保存失败:已存在 ID 为 [${id}] 的抖音号!`, "error");
                    return;
                }
                douyinAccounts.push({ id, name, remark });
                log(`✨ 成功保存并添加新抖音号:【${name}】(ID: ${id})`, "info");
            } else {
                // 修改编辑状态
                const originalId = douyinAccounts[editIndex].id;
                if (originalId !== id) {
                    const exists = douyinAccounts.some((acc, idx) => idx !== editIndex && acc.id === id);
                    if (exists) {
                        log(`❌ 修改失败:已存在 ID 为 [${id}] 的其他抖音号!`, "error");
                        return;
                    }
                }
                douyinAccounts[editIndex] = { id, name, remark };
                log(`✨ 成功修改抖音号:【${name}】(ID: ${id})`, "info");
                exitEditMode();
            }

            // 安全将修改或新增写入油猴持久化沙盒
            GM_setValue(DOUYIN_STORAGE_KEY, JSON.stringify(douyinAccounts));
            renderDouyinList();

            // 成功后自动清空表单
            dyInputId.value = '';
            dyInputName.value = '';
            dyInputRemark.value = '';
        });
    }

    if (btnCancelDouyin) {
        btnCancelDouyin.addEventListener('click', () => {
            exitEditMode();
            log("📝 已取消当前修改并复位表单。", "system");
        });
    }

    // 🌟 第一页:当前选择投放的抖音号下拉菜单切换时,联动沙盒更新并自适应刷新 previewTable 🌟
    if (activeDouyinSelect) {
        activeDouyinSelect.addEventListener('change', (e) => {
            const selectedId = e.target.value;
            GM_setValue(ACTIVE_DOUYIN_KEY, selectedId);
            log(`🎯 已成功切换当前投放抖音号:【${selectedId || '未选择'}】`, 'info');

            // 联动重绘:抖音号切换后,其对应的“已搭”、“未搭”映射列发生改变,必须重新计算当前未搭建的第一行起点进度!
            const nextIdx = getNextUncompletedIndex();
            GM_setValue(PROGRESS_INDEX_KEY, nextIdx);

            const rows = excelData ? excelData.slice(1) : [];
            if (rows[nextIdx]) {
                updateCurrentBuildingDramaName(rows[nextIdx][0]);
            } else {
                updateCurrentBuildingDramaName('--');
            }

            // 触发局部重绘,自动重新刷新 Excel 预览视图的高亮行
            renderPreviewTable();
        });
    }

    // 辅助工具:模糊对应列位置
    function getDouyinColumnIndex(excelData, activeId, activeName) {
        if (!excelData || excelData.length === 0) return -1;
        const headers = excelData[0];
        const cleanId = String(activeId).trim().toLowerCase();
        const cleanName = String(activeName).trim().toLowerCase();
        for (let j = 0; j < headers.length; j++) {
            if (!headers[j]) continue;
            const headerVal = String(headers[j]).trim().toLowerCase();
            if (headerVal === cleanId || headerVal === cleanName || headerVal.includes(cleanId) || headerVal.includes(cleanName)) {
                return j;
            }
        }
        return -1;
    }

    // 智能获取下一行尚未搭建的剧目行索引
    function getNextUncompletedIndex() {
        if (!excelData || excelData.length <= 1) return 0;
        const rows = excelData.slice(1);
        const activeId = GM_getValue(ACTIVE_DOUYIN_KEY, '');
        if (!activeId) return 0;

        const activeAccount = douyinAccounts.find(acc => acc.id === activeId);
        let colIdx = -1;
        if (activeAccount) {
            colIdx = getDouyinColumnIndex(excelData, activeAccount.id, activeAccount.name);
        }

        let yidaRecords = {};
        try {
            const savedYida = GM_getValue(YIDA_RECORDS_KEY, '{}');
            yidaRecords = JSON.parse(savedYida);
        } catch (e) {}

        for (let r = 0; r < rows.length; r++) {
            const drama = String(rows[r][0]).trim().replace(/\s+/g, '');
            const cleanActiveId = String(activeId).trim();
            const key = `${drama}_${cleanActiveId}`;
            const hasYida = yidaRecords[key] === true || (colIdx !== -1 && rows[r][colIdx] && String(rows[r][colIdx]).trim() === '已搭');

            if (!hasYida) {
                return r; // 返回首个未搭行
            }
        }
        return rows.length; // 全部已完成
    }

    // 将特定剧目在安全库与 Excel 中标记为已搭
    function markDramaAsCompleted(dramaName) {
        if (!dramaName || dramaName === '--') return;
        const activeId = GM_getValue(ACTIVE_DOUYIN_KEY, '');
        if (!activeId) return;

        // 1. 持久化至沙盒已搭库
        let yidaRecords = {};
        try {
            const savedYida = GM_getValue(YIDA_RECORDS_KEY, '{}');
            yidaRecords = JSON.parse(savedYida);
        } catch (e) {}
        const cleanDramaName = String(dramaName).trim().replace(/\s+/g, '');
        const cleanActiveId = String(activeId).trim();
        yidaRecords[`${cleanDramaName}_${cleanActiveId}`] = true;
        GM_setValue(YIDA_RECORDS_KEY, JSON.stringify(yidaRecords));

        // 2. 标记本地内存中的 Excel 数据
        if (excelData && excelData.length > 1) {
            const rows = excelData.slice(1);
            const activeAccount = douyinAccounts.find(acc => acc.id === activeId);
            let colIdx = -1;
            if (activeAccount) {
                colIdx = getDouyinColumnIndex(excelData, activeAccount.id, activeAccount.name);
            }
            if (colIdx !== -1) {
                const rIdx = rows.findIndex(r => String(r[0]).trim().replace(/\s+/g, '') === cleanDramaName);
                if (rIdx !== -1) {
                    if (!excelData[rIdx + 1]) excelData[rIdx + 1] = [];
                    while (excelData[rIdx + 1].length <= colIdx) {
                        excelData[rIdx + 1].push("");
                    }
                    excelData[rIdx + 1][colIdx] = "已搭";
                    GM_setValue(STORAGE_KEY, JSON.stringify(excelData));
                }
            }
        }
        log(`📝 剧目 【${dramaName}】 已成功标记为 “已搭”状态!`, 'info');
        renderPreviewTable();
    }

    // 全局提交/发布监控哨兵
    document.addEventListener('click', (e) => {
        const btn = e.target.closest('button, .oc-button, .ovui-button, [role="button"]');
        if (btn) {
            const txt = btn.textContent.trim();
            const isInsidePopup = btn.closest('.ovui-drawer, .ovui-dialog, .product-drawer-wrap, .ovui-modal, [class*="drawer"], [class*="dialog"], [class*="modal"]');
            if (!isInsidePopup) {
                if (txt.includes('发布') || txt.includes('保存') || txt.includes('提交') || txt.includes('确定创建') || txt.includes('新建项目')) {
                    const currentDrama = document.getElementById('current-building-drama-name')?.innerText;
                    if (currentDrama && currentDrama !== '--') {
                        log(`[物理提交拦截] 侦测到官方项目保存动作【${txt}】,自动同步剧目 “${currentDrama}” 为已搭状态...`, 'system');
                        markDramaAsCompleted(currentDrama);

                        const nextIdx = getNextUncompletedIndex();
                        GM_setValue(PROGRESS_INDEX_KEY, nextIdx);
                        renderPreviewTable(); // 🌟 同步渲染
                    }
                }
            }
        }
    }, true);

    // 自动扫描与获取可见 IAP 链接输入框
    function findIapInputElements() {
        return Array.from(document.querySelectorAll('input.ovui-input[placeholder*="IAP"], input[placeholder*="IAP"]'))
                    .filter(isVisible);
    }

    // 🌟 全局高精度自动定位项目管理页面“总计 X 项” 🌟
    function getManageTotalProjects() {
        // 方法 A: 高精度 summary 特征单元格检索
        const summaryCells = document.querySelectorAll('.ovui-t-summary-cell, .ovui-table-cell, .ovui-tr-summary, [class*="summary"]');
        for (const cell of summaryCells) {
            const text = cell.textContent.trim();
            const match = text.match(/总计\s*(\d+)\s*项/);
            if (match) {
                return parseInt(match[1], 10);
            }
        }
        // 方法 B: 降级兜底文字穿透扫描
        const spans = document.querySelectorAll('span, th, td, div');
        for (const el of spans) {
            if (el.children.length === 0 || (el.children.length === 1 && el.firstElementChild?.tagName === 'SPAN')) {
                const text = el.textContent.trim();
                const match = text.match(/总计\s*(\d+)\s*项/);
                if (match) {
                    return parseInt(match[1], 10);
                }
            }
        }
        return null;
    }

    // 🌟 全局高鲁棒性精确定位 IAP 链接 “点击添加” 按钮 🌟
    function findIapAddButton() {
        const firstBtnWrap = document.querySelector('div[data-e2e*="roi/ad/create__creativeTitleGroup"].oc-add-button, div[data-e2e*="creativeTitleGroup].oc-add-button');
        if (firstBtnWrap && isVisible(firstBtnWrap)) {
            const btnEl = firstBtnWrap.querySelector('button') || firstBtnWrap;
            if (btnEl && btnEl.textContent.trim().includes('点击添加')) {
                return btnEl;
            }
        }

        const basicModule = document.querySelector('#BasicMaterialModule');
        if (basicModule) {
            const addButtons = Array.from(basicModule.querySelectorAll('.oc-add-button, .oc-add-button button, div[class*="oc-add-button"]'))
                                    .filter(isVisible);
            const matchedBtn = addButtons.find(el => {
                const txt = el.textContent.trim();
                return txt.includes('点击添加') && !txt.includes('标签') && !txt.includes('商品');
            });
            if (matchedBtn) return matchedBtn;
        }

        const iapInputs = findIapInputElements();
        if (iapInputs.length > 0) {
            const lastInput = iapInputs[iapInputs.length - 1];
            let curr = lastInput;
            for (let d = 0; d < 6; d++) {
                if (!curr.parentElement) break;
                curr = curr.parentElement;

                const innerBtn = curr.querySelector('.oc-add-button, button.ovui-button');
                if (innerBtn && isVisible(innerBtn) && innerBtn.textContent.trim().includes('点击添加')) {
                    return innerBtn;
                }

                let sibling = curr.nextElementSibling;
                while (sibling) {
                    const sibAddBtn = sibling.matches('.oc-add-button') ? sibling : sibling.querySelector('.oc-add-button');
                    if (sibAddBtn && isVisible(sibAddBtn) && sibAddBtn.textContent.trim().includes('点击添加')) {
                        const target = sibAddBtn.querySelector('button') || sibAddBtn;
                        return target;
                    }
                    sibling = sibling.nextElementSibling;
                }
            }
        }

        const candidates = Array.from(document.querySelectorAll('.oc-add-button, .oc-button-wrap-block button, button'));
        return candidates.find(el => {
            if (!isVisible(el)) return false;
            const txt = el.textContent.trim();
            return txt.includes('点击添加') && !txt.includes('商品') && !txt.includes('标签');
        });
    }

    // 链式自增 IAP 链接框生成器
    async function waitAndAddIapInput(targetIndex) {
        let addBtn = findIapAddButton();
        if (addBtn) {
            log(`- 🎯 成功捕获第 ${targetIndex + 1} 个 IAP 链接的 “点击添加” 按钮,正在模拟物理点击...`, 'system');
            triggerClick(addBtn);
            for (let j = 0; j < 25; j++) {
                await sleep(150);
                const currentInputs = findIapInputElements();
                if (currentInputs.length > targetIndex) {
                    log(`- ✨ 第 ${targetIndex + 1} 个 IAP 输入框已动态创建成功!`, 'system');
                    return currentInputs[targetIndex];
                }
            }
        } else {
            log(`❌ 定位 IAP 点击添加按钮失败,可能已达到该模块的最大录入限制。`, 'error');
        }
        return null;
    }

    // 辅助工具:向输入框光标处插入内容 (项目名配置专属)
    function insertAtCursor(myField, myValue) {
        if (!myField) return;
        if (myField.selectionStart || myField.selectionStart === 0) {
            const startPos = myField.selectionStart;
            const endPos = myField.selectionEnd;
            myField.value = myField.value.substring(0, startPos)
                + myValue
                + myField.value.substring(endPos, myField.value.length);
            myField.selectionStart = startPos + myValue.length;
            myField.selectionEnd = startPos + myValue.length;
        } else {
            myField.value += myValue;
        }
        myField.dispatchEvent(new Event('input', { bubbles: true }));
        myField.dispatchEvent(new Event('change', { bubbles: true }));
    }

    // 辅助工具:提取和计算当前日期为 %m%d 形式
    function getFormattedDate() {
        const now = new Date();
        const month = String(now.getUTCMonth() + 1).padStart(2, '0');
        const date = String(now.getUTCDate()).padStart(2, '0');
        return month + date; // e.g. "0610"
    }

    // 辅助工具:解析项目名模板中的占位符标签
    function processProjectNameTemplate(template, dramaName) {
        if (!template) return '';
        let output = template;
        output = output.replace(/<日期>/g, getFormattedDate());
        output = output.replace(/<剧目>/g, dramaName || '');
        const roiVal = GM_getValue(ROI_COEFFICIENT_KEY, '');
        output = output.replace(/<ROI>/g, roiVal || '');
        return output;
    }

    // 🌟 v5.17 深度重构表格预览渲染机制 🌟
    function renderPreviewTable() {
        if (!excelData || excelData.length === 0) {
            dataStatusTitle.innerText = '数据载入状态';
            dataPreviewBox.innerHTML = `
                <div style="font-size: 13px; color: var(--ios-text-sec); text-align: center; padding: 20px 0;">
                    暂无导入数据,请先导入Excel
                </div>
            `;
            btnStartBuild.classList.add('ios-btn-disabled');
            return;
        }

        btnStartBuild.classList.remove('ios-btn-disabled');
        const rowCount = excelData.length;
        const colCount = excelData[0] ? excelData[0].length : 0;

        // 读取当前正处于搭建进程中的行索引 (0-based)
        let currentIndex = parseInt(GM_getValue(PROGRESS_INDEX_KEY, '0'));
        if (isNaN(currentIndex)) currentIndex = 0;

        dataStatusTitle.innerText = `数据已载入 (共 ${rowCount} 行, ${colCount} 列 | 当前第 ${currentIndex + 1} 行)`;

        let tableHTML = `
            <div class="excel-preview-container">
                <table class="excel-table">
                    <thead>
                        <tr>
        `;

        const headers = excelData[0] || [];
        headers.forEach(header => {
            tableHTML += `<th>${header !== undefined ? header : ''}</th>`;
        });
        tableHTML += `
                        </tr>
                    </thead>
                    <tbody>
        `;

        let yidaRecords = {};
        try {
            const savedYida = GM_getValue(YIDA_RECORDS_KEY, '{}');
            yidaRecords = JSON.parse(savedYida);
        } catch (e) {
            console.error(e);
        }

        // 计算当前搭建行所处于的绝对 Excel 数据行索引 (考虑 header 占用 index 0,所以 targetCenter 需为 currentIndex + 1)
        const targetCenter = currentIndex + 1;
        let startRow = Math.max(1, targetCenter - 5);
        let endRow = Math.min(rowCount - 1, targetCenter + 5);

        // 如果上下行边界补齐不够 11 行,则自动朝另一侧拉伸,确保最大限度提供 11 行视图空间
        if (endRow - startRow < 10) {
            if (startRow === 1) {
                endRow = Math.min(rowCount - 1, startRow + 10);
            } else if (endRow === rowCount - 1) {
                startRow = Math.max(1, endRow - 10);
            }
        }

        // 如果上方仍然有被折叠折蔽的数据行,绘制折叠标识
        if (startRow > 1) {
            tableHTML += `
                <tr>
                    <td colspan="${headers.length}" style="text-align: center; color: var(--ios-text-sec); background: #F8F8FA; font-size: 10px; padding: 4px 0; font-style: italic;">
                        ▲ 其余上方 ${startRow - 1} 行数据已折叠...
                    </td>
                </tr>
            `;
        }

        for (let i = startRow; i <= endRow; i++) {
            const isCurrentRow = (i === targetCenter);
            // 经典高亮正在搭建的黄金骨干数据行
            const trStyle = isCurrentRow ? 'style="background: rgba(255, 149, 0, 0.08); border-left: 3px solid var(--ios-orange);"' : '';
            tableHTML += `<tr ${trStyle}>`;
            const row = excelData[i] || [];
            const dramaName = row[0];

            for (let j = 0; j < headers.length; j++) {
                const val = row[j];
                let displayVal = val !== undefined ? val : '';

                let matchedId = '';
                const headerVal = String(headers[j]).trim().toLowerCase();
                const matchedAcc = douyinAccounts.find(acc => {
                    const cleanId = String(acc.id).trim().toLowerCase();
                    const cleanName = String(acc.name).trim().toLowerCase();
                    return headerVal === cleanId || headerVal === cleanName || headerVal.includes(cleanId) || headerVal.includes(cleanName);
                });
                if (matchedAcc) {
                    matchedId = matchedAcc.id;
                }

                const recordKey = `${dramaName}_${matchedId}`;
                const isYida = (matchedId && yidaRecords[recordKey]) || String(displayVal).trim() === '已搭';

                if (isYida) {
                    displayVal = `<span class="yida-badge-btn" data-drama="${dramaName}" data-dyid="${matchedId}" title="点击清除此条已搭标记并准备重搭">已搭 ✕</span>`;
                }
                tableHTML += `<td title="${val !== undefined ? val : ''}">${displayVal}</td>`;
            }
            tableHTML += `</tr>`;
        }

        // 如果下方仍然有折叠折蔽数据,绘制尾部标识
        if (endRow < rowCount - 1) {
            tableHTML += `
                <tr>
                    <td colspan="${headers.length}" style="text-align: center; color: var(--ios-text-sec); background: #F8F8FA; font-size: 10px; padding: 4px 0; font-style: italic;">
                        ▼ 其余下方 ${rowCount - 1 - endRow} 行数据已折叠...
                    </td>
                </tr>
            `;
        }

        tableHTML += `
                    </tbody>
                </table>
            </div>
        `;

        dataPreviewBox.innerHTML = tableHTML;
    }

    // 导入触发
    btnImportExcel.addEventListener('click', () => {
        filePicker.click();
    });

    // 导入处理 (🌟 加入国内多线路动态修补与深度容错)
    filePicker.addEventListener('change', (e) => {
        const file = e.target.files[0];
        if (!file) return;

        log(`正在解析文件: ${file.name}...`, 'system');
        const reader = new FileReader();

        reader.onload = async function(evt) {
            try {
                // 1. 在导入的第一关展开动态 XLSX 安全自检
                const isLoaded = await ensureXLSXLoaded();
                if (!isLoaded) {
                    log('❌ 导入失败:Excel 解析库 (XLSX) 未能正确加载,可能是因为您的网络已被完全切断或遭受防火墙彻底拦截。', 'error');
                    return;
                }

                // 2. 多重全局检索,杜绝 React 页面变量覆盖冲突
                const activeXLSX = typeof XLSX !== 'undefined' ? XLSX : (typeof window.XLSX !== 'undefined' ? window.XLSX : null);

                if (!activeXLSX || typeof activeXLSX.read !== 'function') {
                    log('❌ 导入失败:XLSX 变量未能获得有效的读取函数,请尝试刷新重试。', 'error');
                    return;
                }

                const data = new Uint8Array(evt.target.result);
                const workbook = activeXLSX.read(data, { type: 'array' });
                const firstSheetName = workbook.SheetNames[0];
                const worksheet = workbook.Sheets[firstSheetName];
                const parsedRows = XLSX.utils.sheet_to_json(worksheet, { header: 1 });

                if (parsedRows && parsedRows.length > 0) {
                    excelData = parsedRows;
                    GM_setValue(STORAGE_KEY, JSON.stringify(excelData));
                    log(`成功解析并存储 Excel,读取到 ${excelData.length} 行数据!已在沙盒安全固化。`, 'info');
                    renderPreviewTable();
                    checkAutoResumeState();
                } else {
                    log('Excel表格中未发现有效行数据', 'warning');
                }
            } catch (err) {
                log(`Excel解析失败: ${err.message}`, 'error');
                console.error(err);
            }
            filePicker.value = '';
        };

        reader.readAsArrayBuffer(file);
    });

    // 清空数据
    btnClearData.addEventListener('click', () => {
        if (!excelData) {
            log('当前没有可供清除的数据', 'warning');
            return;
        }
        excelData = null;
        GM_deleteValue(STORAGE_KEY);
        GM_deleteValue(YIDA_RECORDS_KEY);
        GM_deleteValue(AUTO_ACTIVE_KEY);
        GM_deleteValue(PROGRESS_INDEX_KEY);
        updateCurrentBuildingDramaName('--');
        log('沙盒中缓存 of Excel 数据及执行进度已彻底清空', 'system');
        renderPreviewTable();
    });

    // 手动已搭
    btnManualYida.addEventListener('click', () => {
        const currentDrama = document.getElementById('current-building-drama-name')?.innerText;
        if (!currentDrama || currentDrama === '--') {
            log('❌ 手动标记失败:当前没有正在搭建的剧目名称!', 'error');
            return;
        }
        markDramaAsCompleted(currentDrama);

        const nextIdx = getNextUncompletedIndex();
        GM_setValue(PROGRESS_INDEX_KEY, nextIdx);

        const rows = excelData.slice(1);
        if (rows[nextIdx]) {
            updateCurrentBuildingDramaName(rows[nextIdx][0]);
        } else {
            updateCurrentBuildingDramaName('--');
        }
        renderPreviewTable(); // 🌟 同步滚动视角对焦
    });

    // 重新运行
    btnRerunBuild.addEventListener('click', async () => {
        log("🔄 [一键重载] 正在强制重置运行状态并安全复位流程指针...", "system");

        isRunning = false;
        isPaused = false;
        updateBallStyle();

        GM_deleteValue(PROGRESS_INDEX_KEY);
        const correctedIndex = getNextUncompletedIndex();
        GM_setValue(PROGRESS_INDEX_KEY, correctedIndex);

        log(`- 🎯 起点校准完毕:首个未搭建的目标确定为:【第 ${correctedIndex + 1} 行】剧目。`, 'info');

        renderPreviewTable(); // 🌟 同步滚动视角对焦

        if (isCreatePage) {
            log("🔄 正在执行页面物理刷新以清空表单并准备接力...", "warning");
            await sleep(1000);
            location.reload();
        } else {
            log("▶ 正在跳转至“新建项目”...", "info");
            clickCreateProjectButton();
        }
    });

    // 开始构建按钮事件
    btnStartBuild.addEventListener('click', () => {
        if (btnStartBuild.classList.contains('ios-btn-disabled') || !excelData) {
            log('请先导入Excel数据再进行操作', 'warning');
            return;
        }

        const activeId = GM_getValue(ACTIVE_DOUYIN_KEY, '');
        if (!activeId && isCreatePage) {
            log("❌ 启动失败:请先在“当前投放抖音号”下拉框中选择要投放的抖音号!", "error");
            return;
        }

        updatePageStatus();

        if (isManagePage) {
            log('▶ 启动自动跳转至“新建项目”流程...', 'info');
            clickCreateProjectButton();
        } else if (isCreatePage) {
            if (isRunning) {
                log('⏱ 自动化已经在运行中,无需重复点击。', 'warning');
                return;
            }
            log('▶ 开始自动化表单填充流程...', 'info');
            executeAutomationWorkflow();
        } else {
            log('当前页面非巨量投放列表或新建项目页,无法执行此操作。', 'warning');
        }
    });

    // 暂停/继续
    btnPauseBuild.addEventListener('click', () => {
        if (btnPauseBuild.classList.contains('ios-btn-disabled') || !isRunning) {
            return;
        }

        if (!isPaused) {
            isPaused = true;
            btnPauseLabel.innerText = '继续';
            btnPauseBuild.classList.remove('ios-btn-orange');
            btnPauseBuild.classList.add('ios-btn-primary');
            log('⏸ 自动化流程已被用户手动[暂停]。', 'warning');
        } else {
            isPaused = false;
            btnPauseLabel.innerText = '暂停';
            btnPauseBuild.classList.remove('ios-btn-primary');
            btnPauseBuild.classList.add('ios-btn-orange');
            log('▶ 自动化流程已[继续]恢复运行。', 'info');
        }
        updateBallStyle();
    });

    // 定位并跳转到“新建项目”按钮
    function clickCreateProjectButton() {
        const jsSelector = "#app > div > div.oc-card.oc-card--scheme-primary > div > div > div.manage-filter > div:nth-child(1) > div > div.right-content > div:nth-child(1) > div.oc-button-wrap.oc-button-scene-default > button > span";

        let retryCount = 0;
        const maxRetries = 15;
        const interval = 600;

        log('正在查找“新建项目”按钮...', 'system');

        const findAndClickTimer = setInterval(() => {
            let target = null;
            const candidates = Array.from(document.querySelectorAll(jsSelector));
            for (const cand of candidates) {
                if (isVisible(cand)) {
                    target = cand;
                    break;
                }
            }

            if (!target) {
                const buttons = document.querySelectorAll('button');
                for (const b of buttons) {
                    if (b.textContent.trim().includes('新建项目') && isVisible(b)) {
                        target = b;
                        break;
                    }
                }
            }

            if (target) {
                clearInterval(findAndClickTimer);
                log('🎯 成功定位到“新建项目”按钮,正在触发点击并准备跨页面自启动...', 'info');

                GM_setValue(AUTO_ACTIVE_KEY, 'true');

                let nextIdx = GM_getValue(PROGRESS_INDEX_KEY, null);
                if (nextIdx === null) {
                    nextIdx = getNextUncompletedIndex();
                    GM_setValue(PROGRESS_INDEX_KEY, nextIdx);
                }
                log(`- 🎯 起点确认:将从 Excel 表第 ${parseInt(nextIdx) + 1} 行剧目开始跳转配置。`, 'info');

                renderPreviewTable(); // 🌟 同步对焦
                triggerClick(target);
            } else {
                retryCount++;
                log(`等待“新建项目”元素渲染中 (${retryCount}/${maxRetries})...`, 'warning');
                if (retryCount >= maxRetries) {
                    clearInterval(findAndClickTimer);
                    log('无法定位“新建项目”按钮,可能页面未完全加载,请手动点击。', 'error');
                }
            }
        }, interval);
    }

    // 仿真物理点击 (v5.22 高精重构:完美兼容 React/Vue 受控状态与合成事件)
    function triggerClick(element) {
        if (!element) return;
        try {
            // 如果 is 下拉选择器,展开内部的 wrapper 容器进行触发
            if (element.classList.contains('ovui-select') || element.closest('.ovui-select')) {
                const selectEl = element.classList.contains('ovui-select') ? element : element.closest('.ovui-select');
                const wrapper = selectEl.querySelector('.ovui-input__wrapper, .ovui-select__input');
                if (wrapper) {
                    element = wrapper;
                }
            } else if (element.tagName !== 'BUTTON' && element.querySelector('button')) {
                element = element.querySelector('button');
            }

            try {
                element.focus();
            } catch(e){}

            // 1. 优先调用原生原生 .click(),这是触发 React onChange 和事件合成的最佳且最稳定方式
            element.click();
        } catch (e) {
            console.error('原生 click 失败,尝试执行事件派发:', e);
        }

        // 2. 派发辅助鼠标/指针事件作为双重保险,确保物理触发完整
        try {
            const rect = element.getBoundingClientRect();
            const clientX = rect.left + rect.width / 2;
            const clientY = rect.top + rect.height / 2;
            const eventOpts = {
                bubbles: true,
                cancelable: true,
                view: window,
                clientX: clientX,
                clientY: clientY
            };
            element.dispatchEvent(new MouseEvent('mousedown', eventOpts));
            element.dispatchEvent(new MouseEvent('mouseup', eventOpts));
            element.dispatchEvent(new Event('change', { bubbles: true }));
        } catch(e){}
    }

    // 辅助工具:发送回车指令进行搜索 (支持 keydown, keypress, keyup 复合触发)
    function pressEnter(element) {
        if (!element) return;
        const events = ['keydown', 'keypress', 'keyup'];
        events.forEach(name => {
            try {
                element.dispatchEvent(new KeyboardEvent(name, {
                    bubbles: true,
                    cancelable: true,
                    key: 'Enter',
                    keyCode: 13,
                    code: 'Enter',
                    which: 13
                }));
            } catch (e) {}
        });
    }

    // 更新页面状态
    function updatePageStatus() {
        isManagePage = location.href.includes('/ad/web/manage');
        isCreatePage = location.href.includes('/ad/web/roi/ad/create');

        const btnTextLabel = document.getElementById('btn-text-label');
        if (btnTextLabel) {
            btnTextLabel.innerText = isManagePage ? '开始搭建 (跳转)' : '开始自动运行';
        }
    }

    // 异步抓取巨量后台项目总数,计算得出可搭建额度(500 阈值拦截控制)
    async function scrapeManageTotalAndCalc() {
        if (!isManagePage) return;

        log('🔍 正在检测巨量后台已有的总项目数...', 'system');
        let found = false;

        // 强轮询 40 次 (共约 20 秒) 适配网络延迟或 React 数据延迟加载
        for (let attempt = 0; attempt < 40; attempt++) {
            if (!isManagePage) return;
            const total = getManageTotalProjects();
            if (total !== null) {
                const remaining = Math.max(0, 500 - total);
                GM_setValue(TOTAL_CREATED_PROJECTS_KEY, total);
                GM_setValue(REMAINING_PROJECTS_KEY, remaining);

                updateRemainingUI();
                log(`📊 [数据同步成功] 巨量后台已有项目: ${total} 个 | 剩下可搭项目数: ${remaining} 个`, 'info');
                found = true;
                break;
            }
            await new Promise(resolve => setTimeout(resolve, 500));
        }

        if (!found) {
            const textContent = document.body.textContent;
            if (textContent.includes('暂无项目') || textContent.includes('点击上方按钮') || textContent.includes('暂无数据')) {
                GM_setValue(TOTAL_CREATED_PROJECTS_KEY, 0);
                GM_setValue(REMAINING_PROJECTS_KEY, 500);
                updateRemainingUI();
                log(`📊 [数据同步成功] 页面提示暂无项目,剩下可搭项目数: 500 个`, 'info');
            } else {
                log('⚠️ 未能捕获到“总计 X 项”或暂无项目提示,请在页面完全加载后重试。', 'warning');
            }
        }
    }

    // 新建项目自启动及重载接力
    async function checkAutoResumeState() {
        updatePageStatus();

        // 自动处理上一个搭建成功剧目的“已搭”落盘标记 (解决SPA页面销毁时数据无法保存的竞态瓶颈)
        const lastSubmitted = GM_getValue('oceanengine_last_submitted_drama', '');
        if (lastSubmitted) {
            log(`🎉 [自动接力哨兵] 侦测到项目 “${lastSubmitted}” 搭建成功并已成功跳转!自动执行沙盒“已搭”持久化更新...`, 'info');
            markDramaAsCompleted(lastSubmitted);
            GM_deleteValue('oceanengine_last_submitted_drama'); // 消费后立刻擦除,防止重复写盘
        }

        if (isCreatePage) {
            // 核心拦截:检测剩下可搭项目额度
            const remaining = GM_getValue(REMAINING_PROJECTS_KEY, 500);
            if (remaining <= 0) {
                log('🛑 [安全管控] 剩下可搭项目数为 0!已达到 500 个上限,自动搭建流程已安全终止!', 'error');
                isRunning = false;
                updateBallStyle();
                return;
            }

            if (excelData && excelData.length > 1) {
                const isMinimized = GM_getValue('ios_helper_minimized', 'false');
                if (isMinimized === 'true') {
                    togglePanelMinimizeState(true);
                }

                if (!isRunning && !isPaused) {
                    let nextIndex = GM_getValue(PROGRESS_INDEX_KEY, null);
                    if (nextIndex === null) {
                        nextIndex = getNextUncompletedIndex();
                        GM_setValue(PROGRESS_INDEX_KEY, nextIndex);
                    } else {
                        nextIndex = parseInt(nextIndex);
                    }

                    renderPreviewTable(); // 🌟 重绘对焦

                    const dramaList = excelData.slice(1);
                    if (dramaList[nextIndex]) {
                        updateCurrentBuildingDramaName(dramaList[nextIndex][0]);
                    }

                    log(`🔄 [自启动接力] 侦测到进入新建页面,系统将在 2 秒后接力第 ${nextIndex + 1} 行剧目配置...`, 'info');

                    setTimeout(() => {
                        if (!isRunning && !isPaused) {
                            executeAutomationWorkflow();
                        }
                    }, 2000);
                }
            } else {
                log('⚠️ 检测到进入新建页面,但尚未导入Excel数据。请在悬浮窗中先导入数据。', 'warning');
            }
        } else if (isManagePage) {
            // 🌟 1. 先抓取管理页最新的项目数并计算剩余搭建次数 (增加防挂防御沙盒)
            try {
                await scrapeManageTotalAndCalc();
            } catch (e) {
                console.error("安全采集已有项目总数发生崩溃:", e);
            }

            // 🌟 2. 检查抓取计算后的项目安全额度
            const remaining = GM_getValue(REMAINING_PROJECTS_KEY, 500);
            if (remaining <= 0) {
                log('🛑 [安全管控] 剩下可搭项目数为 0!已达到 500 个上限,拒绝执行新建,流程安全挂起。', 'error');
                GM_setValue(AUTO_ACTIVE_KEY, 'false');
                isRunning = false;
                updateBallStyle();
                return;
            }

            const autoActive = GM_getValue(AUTO_ACTIVE_KEY, 'false');
            if (autoActive === 'true') {
                const nextIdx = getNextUncompletedIndex();
                GM_setValue(PROGRESS_INDEX_KEY, nextIdx);
                renderPreviewTable(); // 🌟 同步滚动视角对焦

                // 如果后续已经没有未搭的数据,则彻底宣告大循环终止,重置引擎
                if (excelData && nextIdx >= excelData.length - 1) {
                    log('🎉 所有Excel剧目全量自动搭建投放完毕!自动化投放助手宣告功成身退。', 'info');
                    GM_setValue(AUTO_ACTIVE_KEY, 'false');
                    GM_deleteValue(PROGRESS_INDEX_KEY);
                    isRunning = false;
                    updateBallStyle();
                    return;
                }
                log('[自动流接力] 侦测到已成功退回到管理主页,正在自动为您点击“新建项目”以开始接力下一条剧目...', 'info');
                setTimeout(() => {
                    clickCreateProjectButton();
                }, 1500); // 预留1.5秒安全延迟等待官方React渲染
            }
        }
    }

    // 单页面 SPA 路由哨兵
    let lastUrl = location.href;
    setInterval(async () => {
        if (location.href !== lastUrl) {
            lastUrl = location.href;
            log('检测到网页单页面路由发生变更,正在重校助手状态...', 'system');
            await checkAutoResumeState();
        }
    }, 1200);

    // 🌟 waitAndFindElement 核心机制 🌟
    function waitAndFindElement(selectors, searchTexts = [], timeoutMs = 15000) {
        return new Promise((resolve, reject) => {
            let accumulatedTime = 0;
            const interval = 150; // 极速心跳轮询

            const timer = setInterval(() => {
                if (isPaused) return;
                if (!isRunning) {
                    clearInterval(timer);
                    reject(new Error("用户终止运行"));
                    return;
                }

                let el = null;

                for (const selector of selectors) {
                    if (typeof selector === 'function') {
                        try {
                            const cand = selector();
                            if (isVisible(cand)) {
                                el = cand;
                                break;
                            }
                        } catch(e) {}
                    } else {
                        const candidates = Array.from(document.querySelectorAll(selector));
                        for (const cand of candidates) {
                            if (isVisible(cand)) {
                                el = cand;
                                break;
                            }
                        }
                    }
                    if (el) break;
                }

                // 文本深度穿透扫描与可见性校对过滤
                if (!el && searchTexts.length > 0) {
                    const allElements = Array.from(document.querySelectorAll('div, span, button, input, p, label, a, strong'));
                    for (const candidate of allElements) {
                        if (!isVisible(candidate)) continue;
                        const txt = candidate.textContent.trim();
                        if (searchTexts.some(st => txt === st || txt.includes(st))) {
                            el = candidate;
                            break;
                        }
                    }
                }

                if (el) {
                    clearInterval(timer);
                    resolve(el);
                } else {
                    accumulatedTime += interval;
                    if (accumulatedTime > timeoutMs) {
                        clearInterval(timer);
                        reject(new Error(`等待目标元素超时`));
                    }
                }
            }, interval);
        });
    }

    // 仿真微等待
    async function sleep(ms) {
        let slept = 0;
        const interval = 100;
        while (slept < ms) {
            await checkPauseState();
            if (!isRunning) return;
            await new Promise(resolve => setTimeout(resolve, interval));
            if (!isPaused) {
                slept += interval;
            }
        }
    }

    // 全域 ROI 检索
    function findRoiInputElement() {
        const allLabels = Array.from(document.querySelectorAll('div, span, label, p'));
        for (const el of allLabels) {
            const txt = el.textContent.trim().replace(/\s+/g, '');
            if (txt === '全域ROI系数' || txt === '全域ROI') {
                let parent = el;
                for (let d = 0; d < 4; d++) {
                    if (!parent.parentElement) break;
                    parent = parent.parentElement;
                    const input = parent.querySelector("input[type='number'], input.ovui-input");
                    if (input && isVisible(input)) return input;
                }
            }
        }
        const selectors = [
            "div.oc-row-input input[type='number']",
            "input.ovui-input[type='number'][placeholder='请输入']",
            "div[class*='roi'] input.ovui-input[placeholder*='输入']",
            "div[class*='ROI'] input.ovui-input"
        ];
        for (const sel of selectors) {
            const cand = document.querySelector(sel);
            if (isVisible(cand)) return cand;
        }
        return null;
    }

    // ROI 扫描
    function waitAndFindRoiInput(timeoutMs = 8000) {
        return new Promise((resolve) => {
            const startTime = Date.now();
            const timer = setInterval(() => {
                if (isPaused) return;
                const el = findRoiInputElement();
                if (el) {
                    clearInterval(timer);
                    resolve(el);
                } else if (Date.now() - startTime > timeoutMs) {
                    clearInterval(timer);
                    resolve(null);
                }
            }, 400);
        });
    }

    // 🌟 全局高精度自动定位 IAA 链接输入框 🌟
    function findIaaInputElement() {
        const directPlaceholder = document.querySelector('input.ovui-input[placeholder="请输入IAA链接"], input[placeholder="请输入IAA链接"]');
        if (isVisible(directPlaceholder)) return directPlaceholder;

        const allLabels = Array.from(document.querySelectorAll('div, span, label, p'));
        for (const el of allLabels) {
            const txt = el.textContent.trim().replace(/\s+/g, '');
            if (txt.includes('IAA链接')) {
                let parent = el;
                for (let d = 0; d < 4; d++) {
                    if (!parent.parentElement) break;
                    parent = parent.parentElement;
                    const input = parent.querySelector("input.ovui-input, input");
                    if (isVisible(input)) return input;
                }
            }
        }

        const selectors = [
            "input.ovui-input[placeholder*='IAA']",
            "input[placeholder*='IAA']"
        ];
        for (const sel of selectors) {
            const candidates = Array.from(document.querySelectorAll(sel));
            for (const cand of candidates) {
                if (isVisible(cand)) return cand;
            }
        }
        return null;
    }

    // 🌟 IAA 链接输入框强力阻塞式快速侦测 🌟
    function waitAndFindIaaInput(timeoutMs = 8000) {
        return new Promise((resolve) => {
            const startTime = Date.now();
            const timer = setInterval(() => {
                if (isPaused) return;
                const el = findIaaInputElement();
                if (el) {
                    clearInterval(timer);
                    resolve(el);
                } else if (Date.now() - startTime > timeoutMs) {
                    clearInterval(timer);
                    resolve(null);
                }
            }, 150); // 极速轮询检测
        });
    }

    // 暂停心跳锁
    async function checkPauseState() {
        while (isPaused && isRunning) {
            await new Promise(resolve => setTimeout(resolve, 200));
        }
    }

    // React 16+ 深度状态穿透赋值
    function setReactInputValue(inputElement, value) {
        if (!inputElement) return;
        try {
            inputElement.focus();

            let lastValue = inputElement.value;
            const prototype = inputElement.tagName === 'TEXTAREA' ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;
            const nativeInputValueSetter = Object.getOwnPropertyDescriptor(prototype, 'value').set;
            nativeInputValueSetter.call(inputElement, value);

            const tracker = inputElement._valueTracker;
            if (tracker) {
                tracker.setValue(lastValue);
            }

            // 触发标准 React 值的双端 input 与 change 事件完成底层落盘同步
            inputElement.dispatchEvent(new Event('input', { bubbles: true }));
            inputElement.dispatchEvent(new Event('change', { bubbles: true }));
        } catch (e) {
            console.error('React 状态同步注入异常:', e);
            inputElement.value = value;
            inputElement.dispatchEvent(new Event('input', { bubbles: true }));
        }
    }

    // 提取已选择的数量 (蓝框内容解析)
    function getSelectedCount() {
        const el = document.querySelector('.oc-create-material-submit-bar-count, [class*="submit-bar-count"], .oc-create-material-submit-bar-left span');
        if (!el) return -1;
        const text = el.textContent || '';
        const match = text.match(/(?:已选择|已选)\s*(\d+)/) || text.match(/(\d+)\s*\/\s*\d+/);
        return match ? parseInt(match[1], 10) : -1;
    }

    // 提取视频总数量 (黄框内容解析)
    function getTotalCount() {
        const el = document.querySelector('.create-material-list-card-header-count, [class*="card-header-count"], .ad-video-lib-header .create-material-list-card-header-main');
        if (!el) return -1;
        const text = el.textContent || '';
        const match = text.match(/(?:共|包含)\s*(\d+)/) || text.match(/共\s*(\d+)\s*个/) || text.match(/(\d+)/);
        return match ? parseInt(match[1], 10) : -1;
    }

    // 15. 【新建项目】表单填充自动化核心控制区
    async function executeAutomationWorkflow() {
        const rows = excelData.slice(1);

        if (rows.length === 0) {
            log('未找到有效的剧名数据,请检查导入的Excel!', 'error');
            return;
        }

        const activeId = GM_getValue(ACTIVE_DOUYIN_KEY, '');
        if (!activeId) {
            log("❌ 执行被阻断:请先在助手悬浮窗选择【当前投放抖音号】!", "error");
            isRunning = false;
            updateBallStyle();
            return;
        }

        isRunning = true;
        isPaused = false;
        btnPauseBuild.className = 'ios-btn ios-btn-orange';
        btnPauseLabel.innerText = '暂停';
        updateBallStyle();

        const activeAccount = douyinAccounts.find(acc => acc.id === activeId);
        let colIdx = -1;
        if (activeAccount) {
            colIdx = getDouyinColumnIndex(excelData, activeAccount.id, activeAccount.name);
        }

        let startIndex = GM_getValue(PROGRESS_INDEX_KEY, null);
        if (startIndex === null) {
            startIndex = getNextUncompletedIndex();
            GM_setValue(PROGRESS_INDEX_KEY, startIndex);
        } else {
            startIndex = parseInt(startIndex);
        }

        log(`[流程初始化] 本次从第 [ ${startIndex + 1} / ${rows.length} ] 组数据开始执行...`, 'info');

        const triggerSelectors = [
            "div.create-product-add-empty",
            "div.create-product-add-single",
            "[class*='create-product-add']",
            "#ad-main > div:nth-child(2) > div > div.moduler-wrapper.newModuler > div:nth-child(4) > div > div > div > div > div > div > div > div > div > div > div > div > div",
            "#ad-main > div:nth-child(2) > div > div.moduler-wrapper.newModuler > div:nth-child(4) > div > div > div > div > div > div > div > div > div"
        ];

        // 🌟 黄金级商品搜索框定位器:通过自定义校验机制精确定位黄色框,绝对排除左侧红框下拉菜单
        const inputSelectors = [
            () => {
                const inputs = Array.from(document.querySelectorAll("input"));
                return inputs.find(inp => {
                    if (!isVisible(inp)) return false;

                    // 1. 必须匹配搜索框特有的提示词
                    const placeholder = String(inp.getAttribute('placeholder') || '');
                    if (!placeholder.includes('名称') && !placeholder.includes('ID')) {
                        return false;
                    }

                    // 2. 强力排除:凡是在任何下拉筛选容器(Select)内部的输入框,一律排除!防止误填左侧红框
                    if (inp.closest('.ovui-select') || inp.closest('.oc-select') || inp.closest('[class*="select"]') || inp.closest('[data-auto-id*="select"]')) {
                        return false;
                    }

                    return true;
                });
            },
            "div.oc-drawer-body-wrap input[placeholder*='名称或ID']",
            "div.oc-drawer-body-wrap input[placeholder*='名称']",
            "div.oc-drawer input[placeholder*='名称或ID']",
            ".ovui-drawer input[placeholder*='名称或ID']",
            "div.product-drawer-content input[placeholder*='名称或ID']",
            ".product-drawer-wrap input[placeholder*='名称或ID']",
            "input[placeholder*='请输入商品名称或ID']",
            "input[placeholder*='请输入商品名称']"
        ];

        // 确定按钮定位器
        const confirmSelectors = [
            "div.oc-drawer button.ovui-button--primary",
            "div.oc-drawer-body-wrap button.ovui-button--primary",
            ".ovui-drawer button.ovui-button--primary",
            "div.product-drawer-wrap button.ovui-button--primary",
            "div.product-drawer-wrap button[data-e2e='button']",
            "button.ovui-button--primary-fill",
            "button.ovui-button--primary"
        ];

        const douyinTriggerSelectors = [
            "div.ovui-custom-input__content",
            "[data-e2e='oc_emptyKey_ad/web/roi/ad/create']",
            "span.oc-typography"
        ];

        // 产品卖点高精定位器
        const pointsInputSelectors = [
            "input.ovui-input[placeholder*='产品卖点']",
            "input[placeholder*='最多10个产品卖点']",
            "div.ovui-custom-input__search input.ovui-input",
            "div[class*='productSelling'] input"
        ];

        // 项目名称高精定位器
        const projectNameTextareaSelectors = [
            "textarea.ovui-textarea[placeholder*='项目名称']",
            "textarea[placeholder*='请输入项目名称']",
            "textarea.ovui-textarea",
            "div[class*='project-name'] textarea"
        ];

        for (let i = startIndex; i < rows.length; i++) {
            await checkPauseState();
            if (!isRunning) break;

            // 🌟 每一圈填充开始前进行安全额度自检 🌟
            const remaining = GM_getValue(REMAINING_PROJECTS_KEY, 500);
            if (remaining <= 0) {
                log('🛑 [安全管控] 当前剩下可搭项目数为 0!已达到 500 限制上限,自动搭建已安全退出。', 'error');
                isRunning = false;
                updateBallStyle();
                break;
            }

            GM_setValue(PROGRESS_INDEX_KEY, i);
            renderPreviewTable(); // 🌟 核心补回:让表格预览自动向当前行(第i行)对焦,恢复动态显示上下各5行!

            const currentRow = rows[i];
            const dramaName = currentRow[0];

            if (!dramaName) {
                log(`第 ${i + 1} 行第一列的剧名为空,自动跳过...`, 'warning');
                continue;
            }

            let checkYida = {};
            try {
                const savedYida = GM_getValue(YIDA_RECORDS_KEY, '{}');
                checkYida = JSON.parse(savedYida);
            } catch(e){}
            const cleanDName = String(dramaName).trim().replace(/\s+/g, '');
            const cleanAId = String(activeId).trim();
            const yidaRecordKey = `${cleanDName}_${cleanAId}`;
            const isCompleted = checkYida[yidaRecordKey] === true || (colIdx !== -1 && currentRow[colIdx] && String(currentRow[colIdx]).trim() === '已搭');

            if (isCompleted) {
                log(`- ⏩ 检测到第 ${i + 1} 行剧目 【${dramaName}】 在库中为 “已搭” 状态,自动跳过。`, 'system');
                continue;
            }

            updateCurrentBuildingDramaName(dramaName);
            log(`[任务 ${i + 1}/${rows.length}] 🚀 准备注入剧名: “${dramaName}”`, 'info');

            try {
                // 步骤 1: 触发添加商品抽屉
                log(`- 正在定位“点击添加商品”按钮区域...`, 'system');
                const triggerButton = await waitAndFindElement(triggerSelectors, ['点击添加商品', '添加商品']);

                await checkPauseState();

                log(`- 成功捕获商品卡片,仿真物理点击...`, 'system');
                triggerClick(triggerButton);

                log(`- 等待抽屉展开加载...`, 'system');
                await sleep(1000);
                await checkPauseState();

                // 步骤 2: 定位输入框
                log("- 正在检索黄色搜索输入框...", "system");
                const inputElement = await waitAndFindElement(inputSelectors, []);

                await checkPauseState();

                // 步骤 3: 注入剧名并搜索
                log(`- 已成功直达黄色搜索输入框,强行同步剧名: “${dramaName}”...`, 'system');
                setReactInputValue(inputElement, dramaName);

                log(`- 正在触发自动搜索 “${dramaName}”...`, 'system');
                await sleep(500); // 预留 React 同步缓冲

                // 发送回车指令搜索
                pressEnter(inputElement);
                await sleep(200);

                // 点击放大镜图标(双重保险搜索)
                const parentContainer = inputElement.parentElement;
                if (parentContainer) {
                    const searchIcon = Array.from(parentContainer.querySelectorAll('svg, i, [class*="icon"], [class*="search"]')).find(el => {
                        if (!isVisible(el)) return false;
                        const className = String(el.className || '').toLowerCase();
                        const id = String(el.id || '').toLowerCase();
                        const dataAutoId = String(el.getAttribute('data-auto-id') || '').toLowerCase();

                        if (className.includes('clear') || className.includes('close') || className.includes('delete') || className.includes('cancel')) {
                            return false;
                        }
                        return className.includes('search') || className.includes('magnify') || dataAutoId.includes('search') || el.closest('[class*="search"]');
                    });

                    if (searchIcon) {
                        log(`- 成功捕捉放大镜按钮,辅助触发检索...`, 'system');
                        triggerClick(searchIcon);
                    }
                }

                await checkPauseState();

                // 步骤 4: 等待结果匹配
                log(`- 正在等待搜寻结果渲染就绪...`, 'system');
                let matchedCard = null;
                const searchTimeout = 8000;
                const searchStart = Date.now();

                while (Date.now() - searchStart < searchTimeout) {
                    await checkPauseState();

                    // 直接定位真实可见的商品卡片容器
                    const cards = Array.from(document.querySelectorAll(
                        'div[data-auto-id="create-product-card"], ' +
                        '.oc-create-product-card, ' +
                        '[class*="oc-create-product-card"], ' +
                        '[class*="product-card"]'
                    )).filter(isVisible);

                    // 🌟 策略 1: 文本比对归一化(洗去一切中英文符号、空格影响,彻底杜绝逗号括号比对差异)
                    const cleanDramaName = normalizeString(dramaName);
                    for (const card of cards) {
                        const cleanCardText = normalizeString(card.textContent || '');
                        if (cleanCardText.includes(cleanDramaName) || cleanDramaName.includes(cleanCardText)) {
                            matchedCard = card;
                            log(`- 🎯 文本归一化完美匹配:选中 “${dramaName}”`, 'system');
                            break;
                        }
                    }

                    // 🌟 策略 2: 降级兜底 - 只要搜索结果有卡片且未匹配成功,直接选中第一张卡片
                    if (!matchedCard && cards.length > 0) {
                        matchedCard = cards[0];
                        log(`- ⚠️ 未能精确字符匹配到 “${dramaName}”,启动自动选择列表首张商品卡片机制!`, 'warning');
                    }

                    if (matchedCard) break;
                    await sleep(300);
                }

                await checkPauseState();

                if (matchedCard) {
                    log(`- 🎯 已锁定商品卡片,执行勾选动作...`, 'info');

                    // 定位卡片内部复选框包裹元素
                    const selectableBox = matchedCard.querySelector(
                        'input[type="checkbox"], ' +
                        '.ovui-checkbox__inner, ' +
                        '.ovui-checkbox__wrapper, ' +
                        '.ovui-checkbox, ' +
                        '.oc-checkbox'
                    );

                    if (selectableBox) {
                        log(`- 优先点击复选框确保事件落盘...`, 'system');
                        triggerClick(selectableBox);
                    } else {
                        log(`- 点击卡片本体完成勾选...`, 'system');
                        triggerClick(matchedCard);
                    }

                    await sleep(600);

                    // 步骤 5: 点击“确定”提交商品选择
                    log(`- 提交商品保存,点击“确定”...`, 'system');
                    const confirmBtn = await waitAndFindElement(confirmSelectors, ['确定']);

                    triggerClick(confirmBtn);
                    log(`- 剧名 “${dramaName}” 已成功保存并点击确定!`, 'info');

                    // 步骤 6: 等待抽屉滑出消失
                    log(`- 正在检测等待商品选择侧拉弹窗完全关闭...`, 'system');
                    const drawerSelector = "div.product-drawer-wrap, div.oc-drawer-body-wrap, .ovui-drawer";
                    let drawerClosed = false;
                    const closeTimeout = 5000;
                    const closeStart = Date.now();

                    while (Date.now() - closeStart < closeTimeout) {
                        await checkPauseState();
                        const drawer = document.querySelector(drawerSelector);
                        if (!drawer || drawer.offsetWidth === 0 || drawer.offsetHeight === 0) {
                            drawerClosed = true;
                            break;
                        }
                        await sleep(200);
                    }

                    if (drawerClosed) {
                        log(`- 商品抽屉已顺利关闭。`, 'system');
                    } else {
                        log(`- 未检测到抽屉关闭动作,正在强制继续...`, 'warning');
                    }

                    await sleep(600);
                    await checkPauseState();

                    // 步骤 7: 点击【请选择抖音号】
                    log(`- 正在定位“请选择抖音号”配置项...`, 'system');
                    const douyinTrigger = await waitAndFindElement(douyinTriggerSelectors, ['请选择抖音号']);

                    log(`- 成功捕获抖音号栏,展开账号列表!`, 'info');
                    triggerClick(douyinTrigger);

                    log(`- 等待账号下拉菜单载入...`, 'system');
                    await sleep(650);
                    await checkPauseState();

                    // 步骤 8: 抖音搜索检索
                    const searchDOUYINSelectors = [
                        "input[placeholder*='输入抖音ID']",
                        "input[placeholder*='抖音ID']",
                        "div.ovui-select__dropdown input",
                        "div[class*='dropdown'] input"
                    ];
                    let dySearchInput = null;
                    for (const sel of searchDOUYINSelectors) {
                        dySearchInput = document.querySelector(sel);
                        if (dySearchInput) break;
                    }
                    if (dySearchInput) {
                        log(`- 已定位到抖音号检索框,检索 ID:【${activeId}】...`, 'system');
                        setReactInputValue(dySearchInput, activeId);
                        await sleep(800);
                    }

                    // 步骤 9: 匹配目标抖音号卡片
                    log(`- 正在精准搜寻匹配 ID 【${activeId}】 的抖音配置行...`, 'system');
                    let matchedDYAcountCard = null;
                    let authStatus = '';
                    const matchTimeout = 5000;
                    const matchStart = Date.now();

                    while (Date.now() - matchStart < matchTimeout) {
                        await checkPauseState();

                        const elements = Array.from(document.querySelectorAll('div, span, li, p'));

                        const leafIdElements = elements.filter(el => {
                            const cleanText = el.textContent.trim().replace(/\s+/g, '');
                            return el.children.length <= 1 && cleanText === activeId;
                        });

                        for (const leaf of leafIdElements) {
                            let curr = leaf;
                            let cardBox = null;
                            for (let d = 0; d < 8; d++) {
                                if (!curr.parentElement) break;
                                curr = curr.parentElement;
                                if (curr.textContent.includes('已投放') || curr.textContent.includes('全域投放授权')) {
                                    cardBox = curr;
                                    break;
                                }
                            }
                            if (cardBox) {
                                matchedDYAcountCard = cardBox;
                                const text = cardBox.textContent;
                                if (text.includes('已投放')) {
                                    authStatus = 'delivered';
                                } else if (text.includes('全域投放授权')) {
                                    authStatus = 'authorized';
                                } else {
                                    authStatus = 'other';
                                }
                                break;
                            }
                        }

                        if (matchedDYAcountCard) break;
                        await sleep(300);
                    }

                    await checkPauseState();

                    if (matchedDYAcountCard) {
                        // 分支 A: 匹配为 “全域投放授权”
                        if (authStatus === 'authorized') {
                            log(`- 🎯 匹配成功!该账号为【全域投放授权】状态,执行点击选择!`, 'info');
                            triggerClick(matchedDYAcountCard);

                            await sleep(1000);
                            await checkPauseState();

                            // 步骤 10: 定位并填充 ROI
                            log(`- 正在检索页面上的“全域ROI系数”输入位置...`, 'system');
                            const roiInputElement = await waitAndFindRoiInput(8000);
                            if (roiInputElement) {
                                const activeRoiValue = GM_getValue(ROI_COEFFICIENT_KEY, '');
                                if (activeRoiValue !== '') {
                                    log(`- 🎯 成功锁定全域ROI输入框!强行注入系数:【${activeRoiValue}】...`, 'info');
                                    setReactInputValue(roiInputElement, activeRoiValue);
                                } else {
                                    log(`⚠️ 自动ROI配置警告:未录入和确定ROI系数,跳过注入。`, 'warning');
                                }
                            } else {
                                log(`⚠️ 自动ROI配置失败:未定位到“全域ROI系数”输入框,请手动核查。`, 'warning');
                            }

                            await sleep(800);
                            await checkPauseState();

                            // 步骤 11: 点击“混合投放(IAA+IAP)”
                            log(`- 定位“混合投放(IAA+IAP)”选项...`, 'system');
                            const mixPlacementSelectors = [
                                "[data-e2e='r3project_monetizationMode_2']",
                                "div.ovui-radio-item[data-e2e='r3project_monetizationMode_2']",
                                "div.ovui-radio-item"
                            ];
                            const mixPlacementButton = await waitAndFindElement(mixPlacementSelectors, ["混合投放", "混合投放(IAA+IAP)"]);
                            log(`- 🎯 成功找到并点击“混合投放”单选按钮...`, 'info');
                            triggerClick(mixPlacementButton);

                            await sleep(800);
                            await checkPauseState();

                            // 步骤 12: 点击“添加视频”
                            log(`- 正在定位“添加视频”按钮...`, 'system');
                            const addVideoSelectors = [
                                "div[btn-auto-id='add-video-btn'] button",
                                "div[data-e2e='Add_video'] button",
                                "div[btn-auto-id='add-video-btn']",
                                "div[data-e2e='Add_video']"
                            ];
                            const addVideoButton = await waitAndFindElement(addVideoSelectors, ["添加视频"]);
                            log(`- 🎯 成功锁定添加视频按钮,仿真物理点击...`, 'info');
                            triggerClick(addVideoButton);

                            log(`- 正在等待添加视频弹出对话框完全展开...`, 'system');
                            await sleep(1200);
                            await checkPauseState();

                            // 步骤 13: 在“添加视频”对话框中输入当前剧名并搜索
                            log(`- 正在寻找添加视频弹出面板的检索输入框...`, 'system');
                            const videoInputSelectors = [
                                "input[placeholder*='可搜索视频名称或ID']",
                                "div.ovui-dialog input[placeholder*='可搜索']",
                                "div.ovui-modal input[placeholder*='视频']",
                                "input.ovui-input[placeholder*='视频名称']"
                            ];
                            const videoInputElement = await waitAndFindElement(videoInputSelectors, []);

                            await checkPauseState();
                            log(`- 🎯 已定位到视频检索框,强行注入剧名:【${dramaName}】...`, 'info');
                            setReactInputValue(videoInputElement, dramaName);

                            await sleep(500);
                            await checkPauseState();

                            log(`- 发送回车指令过滤素材...`, 'system');
                            pressEnter(videoInputElement);

                            log(`- ⏳ 正在进行新检索素材 cold 启动重绘缓冲 (1.5秒)...`, 'system');
                            await sleep(1500);
                            await checkPauseState();

                            log(`- 正在等待视频素材列表首张卡片渲染呈现在屏幕上...`, 'system');
                            const videoCardSelectors = [
                                "div[data-auto-id='create-material-card']",
                                "[data-e2e='createad_myVideo__createMaterialList']",
                                ".oc-create-material-card-wrapper",
                                "div.create-material-list-card-item div[data-auto-id='create-material-card']",
                                ".create-material-list-card-item"
                            ];
                            await waitAndFindElement(videoCardSelectors, [], 10000); // 10秒强力阻塞轮询
                            log(`- 🎯 检测到视频素材卡片已渲染就绪!`, 'info');

                            log(`- ⏳ 额外等待 1 秒(1000ms)以确保分页组件动作绑定就绪...`, 'system');
                            await sleep(1000);
                            await checkPauseState();

                            // 步骤 14: 点击 “40条/页” 下拉选择菜单
                            log(`- 正在寻找弹窗底部的分页条数下拉菜单...`, 'system');
                            const pageSelectSelectors = [
                                () => {
                                    const e2eWrapper = document.querySelector("[data-e2e*='pagination_group_select']");
                                    if (e2eWrapper) {
                                        return e2eWrapper.querySelector('.ovui-input__wrapper') || e2eWrapper.querySelector('.ovui-select') || e2eWrapper;
                                    }
                                    return null;
                                },
                                () => {
                                    const inputs = Array.from(document.querySelectorAll("input.ovui-input, input"));
                                    const targetInput = inputs.find(inp => {
                                        const val = String(inp.value || '').trim();
                                        return val.includes("条/页") || val.includes("40条");
                                    });
                                    if (targetInput) {
                                        return targetInput.closest(".ovui-select") || targetInput.closest(".ovui-input__wrapper") || targetInput;
                                    }
                                    return null;
                                },
                                "div[data-e2e='createad_myVideo__createMaterialList_pagination_group_select'] div.ovui-select",
                                "div[data-e2e='createad_myVideo__createMaterialList_pagination_group_select'] input.ovui-input",
                                "div.ovui-dialog input[value='40条/页']",
                                "div.ovui-dialog input[value*='条/页']",
                                "div.ovui-dialog div.ovui-select",
                                "div.ovui-dialog .ovui-select__input",
                                "div.create-material-list-pagination-list-content div.ovui-select input.ovui-input"
                            ];
                            const pageSelectElement = await waitAndFindElement(pageSelectSelectors, ['条/页']);

                            await checkPauseState();

                            log(`- 已锁定分页菜单,等待分页组件底层事件绑定稳定 (300ms)...`, 'system');
                            await sleep(300);
                            await checkPauseState();

                            log(`- 🎯 成功捕获分页菜单,点击展开选择框下拉条目...`, 'system');
                            triggerClick(pageSelectElement);

                            await sleep(300);
                            await checkPauseState();

                            // 步骤 15: 选择 “160条/页” 选项并进行刷新等待
                            log(`- 正在精准检索 “160条/页” 下拉选项卡片...`, 'system');
                            const optionSelectors = [
                                () => {
                                    const candidates = Array.from(document.querySelectorAll('.ovui-option__content, .ovui-option, .ovui-select-option, li'));
                                    return candidates.find(opt => {
                                        const txt = opt.textContent.trim().replace(/\s+/g, '');
                                        return (txt === '160条/页' || txt.includes('160条')) && opt.offsetHeight > 0;
                                    });
                                },
                                "div.ovui-option__content",
                                "div.ovui-select_dropdown div",
                                ".ovui-option"
                            ];
                            const optionElement = await waitAndFindElement(optionSelectors, ["160条/页"]);

                            await checkPauseState();
                            log(`- 🎯 成功锁定 “160条/页” 选项,物理仿真点击切换!`, 'info');
                            triggerClick(optionElement);

                            log(`- 分页条目已重置为 160条/页,等待页面关闭下拉菜单与异步遮罩层清空...`, 'system');

                            // 核心时差等待
                            let dropdownClosed = false;
                            for (let j = 0; j < 30; j++) {
                                const dropdown = document.querySelector(".ovui-select_dropdown, .ovui-select-dropdown, [class*='dropdown']");
                                if (!dropdown || dropdown.offsetHeight === 0) {
                                    dropdownClosed = true;
                                    break;
                                }
                                await sleep(100);
                            }

                            // 核心时差等待
                            let loadingCleared = false;
                            for (let j = 0; j < 30; j++) {
                                const loading = document.querySelector('.ovui-loading, [class*="loading"], .oc-loading, .ovui-loading-mask, [class*="mask"]');
                                if (!loading || loading.offsetHeight === 0) {
                                    loadingCleared = true;
                                    break;
                                }
                                await sleep(100);
                            }

                            log(`- ⏳ 额外预留 300 毫秒重载重绘缓冲时间,确保 React DOM 彻底稳定...`, 'system');
                            await sleep(300);
                            await checkPauseState();

                            // 步骤 16: 自动跨翻页本页全选与数量对齐校验
                            let pageNum = 1;
                            let autoPaginationComplete = false;

                            while (isRunning && !isPaused) {
                                log(`- [第 ${pageNum} 页] 正在定位“本页全选”复选框...`, 'system');
                                const selectAllCheckbox = await waitAndFindElement([
                                    () => {
                                        const labels = Array.from(document.querySelectorAll('.ovui-checkbox, label, [class*="checkbox"]'));
                                        const labelEl = labels.find(l => l.textContent.includes('本页全选') && l.offsetHeight > 0);
                                        if (labelEl) {
                                            return labelEl.querySelector('.ovui-checkbox__inner, .ovui-checkbox__wrapper, input[type="checkbox"]') || labelEl;
                                        }
                                        return null;
                                    },
                                    () => {
                                        const wrapper = document.querySelector("div[data-auto-id='oc-checkbox'], .oc-checkbox");
                                        if (wrapper && wrapper.offsetHeight > 0) {
                                            return wrapper.querySelector('.ovui-checkbox__inner, input[type="checkbox"]') || wrapper;
                                        }
                                        return null;
                                    }
                                ], ['本页全选'], 10000);

                                await checkPauseState();
                                log(`- 🎯 成功捕获第 ${pageNum} 页全选复选框,执行物理仿真点击选中本页所有素材...`, 'info');
                                triggerClick(selectAllCheckbox);

                                // 等待计数更新
                                log(`- ⏳ 正在等待全选后视频选择计数器同步...`, 'system');
                                await sleep(800);
                                await checkPauseState();

                                // 读取已选择数量并进行高精校对
                                let countMatched = false;
                                let lastSelected = -1;
                                let lastTotal = -1;

                                // 循环轮询对比5次
                                for (let attempt = 0; attempt < 5; attempt++) {
                                    await checkPauseState();
                                    const sCount = getSelectedCount();
                                    const tCount = getTotalCount();
                                    lastSelected = sCount;
                                    lastTotal = tCount;

                                    log(`- [第 ${pageNum} 页/校对 ${attempt + 1}] 当前已选择: ${sCount} / 库总共: ${tCount}`, 'system');

                                    if (sCount !== -1 && tCount !== -1 && sCount === tCount) {
                                        countMatched = true;
                                        break;
                                    }
                                    await sleep(300);
                                }

                                if (countMatched) {
                                    log(`- 🎯 数量完美对齐![已选: ${lastSelected}] === [总共: ${lastTotal}]。自动跨页全选圆满结束!`, 'info');
                                    autoPaginationComplete = true;
                                    break;
                                } else {
                                    // 数量不一致,尝试翻到下一页
                                    log(`- ⚠️ 当前页全选后数量不一致 [已选择: ${lastSelected} / 库总共: ${lastTotal}],尝试查找下一页按钮...`, 'warning');

                                    const nextBtn = document.querySelector(".ovui-page-turner__next-icon, [class*='page-turner__next']");
                                    let nextBtnLi = null;
                                    if (nextBtn) {
                                        nextBtnLi = nextBtn.closest('li') || nextBtn;
                                    } else {
                                        const turnerItems = document.querySelectorAll(".ovui-page-turner__item, [class*='page-turner__item']");
                                        if (turnerItems.length > 0) {
                                            nextBtnLi = turnerItems[turnerItems.length - 1];
                                        }
                                    }

                                    const isDisabled = nextBtnLi && (
                                        nextBtnLi.classList.contains('ovui-page-turner__item--disabled') ||
                                        nextBtnLi.classList.contains('ovui-page-turner__item-disabled') ||
                                        nextBtnLi.hasAttribute('disabled') ||
                                        nextBtnLi.getAttribute('aria-disabled') === 'true'
                                    );

                                    if (nextBtnLi && !isDisabled) {
                                        pageNum++;
                                        log(`- 🔄 发现下一页翻页按钮,触发物理点击翻到 [第 ${pageNum} 页]...`, 'info');
                                        triggerClick(nextBtnLi);

                                        // 等待新页面加载渲染
                                        log(`- ⏳ 正在等待新一页视频素材异步载入...`, 'system');
                                        await sleep(1000);

                                        let pageLoadingCleared = false;
                                        for (let j = 0; j < 30; j++) {
                                            const loading = document.querySelector('.ovui-loading, [class*="loading"], .oc-loading, .ovui-loading-mask, [class*="mask"]');
                                            if (!loading || loading.offsetHeight === 0) {
                                                pageLoadingCleared = true;
                                                break;
                                            }
                                            await sleep(100);
                                        }
                                        await sleep(300); // 稳定缓冲
                                    } else {
                                        log(`- ❌ 数量未对齐,且未找到下一页按钮或已达到最末页。跨页全选无法继续。`, 'break');
                                        break;
                                    }
                                }
                            }

                            // 步骤 17: 根据全自动校对结果决定自动提交
                            if (autoPaginationComplete) {
                                log(`- 🎯 开始检索对话框“确定”保存按钮...`, 'info');
                                const modalConfirmBtn = await waitAndFindElement([
                                    () => {
                                        const activeVideoDialog = Array.from(document.querySelectorAll('.ovui-dialog, .ovui-modal, .ovui-drawer'))
                                            .find(container => {
                                                if (!isVisible(container)) return false;
                                                return container.textContent.includes('添加视频') || container.textContent.includes('视频库') || container.textContent.includes('本页全选');
                                            });
                                        if (activeVideoDialog) {
                                            const buttons = Array.from(activeVideoDialog.querySelectorAll('button'));
                                            return buttons.find(btn => (btn.textContent.trim().includes('确定') || btn.textContent.trim() === '确定') && isVisible(btn));
                                        }
                                        return null;
                                    },
                                    () => {
                                        const visibleFooters = Array.from(document.querySelectorAll('.oc-drawer-footer, .oc-modal-footer, .ovui-dialog__footer, [class*="drawer-footer"], [class*="modal-footer"]'))
                                            .filter(isVisible);
                                        for (const footer of visibleFooters) {
                                            const btn = Array.from(footer.querySelectorAll('button')).find(b => b.textContent.includes('确定') && isVisible(b));
                                            if (btn) return btn;
                                        }
                                        return null;
                                    }
                                ], ['确定'], 5000);

                                await checkPauseState();
                                log(`- 🎯 成功锁定提交“确定”按钮,物理仿真点击保存!`, 'info');
                                triggerClick(modalConfirmBtn);

                                // 步骤 18: 等待素材弹窗完全关闭
                                log(`- ⏳ 正在监测并等待素材选择弹窗完全关闭并安全释放 DOM 树...`, 'system');
                                let videoDialogClosed = false;
                                const videoCloseStart = Date.now();
                                while (Date.now() - videoCloseStart < 6000) { // 6秒超时保护
                                    await checkPauseState();
                                    const videoDialog = document.querySelector('.ovui-dialog, .ovui-modal, .ovui-drawer, .product-drawer-wrap, [class*="dialog"], [class*="modal"]');
                                    if (!videoDialog || videoDialog.offsetHeight === 0) {
                                        videoDialogClosed = true;
                                        break;
                                    }
                                    await sleep(100);
                                }
                                if (videoDialogClosed) {
                                    log(`- 🎯 素材弹窗已顺利关闭。开始自动寻找并注入 IAA / IAP 链接...`, 'system');
                                } else {
                                    log(`- ⚠️ 未侦测到弹窗关闭,正在强行寻找并注入链接...`, 'warning');
                                }
                                await sleep(300);

                                // A. 注入 IAA 链接
                                log(`- 正在精准寻找“IAA 链接”输入框位置...`, 'system');
                                const iaaInputElement = await waitAndFindIaaInput(8000);
                                if (iaaInputElement) {
                                    const iaaLinkValue = currentRow[4]; // 精准读取 Excel 数据第 5 列
                                    if (iaaLinkValue !== undefined && String(iaaLinkValue).trim() !== '') {
                                        log(`- 🎯 成功锁定 IAA 链接输入框!正在自动写入链接:【${iaaLinkValue}】...`, 'info');
                                        setReactInputValue(iaaInputElement, String(iaaLinkValue).trim());
                                    } else {
                                        log(`⚠️ IAA 链接提示:Excel 第 ${i + 1} 行剧目 【${dramaName}】 的第 5 列(IAA链接)为空,跳过自动输入。`, 'warning');
                                    }
                                } else {
                                    log(`⚠️ 自动 IAA 链接配置失败:未能在页面上定位到“IAA链接”输入框,请手动核查。`, 'warning');
                                }

                                await sleep(300);

                                // B. 智能链式注入 IAP 链接 (第 6-9 列,索引 5 到 8)
                                log(`- 正在检测并自动注入 IAP 链接列表 (第 6-9 列)...`, 'system');
                                const iapLinks = [];
                                for (let col = 5; col <= 8; col++) {
                                    const val = currentRow[col];
                                    if (val !== undefined && val !== null && String(val).trim() !== '') {
                                        iapLinks.push(String(val).trim());
                                    }
                                }

                                if (iapLinks.length > 0) {
                                    log(`- 检测到当前剧目包含 ${iapLinks.length} 个非空 IAP 链接,开始填充流程...`, 'info');
                                    for (let idx = 0; idx < iapLinks.length; idx++) {
                                        const linkVal = iapLinks[idx];

                                        // 重新扫描可见 IAP 链接输入框
                                        let currentInputs = findIapInputElements();
                                        let targetInput = currentInputs[idx];

                                        // 若当前输入框不存在,代表需要链式生成,触发 "+ 点击添加"
                                        if (!targetInput) {
                                            targetInput = await waitAndAddIapInput(idx);
                                        }

                                        if (targetInput) {
                                            log(`- 🎯 正在向第 ${idx + 1} 个 IAP 链接框注入:【${linkVal}】...`, 'info');
                                            setReactInputValue(targetInput, linkVal);
                                            await sleep(300); // 防 React 状态更新
                                        } else {
                                            log(`❌ 无法生成或定位第 ${idx + 1} 个 IAP 链接框,跳过。`, 'error');
                                        }
                                    }
                                    log(`✨ 成功完成 ${iapLinks.length} 组 IAP 链接的智能填充配置!`, 'info');
                                } else {
                                    log(`⚠️ IAP 链接提示:Excel 第 ${i + 1} 行剧目 【${dramaName}】 的第 6-9 列为空,跳过。`, 'warning');
                                }

                                await sleep(300);

                                // C. 注入随机创意标题
                                log(`- 正在检测并自动注入随机创意标题...`, 'system');
                                const savedTitles = GM_getValue(TITLES_STORAGE_KEY, '');
                                const titleLines = savedTitles.split('\n').map(t => t.trim()).filter(t => t.length > 0);

                                if (titleLines.length > 0) {
                                    log(`- 正在精准寻找“创意标题”输入框位置...`, 'system');
                                    const titleInputSelectors = [
                                        "input.ovui-input[placeholder*='5-55个字的标题']",
                                        "input[placeholder*='请输入5-55个字的标题']",
                                        "input[data-e2e*='creativeTitleGroup_title_input']",
                                        "input[data-e2e*='creativeTitles']"
                                    ];
                                    const creativeTitleInput = await waitAndFindElement(titleInputSelectors, [], 8000).catch(() => null);
                                    if (creativeTitleInput) {
                                        const randomIndex = Math.floor(Math.random() * titleLines.length);
                                        const selectedTitle = titleLines[randomIndex];
                                        log(`- 🎯 成功锁定创意标题输入框!正在随机注入标题:【${selectedTitle}】...`, 'info');
                                        setReactInputValue(creativeTitleInput, selectedTitle);
                                    } else {
                                        log(`⚠️ 自动标题配置失败:未能在页面上定位到“创意标题”输入框,请手动核查。`, 'warning');
                                    }
                                } else {
                                    log(`⚠️ 自动标题配置警告:您未在“安全管理”页面中锁定任何有效标题,已跳过标题自动注入。`, 'warning');
                                }

                                await sleep(500);

                                // D. 自动选择产品主图第一个方块
                                log(`- 正在检测产品主图配置...`, 'system');
                                const addImgBtn = await waitAndFindElement([
                                    "div.oc-create-product-img-add-button",
                                    "div[data-auto-id='oc-create-product-img-add-button']"
                                ], [], 8000).catch(() => null);

                                if (addImgBtn) {
                                    log(`- 🎯 成功捕获产品主图 “+” 添加按钮,模拟物理点击...`, 'info');
                                    triggerClick(addImgBtn);

                                    log(`- 等待素材图片库弹出加载 (1.2秒)...`, 'system');
                                    await sleep(1200);
                                    await checkPauseState();

                                    log(`- 正在寻找并选中弹窗内第一个图片卡片...`, 'system');
                                    const firstImgCard = await waitAndFindElement([
                                        () => {
                                            const activeContainer = Array.from(document.querySelectorAll('.ovui-drawer, .ovui-dialog, .ovui-modal, [class*="drawer"], [class*="dialog"], [class*="modal"]'))
                                                .find(container => {
                                                    if (!isVisible(container)) return false;
                                                    return container.querySelector('.oc-create-material-lib-body, .oc-drawer-body') ||
                                                           container.textContent.includes('我的图片') ||
                                                           container.textContent.includes('本地上传');
                                                });
                                            if (activeContainer) {
                                                const card = Array.from(activeContainer.querySelectorAll('.oc-create-material-card-content-data, div[data-auto-id="oc-image"], div.oc-image-square, .oc-create-material-card-content-show-img'))
                                                    .find(isVisible);
                                                if (card) return card;

                                                const fallback = activeContainer.querySelector('.create-material-list-card-container-body > div:nth-child(1) .oc-create-material-card-content-data');
                                                if (fallback && isVisible(fallback)) return fallback;
                                            }
                                            return null;
                                        }
                                    ], [], 10000).catch(() => null);

                                    if (firstImgCard) {
                                        log(`- 🎯 已成功定位并选中库内第一个图片卡片,模拟点击选择...`, 'info');
                                        triggerClick(firstImgCard);

                                        await sleep(600);
                                        await checkPauseState();

                                        log(`- 正在寻找弹窗底部的 “确定” 保存按钮...`, 'system');
                                        const imgConfirmBtn = await waitAndFindElement([
                                            () => {
                                                const activeContainer = Array.from(document.querySelectorAll('.ovui-drawer, .ovui-dialog, .ovui-modal, [class*="drawer"], [class*="dialog"], [class*="modal"]'))
                                                    .find(container => {
                                                        if (!isVisible(container)) return false;
                                                        return container.querySelector('.oc-create-material-lib-body, .oc-drawer-body') ||
                                                               container.textContent.includes('我的图片') ||
                                                               container.textContent.includes('本地上传');
                                                    });
                                                if (activeContainer && isVisible(activeContainer)) {
                                                    const buttons = Array.from(activeContainer.querySelectorAll('button'));
                                                    return buttons.find(btn => (btn.textContent.trim().includes('确定') || btn.textContent.trim() === '确定') && isVisible(btn));
                                                }
                                                return null;
                                            },
                                            "button.ovui-button--primary",
                                            "button.ovui-button--primary-fill"
                                        ], ['确定'], 6000).catch(() => null);

                                        if (imgConfirmBtn) {
                                            log(`- 🎯 成功锁定确定按钮,点击保存图片选择!`, 'info');
                                            triggerClick(imgConfirmBtn);

                                            // 等待图片弹窗关闭
                                            log(`- ⏳ 正在等待图片弹窗安全关闭...`, 'system');
                                            let imgDialogClosed = false;
                                            const imgCloseStart = Date.now();
                                            while (Date.now() - imgCloseStart < 6000) {
                                                await checkPauseState();
                                                const activeContainer = Array.from(document.querySelectorAll('.ovui-drawer, .ovui-dialog, .ovui-modal'))
                                                    .find(container => {
                                                        if (!isVisible(container)) return false;
                                                        return container.querySelector('.oc-create-material-lib-body, .oc-drawer-body') ||
                                                               container.textContent.includes('我的图片') ||
                                                               container.textContent.includes('本地上传');
                                                    });
                                                if (!activeContainer) {
                                                    imgDialogClosed = true;
                                                    break;
                                                }
                                                await sleep(150);
                                            }
                                            if (imgDialogClosed) {
                                                log(`- ✨ 产品主图已顺利保存并关闭弹窗!`, 'info');
                                            } else {
                                                log(`- ⚠️ 图片库弹窗未检测到完全关闭,正在强行继续...`, 'warning');
                                            }
                                        } else {
                                            log(`❌ 未能定位到图片选择弹窗的“确定”保存按钮,请手动确认。`, 'error');
                                        }
                                    } else {
                                        log(`❌ 未能在弹窗中找到任何可选的图片卡片,请手动选择。`, 'error');
                                    }
                                } else {
                                    log(`⚠️ 未能定位到“产品主图”添加按钮,请核查页面是否展开了产品主图模块。`, 'warning');
                                }

                                await sleep(500);

                                // E. 自动填入产品卖点 (空格联立一次性极速注入)
                                log(`- 正在检测产品卖点配置...`, 'system');
                                const savedPoints = GM_getValue(SELLING_POINTS_STORAGE_KEY, '');
                                const pointsLines = savedPoints.split('\n').map(p => p.trim()).filter(p => p.length > 0);

                                if (pointsLines.length > 0) {
                                    log(`- 正在精准寻找“产品卖点”输入框位置...`, 'system');
                                    const pointsTargetInput = await waitAndFindElement(pointsInputSelectors, [], 8000).catch(() => null);
                                    if (pointsTargetInput) {
                                        log(`- 🎯 成功锁定产品卖点输入框!准备空格全装极速注入中...`, 'info');

                                        const maxPointsToFill = Math.min(pointsLines.length, 10);
                                        const slicedPoints = pointsLines.slice(0, maxPointsToFill);
                                        const combinedPoints = slicedPoints.join(' '); // 用半角空格拼装

                                        log(`- 正在一次性填入卖点: 【${combinedPoints}】`, 'system');
                                        setReactInputValue(pointsTargetInput, combinedPoints);
                                        await sleep(300);

                                        pointsTargetInput.dispatchEvent(new KeyboardEvent('keydown', {
                                            bubbles: true,
                                            cancelable: true,
                                            key: 'Enter',
                                            keyCode: 13,
                                            code: 'Enter',
                                            which: 13
                                        }));

                                        await sleep(400);
                                        log(`- ✨ 产品卖点急速全量注入完成!`, 'info');
                                    } else {
                                        log(`⚠️ 自动产品卖点配置失败:未能在页面上定位到“产品卖点”输入框,请手动核查。`, 'warning');
                                    }
                                } else {
                                    log(`⚠️ 自动产品卖点配置提示:产品卖点库为空或处于未锁定状态,跳过自动注入。`, 'warning');
                                }

                                await sleep(500);

                                // F. 自动注入并更新项目名称 (支持 ROI 占位替换)
                                log(`- 正在检测项目名配置模板...`, 'system');
                                const rawProjectTemplate = GM_getValue(PROJECT_NAME_STORAGE_KEY, '');
                                if (rawProjectTemplate) {
                                    log(`- 正在精准寻找网页上的“项目名称”输入框位置...`, 'system');
                                    const nameTextarea = await waitAndFindElement(projectNameTextareaSelectors, [], 8000).catch(() => null);
                                    if (nameTextarea) {
                                        const evaluatedName = processProjectNameTemplate(rawProjectTemplate, dramaName);
                                        log(`- 🎯 成功锁定项目名称输入框!正在一次性清空并填入:【${evaluatedName}】...`, 'info');

                                        // 强力 React 16+ 双兼容性底层一次性极速注入
                                        setReactInputValue(nameTextarea, evaluatedName);
                                        await sleep(500);
                                        log(`- ✨ 项目名称急速全量注入覆盖完成!`, 'info');
                                    } else {
                                        log(`⚠️ 自动项目名称配置失败:未能在页面上定位到“项目名称”输入框,请手动核查。`, 'warning');
                                    }
                                } else {
                                    log(`⚠️ 自动项目名称配置提示:项目名模板为空或处于未锁定状态,跳过。`, 'warning');
                                }

                                await sleep(600); // 等待过渡
                                log('📝 本页全部表单要素均已配置填装成功!正在模拟点击【保存并投放】...', 'info');

                                // 自动点击【保存并投放】
                                const saveAndLaunchSelectors = [
                                    "button.ovui-button--primary-fill",
                                    "button.ovui-button[data-e2e='button']",
                                    () => {
                                        const buttons = Array.from(document.querySelectorAll('button'));
                                        return buttons.find(b => b.textContent.includes('保存并投放') && isVisible(b));
                                    }
                                ];

                                const saveAndLaunchBtn = await waitAndFindElement(saveAndLaunchSelectors, ['保存并投放'], 6000).catch(() => null);
                                if (saveAndLaunchBtn) {
                                    log(`- 🎯 成功捕获到【保存并投放】按钮,开始触发仿真物理点击提交!`, 'info');

                                    // 先行在油猴沙盒记盘“当前待搭成功的剧目”以防止SPA页面跳转拦截导致的数据写盘断档
                                    GM_setValue('oceanengine_last_submitted_drama', dramaName);
                                    triggerClick(saveAndLaunchBtn);

                                    let redirected = false;
                                    let elapsed = 0;
                                    const checkInterval = 1000;

                                    log(`- ⏳ 已进入最长 5 秒死锁轮询保护,正监听管理主页跳转状态...`, 'system');
                                    while (isRunning && !isPaused) {
                                        await sleep(checkInterval);
                                        elapsed += checkInterval;

                                        // 检测是否成功返回管理主页
                                        if (location.href.includes('/ad/web/manage')) {
                                            redirected = true;
                                            break;
                                        }

                                        // 如果超过 5 秒依然处于配置页,说明巨量网络抖动或表单拦截,执行再次补点提交
                                        if (elapsed >= 5000) {
                                            log(`⚠️ 侦测到 5 秒内未完成跳转,可能巨量发生抖动,重新触发【保存并投放】点击...`, 'warning');
                                            const retryBtn = Array.from(document.querySelectorAll('button')).find(b => b.textContent.includes('保存并投放') && isVisible(b));
                                            if (retryBtn) {
                                                triggerClick(retryBtn);
                                            }
                                            elapsed = 0; // 重置计时器
                                        }
                                    }

                                    if (redirected) {
                                        // 进位重载指针
                                        GM_setValue(PROGRESS_INDEX_KEY, i + 1);
                                        renderPreviewTable(); // 🌟 同步滚动视角对焦
                                        GM_setValue(AUTO_ACTIVE_KEY, 'true'); // 确保返回管理页自启动

                                        // 成功时重置运行标志,等待自启动接力
                                        isRunning = false;
                                        updateBallStyle();

                                        log(`- 🚀 跳转成功!页面正在重新加载,等待执行下行剧目搭建接力...`, 'warning');
                                        await sleep(1000);
                                        return; // 退出当前工作流,等待页面实例自动重载重构
                                    }

                                } else {
                                    log(`❌ 无法捕获【保存并投放】提交按钮,表单无法全自动保存,已挂起暂停。请手动点击!`, 'error');
                                    isPaused = true;
                                    btnPauseLabel.innerText = '继续';
                                    btnPauseBuild.classList.remove('ios-btn-orange');
                                    btnPauseBuild.classList.add('ios-btn-primary');
                                    updateBallStyle();
                                    await checkPauseState();
                                }

                            } else {
                                log(`- ⚠️ 跨页自动全选未能实现数量对齐(可能已达末页、视频总数超出单页160等)。`, 'warning');
                                log(`- 当前已选择:${getSelectedCount()} | 库中总共:${getTotalCount()}。`, 'warning');
                                log(`- 脚本流程已自动[挂起/暂停],请核对并确认无误后,手动点击右下角“确定”按钮提交。`, 'warning');
                                log(`- 手动确认后在脚本悬浮窗点击“继续”,系统将接力配置下一个剧目.`, 'warning');

                                isPaused = true;
                                btnPauseLabel.innerText = '继续';
                                btnPauseBuild.classList.remove('ios-btn-orange');
                                btnPauseBuild.classList.add('ios-btn-primary');
                                updateBallStyle();

                                await checkPauseState();
                            }

                        // 分支 B: 匹配为 “已投放” -> 双层哈希锁定、安全存盘刷新页面接力下一行
                        } else if (authStatus === 'delivered') {
                            log(`- ⚠️ 匹配完毕!此账号当前为 [已投放] 状态!`, 'warning');

                            markDramaAsCompleted(dramaName);

                            log(`- ⏳ 安全同步落盘中,请勿触摸/关闭浏览器...`, 'system');
                            await sleep(600);

                            log(`- 🔄 存盘完毕。触发页面重载自启动,为您接力第 ${i + 2} 行数据!`, 'warning');

                            GM_setValue(PROGRESS_INDEX_KEY, i + 1);
                            renderPreviewTable(); // 🌟 同步滚动视角对焦
                            GM_setValue(AUTO_ACTIVE_KEY, 'true');

                            // 重置状态
                            isRunning = false;
                            updateBallStyle();

                            await sleep(1000);
                            location.reload();
                            return;

                        } else {
                            log(`- ⚠️ 该抖音号状态处于非全域授权,流程挂起...`, 'warning');
                            isPaused = true;
                            btnPauseLabel.innerText = '继续';
                            btnPauseBuild.classList.remove('ios-btn-orange');
                            btnPauseBuild.classList.add('ios-btn-primary');
                            updateBallStyle();
                            await checkPauseState();
                        }
                    } else {
                        log(`- ❌ 列表没有匹配的抖音 ID 【${activeId}】,流程挂起...`, 'error');
                        isPaused = true;
                        btnPauseLabel.innerText = '继续';
                        btnPauseBuild.classList.remove('ios-btn-orange');
                        btnPauseBuild.classList.add('ios-btn-primary');
                        updateBallStyle();
                        await checkPauseState();
                    }

                } else {
                    log(`⚠️ 未能在商品搜索结果中自动匹配到剧名 “${dramaName}”!`, 'warning');
                    log(`- 脚本流程已为您自动[挂起/暂停],请您手动在界面点击选择正确商品,并点击“确定”保存。`, 'warning');
                    log(`- 手动配置完成后,请在脚本悬浮窗点击“继续”,系统将接力配置下一个剧目.`, 'warning');

                    isPaused = true;
                    btnPauseLabel.innerText = '继续';
                    btnPauseBuild.classList.remove('ios-btn-orange');
                    btnPauseBuild.classList.add('ios-btn-primary');
                    updateBallStyle();

                    await checkPauseState();
                    continue;
                }

                // 任务间隔延时
                if (i < rows.length - 1) {
                    log("等待 4 秒后执行下一行配置...", "system");
                    await sleep(4000);
                }

            } catch (error) {
                log(`[报错] 自动注入流在第 ${i + 1} 行中断: ${error.message}`, "error");
                log('流程暂停。请核实页面弹窗状态,随后点击自运行按钮即可一键恢复接力。', 'warning');
                isRunning = false;
                isPaused = false;
                updateBallStyle();
                break;
            }
        }

        isRunning = false;
        isPaused = false;
        btnPauseBuild.className = 'ios-btn ios-btn-disabled';
        btnPauseLabel.innerText = '暂停';
        updateBallStyle();
        log('🎉 所有自动化配置执行完毕!', 'info');
        updateCurrentBuildingDramaName('--');
        GM_deleteValue(PROGRESS_INDEX_KEY);
    }

    // 16. 页面首次加载与初始化
    renderPreviewTable();
    renderDouyinList();
    updateRemainingUI(); // 渲染初始化剩下的搭项目额度数据

    const isMinimized = GM_getValue('ios_helper_minimized', 'false');
    if (isMinimized === 'true') {
        togglePanelMinimizeState(true);
    } else {
        togglePanelMinimizeState(false);
    }

    checkAutoResumeState();

})();