// ==UserScript== // @name wjx // @namespace https://docs.scriptcat.org/ // @version 0.1.0 // @description 最人性化的问卷星自动化填写神器:一行代码不改!直接可视化界面设定比例,点击即可开始刷问卷! // @author zemelee // @match *://*.wjx.* // @match https://www.wjx.cn/* // @match https://w.wjx.com/* // @match https://v.wjx.cn/* // @grant none // ==/UserScript== (function () { 'use strict'; function checkAndRedirect() { const currentUrl = window.location.href; const activityIdMatch = currentUrl.match(/[?&]activityid=([^&]+)/i); if (activityIdMatch) { const activityId = activityIdMatch[1]; const targetUrl = `https://v.wjx.cn/vm/${activityId}.aspx`; window.location.href = targetUrl; } } // 执行检测 checkAndRedirect(); const style = document.createElement('style'); style.textContent = ` .control-panel { position: fixed; top: 20px; left: 20px; background: white; padding: 15px; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.15); z-index: 9999; max-width: 400px; max-height: 80vh; overflow-y: auto; transition: all 0.3s ease; } .control-panel.dragging { transition: none; opacity: 0.9; } .panel-header { cursor: move; user-select: none; padding-top: 40px; border-bottom: 1px solid #eee; color: #666; font-size: 12px; display: flex; align-items: center; gap: 5px; } .panel-header:hover { color: #409eff; background-color: #f5f7fa; } .drag-handle { font-size: 14px; } .control-panel.collapsed { max-height: 50px; overflow: hidden; } .control-panel h3 { margin: 0 0 10px 0; font-size: 16px; color: #333; } .control-panel button { display: block; width: 100%; margin: 5px 0; padding: 8px 12px; background-color: #409eff; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 13px; } .control-panel button:hover { background-color: #66b1ff; } .control-panel button.secondary { background-color: #67c23a; } .control-panel button.secondary:hover { background-color: #85ce61; } .control-panel button.danger { background-color: #f56c6c; } .control-panel button.danger:hover { background-color: #f78989; } .control-panel button.warning { background-color: #e6a23c; } .control-panel button.warning:hover { background-color: #ebb563; } .control-panel .loop-input-group { display: flex; gap: 8px; margin: 8px 0; } .control-panel .loop-input-group input { flex: 1; padding: 8px 12px; border: 1px solid #dcdfe6; border-radius: 4px; font-size: 13px; outline: none; } .control-panel .loop-input-group input:focus { border-color: #409eff; box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2); } .control-panel .loop-status { background: #f0f9ff; padding: 8px; border-radius: 4px; font-size: 12px; color: #409eff; margin: 8px 0; text-align: center; } .data-output { background: #f5f7fa; padding: 10px; border-radius: 4px; font-size: 11px; max-height: 300px; overflow-y: auto; white-space: pre-wrap; word-break: break-all; margin-top: 10px; } .close-btn { position: absolute; top: 10px; right: 20px; background: none; border: none; font-size: 18px; cursor: pointer; color: #999; padding: 0; width: auto; } .close-btn:hover { color: #333; } .toggle-btn { position: absolute; top: 10px; right: 0px; background: none; border: none; font-size: 16px; cursor: pointer; color: #409eff; padding: 0; transition: transform 0.3s ease; } .toggle-btn:hover { color: #66b1ff; } .toggle-btn.collapsed { transform: rotate(180deg); } .panel-content { transition: opacity 0.3s ease; } .control-panel.collapsed .panel-content { opacity: 0; pointer-events: none; } .ad-banner { color: white; padding: 12px; border-radius: 6px; margin-top: 15px; text-align: center; animation: gradientShift 8s ease infinite; background-size: 200% 200%; } .ad-banner.style-1 { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); } .ad-banner.style-2 { background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); } .ad-banner.style-3 { background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); } .ad-banner.style-4 { background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%); } .ad-banner.style-5 { background: linear-gradient(135deg, #fa709a 0%, #fee140 100%); } .ad-banner.style-6 { background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%); color: #333; } @keyframes gradientShift { 0% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } 100% { background-position: 0% 50%; } } .ad-banner a { color: inherit; text-decoration: none; font-weight: bold; font-size: 14px; display: block; margin-bottom: 8px; } .ad-banner a:hover { text-decoration: underline; } .ad-desc { font-size: 11px; line-height: 1.5; opacity: 0.9; } .ad-desc span { display: block; margin: 2px 0; } .radio-input, .matrix-input { padding: 4px 8px; margin-right: 8px !important; margin-left: 4px; border: 1px solid #dcdfe6; border-radius: 4px; height: 28px; line-height: 28px; font-size: 12px; width: 60px; box-sizing: border-box; outline: none; } .radio-input:focus { border-color: #409eff; box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2); } .radio-input[type="text"] { width: 120px; } .matrix-input{ width: 40px; } .percent-display { display: inline-block; margin-left: 8px; padding: 2px 6px; background-color: #f0f9ff; border: 1px solid #409eff; border-radius: 3px; color: #409eff; font-size: 11px; font-weight: bold; min-width: 45px; text-align: center; } .text-input { padding: 8px 10px; margin-right: 8px !important; margin-left: 4px; border: 1px solid #dcdfe6; border-radius: 4px; font-size: 12px; width: 300px; /* 比单行输入框宽,适配多行输入 */ min-height: 100px; /* 最小高度,保证能看到多行 */ box-sizing: border-box; outline: none; line-height: 1.5; /* 行高适配,阅读更舒适 */ resize: vertical; /* 仅允许垂直调整高度 */ } .text-input:focus { border-color: #409eff; box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2); } `; // 将样式表插入到页面头部 document.head.appendChild(style); function randint(a, b) { return Math.floor(Math.random() * (b - a + 1) + a); } // ========== 百分比计算函数(普通题型) ========== function handlePercentInput(event) { const input = event.target; const topic = input.dataset.topic; // 获取同一题目下的所有输入框 const inputs = document.querySelectorAll(`input[data-topic="${topic}"].radio-input`); const percentDisplays = document.querySelectorAll(`input[data-topic="${topic}"].radio-input + span.percent-display`); // 计算总和 let total = 0; inputs.forEach(inp => { const value = parseFloat(inp.value) || 0; total += value; }); // 计算并更新每个选项的百分比 inputs.forEach((inp, index) => { const value = parseFloat(inp.value) || 0; let percent = 0; if (total > 0) { percent = (value / total) * 100; } if (percentDisplays[index]) { percentDisplays[index].textContent = percent.toFixed(0) + '%'; } }); // 实时保存数据 window.wjxSaveData(); } // ========== 矩阵题百分比计算函数 ========== function handleMatrixPercentInput(event) { const input = event.target; const topic = input.dataset.topic; const row = input.dataset.row; // 获取同一题目同一行下的所有输入框 const inputs = document.querySelectorAll(`input[data-topic="${topic}"][data-row="${row}"].matrix-input`); const percentDisplays = document.querySelectorAll(`input[data-topic="${topic}"][data-row="${row}"].matrix-input + span.percent-display`); // 计算该行的总和 let total = 0; inputs.forEach(inp => { const value = parseFloat(inp.value) || 0; total += value; }); // 计算并更新该行每个选项的百分比 inputs.forEach((inp, index) => { const value = parseFloat(inp.value) || 0; let percent = 0; if (total > 0) { percent = (value / total) * 100; } if (percentDisplays[index]) { percentDisplays[index].textContent = percent.toFixed(0) + '%'; } }); // 实时保存数据 window.wjxSaveData(); } // ========== localStorage 相关函数 ========== const STORAGE_KEY = 'wjx_percent_data'; const LOOP_KEY = 'wjx_loop_count'; const LOOP_CURRENT_KEY = 'wjx_loop_current'; let savedData = null; // 缓存加载的数据 // 实时保存数据到 localStorage window.wjxSaveData = function () { const data = collectAllData(); localStorage.setItem(STORAGE_KEY, JSON.stringify(data)); } // 从 localStorage 重新加载数据 window.wjxLoadData = function () { const saved = localStorage.getItem(STORAGE_KEY); const data = JSON.parse(saved); savedData = data; applyData(data); } // 获取保存的数据中指定题目的值 function getSavedValue(topic, row, index, text = 0) { if (!savedData || !savedData[topic]) return text ? '' : 1; // 默认值:文本题返回空字符串,其他返回1 const topicData = savedData[topic]; // 文本题直接返回整个数组 if (text) { if (Array.isArray(topicData)) { return topicData.join('\n'); } return ''; } if (row) { // 矩阵题 if (topicData && topicData[row] && topicData[row][index] !== undefined) { return topicData[row][index]; } } else { // 普通题型 if (topicData && topicData[index] !== undefined) { return topicData[index]; } } return 1; // 默认值1 } // 清除 localStorage 数据 window.wjxClearData = function () { localStorage.removeItem(STORAGE_KEY); alert('缓存已清除'); } // 收集所有题目数据 function collectAllData() { const result = {}; const processedTopics = new Set(); // 普通题型数据收集(包括 .radio-input 和 .matrix-input) const allInputs = document.querySelectorAll('.radio-input[data-topic], .matrix-input[data-topic]'); allInputs.forEach(input => { const topic = input.dataset.topic; const row = input.dataset.row; if (processedTopics.has(`${topic}-${row || 'default'}`)) { return; } processedTopics.add(`${topic}-${row || 'default'}`); if (row) { // 矩阵题 if (!result[topic]) { result[topic] = []; // 二维数组 } // 一行的输入框 const rowInputs = document.querySelectorAll(`.radio-input[data-topic="${topic}"][data-row="${row}"], .matrix-input[data-topic="${topic}"][data-row="${row}"]`); const values = []; // 一行的比例 rowInputs.forEach(inp => { values.push(parseFloat(inp.value) || 0); }); result[topic].push(values); // 一行的比例 } else { // 普通题型 const topicInputs = document.querySelectorAll(`.radio-input[data-topic="${topic}"]:not([data-row])`); const values = []; topicInputs.forEach(inp => { values.push(parseFloat(inp.value) || 0); }); result[topic] = values; } }); // 文本题型数据收集 const textInputs = document.querySelectorAll('.text-input'); textInputs.forEach((input) => { const topic = input.dataset.topic const valueArray = input.value.split('\n').filter(item => item.trim() !== ''); result[`${topic}`] = valueArray; }); return result; } // 应用数据到输入框 function applyData(data) { // 应用普通题型和矩阵题数据 Object.keys(data).forEach((topic) => { const topicData = data[topic]; // 矩阵题:二维数组 if (topicData && topicData.length > 0 && Array.isArray(topicData[0])) { Object.keys(topicData).forEach(row => { const rowInputs = document.querySelectorAll(`.radio-input[data-topic="${topic}"][data-row="${row}"], .matrix-input[data-topic="${topic}"][data-row="${row}"]`); topicData[row].forEach((value, index) => { if (rowInputs[index]) { rowInputs[index].value = value; rowInputs[index].dispatchEvent(new Event('input')); } }); }); } else if (topicData && topicData.length > 0 && typeof topicData[0] === 'number') { // 普通题型:数字数组 const inputs = document.querySelectorAll(`.radio-input[data-topic="${topic}"]:not([data-row])`); topicData.forEach((value, idx) => { if (inputs[idx]) { inputs[idx].value = value; inputs[idx].dispatchEvent(new Event('input')); } }); } else if (topicData && topicData.length > 0 && typeof topicData[0] === 'string') { // 文本题:字符串数组 const textInputs = document.querySelectorAll(`.text-input[data-topic="${topic}"]:not([data-row])`); if (textInputs.length > 0) { textInputs[0].value = topicData.join('\n'); } } }); } // 开始循环提交 window.wjxStartLoop = async function (totalCount) { if (!totalCount || totalCount < 1) { alert('请输入有效的循环次数'); return; } localStorage.setItem(LOOP_KEY, totalCount); localStorage.setItem(LOOP_CURRENT_KEY, 0); await wjxAutoFill(); } // 停止循环 window.wjxStopLoop = function () { localStorage.removeItem(LOOP_KEY); localStorage.removeItem(LOOP_CURRENT_KEY); alert('循环已停止'); location.reload(); } // 获取当前循环状态 function getLoopStatus() { const totalCount = localStorage.getItem(LOOP_KEY); const currentCount = localStorage.getItem(LOOP_CURRENT_KEY); return { totalCount: totalCount ? parseInt(totalCount) : 0, currentCount: currentCount ? parseInt(currentCount) : 0 }; } // 更新循环状态显示 function updateLoopStatus() { const status = getLoopStatus(); const statusEl = document.querySelector('.loop-status'); if (statusEl) { if (status.totalCount > 0) { statusEl.textContent = `循环进度: ${status.currentCount} / ${status.totalCount}`; } else { statusEl.textContent = '未开始循环'; } } } // 根据比例自动填写问卷 window.wjxAutoFill = async function () { const data = collectAllData(); // 填写单选题和矩阵题 Object.keys(data).forEach(topic => { const topicData = data[topic]; const type = document.querySelector(`#div${topic}`).getAttribute("type") // 填写矩阵题:二维数组 if (topicData && topicData.length > 0 && Array.isArray(topicData[0])) { Object.keys(topicData).forEach(row => { // 二维数组,row表示当前行的比例 const values = topicData[row]; const total = values.reduce((sum, val) => sum + val, 0); // 一行和 if (total > 0) { const probabilities = values.map(val => val / total); const random = Math.random(); let cumulative = 0; let selectedIndex = 0; for (let i = 0; i < probabilities.length; i++) { cumulative += probabilities[i]; if (random <= cumulative) { selectedIndex = i; break; } } const tds = document.querySelectorAll(`#divRefTab${topic} tr[rowindex="${row}"] td`); const tdElements = Array.from(tds).slice(1); if (tdElements[selectedIndex]) { tdElements[selectedIndex].click(); } } }); } else if (topicData && topicData.length > 0 && typeof topicData[0] === "number") { // 填写普通题型:数字数组 const total = topicData.reduce((sum, val) => sum + val, 0); if (total > 0) { // 计算每个选项的概率 const probabilities = topicData.map(val => val / total); // 生成随机数选择选项 const random = Math.random(); let cumulative = 0; let selectedIndex = 0; for (let i = 0; i < probabilities.length; i++) { cumulative += probabilities[i]; if (random <= cumulative) { selectedIndex = i; break; } } if (type == "3" || type == "4") { const opt = document.querySelector(`#div${topic} > div.ui-controlgroup.column1 > div:nth-child(${selectedIndex + 1})`); if (opt) { opt.click(); } } else if (type == "5") { const opt = document.querySelector(`#div${topic} > div.scale-div.defaultScale > div > ul > li:nth-child(${selectedIndex + 1})`) if (opt) { opt.click(); } } else if (type == "8") { const textInput = document.querySelector(`#q${topic}`) textInput.value = randint(topicData[0], topicData[1]) } } } else if (topicData && (type == "1" || type == "2")) { let idx = randint(0, topicData.length - 1); // 随机选一个答案 document.querySelector("#q3").value = topicData[idx] } }); await submit() } async function submit() { const status = getLoopStatus(); await new Promise((resolve) => { setTimeout(() => { //点击提交按钮 const nextBtn = document.querySelector("#ctlNext") if (nextBtn) { nextBtn.click(); resolve(); } }, 500); }); // 延迟 2 秒后点击验证按钮 await new Promise((resolve) => { setTimeout(() => { let verify = document.querySelector("#layui-layer1 > div.layui-layer-btn.layui-layer-btn- > a") if(verify){ verify.click(); } resolve(); }, 2000); }); // 如果正在循环,更新计数并刷新 if (status.totalCount > 0) { const newCount = status.currentCount + 1; if (newCount < status.totalCount) { localStorage.setItem(LOOP_CURRENT_KEY, newCount); // 延迟3秒后刷新页面继续 setTimeout(() => { location.reload(); }, 3000); } else { // 循环完成 localStorage.removeItem(LOOP_KEY); localStorage.removeItem(LOOP_CURRENT_KEY); } } } // 创建控制面板 function createControlPanel() { const panel = document.createElement('div'); panel.className = 'control-panel'; // 随机选择广告样式 const adStyles = ['style-1', 'style-2', 'style-3', 'style-4', 'style-5', 'style-6']; const randomStyle = adStyles[Math.floor(Math.random() * adStyles.length)]; // 获取循环状态 const loopStatus = getLoopStatus(); panel.innerHTML = `
⋮⋮ 控制面板
${loopStatus.totalCount > 0 ? `循环进度: ${loopStatus.currentCount} / ${loopStatus.totalCount}` : '未开始循环'}
SugarBlack - 专业问卷填写平台
✓ 支持IP切换,比例定制 ✓ 保证填写时长,信效度分析 ✓ 支持数据反填,模型假设
`; document.body.appendChild(panel); // 初始化拖拽功能 initDraggable(panel); // 如果正在循环,自动开始填写 if (loopStatus.totalCount > 0 && loopStatus.currentCount < loopStatus.totalCount) { setTimeout(() => { wjxAutoFill(); }, 1000); } } // 切换面板折叠/展开状态 window.togglePanel = function (btn) { const panel = btn.closest('.control-panel'); panel.classList.toggle('collapsed'); btn.classList.toggle('collapsed'); btn.textContent = panel.classList.contains('collapsed') ? '▲' : '▼'; } // 初始化面板拖拽功能 function initDraggable(panel) { const header = panel.querySelector('.panel-header'); let isDragging = false; let startX, startY, initialX, initialY; header.addEventListener('mousedown', (e) => { isDragging = true; panel.classList.add('dragging'); startX = e.clientX; startY = e.clientY; const rect = panel.getBoundingClientRect(); initialX = rect.left; initialY = rect.top; }); document.addEventListener('mousemove', (e) => { if (!isDragging) return; e.preventDefault(); const dx = e.clientX - startX; const dy = e.clientY - startY; panel.style.left = `${initialX + dx}px`; panel.style.top = `${initialY + dy}px`; panel.style.right = 'auto'; }); document.addEventListener('mouseup', () => { if (isDragging) { isDragging = false; panel.classList.remove('dragging'); } }); } // ========== 原有逻辑保持不变 ========== document.body.style.userSelect = 'text'; document.oncopy = document.oncut = document.onselectstart = null; let fieldsets = document.querySelectorAll("#divQuestion>fieldset"); fieldsets.forEach(fieldset => { fieldset.style.display = "" const questionDivs = fieldset.querySelectorAll('div[topic]'); questionDivs.forEach((questionDiv) => { const topic = questionDiv.getAttribute('topic'); const type = questionDiv.getAttribute('type'); if (type == "1" || type == "2") { const targetElement = document.querySelector(`#div${topic} > div.ui-input-text`); // 增加元素存在性检查,避免报错 if (targetElement) { const inputElement = document.createElement("textarea"); inputElement.placeholder = "一行一个答案"; inputElement.className = "text-input"; inputElement.dataset.topic = topic; inputElement.value = getSavedValue(topic, null, 0, 1); // 从缓存读取 inputElement.addEventListener('input', window.wjxSaveData); targetElement.parentNode.insertBefore(inputElement, targetElement); } } if (type == "3") { let opts = document.querySelectorAll(`#div${topic} > div.ui-controlgroup.column1 > div.ui-radio`) opts.forEach((opt, index) => { let radioInput = document.createElement("input"); radioInput.type = "number"; radioInput.placeholder = "占比"; radioInput.className = "radio-input"; radioInput.dataset.topic = topic; radioInput.value = getSavedValue(topic, null, index); // 从缓存读取 radioInput.addEventListener('input', handlePercentInput); opt.appendChild(radioInput); let percentDisplay = document.createElement("span"); percentDisplay.className = "percent-display"; percentDisplay.textContent = "0%"; opt.appendChild(percentDisplay); }) } if (type == "4") { let opts = document.querySelectorAll(`#div${topic} > div.ui-controlgroup.column1 > div.ui-checkbox`) opts.forEach((opt, index) => { let radioInput = document.createElement("input"); radioInput.type = "number"; radioInput.placeholder = "占比"; radioInput.className = "radio-input"; radioInput.dataset.topic = topic; radioInput.value = getSavedValue(topic, null, index); // 从缓存读取 radioInput.addEventListener('input', handlePercentInput); opt.appendChild(radioInput); let percentDisplay = document.createElement("span"); percentDisplay.className = "percent-display"; percentDisplay.textContent = "0%"; opt.appendChild(percentDisplay); }) } if (type == "5") { let opts = document.querySelectorAll(`#div${topic} > div.scale-div.defaultScale > div > ul > li`) for (let i = 0; i < opts.length; i++) { let radioInput = document.createElement("input"); radioInput.type = "number"; radioInput.placeholder = "占比"; radioInput.className = "radio-input"; radioInput.dataset.topic = topic; radioInput.value = getSavedValue(topic, null, i); // 从缓存读取 radioInput.addEventListener('input', handlePercentInput); questionDiv.appendChild(radioInput); let percentDisplay = document.createElement("span"); percentDisplay.className = "percent-display"; percentDisplay.textContent = "0%"; questionDiv.appendChild(percentDisplay); } } // 矩阵 if (type == "6") { const allTrs = questionDiv.getElementsByTagName('tr'); const RowIndexTrs = []; // 第一步:筛选出rowindex为有效数字的tr元素 for (let tr of allTrs) { const rowIndexVal = tr.getAttribute('rowindex'); // 属性存在 + 是纯数字 if (/^\d+$/.test(rowIndexVal)) { const num = parseInt(rowIndexVal); if (num >= 0 && num < 100) { RowIndexTrs.push(tr); } } } // 为每一行的每个选项添加输入框和百分比显示 RowIndexTrs.forEach(tr => { const allTds = tr.getElementsByTagName('td'); const tdsArray = Array.from(allTds); const tdsExceptFirst = tdsArray.slice(1); const rowIndex = tr.getAttribute('rowindex'); let colIndex = 0; tdsExceptFirst.forEach((td) => { let radioInput = document.createElement("input"); radioInput.type = "number"; radioInput.min = "0"; radioInput.placeholder = "占比"; radioInput.className = "matrix-input"; // 使用 topic-row 作为标识,每一行独立计算 radioInput.dataset.topic = topic; radioInput.dataset.row = rowIndex; radioInput.value = getSavedValue(topic, rowIndex, colIndex); // 从缓存读取 radioInput.addEventListener('input', handleMatrixPercentInput); td.appendChild(radioInput); let percentDisplay = document.createElement("span"); percentDisplay.className = "percent-display"; percentDisplay.textContent = "0%"; td.appendChild(percentDisplay); colIndex++; }); }); } // 滑块 if (type == "8") { let errorMessage = document.querySelector(`#div${topic} > div.errorMessage`) let input1 = document.createElement("input"); let input2 = document.createElement("input"); input1.type = "number"; input1.placeholder = "最小值"; input1.className = "radio-input"; input1.dataset.topic = topic; input1.value = "1"; // 默认值为1 input2.type = "number"; input2.placeholder = "最大值"; input2.className = "radio-input"; input2.dataset.topic = topic; input2.value = "1"; // 默认值为1 errorMessage.parentNode.insertBefore(input1, errorMessage); errorMessage.parentNode.insertBefore(input2, errorMessage); } }); }); // 初始化缓存数据 let saved = localStorage.getItem(STORAGE_KEY); if (saved) { savedData = JSON.parse(saved); } // 创建控制面板 createControlPanel(); // 延迟计算百分比,确保输入框已创建 setTimeout(() => { window.wjxLoadData(); }, 100); // 定时更新循环状态 setInterval(() => { updateLoopStatus(); }, 1000); })();