exam

exam faster

Você precisará instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Você precisará instalar uma extensão como o Tampermonkey para instalar este script.

Você precisará instalar um gerenciador de scripts de usuário para instalar este script.

(Eu já tenho um gerenciador de scripts de usuário, me deixe instalá-lo!)

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

(Eu já possuo um gerenciador de estilos de usuário, me deixar fazer a instalação!)

// ==UserScript==
// @name         exam
// @description  exam faster
// @namespace    http://tampermonkey.net/
// @version      0.5.0
// @author       You
// @match        https://l.jd.com/student/exam/analyze.du*
// @match        https://l.jd.com/student/exam/no-answer-analyze.du*
// @match        https://l.jd.com/student/exam/exam.du*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=jd.com
// @grant        none
// @license      MIT
// ==/UserScript==
;(function () {
  "use strict"

  const STORAGE_KEY = "exam_answers_map"

  function getStoredAnswers() {
    const stored = localStorage.getItem(STORAGE_KEY)
    return stored ? JSON.parse(stored) : {}
  }

  function saveAnswers(answers) {
    localStorage.setItem(STORAGE_KEY, JSON.stringify(answers))
  }

  function mergeAnswers(oldAnswers, newAnswers) {
    return { ...oldAnswers, ...newAnswers }
  }

  function createModal(existingAnswers, newAnswers) {
    const overlay = document.createElement("div")
    overlay.style.cssText = `
      position: fixed;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      background: rgba(0,0,0,0.5);
      z-index: 99999;
      display: flex;
      align-items: center;
      justify-content: center;
    `

    const modal = document.createElement("div")
    modal.style.cssText = `
      background: white;
      border-radius: 8px;
      padding: 20px;
      max-width: 800px;
      width: 90%;
      max-height: 80vh;
      overflow: auto;
    `

    const title = document.createElement("h3")
    title.innerText = "答案管理"
    title.style.cssText = "margin: 0 0 15px 0; text-align: center;"

    const content = document.createElement("div")
    content.style.cssText = "display: flex; gap: 20px; margin-bottom: 20px;"

    const leftPanel = document.createElement("div")
    leftPanel.style.cssText = "flex: 1; border: 1px solid #ddd; padding: 10px; border-radius: 4px; max-height: 400px; overflow: auto;"
    const leftTitle = document.createElement("h4")
    leftTitle.innerText = "已有答案"
    leftTitle.style.cssText = "margin: 0 0 10px 0; color: #333;"
    const leftContent = document.createElement("pre")
    leftContent.style.cssText = "margin: 0; font-size: 12px; white-space: pre-wrap; word-break: break-all;"
    leftContent.innerText = JSON.stringify(existingAnswers, null, 2)
    leftPanel.appendChild(leftTitle)
    leftPanel.appendChild(leftContent)

    const rightPanel = document.createElement("div")
    rightPanel.style.cssText = "flex: 1; border: 1px solid #ddd; padding: 10px; border-radius: 4px; max-height: 400px; overflow: auto;"
    const rightTitle = document.createElement("h4")
    rightTitle.innerText = "新获取答案"
    rightTitle.style.cssText = "margin: 0 0 10px 0; color: #333;"
    const rightContent = document.createElement("pre")
    rightContent.style.cssText = "margin: 0; font-size: 12px; white-space: pre-wrap; word-break: break-all;"
    rightContent.innerText = JSON.stringify(newAnswers, null, 2)
    rightPanel.appendChild(rightTitle)
    rightPanel.appendChild(rightContent)

    content.appendChild(leftPanel)
    content.appendChild(rightPanel)

    const btnGroup = document.createElement("div")
    btnGroup.style.cssText = "display: flex; gap: 10px; justify-content: center;"

    const overwriteBtn = document.createElement("button")
    overwriteBtn.innerText = "覆盖更新"
    overwriteBtn.style.cssText = "padding: 8px 20px; cursor: pointer; background: #ff6b6b; color: white; border: none; border-radius: 4px;"
    overwriteBtn.onclick = () => {
      saveAnswers(newAnswers)
      document.body.removeChild(overlay)
      alert("已覆盖更新到 localStorage")
    }

    const mergeBtn = document.createElement("button")
    mergeBtn.innerText = "合并更新"
    mergeBtn.style.cssText = "padding: 8px 20px; cursor: pointer; background: #4ecdc4; color: white; border: none; border-radius: 4px;"
    mergeBtn.onclick = () => {
      const merged = mergeAnswers(existingAnswers, newAnswers)
      saveAnswers(merged)
      document.body.removeChild(overlay)
      alert("已合并更新到 localStorage")
    }

    const cancelBtn = document.createElement("button")
    cancelBtn.innerText = "取消"
    cancelBtn.style.cssText = "padding: 8px 20px; cursor: pointer; background: #ddd; color: #333; border: none; border-radius: 4px;"
    cancelBtn.onclick = () => {
      document.body.removeChild(overlay)
    }

    btnGroup.appendChild(overwriteBtn)
    btnGroup.appendChild(mergeBtn)
    btnGroup.appendChild(cancelBtn)

    modal.appendChild(title)
    modal.appendChild(content)
    modal.appendChild(btnGroup)
    overlay.appendChild(modal)
    document.body.appendChild(overlay)
  }

  function exportToFile() {
    const answers = getStoredAnswers()
    const dataStr = JSON.stringify(answers, null, 2)
    const blob = new Blob([dataStr], { type: "application/json" })
    const url = URL.createObjectURL(blob)
    const a = document.createElement("a")
    a.href = url
    a.download = `exam_answers_${new Date().toISOString().slice(0, 10)}.json`
    a.click()
    URL.revokeObjectURL(url)
  }

  function importFromFile() {
    const input = document.createElement("input")
    input.type = "file"
    input.accept = ".json"
    input.onchange = (e) => {
      const file = e.target.files[0]
      if (!file) return
      const reader = new FileReader()
      reader.onload = (event) => {
        try {
          const importedAnswers = JSON.parse(event.target.result)
          const currentAnswers = getStoredAnswers()
          const merged = mergeAnswers(currentAnswers, importedAnswers)
          saveAnswers(merged)
          alert("导入成功")
        } catch (err) {
          alert("文件格式错误,请确保是有效的 JSON")
        }
      }
      reader.readAsText(file)
    }
    input.click()
  }

  function createImportExportModal() {
    const overlay = document.createElement("div")
    overlay.style.cssText = `
      position: fixed;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      background: rgba(0,0,0,0.5);
      z-index: 99999;
      display: flex;
      align-items: center;
      justify-content: center;
    `

    const modal = document.createElement("div")
    modal.style.cssText = `
      background: white;
      border-radius: 8px;
      padding: 20px;
      max-width: 800px;
      width: 90%;
      max-height: 80vh;
      overflow: auto;
    `

    const title = document.createElement("h3")
    title.innerText = "答案管理"
    title.style.cssText = "margin: 0 0 15px 0; text-align: center;"

    const textarea = document.createElement("textarea")
    textarea.style.cssText = `
      width: 100%;
      height: 400px;
      font-family: monospace;
      font-size: 12px;
      padding: 10px;
      border: 1px solid #ddd;
      border-radius: 4px;
      resize: vertical;
      box-sizing: border-box;
    `
    const currentAnswers = getStoredAnswers()
    textarea.value = JSON.stringify(currentAnswers, null, 2)

    const btnGroup = document.createElement("div")
    btnGroup.style.cssText = "display: flex; gap: 10px; justify-content: center; margin-top: 15px;"

    const exportBtn = document.createElement("button")
    exportBtn.innerText = "导出到文件"
    exportBtn.style.cssText = "padding: 8px 20px; cursor: pointer; background: #4ecdc4; color: white; border: none; border-radius: 4px;"
    exportBtn.onclick = () => {
      exportToFile()
    }

    const importBtn = document.createElement("button")
    importBtn.innerText = "从文件导入"
    importBtn.style.cssText = "padding: 8px 20px; cursor: pointer; background: #ff6b6b; color: white; border: none; border-radius: 4px;"
    importBtn.onclick = () => {
      importFromFile()
      textarea.value = JSON.stringify(getStoredAnswers(), null, 2)
    }

    const saveBtn = document.createElement("button")
    saveBtn.innerText = "保存修改"
    saveBtn.style.cssText = "padding: 8px 20px; cursor: pointer; background: #51cf66; color: white; border: none; border-radius: 4px;"
    saveBtn.onclick = () => {
      try {
        const answers = JSON.parse(textarea.value)
        saveAnswers(answers)
        alert("保存成功")
      } catch (e) {
        alert("JSON 格式错误")
      }
    }

    const closeBtn = document.createElement("button")
    closeBtn.innerText = "关闭"
    closeBtn.style.cssText = "padding: 8px 20px; cursor: pointer; background: #ddd; color: #333; border: none; border-radius: 4px;"
    closeBtn.onclick = () => {
      document.body.removeChild(overlay)
    }

    btnGroup.appendChild(exportBtn)
    btnGroup.appendChild(importBtn)
    btnGroup.appendChild(saveBtn)
    btnGroup.appendChild(closeBtn)

    modal.appendChild(title)
    modal.appendChild(textarea)
    modal.appendChild(btnGroup)
    overlay.appendChild(modal)
    document.body.appendChild(overlay)
  }

  const paper = document.querySelector('.jdu-exam')
  const btnCtn = document.createElement("div")
  btnCtn.className = "jdu-btn-con";
  btnCtn.style.position = "fixed";
  paper.appendChild(btnCtn)

  const btn = document.createElement("a")
  btn.href = "#"
  btn.innerText = "Go!"

  const importExportBtn = document.createElement("a")
  importExportBtn.href = "#"
  importExportBtn.innerText = "答案管理"
  importExportBtn.style.marginLeft = "10px"
  importExportBtn.addEventListener("click", (e) => {
    e.preventDefault()
    createImportExportModal()
  })

  btnCtn.appendChild(btn)
  btnCtn.appendChild(importExportBtn)
  setTimeout(() => {
    if (location.pathname.endsWith("analyze.du")) {
      btn.addEventListener("click", () => {
        const questions = document.querySelectorAll(".question")
        const newAnswers = {}

        Array.from(questions).map((question) => {
          const questionId = question.id
          const options = Array.from(
            question.querySelectorAll(".answer-options li")
          )
          const answersStr = question
            .querySelector(".jdu-answer-analyze > div:first-child")
            .innerText.substr(5)
          newAnswers[questionId] = answersStr.split("").reduce((prev, answerKey) => {
            const answerStr = options.find(
              (option) => option.innerText[1] == answerKey
            )
            const answer = answerStr.innerText.substr(1)
            prev.push(answer.trim().replace(/^ ?[ABCDEFGH]\. ?/, ""))
            return prev
          }, [])
        })

        const existingAnswers = getStoredAnswers()
        createModal(existingAnswers, newAnswers)
      })
    }

    if (location.pathname.endsWith("exam.du")) {
      btn.addEventListener("click", () => {
        const questions = document.querySelectorAll(".question")
        const answersMap = getStoredAnswers()

        if (Object.keys(answersMap).length === 0) {
          alert("localStorage 中没有答案数据")
          return
        }

        questions.forEach((question) => {
          const answers = answersMap[question.id] || []
          const options = question.querySelectorAll(".answer-options li")
          options.forEach((option) => {
            const purifiedOption = option.innerText
              .trim()
              .replace(/^ ?[ABCDEFGH]\. ?/, "")
            if (
              answers.some((answer) => {
                return answer === purifiedOption
              })
            ) {
              option.click()
            }
          })
        })
      })
    }
  }, 1000)
})()