// ==UserScript== // @name 广大第二课堂申报表单助手 // @namespace https://webvpn.gzhu.edu.cn/ // @version 1.0.0 // @description 优化第二课堂新建申报页:固定提交入口、快速定位附件、可关闭的图片预览和更适合电脑填写的版式。 // @author Codex // @match *://webvpn.gzhu.edu.cn/http/*/second_class_sj/newForm* // @run-at document-idle // @grant none // @license MIT // ==/UserScript== (function () { 'use strict'; const PREFIX = 'gzhu-form-helper'; const $ = (selector, root = document) => root.querySelector(selector); const $$ = (selector, root = document) => Array.from(root.querySelectorAll(selector)); const buttonText = (button) => button.textContent.replace(/\s+/g, '').trim(); if (window.top !== window.self || document.getElementById(`${PREFIX}-toolbar`)) { return; } const style = document.createElement('style'); style.id = `${PREFIX}-style`; style.textContent = ` :root { --gzhu-blue: #2563eb; --gzhu-blue-dark: #1d4ed8; --gzhu-border: #dbe4f0; --gzhu-shadow: 0 14px 40px rgba(15, 23, 42, .16); } html { scroll-behavior: smooth; } body { background: #f4f7fb !important; padding-bottom: 92px !important; } #app { width: min(1180px, 100%); min-height: 100vh; margin: 0 auto; background: #fff; box-shadow: 0 0 32px rgba(15, 23, 42, .06); } #app .van-cell { padding-top: 14px; padding-bottom: 14px; } #app img.img { cursor: zoom-in !important; border: 2px solid transparent; border-radius: 8px; object-fit: cover; transition: transform .18s ease, border-color .18s ease, box-shadow .18s ease; } #app img.img:hover { transform: translateY(-2px); border-color: #93b4f8; box-shadow: 0 8px 20px rgba(37, 99, 235, .18); } #app .file { max-width: 190px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } #app .van-button--block { margin-bottom: 24px; } #${PREFIX}-toolbar { position: fixed; right: 20px; bottom: 18px; z-index: 2147483000; display: flex; align-items: center; gap: 8px; padding: 10px; border: 1px solid rgba(148, 163, 184, .35); border-radius: 16px; background: rgba(255, 255, 255, .96); box-shadow: var(--gzhu-shadow); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; } #${PREFIX}-toolbar button { min-height: 40px; padding: 0 14px; border: 1px solid var(--gzhu-border); border-radius: 10px; background: #fff; color: #334155; font-size: 14px; font-weight: 600; cursor: pointer; transition: background .15s ease, border-color .15s ease, transform .15s ease; } #${PREFIX}-toolbar button:hover { border-color: #9db8ee; background: #f5f8ff; transform: translateY(-1px); } #${PREFIX}-toolbar .primary { min-width: 116px; border-color: var(--gzhu-blue); background: var(--gzhu-blue); color: #fff; } #${PREFIX}-toolbar .primary:hover { border-color: var(--gzhu-blue-dark); background: var(--gzhu-blue-dark); } #${PREFIX}-toolbar .primary:disabled { border-color: #94a3b8; background: #94a3b8; cursor: wait; transform: none; } #${PREFIX}-viewer { position: fixed; inset: 0; z-index: 2147483600; display: none; align-items: center; justify-content: center; background: rgba(2, 6, 23, .88); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; overscroll-behavior: contain; } #${PREFIX}-viewer.open { display: flex; } #${PREFIX}-viewer .stage { display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; padding: 76px 28px 92px; overflow: auto; } #${PREFIX}-viewer img { display: block; max-width: 92vw; max-height: calc(100vh - 180px); border-radius: 8px; background: #fff; box-shadow: 0 22px 70px rgba(0, 0, 0, .45); transform: scale(var(--gzhu-scale, 1)); transform-origin: center; transition: transform .12s ease; user-select: none; -webkit-user-drag: none; } #${PREFIX}-viewer .close { position: absolute; top: 18px; right: 20px; z-index: 2; width: 46px; height: 46px; border: 1px solid rgba(255, 255, 255, .35); border-radius: 50%; background: rgba(15, 23, 42, .78); color: #fff; font-size: 28px; line-height: 1; cursor: pointer; } #${PREFIX}-viewer .controls { position: absolute; left: 50%; bottom: 22px; z-index: 2; display: flex; align-items: center; gap: 8px; padding: 8px; border: 1px solid rgba(255, 255, 255, .24); border-radius: 14px; background: rgba(15, 23, 42, .78); transform: translateX(-50%); color: #fff; } #${PREFIX}-viewer .controls button { min-width: 42px; height: 36px; padding: 0 12px; border: 0; border-radius: 8px; background: rgba(255, 255, 255, .13); color: #fff; font-size: 15px; cursor: pointer; } #${PREFIX}-viewer .controls button:hover { background: rgba(255, 255, 255, .24); } #${PREFIX}-viewer .hint { position: absolute; top: 24px; left: 50%; color: rgba(255, 255, 255, .82); font-size: 13px; transform: translateX(-50%); pointer-events: none; } @media (max-width: 700px) { body { padding-bottom: 84px !important; } #app { width: 100%; box-shadow: none; } #${PREFIX}-toolbar { right: 8px; bottom: 8px; left: 8px; justify-content: center; border-radius: 14px; } #${PREFIX}-toolbar button { flex: 1; padding: 0 8px; } #${PREFIX}-toolbar .primary { flex: 1.5; min-width: 0; } #${PREFIX}-viewer .hint { display: none; } #${PREFIX}-viewer .stage { padding: 70px 12px 86px; } } `; document.head.appendChild(style); const toolbar = document.createElement('div'); toolbar.id = `${PREFIX}-toolbar`; toolbar.setAttribute('role', 'toolbar'); toolbar.setAttribute('aria-label', '申报表单快捷操作'); toolbar.innerHTML = ` `; document.body.appendChild(toolbar); const viewer = document.createElement('div'); viewer.id = `${PREFIX}-viewer`; viewer.setAttribute('role', 'dialog'); viewer.setAttribute('aria-modal', 'true'); viewer.setAttribute('aria-label', '附件图片预览'); viewer.innerHTML = `
Esc 或点击黑色空白处关闭
附件预览
100%
`; document.body.appendChild(viewer); const previewImage = $('img', viewer); const zoomLabel = $('.zoom', viewer); let scale = 1; let currentIndex = 0; let previousBodyOverflow = ''; function originalSubmitButton() { return $$('button').find((button) => !button.closest(`#${PREFIX}-toolbar`) && buttonText(button) === '确定申请' ); } function attachmentSection() { const marker = $$('span, p, div').find((node) => node.children.length === 0 && node.textContent.trim() === '上传附件' ); return marker?.closest('.van-cell-group') || marker?.parentElement || null; } function sourceImages() { return $$('#app img.img').filter((image) => image.src && image.offsetParent !== null); } function setScale(nextScale) { scale = Math.min(3, Math.max(0.25, Math.round(nextScale * 100) / 100)); previewImage.style.setProperty('--gzhu-scale', String(scale)); zoomLabel.textContent = `${Math.round(scale * 100)}%`; } function showImage(index) { const images = sourceImages(); if (!images.length) return; currentIndex = (index + images.length) % images.length; const source = images[currentIndex]; previewImage.src = source.currentSrc || source.src; previewImage.alt = source.alt || `附件图片 ${currentIndex + 1}`; setScale(1); } function openViewer(image) { const images = sourceImages(); const index = images.indexOf(image); if (index < 0) return; previousBodyOverflow = document.body.style.overflow; showImage(index); viewer.classList.add('open'); document.body.style.overflow = 'hidden'; $('.close', viewer).focus(); } function closeViewer() { if (!viewer.classList.contains('open')) return; viewer.classList.remove('open'); document.body.style.overflow = previousBodyOverflow; previewImage.removeAttribute('src'); } toolbar.addEventListener('click', (event) => { const action = event.target.closest('button')?.dataset.action; if (action === 'top') { window.scrollTo({ top: 0, behavior: 'smooth' }); return; } if (action === 'files') { const section = attachmentSection(); if (section) { section.scrollIntoView({ behavior: 'smooth', block: 'center' }); } else { window.scrollTo({ top: document.documentElement.scrollHeight, behavior: 'smooth' }); } return; } if (action === 'submit') { const original = originalSubmitButton(); if (!original) { const submit = $('[data-action="submit"]', toolbar); submit.textContent = '正在加载…'; submit.disabled = true; window.setTimeout(() => { submit.textContent = '确定申请'; submit.disabled = false; }, 1200); return; } // 仅代理点击原站按钮,校验、确认和提交仍全部走学校页面自身逻辑。 original.click(); } }); document.addEventListener('click', (event) => { const image = event.target.closest?.('#app img.img'); if (!image) return; event.preventDefault(); event.stopImmediatePropagation(); openViewer(image); }, true); viewer.addEventListener('click', (event) => { const action = event.target.closest('button')?.dataset.view; if (event.target === viewer || event.target.classList.contains('stage') || event.target.closest('.close')) { closeViewer(); return; } if (action === 'in') setScale(scale + 0.25); if (action === 'out') setScale(scale - 0.25); if (action === 'reset') setScale(1); if (action === 'prev') showImage(currentIndex - 1); if (action === 'next') showImage(currentIndex + 1); }); viewer.addEventListener('wheel', (event) => { if (!viewer.classList.contains('open')) return; event.preventDefault(); setScale(scale + (event.deltaY < 0 ? 0.15 : -0.15)); }, { passive: false }); document.addEventListener('keydown', (event) => { if (!viewer.classList.contains('open')) return; if (event.key === 'Escape') closeViewer(); if (event.key === 'ArrowLeft') showImage(currentIndex - 1); if (event.key === 'ArrowRight') showImage(currentIndex + 1); if (event.key === '+' || event.key === '=') setScale(scale + 0.25); if (event.key === '-') setScale(scale - 0.25); }); })();