// ==UserScript== // @name 智学网作文小分显示 // @namespace zhixue-essay-score // @version 1.1.0 // @description 支持 06/07 多作文(多写作题)同时展示老师分、均分、最终分 // @match *://*.zhixue.com/* // @run-at document-start // @grant none // ==/UserScript== (function () { 'use strict'; var ROOT_ID = 'tm-essay-score-root'; var BODY_ID = 'tm-essay-score-body'; var state = { minimized: false, mounted: false }; function deepParse(v) { var i, cur = v, p; for (i = 0; i < 5; i++) { if (cur == null) return null; if (typeof cur === 'object') return cur; if (typeof cur !== 'string') return null; try { p = JSON.parse(cur); } catch (e) { return null; } cur = p; } return cur; } function css() { return '' + '#' + ROOT_ID + '{position:fixed;right:20px;bottom:20px;z-index:2147483647;width:360px;max-width:calc(100vw - 24px);' + 'color:#eaf2ff;font:14px/1.55 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang SC","Microsoft YaHei",sans-serif;' + 'border-radius:14px;overflow:hidden;background:linear-gradient(135deg,rgba(24,31,53,.96),rgba(33,44,76,.96));' + 'box-shadow:0 12px 36px rgba(0,0,0,.35),0 2px 8px rgba(0,0,0,.22);border:1px solid rgba(255,255,255,.12);backdrop-filter:blur(10px);}' + '#' + ROOT_ID + '.light{color:#1f2937;background:linear-gradient(135deg,rgba(255,255,255,.96),rgba(244,247,255,.96));' + 'border:1px solid rgba(17,24,39,.08);box-shadow:0 10px 30px rgba(15,23,42,.15),0 2px 8px rgba(15,23,42,.08);}' + '#' + ROOT_ID + ' .tm-head{cursor:move;user-select:none;display:flex;align-items:center;justify-content:space-between;padding:10px 12px;' + 'background:linear-gradient(90deg,rgba(74,144,255,.28),rgba(82,196,255,.18));border-bottom:1px solid rgba(255,255,255,.12);}' + '#' + ROOT_ID + '.light .tm-head{border-bottom:1px solid rgba(17,24,39,.08);}' + '#' + ROOT_ID + ' .tm-title{font-weight:700;font-size:13px;letter-spacing:.2px;display:flex;align-items:center;gap:8px;}' + '#' + ROOT_ID + ' .tm-dot{width:8px;height:8px;border-radius:999px;background:#52c41a;box-shadow:0 0 10px #52c41a;}' + '#' + ROOT_ID + ' .tm-actions{display:flex;gap:6px;}' + '#' + ROOT_ID + ' .tm-btn{width:24px;height:24px;border-radius:8px;border:0;cursor:pointer;background:rgba(255,255,255,.14);color:inherit;font-weight:700;}' + '#' + ROOT_ID + '.light .tm-btn{background:rgba(17,24,39,.08);}' + '#' + ROOT_ID + ' .tm-btn:hover{transform:translateY(-1px);}' + '#' + BODY_ID + '{padding:12px;max-height:60vh;overflow:auto;}' + '#' + ROOT_ID + '.mini #' + BODY_ID + '{display:none;}' + '.tm-row{margin-bottom:8px;}' + '.tm-kv{display:flex;justify-content:space-between;gap:10px;background:rgba(255,255,255,.08);border:1px solid rgba(255,255,255,.1);border-radius:10px;padding:8px 10px;}' + '#' + ROOT_ID + '.light .tm-kv{background:rgba(17,24,39,.04);border:1px solid rgba(17,24,39,.08);}' + '.tm-label{opacity:.86;font-size:12px;}.tm-value{font-weight:700;}' + '.tm-list{margin:8px 0 0;padding:0;list-style:none;}' + '.tm-item{margin:6px 0;padding:8px 10px;border-radius:10px;background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.1);display:flex;align-items:center;justify-content:space-between;gap:8px;}' + '#' + ROOT_ID + '.light .tm-item{background:rgba(17,24,39,.04);border:1px solid rgba(17,24,39,.08);}' + '.tm-badge{padding:2px 8px;border-radius:999px;font-size:12px;font-weight:700;background:rgba(82,196,255,.2);color:#8bd6ff;}' + '#' + ROOT_ID + '.light .tm-badge{background:rgba(59,130,246,.14);color:#2563eb;}' + '.tm-empty{opacity:.85;padding:10px;border-radius:10px;background:rgba(255,255,255,.06);border:1px dashed rgba(255,255,255,.2);}' + '#' + ROOT_ID + '.light .tm-empty{background:rgba(17,24,39,.03);border:1px dashed rgba(17,24,39,.2);}' + '.tm-divider{height:1px;background:rgba(255,255,255,.15);margin:10px 0;}'; } function injectStyle() { if (document.getElementById('tm-essay-style')) return; var s = document.createElement('style'); s.id = 'tm-essay-style'; s.textContent = css(); (document.head || document.documentElement).appendChild(s); } function prefersLight() { try { return window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches; } catch (e) { return false; } } function ensureUI() { var root = document.getElementById(ROOT_ID); if (root) return root; injectStyle(); root = document.createElement('div'); root.id = ROOT_ID; if (prefersLight()) root.className = 'light'; root.innerHTML = '
' + '
作文/写作多评打分
' + '
' + '' + '' + '
' + '
' + '
等待接口返回...
'; (document.body || document.documentElement).appendChild(root); state.mounted = true; root.querySelector('#tm-min').addEventListener('click', function () { state.minimized = !state.minimized; if (state.minimized) root.classList.add('mini'); else root.classList.remove('mini'); this.textContent = state.minimized ? '+' : '—'; this.title = state.minimized ? '展开' : '收起'; }); root.querySelector('#tm-close').addEventListener('click', function () { if (root.parentNode) root.parentNode.removeChild(root); state.mounted = false; }); enableDrag(root, root.querySelector('.tm-head')); return root; } function enableDrag(panel, handle) { var startX = 0, startY = 0, startRight = 0, startBottom = 0, dragging = false; handle.addEventListener('mousedown', function (e) { if (e.button !== 0) return; dragging = true; startX = e.clientX; startY = e.clientY; var rect = panel.getBoundingClientRect(); startRight = window.innerWidth - rect.right; startBottom = window.innerHeight - rect.bottom; document.addEventListener('mousemove', onMove); document.addEventListener('mouseup', onUp); e.preventDefault(); }); function onMove(e) { if (!dragging) return; var dx = e.clientX - startX; var dy = e.clientY - startY; var right = startRight - dx; var bottom = startBottom - dy; var maxRight = window.innerWidth - 120; var maxBottom = window.innerHeight - 40; if (right < 6) right = 6; if (bottom < 6) bottom = 6; if (right > maxRight) right = maxRight; if (bottom > maxBottom) bottom = maxBottom; panel.style.right = right + 'px'; panel.style.bottom = bottom + 'px'; panel.style.left = 'auto'; panel.style.top = 'auto'; } function onUp() { dragging = false; document.removeEventListener('mousemove', onMove); document.removeEventListener('mouseup', onUp); } } function getAnswerRecordDetails(payload) { if (!payload || payload.errorCode !== 0 || !payload.result) return []; var result = payload.result; var sheetDatas = deepParse(result.sheetDatas); var details = null; if (sheetDatas && sheetDatas.userAnswerRecordDTO && sheetDatas.userAnswerRecordDTO.answerRecordDetails) { details = sheetDatas.userAnswerRecordDTO.answerRecordDetails; } if (!details && result.userAnswerRecordDTO && result.userAnswerRecordDTO.answerRecordDetails) { details = result.userAnswerRecordDTO.answerRecordDetails; } return (details && details.length) ? details : []; } function extractCompositions(payload) { var details = getAnswerRecordDetails(payload); if (!details.length) return []; var list = []; var i, d, ss, typeId; for (i = 0; i < details.length; i++) { d = details[i]; if (!d) continue; typeId = String(d.topicTypeId || ''); ss = Number(d.standardScore || 0); // 主规则:写作类主观题 06/07,标准分>=10 if (d.answerType === 's02Image' && (typeId === '06' || typeId === '07') && ss >= 10) { list.push(d); } } // 兜底:若未命中,仅选“多评写作特征题”(避免把数理化普通大题选进来) if (!list.length) { var subjective = []; for (i = 0; i < details.length; i++) { d = details[i]; if (d && d.answerType === 's02Image' && Number(d.standardScore || 0) >= 10) { var sub0 = (d.subTopics && d.subTopics.length) ? d.subTopics[0] : null; var scoreSource = sub0 && sub0.scoreSource ? String(sub0.scoreSource) : ''; var records = (sub0 && sub0.teacherMarkingRecords) ? sub0.teacherMarkingRecords : []; var isMultiRater = (scoreSource === 'average') || (records && records.length >= 2); if (isMultiRater) subjective.push(d); } } subjective.sort(function (a, b) { return Number(b.standardScore || 0) - Number(a.standardScore || 0); }); for (i = 0; i < subjective.length && i < 3; i++) list.push(subjective[i]); } // 去重(按 topicNumber) var seen = {}; var unique = []; for (i = 0; i < list.length; i++) { var key = String(list[i].topicNumber || ('idx_' + i)); if (!seen[key]) { seen[key] = 1; unique.push(list[i]); } } unique.sort(function (a, b) { return Number(a.topicNumber || 0) - Number(b.topicNumber || 0); }); return unique; } function buildTeacherScores(comp) { var scores = []; var subTopics = comp && comp.subTopics ? comp.subTopics : []; var i, j, st, recs, r; for (i = 0; i < subTopics.length; i++) { st = subTopics[i]; recs = st && st.teacherMarkingRecords ? st.teacherMarkingRecords : []; if (recs.length) { for (j = 0; j < recs.length; j++) { r = recs[j]; if (r && r.score != null) scores.push(Number(r.score)); } } else if (st && st.score != null) { scores.push(Number(st.score)); } } return scores; } function renderCompositions(comps) { var root = ensureUI(); var body = root.querySelector('#' + BODY_ID); if (!body) return; if (!comps || !comps.length) { body.innerHTML = '
已命中接口,但未识别到作文/写作大题
'; return; } var html = ''; var i, j; for (i = 0; i < comps.length; i++) { var c = comps[i]; var title = c.dispTitle != null ? c.dispTitle : (c.topicNumber != null ? c.topicNumber : '-'); var typeId = c.topicTypeId != null ? String(c.topicTypeId) : '-'; var finalScore = c.score != null ? c.score : '-'; var fullScore = c.standardScore != null ? c.standardScore : '-'; html += '
题号 / 类型' + title + ' / ' + typeId + '
'; html += '
最终得分' + finalScore + ' / ' + fullScore + '
'; var scores = buildTeacherScores(c); if (scores.length) { var sum = 0; for (j = 0; j < scores.length; j++) sum += scores[j]; var avg = Math.round((sum / scores.length) * 100) / 100; html += ''; } else { html += '
无老师明细,仅有最终分
'; } if (i !== comps.length - 1) html += '
'; } body.innerHTML = html; } function maybeHandle(url, text) { if (!/checksheet/i.test(String(url || ''))) return; var payload = deepParse(text); if (!payload || typeof payload !== 'object') return; var comps = extractCompositions(payload); renderCompositions(comps); } // 初始化UI ensureUI(); // 防止 body 晚加载 var timer = setInterval(function () { if (!state.mounted) ensureUI(); }, 500); setTimeout(function () { clearInterval(timer); }, 15000); // Hook fetch var rawFetch = window.fetch; if (typeof rawFetch === 'function') { window.fetch = function () { var args = arguments; return rawFetch.apply(this, args).then(function (resp) { try { var req0 = args[0]; var url = (req0 && req0.url) ? req0.url : String(req0 || ''); if (/checksheet/i.test(url) && resp && typeof resp.clone === 'function') { resp.clone().text().then(function (txt) { maybeHandle(url, txt); }); } } catch (e) {} return resp; }); }; } // Hook XHR var rawOpen = XMLHttpRequest.prototype.open; var rawSend = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.open = function (method, url, async, user, password) { this.__tm_url = url; return rawOpen.call(this, method, url, async, user, password); }; XMLHttpRequest.prototype.send = function () { this.addEventListener('load', function () { try { maybeHandle(this.__tm_url, this.responseText); } catch (e) {} }); return rawSend.apply(this, arguments); }; })();