
每天做作业要点好长时间。我打开了虚拟机默默点开了workbuddy把网页丢了进去选择最贵的模型然后告诉workbuddy该干活了行くぞう相棒。几分钟过去了js脚本生成了。到了单位先瞌睡一下默默的点开作业网页F12console粘贴进去然后回车。几秒钟以后跑完了。不对有几条项次报错不慌不慌点开js脚本修改一下刷新网页再跑一遍bingo。默默的把脚本发到群里然后给好基友单独发一份打开论坛粘贴上去完工散花。/* * QC31 点检实绩自动填写脚本Chrome Console 用 * * 使用方法 * 1. 登录系统打开 QC31「点检实绩登录」页面 * 2. 按 F12 打开开发者工具切到 Console * 3. 把本文件【全部内容】粘贴进去回车执行。 * * 脚本流程与手工操作一致 * 数据类别定量 → 项次类别仅定量 → 查询 → 100条/页(→点显示若存在) * → 循环点击计划清单每条标准 → 勾选项次清单每个项次 → 按规则填写本次结果 * → 提交单元格等价于按 Enter复用页面自身的判定逻辑→ 校验判定正常 * → 点击保存 → 处理下一条标准。 * * 本次结果填写规则 * 标准不为空 → 本次结果 标准值 * 标准空 下限空 → 本次结果 上限 / 5 * 标准空 上限空 → 本次结果 下限 0.1 * 标准空 上下限都有 → 本次结果 (上限 下限) / 2 * * 建议第一次先把下面 CONFIG.dryRun 改成 true 演练一遍只填不保存 * 确认填写值无误后再改回 false 正式执行。 * */ (async function QC31_AUTO() { const CONFIG { pageSize: 100, // 每页条数 dryRun: false, // true 演练模式只填写不点保存 stepDelay: 300, // 每步之间停顿(ms) ajaxTimeout: 60000 // 单次后台请求最长等待(ms) }; // ---------- 工具函数 ---------- const sleep ms new Promise(r setTimeout(r, ms)); const log (...a) console.log(%c[QC31], color:#0a0;font-weight:bold, ...a); const warn (...a) console.warn([QC31] ⚠, ...a); if (typeof efgrid undefined || typeof efgrid_onRowClicked undefined) { console.error([QC31] 当前页面不是 QC31 点检实绩登录页缺少 efgrid 框架脚本终止。); return; } // 等待所有后台 AJAX 请求结束连续两次检测到空闲才算完 async function waitAjaxIdle(timeout CONFIG.ajaxTimeout) { const t0 Date.now(); await sleep(150); // 等请求先起来 while (true) { const act window.jQuery ? jQuery.active : 0; if (act 0) { await sleep(250); if ((window.jQuery ? jQuery.active : 0) 0) return; } if (Date.now() - t0 timeout) throw new Error(等待后台响应超时); await sleep(200); } } // ---------- 拦截 alert/confirm防止弹窗阻塞自动流程 ---------- const _alert window.alert, _confirm window.confirm; const blockedAlerts []; window.alert m { blockedAlerts.push(String(m)); warn(拦截到 alert:, m); }; window.confirm m { warn(拦截到 confirm自动选确定:, m); return true; }; // ---------- 挂钩后台通信记录每次服务调用结果用于确认保存是否成功 ---------- window.__qc31_last null; if (!EiCommunicator.__qc31_hooked) { const _send EiCommunicator.send; EiCommunicator.send function (sService, sMethod, sEiInfo, sCallback) { const args Array.prototype.slice.call(arguments); const orig sCallback; args[3] { onSuccess(ei) { let status 0, msg ; try { status ei.getStatus(); } catch (e) {} try { msg ei.getMsg(); } catch (e) { try { msg ei.msg; } catch (e2) {} } window.__qc31_last { service: sService, method: sMethod, ok: status ! -1, msg }; if (orig typeof orig.onSuccess function) orig.onSuccess(ei); }, onFail() { window.__qc31_last { service: sService, method: sMethod, ok: false, msg: String(arguments[0] || ) }; if (orig typeof orig.onFail function) orig.onFail.apply(null, arguments); } }; return _send.apply(EiCommunicator, args); }; EiCommunicator.__qc31_hooked true; } // ---------- 主流程 ---------- const summary []; try { // 第 1 步设置查询条件下拉框 function setSelect(id, val, label) { const sel document.getElementById(id); if (!sel) throw new Error(找不到下拉框 # id); sel.value val; sel.dispatchEvent(new Event(change, { bubbles: true })); log(label 已选为「 sel.options[sel.selectedIndex].text 」); } setSelect(inqu_status-0-dataType, 20, 数据类别); // 定量 setSelect(inqu_status-0-xcDataType, 20, 项次类别); // 仅定量 // 第 2 步点击查询 log(点击【查询】…); button_query_onclick(); await waitAjaxIdle(); // 第 3 步每页条数改为 100右侧有显示按钮则点击 const sizeSel document.querySelector(#ef_grid_result__func_bar_div .i-pager-select) || document.querySelector(#ef_grid_result .i-pager-select); if (sizeSel) { sizeSel.value String(CONFIG.pageSize); if (window.jQuery) jQuery(sizeSel).trigger(change); else sizeSel.dispatchEvent(new Event(change, { bubbles: true })); log(每页条数已设为 CONFIG.pageSize 条/页); await sleep(200); // 兼容某些版本分页栏右侧存在显示按钮时点击它 const funcBar document.getElementById(ef_grid_result__func_bar_div); const showBtn funcBar [...funcBar.querySelectorAll(a,button,span,li,input)] .find(el (el.textContent || ).trim() 显示 || el.title 显示 || el.value 显示); if (showBtn) { showBtn.click(); log(已点击分页栏右侧的【显示】按钮); } await waitAjaxIdle(); } else { warn(未找到每页条数下拉框按当前分页继续); } // 第 4 步循环处理计划清单每条标准 const resultGrid efgrid.getGridObject(ef_grid_result); const planCount resultGrid.getRowCount(); log(计划清单当前页共 planCount 条标准开始逐条处理…); if (planCount 0) { warn(计划清单为空请检查查询条件); return; } for (let i 0; i planCount; i) { const stdId resultGrid.getCellValueByColumnName(i, checkStandardId); log(──── 第 (i 1) / planCount 条标准 stdId ────); // 点击该标准行复用页面自身的行点击逻辑自动加载项次清单 efgrid_onRowClicked(ef_grid_result, i); await waitAjaxIdle(); await sleep(CONFIG.stepDelay); const grid efgrid.getGridObject(ef_grid_slave); const rows grid.getRowCount(); if (rows 0) { warn(该标准下没有项次跳过); summary.push({ stdId, rows: 0, abnormal: [], saved: 跳过 }); continue; } const fixcount grid.getColCount(TYPE_FIX); const curIdx grid.customCols[index][currentResult] - fixcount 1; const num v { const n parseFloat(v); return isNaN(n) ? null : n; }; const fmt n String(Math.round(n * 10000) / 10000); // 去除浮点误差 const abnormal []; for (let r 0; r rows; r) { // 勾选该项次走框架的 checkbox 点击保证内部状态一致 if (!grid.isRowChecked(r)) { const cb document.querySelector(#ef_grid_slave__fix_table tr[rawrowindex r ] .ef-grid-checkbox); if (cb !cb.checked) cb.click(); else grid.setRowChecked(r, true); } // 按规则计算本次结果 const criteria grid.getCellValueByColumnName(r, criteria); // 标准 const top grid.getCellValueByColumnName(r, qatTop); // 上限 const bottom grid.getCellValueByColumnName(r, qatBottom); // 下限 let value null; //if (criteria ! ) value criteria; if ( criteria !null criteria ! !isNaN(Number(criteria)) ) value criteria; else if (num(top) ! null num(bottom) ! null) value fmt((num(top) num(bottom)) / 2); //if (num(top) ! null num(bottom) ! null) value fmt((num(top) num(bottom)) / 2); else if (num(top) ! null) value fmt(num(top) / 5); else if (num(bottom) ! null) value fmt(num(bottom) 0.1); const seqNo grid.getCellValueByColumnName(r, checkStandardSeqNo) || String(r 1); if (value null) { warn(项次 seqNo 标准/上限/下限均为空无法计算本次结果保存时将排除此行); abnormal.push(r); continue; } // 写入本次结果并提交单元格 —— 等价于在单元格里输入后按 Enter // 直接复用页面自带的 efgrid_onDataCellSaved判定逻辑与手工输入完全一致 grid.setCellValue(r, curIdx, value, TYPE_DATA); grid.refreshCell(r, curIdx, TYPE_DATA); efgrid_onDataCellSaved(ef_grid_slave, r, curIdx, TYPE_DATA); // 检查判定结果10正常 const det grid.getCellValueByColumnName(r, determine); if (det 10) { log(项次 seqNo 本次结果 value 判定正常 ✓); } else { warn(项次 seqNo 本次结果 value 判定≠正常(determine det )保存时将排除此行); abnormal.push(r); } } // 取消勾选判定非正常/无法计算的行保证只保存判定正常的项次 for (const r of abnormal) { const cb document.querySelector(#ef_grid_slave__fix_table tr[rawrowindex r ] .ef-grid-checkbox); if (cb cb.checked) cb.click(); else grid.setRowChecked(r, false); } if (abnormal.length rows) { warn(该标准所有项次均无法保存跳过保存); summary.push({ stdId, rows, abnormal, saved: 跳过 }); continue; } if (CONFIG.dryRun) { log(演练模式不点击保存dryRuntrue); summary.push({ stdId, rows, abnormal, saved: 演练未保存 }); continue; } // 点击右下角【保存】 log(点击【保存】…); blockedAlerts.length 0; button_save_onclick(); await waitAjaxIdle(); const last window.__qc31_last; if (last last.method save) { if (last.ok) log(标准 stdId 保存成功 (last.msg ? last.msg : )); else warn(标准 stdId 保存失败 last.msg); summary.push({ stdId, rows, abnormal, saved: last.ok ? 成功 : 失败, msg: last.msg }); } else { warn(未捕获到保存响应可能被页面校验拦截 (blockedAlerts.join( | ) || 未知原因)); summary.push({ stdId, rows, abnormal, saved: 未提交, msg: blockedAlerts.join( | ) }); } await sleep(CONFIG.stepDelay); } // 汇总 console.log(%c[QC31] 全部处理完毕汇总如下 , color:#00f;font-weight:bold); console.table(summary.map(s ({ 点检标准编号: s.stdId, 项次数: s.rows, 被排除的项次(行号从1起): (s.abnormal || []).map(x x 1).join(,) || 无, 保存结果: s.saved, 备注: s.msg || }))); const failCnt summary.filter(s s.saved 失败 || s.saved 未提交).length; if (failCnt 0) log(所有标准均已处理完成无失败项 ); else warn(有 failCnt 条标准保存失败/未提交请向上翻日志查看原因); if (resultGrid.getRowCount() CONFIG.pageSize) warn(当前页条数已达到每页上限如总记录超过 CONFIG.pageSize 条请翻页后再次运行本脚本); } catch (e) { console.error([QC31] 脚本异常终止, e); } finally { // 恢复原生弹窗函数 window.alert _alert; window.confirm _confirm; } })();