// ==UserScript== // @name 标注助手 - 框数统计 // @namespace https://dawnchan.online // @version 1.0.1 // @description 自动累加标注框数,支持手动累加和自动遍历,可自定义等待时间、帧数,面板可拖动/缩放。任务模式支持多任务管理,显示变化量及记录时间,自动跳转第一帧。 // @author cc // @match *://*/*pointcloud* // 请根据实际页面修改 // @match *://*/w/pointcloud/* // @grant none // ==/UserScript== (function() { 'use strict'; console.log('标注助手脚本已加载 v1.0.2'); // ========== 配置 ========== const STORAGE_KEY_SINGLE = 'daily_box_total'; const STORAGE_KEY_TASKS = 'annotation_tasks'; const DEFAULT_WAIT_MS = 100; const DEFAULT_MAX_FRAMES = 81; // 全局变量 let stopAutoFlag = false; let currentMode = 'single'; // 'single' 或 'task' let currentTaskId = ''; // ========== 单次模式存储 ========== function loadSingleTotal() { let val = localStorage.getItem(STORAGE_KEY_SINGLE); return val ? parseInt(val, 10) : 0; } function saveSingleTotal(total) { localStorage.setItem(STORAGE_KEY_SINGLE, total); } function addToSingleTotal(frameTotal) { let running = loadSingleTotal(); running += frameTotal; saveSingleTotal(running); return running; } function resetSingleTotal() { localStorage.removeItem(STORAGE_KEY_SINGLE); } // ========== 任务模式存储 ========== function loadTasks() { let tasks = localStorage.getItem(STORAGE_KEY_TASKS); return tasks ? JSON.parse(tasks) : {}; } function saveTasks(tasks) { localStorage.setItem(STORAGE_KEY_TASKS, JSON.stringify(tasks)); } function getCurrentTask() { if (!currentTaskId) return null; const tasks = loadTasks(); return tasks[currentTaskId] || null; } function getAllTaskIds() { const tasks = loadTasks(); return Object.keys(tasks); } function updateTaskTotal(taskId, newTotal, startTime = '', endTime = '') { const tasks = loadTasks(); let task = tasks[taskId]; const now = new Date().toISOString(); let change = newTotal; let lastTotal = 0; if (task) { lastTotal = task.totalBoxes; change = newTotal - lastTotal; task.totalBoxes = newTotal; task.lastUpdate = now; if (startTime) task.startTime = startTime; if (endTime) task.endTime = endTime; if (!task.history) task.history = []; task.history.unshift({ total: newTotal, timestamp: now, change: change }); if (task.history.length > 10) task.history.pop(); } else { task = { id: taskId, totalBoxes: newTotal, startTime: startTime || '', endTime: endTime || '', lastUpdate: now, history: [{ total: newTotal, timestamp: now, change: newTotal }] }; tasks[taskId] = task; } saveTasks(tasks); return { lastTotal, change }; } function deleteTask(taskId) { const tasks = loadTasks(); delete tasks[taskId]; saveTasks(tasks); if (currentTaskId === taskId) { currentTaskId = ''; const taskSelect = document.getElementById('task-select'); if (taskSelect) taskSelect.value = ''; updateTaskDisplay(); } } // 辅助函数:判断当前是否为第一帧 function isFirstFrame() { // 方法1:检查页码文本 const currentPageElem = document.querySelector('#pages .page-select'); if (currentPageElem && currentPageElem.textContent.trim() === '1') { return true; } // 方法2:检查进度文本 "1/81" const progressText = document.getElementById('page-progress-text'); if (progressText && progressText.textContent.trim().startsWith('1/')) { return true; } return false; } // 辅助函数:跳转到第一帧 async function jumpToFirstFrame(waitMs) { const firstPage = document.querySelector('#pages p.page:first-child'); if (!firstPage) { console.warn('未找到第一帧页码元素'); return false; } if (isFirstFrame()) { console.log('已经是第一帧,无需跳转'); return true; } firstPage.click(); // 等待页面加载 await new Promise(resolve => setTimeout(resolve, waitMs)); // 可选:等待进度条更新或页码变化 let retries = 0; while (!isFirstFrame() && retries < 5) { await new Promise(resolve => setTimeout(resolve, 500)); retries++; } return isFirstFrame(); } // ========== 获取当前帧框数 ========== function getCurrentFrameTotal() { let frameTotal = 0; const elements = document.querySelectorAll('.left-seg-num'); elements.forEach(el => { let match = el.innerText.match(/\(\s*(\d+)\s*\)/); if (match) frameTotal += parseInt(match[1], 10); }); return frameTotal; } // ========== 单次模式手动累加 ========== function handleSingleAddFrame() { const frameTotal = getCurrentFrameTotal(); const newTotal = addToSingleTotal(frameTotal); appendLog(`累加当前帧: ${frameTotal} 框 | 单次累计: ${newTotal}`); updateSingleDisplay(); } // ========== 自动遍历 ========== async function autoCountAllFrames(waitMs, maxFrames) { const nextBtn = document.querySelector('#page-actions button:last-child'); if (!nextBtn) { alert('未找到“下一帧”按钮,请检查页面结构'); return; } waitMs = parseInt(waitMs, 10); if (isNaN(waitMs) || waitMs <= 0) waitMs = DEFAULT_WAIT_MS; maxFrames = parseInt(maxFrames, 10); if (isNaN(maxFrames) || maxFrames <= 0) maxFrames = Infinity; if (currentMode === 'single') { // 单次模式:逐帧累加,显示每帧细节 if (!confirm(`将自动统计最多 ${maxFrames === Infinity ? '全部' : maxFrames} 帧,每帧等待 ${waitMs} 毫秒。\n模式: 单次累计\n请确保当前是第一帧且数字已加载。\n确定开始吗?`)) return; stopAutoFlag = false; appendLog(`开始自动统计,模式: 单次,等待 ${waitMs}ms,最多 ${maxFrames === Infinity ? '不限' : maxFrames} 帧`); let countedFrames = 0; // 先累加当前帧 let frameTotal = getCurrentFrameTotal(); let newTotal = addToSingleTotal(frameTotal); countedFrames++; appendLog(`帧 ${countedFrames}: ${frameTotal} 框 | 单次累计: ${newTotal}`); updateSingleDisplay(); // 循环后续帧 while (countedFrames < maxFrames) { if (stopAutoFlag) { // <-- 检查停止 appendLog('用户手动停止了自动遍历'); alert('已停止自动遍历'); return; } if (nextBtn.disabled || nextBtn.classList.contains('is-disabled')) { appendLog(`已到达最后一帧,共统计 ${countedFrames} 帧`); alert(`自动统计完成!共统计 ${countedFrames} 帧,单次累计总数: ${loadSingleTotal()}`); return; } nextBtn.click(); await new Promise(resolve => setTimeout(resolve, waitMs)); frameTotal = getCurrentFrameTotal(); newTotal = addToSingleTotal(frameTotal); countedFrames++; appendLog(`帧 ${countedFrames}: ${frameTotal} 框 | 单次累计: ${newTotal}`); updateSingleDisplay(); } appendLog(`已达到设定的最大帧数 ${maxFrames},自动停止`); alert(`已达到最大帧数 ${maxFrames},当前单次累计: ${loadSingleTotal()}`); } else if (currentMode === 'task') { // 任务模式:先跳转到第一帧,然后统计整个任务的总框数 if (!currentTaskId) { alert('请先选择或创建任务'); return; } // 检查是否需要跳转第一帧 if (!isFirstFrame()) { const doJump = confirm('当前不是第一帧,是否跳转到第一帧开始统计?\n(选择“取消”则从当前帧开始统计)'); if (doJump) { appendLog('正在跳转到第一帧...'); const jumped = await jumpToFirstFrame(waitMs); if (!jumped) { alert('跳转到第一帧失败,请手动切换到第一帧后重试'); return; } appendLog('已跳转到第一帧'); } else { appendLog('用户选择不从第一帧开始,将从当前帧开始统计'); } } else { appendLog('当前已是第一帧,开始统计'); } if (!confirm(`将自动统计任务 [${currentTaskId}] 的总框数,从当前帧开始到最后一帧,每帧等待 ${waitMs} 毫秒。\n请确保当前是第一帧且数字已加载。\n确定开始吗?`)) return; appendLog(`开始统计任务 [${currentTaskId}] 总框数,等待 ${waitMs}ms`); let totalBoxes = 0; let countedFrames = 0; // 先累加当前帧 totalBoxes += getCurrentFrameTotal(); countedFrames++; // 循环后续帧 while (countedFrames < maxFrames) { if (nextBtn.disabled || nextBtn.classList.contains('is-disabled')) { appendLog(`已到达最后一帧,共统计 ${countedFrames} 帧`); break; } nextBtn.click(); await new Promise(resolve => setTimeout(resolve, waitMs)); totalBoxes += getCurrentFrameTotal(); countedFrames++; if (countedFrames >= maxFrames) break; } // 获取起止时间 const startTime = document.getElementById('task-start-time')?.value || ''; const endTime = document.getElementById('task-end-time')?.value || ''; const { lastTotal, change } = updateTaskTotal(currentTaskId, totalBoxes, startTime, endTime); appendLog(`任务 [${currentTaskId}] 总框数: ${totalBoxes} | 上次记录: ${lastTotal} | 变化: ${change >= 0 ? '+' : ''}${change}`); updateTaskDisplay(); alert(`任务 [${currentTaskId}] 统计完成!\n总框数: ${totalBoxes}\n较上次${change >= 0 ? '增加' : '减少'}: ${Math.abs(change)}`); populateTaskSelect(); } } // 手动输入任务总框数 function manualUpdateTaskTotal() { if (!currentTaskId) { alert('请先选择或创建任务'); return; } const input = prompt(`请输入任务 [${currentTaskId}] 的总框数:`); if (input === null) return; const newTotal = parseInt(input, 10); if (isNaN(newTotal)) { alert('请输入有效的数字'); return; } const startTime = document.getElementById('task-start-time')?.value || ''; const endTime = document.getElementById('task-end-time')?.value || ''; const { lastTotal, change } = updateTaskTotal(currentTaskId, newTotal, startTime, endTime); appendLog(`手动更新任务 [${currentTaskId}] 总框数: ${newTotal} | 上次: ${lastTotal} | 变化: ${change >= 0 ? '+' : ''}${change}`); updateTaskDisplay(); alert(`任务 [${currentTaskId}] 总框数已更新为 ${newTotal},较上次${change >= 0 ? '增加' : '减少'} ${Math.abs(change)}`); populateTaskSelect(); } // ========== 重置 ========== function resetCurrentMode() { if (currentMode === 'single') { if (confirm('确定要重置单次累计数吗?')) { resetSingleTotal(); updateSingleDisplay(); appendLog('单次累计已重置为0'); } } else { if (!currentTaskId) { alert('未选择任务'); return; } if (confirm(`确定要删除任务 [${currentTaskId}] 吗?`)) { deleteTask(currentTaskId); appendLog(`任务 [${currentTaskId}] 已删除`); currentTaskId = ''; const taskSelect = document.getElementById('task-select'); if (taskSelect) taskSelect.value = ''; const startTimeInput = document.getElementById('task-start-time'); const endTimeInput = document.getElementById('task-end-time'); if (startTimeInput) startTimeInput.value = ''; if (endTimeInput) endTimeInput.value = ''; updateTaskDisplay(); populateTaskSelect(); } } } // ========== UI 更新 ========== function updateSingleDisplay() { const totalSpan = document.getElementById('box-total-display'); if (totalSpan) totalSpan.textContent = loadSingleTotal(); } function updateTaskDisplay() { const task = getCurrentTask(); const totalSpan = document.getElementById('task-total-display'); const lastSpan = document.getElementById('task-last-display'); const changeSpan = document.getElementById('task-change-display'); const timeSpan = document.getElementById('task-time-display'); if (totalSpan) totalSpan.textContent = task ? task.totalBoxes : '0'; if (lastSpan) lastSpan.textContent = task && task.history && task.history[1] ? task.history[1].total : '-'; if (changeSpan) { if (task && task.history && task.history[0]) { const change = task.history[0].change; changeSpan.textContent = (change >= 0 ? '+' : '') + change; changeSpan.style.color = change >= 0 ? '#2ecc71' : '#e74c3c'; } else { changeSpan.textContent = '0'; } } if (timeSpan && task) { const lastUpdate = task.lastUpdate ? new Date(task.lastUpdate).toLocaleString() : '-'; timeSpan.textContent = lastUpdate; } } function populateTaskSelect() { const select = document.getElementById('task-select'); if (!select) return; const tasks = loadTasks(); const currentId = currentTaskId; select.innerHTML = ''; for (const id of Object.keys(tasks).sort()) { const option = document.createElement('option'); option.value = id; option.textContent = `${id} (${tasks[id].totalBoxes}框)`; if (id === currentId) option.selected = true; select.appendChild(option); } } function onTaskSelectChange() { const select = document.getElementById('task-select'); const newId = select.value; if (!newId) { currentTaskId = ''; updateTaskDisplay(); return; } currentTaskId = newId; const task = getCurrentTask(); if (task) { if (task.startTime) document.getElementById('task-start-time').value = task.startTime.slice(0,16); if (task.endTime) document.getElementById('task-end-time').value = task.endTime.slice(0,16); } else { document.getElementById('task-start-time').value = ''; document.getElementById('task-end-time').value = ''; } updateTaskDisplay(); appendLog(`切换到任务: ${currentTaskId}`); } function createNewTask() { const newId = prompt('请输入新任务ID(例如 TASK-001):'); if (!newId || newId.trim() === '') return; const trimmedId = newId.trim(); const tasks = loadTasks(); if (tasks[trimmedId]) { alert('任务ID已存在,请使用其他ID'); return; } updateTaskTotal(trimmedId, 0, '', ''); populateTaskSelect(); const select = document.getElementById('task-select'); if (select) { select.value = trimmedId; onTaskSelectChange(); } appendLog(`创建新任务: ${trimmedId}`); } // ========== 日志 ========== function appendLog(msg) { const logArea = document.getElementById('stat-log'); const now = new Date(); const timeStr = `${now.getHours().toString().padStart(2,'0')}:${now.getMinutes().toString().padStart(2,'0')}:${now.getSeconds().toString().padStart(2,'0')}`; const formattedMsg = `[${timeStr}] ${msg}`; if (logArea) { const entry = document.createElement('div'); entry.textContent = formattedMsg; entry.style.borderBottom = '1px solid #444'; entry.style.padding = '2px 0'; logArea.appendChild(entry); logArea.scrollTop = logArea.scrollHeight; } else { console.log(formattedMsg); } } function clearLog() { const logArea = document.getElementById('stat-log'); if (logArea) logArea.innerHTML = ''; } // ========== 模式切换 ========== function setMode(mode) { currentMode = mode; const singlePanel = document.getElementById('single-panel'); const taskPanel = document.getElementById('task-panel'); if (mode === 'single') { if (singlePanel) singlePanel.style.display = 'block'; if (taskPanel) taskPanel.style.display = 'none'; updateSingleDisplay(); } else { if (singlePanel) singlePanel.style.display = 'none'; if (taskPanel) taskPanel.style.display = 'block'; populateTaskSelect(); updateTaskDisplay(); } } // ========== 可拖动 & 可缩放面板 ========== function makeDraggableAndResizable(panel) { let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; const header = panel.querySelector('.panel-header'); if (!header) return; header.style.cursor = 'move'; header.onmousedown = dragMouseDown; function dragMouseDown(e) { e.preventDefault(); pos3 = e.clientX; pos4 = e.clientY; document.onmouseup = closeDragElement; document.onmousemove = elementDrag; } function elementDrag(e) { e.preventDefault(); pos1 = pos3 - e.clientX; pos2 = pos4 - e.clientY; pos3 = e.clientX; pos4 = e.clientY; let top = panel.offsetTop - pos2; let left = panel.offsetLeft - pos1; top = Math.min(Math.max(0, top), window.innerHeight - panel.offsetHeight); left = Math.min(Math.max(0, left), window.innerWidth - panel.offsetWidth); panel.style.top = top + 'px'; panel.style.left = left + 'px'; panel.style.bottom = 'auto'; panel.style.right = 'auto'; } function closeDragElement() { document.onmouseup = null; document.onmousemove = null; } const resizeHandle = document.createElement('div'); resizeHandle.style.cssText = ` position: absolute; bottom: 0; right: 0; width: 15px; height: 15px; cursor: nw-resize; background: #7f8c8d; border-radius: 2px 0 2px 0; `; panel.appendChild(resizeHandle); let startX, startY, startWidth, startHeight; resizeHandle.onmousedown = function(e) { e.preventDefault(); startX = e.clientX; startY = e.clientY; startWidth = panel.offsetWidth; startHeight = panel.offsetHeight; document.onmousemove = resize; document.onmouseup = stopResize; }; function resize(e) { let newWidth = startWidth + (e.clientX - startX); let newHeight = startHeight + (e.clientY - startY); newWidth = Math.min(Math.max(260, newWidth), 600); newHeight = Math.min(Math.max(280, newHeight), 600); panel.style.width = newWidth + 'px'; panel.style.height = newHeight + 'px'; } function stopResize() { document.onmousemove = null; document.onmouseup = null; } } // ========== UI 构建 ========== function createControlPanel() { if (document.getElementById('box-helper-panel')) return; const panel = document.createElement('div'); panel.id = 'box-helper-panel'; panel.style.cssText = ` position: fixed; top: 10px; right: 10px; background: #2c3e50; color: #ecf0f1; border-radius: 8px; padding: 12px; z-index: 999999; font-family: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif; font-size: 13px; box-shadow: 0 2px 10px rgba(0,0,0,0.3); width: 340px; height: auto; text-align: left; resize: both; overflow: auto; `; const header = document.createElement('div'); header.className = 'panel-header'; header.textContent = '框数统计助手'; header.style.fontWeight = 'bold'; header.style.marginBottom = '12px'; header.style.fontSize = '16px'; header.style.padding = '4px 0'; header.style.userSelect = 'none'; header.style.borderBottom = '1px solid #555'; panel.appendChild(header); // 模式切换按钮 const modeSwitchDiv = document.createElement('div'); modeSwitchDiv.style.display = 'flex'; modeSwitchDiv.style.gap = '10px'; modeSwitchDiv.style.marginBottom = '15px'; const singleModeBtn = document.createElement('button'); singleModeBtn.textContent = '单次模式'; singleModeBtn.style.cssText = `flex:1; background:#3498db; border:none; color:#fff; padding:8px; border-radius:4px; cursor:pointer; font-weight:bold;`; singleModeBtn.onclick = () => setMode('single'); const taskModeBtn = document.createElement('button'); taskModeBtn.textContent = '任务模式'; taskModeBtn.style.cssText = `flex:1; background:#e67e22; border:none; color:#fff; padding:8px; border-radius:4px; cursor:pointer; font-weight:bold;`; taskModeBtn.onclick = () => setMode('task'); modeSwitchDiv.appendChild(singleModeBtn); modeSwitchDiv.appendChild(taskModeBtn); panel.appendChild(modeSwitchDiv); // 单次模式面板 const singlePanel = document.createElement('div'); singlePanel.id = 'single-panel'; const totalDiv = document.createElement('div'); totalDiv.style.marginBottom = '12px'; totalDiv.style.fontSize = '14px'; totalDiv.innerHTML = `累计框数:${loadSingleTotal()}`; singlePanel.appendChild(totalDiv); const singleBtnGroup = document.createElement('div'); singleBtnGroup.style.display = 'flex'; singleBtnGroup.style.gap = '8px'; singleBtnGroup.style.marginBottom = '12px'; const addBtn = document.createElement('button'); addBtn.textContent = '累加当前帧'; addBtn.style.cssText = `flex:1; background:#3498db; border:none; color:#fff; padding:6px; border-radius:4px; cursor:pointer;`; addBtn.onclick = () => handleSingleAddFrame(); const resetBtn = document.createElement('button'); resetBtn.textContent = '重置累计'; resetBtn.style.cssText = `flex:1; background:#e67e22; border:none; color:#fff; padding:6px; border-radius:4px; cursor:pointer;`; resetBtn.onclick = () => resetCurrentMode(); singleBtnGroup.appendChild(addBtn); singleBtnGroup.appendChild(resetBtn); singlePanel.appendChild(singleBtnGroup); panel.appendChild(singlePanel); // 任务模式面板 const taskPanel = document.createElement('div'); taskPanel.id = 'task-panel'; taskPanel.style.display = 'none'; const taskSelectDiv = document.createElement('div'); taskSelectDiv.style.marginBottom = '12px'; taskSelectDiv.style.display = 'flex'; taskSelectDiv.style.gap = '8px'; taskSelectDiv.style.alignItems = 'center'; const taskSelectLabel = document.createElement('span'); taskSelectLabel.textContent = '任务:'; taskSelectLabel.style.fontSize = '12px'; const taskSelect = document.createElement('select'); taskSelect.id = 'task-select'; taskSelect.style.flex = '1'; taskSelect.style.padding = '4px'; taskSelect.style.borderRadius = '4px'; taskSelect.style.border = 'none'; taskSelect.onchange = onTaskSelectChange; const newTaskBtn = document.createElement('button'); newTaskBtn.textContent = '新建'; newTaskBtn.style.cssText = `background:#2ecc71; border:none; color:#fff; padding:4px 8px; border-radius:4px; cursor:pointer;`; newTaskBtn.onclick = createNewTask; taskSelectDiv.appendChild(taskSelectLabel); taskSelectDiv.appendChild(taskSelect); taskSelectDiv.appendChild(newTaskBtn); taskPanel.appendChild(taskSelectDiv); const timeDiv = document.createElement('div'); timeDiv.style.marginBottom = '12px'; timeDiv.innerHTML = `
`; taskPanel.appendChild(timeDiv); const taskStatsDiv = document.createElement('div'); taskStatsDiv.style.marginBottom = '12px'; taskStatsDiv.style.backgroundColor = '#1e2a36'; taskStatsDiv.style.padding = '8px'; taskStatsDiv.style.borderRadius = '4px'; taskStatsDiv.innerHTML = `
总框数: 0
上次记录: -
变化量: 0
统计时间: -
`; taskPanel.appendChild(taskStatsDiv); const taskBtnGroup = document.createElement('div'); taskBtnGroup.style.display = 'flex'; taskBtnGroup.style.gap = '8px'; taskBtnGroup.style.marginBottom = '12px'; const manualUpdateBtn = document.createElement('button'); manualUpdateBtn.textContent = '手动输入总框数'; manualUpdateBtn.style.cssText = `flex:1; background:#f39c12; border:none; color:#fff; padding:6px; border-radius:4px; cursor:pointer;`; manualUpdateBtn.onclick = () => manualUpdateTaskTotal(); const taskResetBtn = document.createElement('button'); taskResetBtn.textContent = '删除任务'; taskResetBtn.style.cssText = `flex:1; background:#e67e22; border:none; color:#fff; padding:6px; border-radius:4px; cursor:pointer;`; taskResetBtn.onclick = () => resetCurrentMode(); taskBtnGroup.appendChild(manualUpdateBtn); taskBtnGroup.appendChild(taskResetBtn); taskPanel.appendChild(taskBtnGroup); panel.appendChild(taskPanel); // 公共参数区域 const paramDiv = document.createElement('div'); paramDiv.style.marginBottom = '12px'; paramDiv.style.display = 'flex'; paramDiv.style.gap = '8px'; paramDiv.style.alignItems = 'center'; paramDiv.style.flexWrap = 'wrap'; paramDiv.style.borderTop = '1px solid #555'; paramDiv.style.paddingTop = '10px'; const waitLabel = document.createElement('span'); waitLabel.textContent = '等待(ms):'; waitLabel.style.fontSize = '12px'; const waitInput = document.createElement('input'); waitInput.type = 'number'; waitInput.value = DEFAULT_WAIT_MS; waitInput.style.width = '70px'; waitInput.style.padding = '4px'; waitInput.style.borderRadius = '4px'; waitInput.style.border = 'none'; const maxGroup = document.createElement('span'); maxGroup.style.whiteSpace = 'nowrap'; const maxLabel = document.createElement('span'); maxLabel.textContent = '最多帧数:'; maxLabel.style.fontSize = '12px'; const maxInput = document.createElement('input'); maxInput.type = 'number'; maxInput.value = ''; maxInput.placeholder = '不限'; maxInput.style.width = '70px'; maxInput.style.padding = '4px'; maxInput.style.borderRadius = '4px'; maxInput.style.border = 'none'; maxGroup.appendChild(maxLabel); maxGroup.appendChild(maxInput); paramDiv.appendChild(waitLabel); paramDiv.appendChild(waitInput); paramDiv.appendChild(maxGroup); // 创建按钮行容器 const buttonRow = document.createElement('div'); buttonRow.style.display = 'flex'; buttonRow.style.justifyContent = 'center'; buttonRow.style.gap = '8px'; buttonRow.style.marginTop = '5px'; // 自动遍历按钮 const autoBtn = document.createElement('button'); autoBtn.textContent = '自动遍历'; autoBtn.style.cssText = `flex:0 0 auto; min-height: 35px; min-width: 90px; white-space: nowrap; background:#2ecc71; border:none; color:#fff; padding:4px 12px; border-radius:4px; cursor:pointer;`; autoBtn.onclick = () => autoCountAllFrames(waitInput.value, maxInput.value); // 停止按钮 const stopBtn = document.createElement('button'); stopBtn.textContent = '停止'; stopBtn.style.cssText = `flex:0 0 auto; min-height: 35px; min-width: 60px; white-space: nowrap; background:#e74c3c; border:none; color:#fff; padding:4px 12px; border-radius:4px; cursor:pointer; margin-left: auto;`; stopBtn.onclick = () => { stopAutoFlag = true; appendLog('停止按钮已按下,将在当前帧完成后停止'); }; buttonRow.appendChild(autoBtn); buttonRow.appendChild(stopBtn); paramDiv.appendChild(buttonRow); panel.appendChild(paramDiv); // 日志区域 const logContainer = document.createElement('div'); logContainer.style.marginTop = '8px'; logContainer.style.borderTop = '1px solid #555'; logContainer.style.paddingTop = '8px'; const logHeader = document.createElement('div'); logHeader.style.display = 'flex'; logHeader.style.justifyContent = 'space-between'; logHeader.style.marginBottom = '4px'; const logTitle = document.createElement('span'); logTitle.textContent = '统计记录'; logTitle.style.fontWeight = 'bold'; const clearLogBtn = document.createElement('button'); clearLogBtn.textContent = '清空'; clearLogBtn.style.cssText = `background:#7f8c8d; border:none; color:#fff; padding:2px 6px; border-radius:4px; cursor:pointer; font-size:11px;`; clearLogBtn.onclick = clearLog; logHeader.appendChild(logTitle); logHeader.appendChild(clearLogBtn); logContainer.appendChild(logHeader); const logArea = document.createElement('div'); logArea.id = 'stat-log'; logArea.style.maxHeight = '200px'; logArea.style.overflowY = 'auto'; logArea.style.backgroundColor = '#1e2a36'; logArea.style.padding = '4px'; logArea.style.borderRadius = '4px'; logArea.style.fontSize = '11px'; logArea.style.fontFamily = 'monospace'; logContainer.appendChild(logArea); panel.appendChild(logContainer); document.body.appendChild(panel); console.log('控制面板已添加到页面'); makeDraggableAndResizable(panel); setMode('single'); } function init() { if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { setTimeout(createControlPanel, 500); }); } else { setTimeout(createControlPanel, 500); } } init(); })();