dedsudiyu 1 week ago
parent
commit
a0bbd5af5f
24 changed files with 3959 additions and 512 deletions
  1. 51 0
      src/assets/styles/commonality.scss
  2. 1 1
      src/components/exportComponent/exportComponent.vue
  3. 2 2
      src/components/tinymceContainer/index.vue
  4. 846 0
      src/components/tinymceContainer/tinymceText.vue
  5. 555 0
      src/views/safetyEducationExaminationNew/components/examDetails.vue
  6. 1 49
      src/views/safetyEducationExaminationNew/courseManagement/courseManagement/addPage.vue
  7. 1 1
      src/views/safetyEducationExaminationNew/courseManagement/courseManagement/drawerItem.vue
  8. 1 0
      src/views/safetyEducationExaminationNew/courseManagement/courseManagement/studyCourses.vue
  9. 1 49
      src/views/safetyEducationExaminationNew/courseManagement/coursewareManagement/addPage.vue
  10. 229 86
      src/views/safetyEducationExaminationNew/examManagement/examinationArrange/addPage.vue
  11. 13 23
      src/views/safetyEducationExaminationNew/examManagement/examinationArrange/index.vue
  12. 171 0
      src/views/safetyEducationExaminationNew/examManagement/examinationArrange/selectCertificate.vue
  13. 208 0
      src/views/safetyEducationExaminationNew/examManagement/examinationArrange/selectCourse.vue
  14. 194 0
      src/views/safetyEducationExaminationNew/examManagement/examinationArrange/selectScope.vue
  15. 214 0
      src/views/safetyEducationExaminationNew/examManagement/examinationArrange/selectTestPaper.vue
  16. 194 0
      src/views/safetyEducationExaminationNew/examManagement/examinationArrange/selectUser.vue
  17. 373 0
      src/views/safetyEducationExaminationNew/examManagement/examinationArrange/studentsTakingMakeUpExams.vue
  18. 38 56
      src/views/safetyEducationExaminationNew/examManagement/examinationResult/index.vue
  19. 54 0
      src/views/safetyEducationExaminationNew/examManagement/examinationResult/learningProgress.vue
  20. 254 0
      src/views/safetyEducationExaminationNew/examManagement/examinationResult/viewGrades.vue
  21. 555 159
      src/views/safetyEducationExaminationNew/examManagement/testPaperManagement/addPage.vue
  22. 1 38
      src/views/safetyEducationExaminationNew/publicConfiguration/basicConfiguration/index.vue
  23. 1 1
      src/views/safetyEducationExaminationNew/questionBankManagement/practiceSolvingProblems/index.vue
  24. 1 47
      src/views/safetyEducationExaminationNew/questionBankManagement/questionBankManagement/addPage.vue

+ 51 - 0
src/assets/styles/commonality.scss

@@ -1303,6 +1303,57 @@
   height:38px;
   line-height:38px;
 }
+/************* 新子组件头部标题与按钮样式 ****************/
+.page-container-top-max-big-box {
+  height: 60px;
+  display: flex;
+  overflow: hidden;
+  padding: 0 20px;
+  border-bottom: 1px solid #dedede;
+  align-items: center;
+
+  .top-1-p {
+    flex: 1;
+    font-size: 16px;
+    font-weight: 700;
+  }
+
+  .top-2-p {
+    height: 35px;
+    line-height: 33px;
+    margin-left: 20px;
+    padding: 0 20px;
+    border-radius: 4px;
+    background-color: #fff;
+    border: 1px solid #999;
+    color: #999;
+    cursor: pointer;
+  }
+
+  .top-3-p {
+    height: 35px;
+    line-height: 33px;
+    margin-left: 20px;
+    padding: 0 20px;
+    border-radius: 4px;
+    background-color: #dedede;
+    border: 1px solid #dedede;
+    color: #333;
+    cursor: pointer;
+  }
+
+  .top-4-p {
+    height: 35px;
+    line-height: 33px;
+    margin-left: 20px;
+    padding: 0 20px;
+    border-radius: 4px;
+    background-color: #0183fa;
+    border: 1px solid #0183fa;
+    color: #fff;
+    cursor: pointer;
+  }
+}
 /************* 单选按钮 位置修复 ****************/
 .el-radio{
   margin-top:5px;

+ 1 - 1
src/components/exportComponent/exportComponent.vue

@@ -21,7 +21,7 @@
     ids:'',                                        //勾选导出,勾选的IDS
     fileName:'导出文件名',                         //导出文件的命名
     customKey:null,                                //自定义参数KEY(非必填)
-    customValue:null                               //自定义参数VALUE(非必填)
+    customValue:null,                               //自定义参数VALUE(非必填)
     screenData:{}                                  //导出筛选数据(非必要-如有该参数组件会显示导出筛选数据按钮)
   },
 

+ 2 - 2
src/components/tinymceContainer/index.vue

@@ -374,7 +374,7 @@ export default {
             if (text.length >= this.maxLength) {
               if (e.type === 'keydown') {
                 e.preventDefault()
-                if (this.$message) this.$message.warning(`内容长度不能超过 ${this.maxLength} 个字符`)
+                this.msgError(`内容长度不能超过 ${this.maxLength} 个字符`)
               }
             }
           })
@@ -470,7 +470,7 @@ export default {
               }
             })
             if (this.$message) {
-              this.$message.warning(`内容长度不能超过 ${this.maxLength} 个字符`)
+              this.msgError(`内容长度不能超过 ${this.maxLength} 个字符`)
             }
             return
           }

+ 846 - 0
src/components/tinymceContainer/tinymceText.vue

@@ -0,0 +1,846 @@
+<!--
+
+        <tinymce-text
+          v-model="newData.data5"
+          :height="300"
+          :max-length="500"
+          :upload-url="editorUploadUrl"
+          :upload-headers="editorUploadHeaders"
+        />
+
+        import { getToken } from "@/utils/auth";
+        import tinymceText from "@/components/tinymceContainer/tinymceText.vue";
+
+        components: { tinymceText },
+
+        editorUploadUrl: this.uploadUrl(),
+        editorUploadHeaders: {
+          Authorization:getToken(),
+        },
+
+-->
+<template>
+  <div class="tinymce-container">
+    <Editor
+      v-model="editorContent"
+      :init="editorInit"
+      :disabled="disabled"
+    />
+
+    <!-- 插入公式弹窗 -->
+    <div v-if="latexDialogVisible" class="fm-mask" @click.self="latexDialogVisible = false">
+      <div class="fm-dialog">
+        <!-- 标题栏 -->
+        <div class="fm-header">
+          <span>插入公式</span>
+          <span class="fm-close" @click="latexDialogVisible = false">×</span>
+        </div>
+
+        <!-- 工具栏 -->
+        <div class="fm-toolbar">
+          <!-- 预设下拉 -->
+          <div class="fm-preset-wrap">
+            <div class="fm-preset-btn" @click="presetOpen = !presetOpen">
+              <span class="fm-preset-icon">f<sub>(x)</sub></span>
+              <span class="fm-preset-label">预设</span>
+              <span class="fm-preset-arrow">▾</span>
+            </div>
+            <div v-if="presetOpen" class="fm-preset-dropdown">
+              <div v-for="p in presets" :key="p.label" class="fm-preset-item"
+                   @click="applyPreset(p.value)">{{ p.label }}</div>
+            </div>
+          </div>
+
+          <div class="fm-divider"></div>
+
+          <!-- 常用符号面板 -->
+          <div class="fm-symbol-grid">
+            <button v-for="s in symbolList" :key="s.label" type="button"
+                    class="fm-sym-btn" :title="s.tip" @click="insertAtCursor(s.value)">
+              {{ s.label }}
+            </button>
+          </div>
+
+          <div class="fm-divider"></div>
+
+          <!-- 分类按钮组 -->
+          <div class="fm-category-row">
+            <div v-for="cat in categories" :key="cat.id" class="fm-cat-wrap">
+              <button type="button" class="fm-cat-btn"
+                      :class="{active: activeCat === cat.id}"
+                      @click="activeCat = activeCat === cat.id ? null : cat.id">
+                <span class="fm-cat-icon" v-html="cat.icon"></span>
+                <span class="fm-cat-label">{{ cat.label }}</span>
+                <span class="fm-cat-arrow">▾</span>
+              </button>
+              <div v-if="activeCat === cat.id" class="fm-cat-dropdown">
+                <button v-for="item in cat.items" :key="item.label" type="button"
+                        class="fm-cat-item" :title="item.tip"
+                        @click="insertAtCursor(item.value); activeCat = null">
+                  <span v-html="item.label"></span>
+                </button>
+              </div>
+            </div>
+          </div>
+        </div>
+
+        <!-- 输入区 -->
+        <div class="fm-body">
+          <textarea
+            ref="fmInput"
+            v-model="latexInput"
+            class="fm-input"
+            placeholder="在此处键入公式"
+            spellcheck="false"
+            @input="onInputChange"
+          />
+          <!-- 实时预览:由 renderPreview 方法直接操作 DOM,避免 MathJax 缓存问题 -->
+          <div class="fm-preview" ref="fmPreview">
+            <span class="fm-preview-empty">公式预览</span>
+          </div>
+        </div>
+
+        <!-- 底部 -->
+        <div class="fm-footer">
+          <button class="fm-btn-close" type="button" @click="latexDialogVisible = false">关闭</button>
+          <button class="fm-btn-save" type="button" @click="insertLatex">保存</button>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+  import Editor from '@tinymce/tinymce-vue'
+  import tinymce from 'tinymce/tinymce'
+
+  // 计算静态资源根路径,兼容 /bigWeb/ 或 / 等任意子目录部署
+  // publicPath="./" 时,index.html 放在子目录下,tinymce 资源也在同级
+  function getStaticBase() {
+    const base = document.baseURI || window.location.href
+    return base.replace(/#.*$/, '').replace(/\/[^/]*$/, '') + '/'
+  }
+  const staticBase = getStaticBase()
+
+  import 'tinymce/themes/silver'
+  import 'tinymce/icons/default'
+  import 'tinymce/models/dom'
+  import 'tinymce/plugins/advlist'
+  import 'tinymce/plugins/autolink'
+  import 'tinymce/plugins/autoresize'
+  import 'tinymce/plugins/autosave'
+  import 'tinymce/plugins/charmap'
+  import 'tinymce/plugins/code'
+  import 'tinymce/plugins/codesample'
+  import 'tinymce/plugins/directionality'
+  import 'tinymce/plugins/emoticons'
+  import 'tinymce/plugins/fullscreen'
+  import 'tinymce/plugins/help'
+  import 'tinymce/plugins/image'
+  import 'tinymce/plugins/importcss'
+  import 'tinymce/plugins/insertdatetime'
+  import 'tinymce/plugins/link'
+  import 'tinymce/plugins/lists'
+  import 'tinymce/plugins/media'
+  import 'tinymce/plugins/nonbreaking'
+  import 'tinymce/plugins/pagebreak'
+  import 'tinymce/plugins/preview'
+  import 'tinymce/plugins/quickbars'
+  import 'tinymce/plugins/save'
+  import 'tinymce/plugins/searchreplace'
+  import 'tinymce/plugins/table'
+  import 'tinymce/plugins/template'
+  import 'tinymce/plugins/visualblocks'
+  import 'tinymce/plugins/visualchars'
+  import 'tinymce/plugins/wordcount'
+
+  export default {
+    name: 'TinymceContainer',
+    components: { Editor },
+    props: {
+      value: { type: String, default: '' },
+      disabled: { type: Boolean, default: false },
+      height: { type: [Number, String], default: 400 },
+      // 富文本纯文本内容最大长度,0 表示不限制
+      maxLength: { type: Number, default: 0 },
+      uploadUrl: { type: String, default: '' },
+      uploadHeaders: { type: Object, default: () => ({}) }
+    },
+    data() {
+      return {
+        latexDialogVisible: false,
+        latexInput: '',
+        presetOpen: false,
+        activeCat: null,
+        // 预设模板
+        presets: [
+          { label: '二次方程', value: 'x = \\frac{-b \\pm \\sqrt{b^2 - 4ac}}{2a}' },
+          { label: '勾股定理', value: 'a^2 + b^2 = c^2' },
+          { label: '欧拉公式', value: 'e^{i\\pi} + 1 = 0' },
+          { label: '正态分布', value: 'f(x)=\\frac{1}{\\sigma\\sqrt{2\\pi}}e^{-\\frac{(x-\\mu)^2}{2\\sigma^2}}' },
+          { label: '泰勒展开', value: 'f(x) = \\sum_{n=0}^{\\infty} \\frac{f^{(n)}(a)}{n!}(x-a)^n' },
+          { label: '矩阵行列式', value: '\\begin{vmatrix} a & b \\\\ c & d \\end{vmatrix} = ad - bc' },
+        ],
+        // 常用符号
+        symbolList: [
+          { label: '±', value: '\\pm ', tip: '正负号' },
+          { label: '∞', value: '\\infty ', tip: '无穷' },
+          { label: '=', value: '= ', tip: '等号' },
+          { label: '~', value: '\\sim ', tip: '相似' },
+          { label: '×', value: '\\times ', tip: '乘号' },
+          { label: '÷', value: '\\div ', tip: '除号' },
+          { label: '!', value: '! ', tip: '阶乘' },
+          { label: '<', value: '< ', tip: '小于' },
+          { label: '≪', value: '\\ll ', tip: '远小于' },
+          { label: '>', value: '> ', tip: '大于' },
+          { label: '≫', value: '\\gg ', tip: '远大于' },
+          { label: '≤', value: '\\leq ', tip: '小于等于' },
+          { label: '≥', value: '\\geq ', tip: '大于等于' },
+          { label: '∓', value: '\\mp ', tip: '负正号' },
+          { label: '≅', value: '\\cong ', tip: '全等' },
+          { label: '≡', value: '\\equiv ', tip: '恒等' },
+          { label: '∝', value: '\\propto ', tip: '正比于' },
+          { label: '≈', value: '\\approx ', tip: '约等于' },
+        ],
+        // 分类按钮组
+        categories: [
+          {
+            id: 'fraction', label: '分数', icon: '<i style="font-style:normal">x/y</i>',
+            items: [
+              { label: 'x/y', value: '\\frac{x}{y}', tip: '分数' },
+              { label: 'x/y (斜)', value: '{}^{x}/{}_{y}', tip: '斜分数' },
+              { label: 'x·y⁻¹', value: 'x \\cdot y^{-1}', tip: '倒数形式' },
+            ]
+          },
+          {
+            id: 'power', label: '上下标', icon: '<i style="font-style:normal">e<sup>x</sup></i>',
+            items: [
+              { label: 'x²', value: 'x^{2}', tip: '上标' },
+              { label: 'xₙ', value: 'x_{n}', tip: '下标' },
+              { label: 'xₙ²', value: 'x_{n}^{2}', tip: '上下标' },
+              { label: 'eˣ', value: 'e^{x}', tip: 'e的幂次' },
+            ]
+          },
+          {
+            id: 'root', label: '根式', icon: '<i style="font-style:normal">ⁿ√x</i>',
+            items: [
+              { label: '√x', value: '\\sqrt{x}', tip: '平方根' },
+              { label: 'ⁿ√x', value: '\\sqrt[n]{x}', tip: 'n次根' },
+              { label: '∛x', value: '\\sqrt[3]{x}', tip: '立方根' },
+            ]
+          },
+          {
+            id: 'integral', label: '积分', icon: '<i style="font-style:normal">∫</i>',
+            items: [
+              { label: '∫', value: '\\int_{a}^{b} f(x)\\,dx', tip: '定积分' },
+              { label: '∬', value: '\\iint_{D} f(x,y)\\,dx\\,dy', tip: '二重积分' },
+              { label: '∭', value: '\\iiint_{V} f\\,dV', tip: '三重积分' },
+              { label: '∮', value: '\\oint_{C} f\\,ds', tip: '曲线积分' },
+            ]
+          },
+          {
+            id: 'large', label: '大型运算符', icon: '<i style="font-style:normal">Σ</i>',
+            items: [
+              { label: 'Σ 求和', value: '\\sum_{i=1}^{n} x_i', tip: '求和' },
+              { label: 'Π 连乘', value: '\\prod_{i=1}^{n} x_i', tip: '连乘积' },
+              { label: 'lim 极限', value: '\\lim_{x \\to 0} f(x)', tip: '极限' },
+              { label: 'max', value: '\\max_{x \\in D}', tip: '最大值' },
+              { label: 'min', value: '\\min_{x \\in D}', tip: '最小值' },
+            ]
+          },
+          {
+            id: 'bracket', label: '括号', icon: '<i style="font-style:normal">{()}</i>',
+            items: [
+              { label: '()', value: '\\left( x \\right)', tip: '圆括号' },
+              { label: '[]', value: '\\left[ x \\right]', tip: '方括号' },
+              { label: '{}', value: '\\left\\{ x \\right\\}', tip: '花括号' },
+              { label: '||', value: '\\left| x \\right|', tip: '绝对值' },
+              { label: '‖‖', value: '\\left\\| x \\right\\|', tip: '范数' },
+              { label: '矩阵()', value: '\\begin{pmatrix} a & b \\\\ c & d \\end{pmatrix}', tip: '圆括号矩阵' },
+              { label: '矩阵[]', value: '\\begin{bmatrix} a & b \\\\ c & d \\end{bmatrix}', tip: '方括号矩阵' },
+            ]
+          },
+          {
+            id: 'func', label: '函数', icon: '<i style="font-style:normal">sinθ</i>',
+            items: [
+              { label: 'sin', value: '\\sin\\theta', tip: '正弦' },
+              { label: 'cos', value: '\\cos\\theta', tip: '余弦' },
+              { label: 'tan', value: '\\tan\\theta', tip: '正切' },
+              { label: 'ln', value: '\\ln x', tip: '自然对数' },
+              { label: 'log', value: '\\log_{a} x', tip: '对数' },
+              { label: 'lg', value: '\\lg x', tip: '常用对数' },
+              { label: '∂', value: '\\frac{\\partial f}{\\partial x}', tip: '偏导数' },
+              { label: "f'", value: "f'(x)", tip: '导数' },
+            ]
+          },
+        ],
+        editorInit: {
+          language_url: staticBase + 'tinymce/langs/zh_CN.js',
+          language: 'zh_CN',
+          skin_url: staticBase + 'tinymce/skins/ui/oxide',
+          content_css: staticBase + 'tinymce/skins/content/default/content.css',
+          // 编辑区样式:公式 span 不可编辑,光标正常显示
+          content_style: `
+          .math-formula {
+            display: inline-block;
+            cursor: default;
+            user-select: none;
+            background: #f0f4ff;
+            border-radius: 3px;
+            padding: 1px 4px;
+            font-size: 0.95em;
+          }
+        `,
+          // 禁止 TinyMCE 把 data URI 图片自动上传(公式以 span 文本形式存储,不产生 data URI)
+          automatic_uploads: false,
+          images_reuse_filename: true,
+          // 指定 emoticons 数据文件路径,避免动态加载时 404
+          emoticons_database_url: staticBase + 'tinymce/plugins/emoticons/js/emojis.js',
+          height: this.height,
+          menubar: false,
+          plugins: 'advlist autolink autoresize autosave charmap code codesample directionality emoticons fullscreen help image importcss insertdatetime link lists media nonbreaking pagebreak preview quickbars save searchreplace table template visualblocks visualchars wordcount',
+          toolbar:
+            'table | codesample | code | preview | fullscreen | image | media | attachment | ' +
+            'fontsizeselect | forecolor | ' +
+            'bold italic | ' +
+            'alignleft aligncenter alignright alignjustify | ' +
+            'bullist numlist | removeformat',
+          toolbar_mode: 'wrap',
+          quickbars_selection_toolbar: 'bold italic underline | quicklink h2 h3 blockquote',
+          quickbars_insert_toolbar: false,
+          // 插入/编辑图片弹窗:隐藏上传 tab,只保留 URL 输入
+          image_uploadtab: false,
+          // 插入/编辑媒体弹窗:只保留"一般"tab,隐藏内嵌和高级
+          media_live_embeds: false,
+          media_alt_source: false,
+          media_poster: false,
+          font_formats:
+            '微软雅黑=Microsoft YaHei,Helvetica Neue,PingFang SC,sans-serif;' +
+            '宋体=SimSun,serif;黑体=SimHei,sans-serif;楷体=KaiTi,serif;' +
+            '仿宋=FangSong,serif;Arial=arial,helvetica,sans-serif;' +
+            'Times New Roman=times new roman,times,serif;',
+          fontsize_formats: '12px 14px 16px 18px 20px 24px 28px 32px 36px',
+          codesample_languages: [
+            { text: 'HTML/XML', value: 'markup' },
+            { text: 'JavaScript', value: 'javascript' },
+            { text: 'CSS', value: 'css' },
+            { text: 'Python', value: 'python' },
+            { text: 'Java', value: 'java' },
+            { text: 'C', value: 'c' },
+            { text: 'C++', value: 'cpp' },
+            { text: 'SQL', value: 'sql' },
+          ],
+          paste_data_images: true,
+          autosave_interval: '30s',
+          autosave_prefix: 'tinymce-autosave-{path}{query}-{id}-',
+          autosave_restore_when_empty: false,
+          images_upload_handler: (blobInfo) =>
+            new Promise((resolve, reject) => {
+              if (this.uploadUrl) {
+                const xhr = new XMLHttpRequest()
+                xhr.open('POST', this.uploadUrl)
+                Object.keys(this.uploadHeaders).forEach(k => xhr.setRequestHeader(k, this.uploadHeaders[k]))
+                xhr.onload = () => {
+                  if (xhr.status < 200 || xhr.status >= 300) { reject('上传失败: ' + xhr.status); return }
+                  try {
+                    const res = JSON.parse(xhr.responseText)
+                    // 兼容 { url } / { data: { url } } / { data: url }
+                    const url = res.url || (res.data && (res.data.url || res.data)) || ''
+                    if (url) resolve(url)
+                    else reject('接口未返回图片地址')
+                  } catch (e) { reject('接口响应解析失败') }
+                }
+                xhr.onerror = () => reject('网络错误')
+                const fd = new FormData()
+                fd.append('file', blobInfo.blob(), blobInfo.filename())
+                xhr.send(fd)
+              } else {
+                const reader = new FileReader()
+                reader.onload = () => resolve(reader.result)
+                reader.onerror = () => reject('图片读取失败')
+                reader.readAsDataURL(blobInfo.blob())
+              }
+            }),
+          setup: (editor) => {
+            // 字数限制:在 input 事件后检查纯文本长度
+            editor.on('input keydown', (e) => {
+              if (!this.maxLength) return
+              // keydown 时只拦截非删除类按键
+              if (e.type === 'keydown') {
+                const allowKeys = ['Backspace', 'Delete', 'ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown']
+                if (allowKeys.includes(e.key) || e.ctrlKey || e.metaKey) return
+              }
+              const text = editor.getContent({ format: 'text' })
+              if (text.length >= this.maxLength) {
+                if (e.type === 'keydown') {
+                  e.preventDefault()
+                  this.msgError(`内容长度不能超过 ${this.maxLength} 个字符`)
+                }
+              }
+            })
+            editor.ui.registry.addButton('latex', {
+              text: '公式',
+              tooltip: '插入公式',
+              icon: 'insert-character',
+              onAction: () => {
+                this.latexInput = ''
+                this.presetOpen = false
+                this.activeCat = null
+                this.latexDialogVisible = true
+                this._editor = editor
+              }
+            })
+            editor.ui.registry.addButton('attachment', {
+              text: '',
+              tooltip: '插入图片',
+              icon: 'image',
+              onAction: () => {
+                const input = document.createElement('input')
+                input.type = 'file'
+                input.accept = 'image/jpeg,image/png,image/jpg'
+                input.onchange = (e) => {
+                  const file = e.target.files[0]
+                  if (!file) return
+                  // 类型校验
+                  const allowedTypes = ['image/jpeg', 'image/png', 'image/jpg']
+                  if (!allowedTypes.includes(file.type)) {
+                    alert('仅支持 jpg / jpeg / png 格式的图片')
+                    return
+                  }
+                  // 大小校验:30MB
+                  if (file.size > 30 * 1024 * 1024) {
+                    alert('图片大小不能超过 30MB')
+                    return
+                  }
+                  if (this.uploadUrl) {
+                    const xhr = new XMLHttpRequest()
+                    xhr.open('POST', this.uploadUrl)
+                    Object.keys(this.uploadHeaders).forEach(k => xhr.setRequestHeader(k, this.uploadHeaders[k]))
+                    xhr.onload = () => {
+                      if (xhr.status < 200 || xhr.status >= 300) {
+                        alert('上传失败: ' + xhr.status)
+                        return
+                      }
+                      try {
+                        const res = JSON.parse(xhr.responseText)
+                        const url = res.url || (res.data && (res.data.url || res.data)) || ''
+                        if (url) editor.insertContent(`<img src="${url}" alt="${file.name}" style="max-width:100%;" />&nbsp;`)
+                        else alert('接口未返回图片地址')
+                      } catch (err) { alert('接口响应解析失败') }
+                    }
+                    xhr.onerror = () => alert('网络错误,上传失败')
+                    const fd = new FormData()
+                    fd.append('file', file)
+                    xhr.send(fd)
+                  } else {
+                    // 未配置接口时转 base64 预览
+                    const reader = new FileReader()
+                    reader.onload = (ev) => {
+                      editor.insertContent(`<img src="${ev.target.result}" alt="${file.name}" style="max-width:100%;" />&nbsp;`)
+                    }
+                    reader.readAsDataURL(file)
+                  }
+                }
+                input.click()
+              }
+            })
+            editor.on('init', () => {
+              this._editor = editor
+            })
+          },
+          branding: false,
+          promotion: false,
+          statusbar: true,
+          resize: true,
+        }
+      }
+    },
+    computed: {
+      editorContent: {
+        get() { return this.value },
+        set(val) {
+          if (this.maxLength > 0) {
+            const text = val.replace(/<[^>]*>/g, '')
+            if (text.length > this.maxLength) {
+              this.$nextTick(() => {
+                if (this._editor) {
+                  this._editor.setContent(this.value)
+                  this._editor.selection.select(this._editor.getBody(), true)
+                  this._editor.selection.collapse(false)
+                }
+              })
+              this.msgError(`内容长度不能超过 ${this.maxLength} 个字符`)
+              return
+            }
+          }
+          this.$emit('input', val)
+        }
+      }
+    },
+    mounted() {
+      tinymce.init({})
+      document.addEventListener('click', this.closeDropdowns)
+    },
+    beforeDestroy() {
+      document.removeEventListener('click', this.closeDropdowns)
+    },
+    methods: {
+      closeDropdowns(e) {
+        if (!this.$el.contains(e.target)) {
+          this.presetOpen = false
+          this.activeCat = null
+        }
+      },
+      applyPreset(value) {
+        this.latexInput = value
+        this.presetOpen = false
+        this.$nextTick(() => this.renderPreview())
+      },
+      // 在光标处插入文本(textarea)
+      insertAtCursor(text) {
+        const el = this.$refs.fmInput
+        if (!el) { this.latexInput += text; return }
+        const start = el.selectionStart
+        const end = el.selectionEnd
+        this.latexInput = this.latexInput.slice(0, start) + text + this.latexInput.slice(end)
+        this.$nextTick(() => {
+          el.focus()
+          const pos = start + text.length
+          el.setSelectionRange(pos, pos)
+          this.renderPreview()
+        })
+      },
+      onInputChange() {
+        this.renderPreview()
+      },
+      renderPreview() {
+        this.$nextTick(() => {
+          const el = this.$refs.fmPreview
+          if (!el) return
+          const latex = this.latexInput.trim()
+          if (!latex) {
+            el.innerHTML = '<span class="fm-preview-empty">公式预览</span>'
+            return
+          }
+          // 每次重置 innerHTML,强制 MathJax 重新解析,避免缓存导致预览不更新
+          el.innerHTML = `$$${latex}$$`
+          if (!window.MathJax) return
+          window.MathJax.typesetClear([el])
+          window.MathJax.typesetPromise([el]).catch(() => {})
+        })
+      },
+      typeset(editor) {
+        try {
+          const body = editor.getBody()
+          if (window.MathJax && window.MathJax.typesetPromise && body) {
+            window.MathJax.typesetClear([body])
+            window.MathJax.typesetPromise([body]).catch(() => {})
+          }
+        } catch (e) {}
+      },
+      insertLatex() {
+        if (!this.latexInput.trim()) return
+        const editor = this._editor
+        if (!editor) return
+        const latex = this.latexInput.trim()
+        this.latexDialogVisible = false
+        // 用主页面已加载的 MathJax 3 将 LaTeX 转成 SVG,以 <img> 插入编辑区
+        // 避免 iframe 内独立加载 MathJax 的时序问题
+        const doInsert = () => {
+          const mj = window.MathJax
+          if (mj && mj.tex2svgPromise) {
+            mj.tex2svgPromise(latex, { display: true }).then(node => {
+              const svg = node.querySelector('svg')
+              if (!svg) { this._insertLatexFallback(editor, latex); return }
+              // 给 SVG 加固定宽高,避免尺寸为 0
+              if (!svg.getAttribute('width') || svg.getAttribute('width') === '0') {
+                svg.setAttribute('width', '200')
+              }
+              svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg')
+              const svgStr = svg.outerHTML
+              const dataUri = 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svgStr)))
+              editor.insertContent(
+                `<img class="math-formula-img" src="${dataUri}" data-latex="${encodeURIComponent(latex)}" alt="${latex}" style="vertical-align:middle;max-height:3em;display:inline-block;" />&nbsp;`
+              )
+            }).catch(() => this._insertLatexFallback(editor, latex))
+          } else if (mj && mj.startup && mj.startup.promise) {
+            // MathJax 还未完成初始化,等待后重试
+            mj.startup.promise.then(() => this.insertLatex())
+          } else {
+            this._insertLatexFallback(editor, latex)
+          }
+        }
+        doInsert()
+      },
+      _insertLatexFallback(editor, latex) {
+        // MathJax 不可用时退回文本形式
+        editor.insertContent(
+          `<span class="math-formula" contenteditable="false" data-latex="${encodeURIComponent(latex)}">\\(${latex}\\)</span>&nbsp;`
+        )
+      },
+      _typesetWhenReady(editor, attempts) {
+        attempts = attempts || 0
+        if (attempts > 40) return
+        try {
+          const iframeWin = this._iframeWin || editor.getWin()
+          if (iframeWin.MathJax && iframeWin.MathJax.typesetPromise) {
+            iframeWin.MathJax.typesetPromise([editor.getBody()]).catch(() => {})
+          } else {
+            setTimeout(() => this._typesetWhenReady(editor, attempts + 1), 100)
+          }
+        } catch (e) {}
+      }
+    }
+  }
+</script>
+
+<style scoped>
+  .tinymce-container {
+    width: 100%;
+    line-height: normal;
+    position: relative;
+  }
+
+  /* ===== 弹窗遮罩 ===== */
+  .fm-mask {
+    position: fixed;
+    inset: 0;
+    background: rgba(0,0,0,0.5);
+    z-index: 99999;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+
+  .fm-dialog {
+    background: #fff;
+    border-radius: 6px;
+    box-shadow: 0 6px 30px rgba(0,0,0,0.2);
+    width: 1212px;
+    max-width: 96vw;
+    display: flex;
+    flex-direction: column;
+    max-height: 90vh;
+  }
+
+  /* ===== 标题栏 ===== */
+  .fm-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    padding: 12px 18px;
+    font-size: 16px;
+    font-weight: 600;
+    color: #333;
+    border-bottom: 1px solid #e4e4e4;
+    background: #f7f7f7;
+    border-radius: 6px 6px 0 0;
+  }
+  .fm-close {
+    cursor: pointer;
+    font-size: 22px;
+    color: #aaa;
+    line-height: 1;
+    padding: 0 2px;
+  }
+  .fm-close:hover { color: #333; }
+
+  /* ===== 工具栏 ===== */
+  .fm-toolbar {
+    display: flex;
+    align-items: center;
+    flex-wrap: nowrap;
+    gap: 3px;
+    padding: 6px 10px;
+    border-bottom: 1px solid #e4e4e4;
+    background: #fafafa;
+  }
+  .fm-divider {
+    width: 1px;
+    height: 28px;
+    background: #ddd;
+    margin: 0 4px;
+    flex-shrink: 0;
+  }
+
+  /* 预设 */
+  .fm-preset-wrap { position: relative; }
+  .fm-preset-btn {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    padding: 0 10px;
+    border: 1px solid #ccc;
+    border-radius: 4px;
+    background: #e8f4e8;
+    cursor: pointer;
+    min-width: 48px;
+    height: 60px;
+    box-sizing: border-box;
+    user-select: none;
+  }
+  .fm-preset-btn:hover { background: #d0ebd0; }
+  .fm-preset-icon { font-size: 14px; font-weight: bold; color: #2a7a2a; line-height: 1.2; }
+  .fm-preset-label { font-size: 11px; color: #2a7a2a; }
+  .fm-preset-arrow { font-size: 10px; color: #2a7a2a; }
+  .fm-preset-dropdown {
+    position: absolute;
+    top: calc(100% + 4px);
+    left: 0;
+    background: #fff;
+    border: 1px solid #ddd;
+    border-radius: 4px;
+    box-shadow: 0 4px 12px rgba(0,0,0,0.12);
+    z-index: 10;
+    min-width: 160px;
+  }
+  .fm-preset-item {
+    padding: 7px 14px;
+    font-size: 13px;
+    cursor: pointer;
+    color: #333;
+    white-space: nowrap;
+  }
+  .fm-preset-item:hover { background: #f0f7ff; color: #0183FB; }
+
+  /* 符号格子 */
+  .fm-symbol-grid {
+    display: flex;
+    flex-wrap: nowrap;
+    gap: 2px;
+    flex-shrink: 0;
+  }
+  .fm-sym-btn {
+    width: 40px;
+    height: 60px;
+    border: 1px solid #ddd;
+    background: #fff;
+    border-radius: 3px;
+    cursor: pointer;
+    font-size: 13px;
+    color: #333;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    padding: 0;
+  }
+  .fm-sym-btn:hover { background: #e8f0fe; border-color: #0183FB; color: #0183FB; }
+
+  /* 分类按钮组 */
+  .fm-category-row {
+    display: flex;
+    gap: 3px;
+    flex-wrap: nowrap;
+    flex-shrink: 0;
+  }
+  .fm-cat-wrap { position: relative; }
+  .fm-cat-btn {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    padding: 0 8px;
+    border: 1px solid #ccc;
+    border-radius: 4px;
+    background: #fff;
+    cursor: pointer;
+    min-width: 44px;
+    height: 60px;
+    box-sizing: border-box;
+    user-select: none;
+    gap: 1px;
+  }
+  .fm-cat-btn:hover, .fm-cat-btn.active { background: #e8f0fe; border-color: #0183FB; }
+  .fm-cat-icon { font-size: 14px; color: #333; line-height: 1; }
+  .fm-cat-label { font-size: 11px; color: #555; }
+  .fm-cat-arrow { font-size: 9px; color: #888; }
+  .fm-cat-dropdown {
+    position: absolute;
+    top: calc(100% + 4px);
+    left: 0;
+    background: #fff;
+    border: 1px solid #ddd;
+    border-radius: 4px;
+    box-shadow: 0 4px 12px rgba(0,0,0,0.12);
+    z-index: 10;
+    display: flex;
+    flex-wrap: wrap;
+    gap: 4px;
+    padding: 8px;
+    min-width: 160px;
+  }
+  .fm-cat-item {
+    padding: 5px 10px;
+    border: 1px solid #ddd;
+    border-radius: 3px;
+    background: #fff;
+    cursor: pointer;
+    font-size: 13px;
+    color: #333;
+    white-space: nowrap;
+  }
+  .fm-cat-item:hover { background: #e8f0fe; border-color: #0183FB; color: #0183FB; }
+
+  /* ===== 输入区 ===== */
+  .fm-body {
+    flex: 1;
+    display: flex;
+    gap: 0;
+    min-height: 200px;
+    overflow: hidden;
+  }
+  .fm-input {
+    flex: 1;
+    border: none;
+    border-right: 1px solid #e4e4e4;
+    outline: none;
+    padding: 20px;
+    font-size: 16px;
+    font-family: 'Courier New', monospace;
+    resize: none;
+    color: #333;
+    background: #fff;
+    line-height: 1.6;
+  }
+  .fm-input::placeholder {
+    color: #bbb;
+    font-family: inherit;
+  }
+  .fm-preview {
+    flex: 1;
+    padding: 20px;
+    background: #fafafa;
+    font-size: 18px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    min-height: 120px;
+    overflow: auto;
+    color: #333;
+  }
+  .fm-preview-empty { color: #ccc; font-size: 14px; font-family: sans-serif; }
+
+  /* ===== 底部 ===== */
+  .fm-footer {
+    display: flex;
+    justify-content: flex-end;
+    gap: 10px;
+    padding: 12px 18px;
+    border-top: 1px solid #e4e4e4;
+    background: #f7f7f7;
+    border-radius: 0 0 6px 6px;
+  }
+  .fm-btn-close, .fm-btn-save {
+    padding: 7px 28px;
+    border-radius: 4px;
+    font-size: 14px;
+    cursor: pointer;
+    border: 1px solid #ddd;
+    outline: none;
+  }
+  .fm-btn-close { background: #fff; color: #555; }
+  .fm-btn-close:hover { background: #f0f0f0; }
+  .fm-btn-save { background: #0183FB; border-color: #0183FB; color: #fff; }
+  .fm-btn-save:hover { background: #0072e0; }
+</style>

+ 555 - 0
src/views/safetyEducationExaminationNew/components/examDetails.vue

@@ -0,0 +1,555 @@
+<!-- 试卷详情 -->
+<template>
+  <div class="page-container examDetails-addPage">
+    <!-- 左侧面板 -->
+    <div class="left-panel">
+      <div class="exam-title">{{ examInfo.title }}</div>
+      <div class="exam-subtitle">总{{ examInfo.totalCount }}题 / 共{{ examInfo.totalScore }}分</div>
+
+      <div class="exam-name-box">{{ examInfo.name }}</div>
+
+      <div class="exam-info-box">
+        <div class="info-item"><span class="label">试卷总分:</span>{{ examInfo.totalScore }}分</div>
+        <div class="info-item"><span class="label">及格分数:</span>{{ examInfo.passScore }}分</div>
+        <div class="info-item"><span class="label">我的得分:</span>{{ examInfo.myScore }}分</div>
+        <div class="info-item"><span class="label">考试用时:</span>{{ examInfo.duration }}</div>
+        <div class="info-item">
+          <span class="label">考试结果:</span>
+          <span :class="examInfo.passed ? 'result-pass' : 'result-fail'">
+            {{ examInfo.passed ? '通过' : '无效' }}
+          </span>
+        </div>
+      </div>
+
+      <!-- 题目导航 -->
+      <div v-for="group in questionGroups" :key="group.type" class="question-group">
+        <div class="group-header">
+          <span class="group-title">{{ group.typeName }}</span>
+          <span class="group-count">{{ group.questions.length }}题 / 共{{ group.totalScore }}分</span>
+        </div>
+        <div class="question-nums">
+          <div
+            v-for="q in group.questions"
+            :key="q.index"
+            :class="['num-item', getNumClass(q), currentIndex === q.index ? 'num-active' : '']"
+            @click="selectQuestion(q.index)"
+          >
+            {{ q.index }}
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <!-- 右侧内容区:只展示当前选中题目 -->
+    <div class="right-panel" v-if="currentQuestion">
+      <div class="question-card">
+        <!-- 题目头部 -->
+        <div class="question-header">
+          <div class="question-index-box">{{ currentQuestion.index }}</div>
+          <div class="question-type-score">{{ currentQuestion.typeName }}({{ currentQuestion.score }}分)</div>
+          <div class="question-stars">
+            <i
+              v-for="s in 4"
+              :key="s"
+              :class="['star-icon', s <= currentQuestion.starLevel ? 'star-active' : 'star-empty']"
+            >★</i>
+          </div>
+        </div>
+
+        <!-- 题目内容(富文本) -->
+        <div class="question-content" v-html="currentQuestion.content"></div>
+
+        <!-- 单选题选项 -->
+        <div v-if="currentQuestion.type === 'single'" class="options-list">
+          <div
+            v-for="opt in currentQuestion.options"
+            :key="opt.key"
+            :class="['option-item', isSelectedOption(currentQuestion, opt.key) ? 'option-selected' : '']"
+          >
+            <span class="option-radio">
+              <i v-if="isSelectedOption(currentQuestion, opt.key)" class="el-icon-check radio-checked"></i>
+              <i v-else class="radio-empty"></i>
+            </span>
+            <span class="option-key">{{ opt.key }}</span>
+            <span class="option-text" v-html="opt.text"></span>
+          </div>
+        </div>
+
+        <!-- 多选题选项 -->
+        <div v-if="currentQuestion.type === 'multiple'" class="options-list">
+          <div
+            v-for="opt in currentQuestion.options"
+            :key="opt.key"
+            :class="['option-item', isSelectedOption(currentQuestion, opt.key) ? 'option-selected' : '']"
+          >
+            <span class="option-checkbox">
+              <el-checkbox :value="isSelectedOption(currentQuestion, opt.key)" disabled></el-checkbox>
+            </span>
+            <span class="option-key">{{ opt.key }}</span>
+            <span class="option-text" v-html="opt.text"></span>
+          </div>
+        </div>
+
+        <!-- 判断题选项 -->
+        <div v-if="currentQuestion.type === 'judge'" class="options-list">
+          <div
+            v-for="opt in judgeOptions"
+            :key="opt.key"
+            :class="['option-item', isSelectedOption(currentQuestion, opt.key) ? 'option-selected' : '']"
+          >
+            <span class="option-radio">
+              <i v-if="isSelectedOption(currentQuestion, opt.key)" class="el-icon-check radio-checked"></i>
+              <i v-else class="radio-empty"></i>
+            </span>
+            <span class="option-key">{{ opt.key }}</span>
+            <span class="option-text">{{ opt.text }}</span>
+          </div>
+        </div>
+
+        <!-- 答题结果 -->
+        <div class="answer-result-box">
+          <div class="answer-status">
+            <span v-if="currentQuestion.isCorrect" class="status-correct">✅ 回答正确</span>
+            <span v-else class="status-wrong">❌ 回答错误</span>
+          </div>
+          <div class="answer-detail">
+            <div class="answer-row"><span class="answer-label">回答选项:</span>{{ currentQuestion.userAnswer }}</div>
+            <div class="analysis-title">试题解析</div>
+            <div class="analysis-content" v-html="currentQuestion.analysis"></div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+  import { examElExamResultOverview, examElExamResultQuestionDetail } from '@/api/safetyEducationExaminationNew/index'
+  export default {
+    name: 'examDetails',
+    props: {
+      propsData: {}
+    },
+    data() {
+      return {
+        // 当前选中的题目序号
+        currentIndex: null,
+        // 左侧考试基本信息(来自 examElExamResultOverview 接口)
+        examInfo: {
+          title: '新生入学考试',
+          name: '考试名称考试名称考试名称考试名称考试名称考试名称考试名称考试名称考试名称',
+          totalCount: 12,
+          totalScore: 100,
+          passScore: 90,
+          myScore: 70,
+          duration: '29分56秒',
+          passed: false
+        },
+        judgeOptions: [
+          { key: 'A', text: '正确' },
+          { key: 'B', text: '错误' }
+        ],
+        // 左侧题目分组(来自 examElExamResultOverview 接口)
+        // status: 'correct' | 'wrong' | 'unanswered'
+        questionGroups: [
+          {
+            type: 'single', typeName: '单选题', totalScore: 60,
+            questions: [
+              { index: 1, status: 'correct', score: 10 },
+              { index: 2, status: 'wrong', score: 10 },
+              { index: 3, status: 'correct', score: 10 },
+              { index: 4, status: 'wrong', score: 10 },
+              { index: 5, status: 'unanswered', score: 10 },
+              { index: 6, status: 'correct', score: 10 }
+            ]
+          },
+          {
+            type: 'multiple', typeName: '多选题', totalScore: 30,
+            questions: [
+              { index: 7, status: 'wrong', score: 10 },
+              { index: 8, status: 'correct', score: 10 },
+              { index: 9, status: 'unanswered', score: 10 }
+            ]
+          },
+          {
+            type: 'judge', typeName: '判断题', totalScore: 10,
+            questions: [
+              { index: 10, status: 'correct', score: 10 },
+              { index: 11, status: 'wrong', score: 10 },
+              { index: 12, status: 'unanswered', score: 10 }
+            ]
+          }
+        ],
+        // 已加载的题目详情缓存,key 为题目序号
+        questionCache: {},
+        // 当前题目详情(来自 examElExamResultQuestionDetail 接口)
+        currentQuestion: null,
+        // 右侧加载中状态
+        questionLoading: false,
+        // 假数据:模拟 examElExamResultQuestionDetail 接口按序号返回
+        mockQuestions: {
+          1:  { index: 1,  type: 'single',   typeName: '单选题', score: 10, starLevel: 3, userAnswer: 'B', isCorrect: true,
+                content: '<p>第1题:题目内容题目内容题目内容题目内容题目内容题目内容题目内容题目内容题目内容</p>',
+                options: [{ key: 'A', text: '选项内容A选项内容A' }, { key: 'B', text: '选项内容B选项内容B' }, { key: 'C', text: '选项内容C选项内容C' }, { key: 'D', text: '选项内容D选项内容D' }],
+                analysis: '<p>本题考察...正确答案为B,因为...</p>' },
+          2:  { index: 2,  type: 'single',   typeName: '单选题', score: 10, starLevel: 2, userAnswer: 'A', isCorrect: false,
+                content: '<p>第2题:题目内容题目内容题目内容题目内容题目内容题目内容题目内容题目内容题目内容</p>',
+                options: [{ key: 'A', text: '选项内容A' }, { key: 'B', text: '选项内容B' }, { key: 'C', text: '选项内容C' }, { key: 'D', text: '选项内容D' }],
+                analysis: '<p>本题正确答案为C,因为...</p>' },
+          3:  { index: 3,  type: 'single',   typeName: '单选题', score: 10, starLevel: 1, userAnswer: 'C', isCorrect: true,
+                content: '<p>第3题:题目内容题目内容题目内容题目内容题目内容</p>',
+                options: [{ key: 'A', text: '选项A' }, { key: 'B', text: '选项B' }, { key: 'C', text: '选项C' }, { key: 'D', text: '选项D' }],
+                analysis: '<p>解析内容。</p>' },
+          4:  { index: 4,  type: 'single',   typeName: '单选题', score: 10, starLevel: 0, userAnswer: 'D', isCorrect: false,
+                content: '<p>第4题:题目内容题目内容题目内容</p>',
+                options: [{ key: 'A', text: '选项A' }, { key: 'B', text: '选项B' }, { key: 'C', text: '选项C' }, { key: 'D', text: '选项D' }],
+                analysis: '<p>解析内容。</p>' },
+          5:  { index: 5,  type: 'single',   typeName: '单选题', score: 10, starLevel: 0, userAnswer: '',  isCorrect: false,
+                content: '<p>第5题:题目内容题目内容题目内容</p>',
+                options: [{ key: 'A', text: '选项A' }, { key: 'B', text: '选项B' }, { key: 'C', text: '选项C' }, { key: 'D', text: '选项D' }],
+                analysis: '<p>解析内容。</p>' },
+          6:  { index: 6,  type: 'single',   typeName: '单选题', score: 10, starLevel: 0, userAnswer: 'B', isCorrect: true,
+                content: '<p>第6题:题目内容题目内容题目内容</p>',
+                options: [{ key: 'A', text: '选项A' }, { key: 'B', text: '选项B' }, { key: 'C', text: '选项C' }, { key: 'D', text: '选项D' }],
+                analysis: '<p>解析内容。</p>' },
+          7:  { index: 7,  type: 'multiple', typeName: '多选题', score: 10, starLevel: 2, userAnswer: 'A,B', isCorrect: false,
+                content: '<p>第7题:多选题题目内容多选题题目内容多选题题目内容多选题题目内容多选题题目内容</p>',
+                options: [{ key: 'A', text: '选项内容A' }, { key: 'B', text: '选项内容B' }, { key: 'C', text: '选项内容C' }, { key: 'D', text: '选项内容D' }],
+                analysis: '<p>正确答案为A、B、C,解析内容...</p>' },
+          8:  { index: 8,  type: 'multiple', typeName: '多选题', score: 10, starLevel: 0, userAnswer: 'A,C', isCorrect: true,
+                content: '<p>第8题:多选题题目内容多选题题目内容</p>',
+                options: [{ key: 'A', text: '选项A' }, { key: 'B', text: '选项B' }, { key: 'C', text: '选项C' }, { key: 'D', text: '选项D' }],
+                analysis: '<p>解析内容。</p>' },
+          9:  { index: 9,  type: 'multiple', typeName: '多选题', score: 10, starLevel: 0, userAnswer: '',  isCorrect: false,
+                content: '<p>第9题:多选题题目内容多选题题目内容</p>',
+                options: [{ key: 'A', text: '选项A' }, { key: 'B', text: '选项B' }, { key: 'C', text: '选项C' }, { key: 'D', text: '选项D' }],
+                analysis: '<p>解析内容。</p>' },
+          10: { index: 10, type: 'judge',    typeName: '判断题', score: 10, starLevel: 1, userAnswer: 'A', isCorrect: true,
+                content: '<p>第10题:以下说法正确的是:安全生产是企业第一责任。</p>',
+                options: [],
+                analysis: '<p>此说法正确,安全生产是企业的首要责任。</p>' },
+          11: { index: 11, type: 'judge',    typeName: '判断题', score: 10, starLevel: 0, userAnswer: 'B', isCorrect: false,
+                content: '<p>第11题:判断题题目内容判断题题目内容判断题题目内容</p>',
+                options: [],
+                analysis: '<p>解析内容。</p>' },
+          12: { index: 12, type: 'judge',    typeName: '判断题', score: 10, starLevel: 0, userAnswer: '',  isCorrect: false,
+                content: '<p>第12题:判断题题目内容判断题题目内容判断题题目内容</p>',
+                options: [],
+                analysis: '<p>解析内容。</p>' }
+        }
+      }
+    },
+    computed: {},
+    created() {},
+    mounted() {
+      this.initialize()
+    },
+    methods: {
+      initialize() {
+        // 默认选中第一题(假数据直接从 data 里读取,无需接口)
+        const firstGroup = this.questionGroups[0]
+        if (firstGroup && firstGroup.questions.length) {
+          this.$set(this, 'currentIndex', firstGroup.questions[0].index)
+          this.$set(this, 'currentQuestion', this.mockQuestions[firstGroup.questions[0].index] || null)
+        }
+      },
+      backPage() {
+        this.$parent.tableButton(6)
+      },
+      // 点击左侧题号:切换当前题目,按需从接口加载详情
+      selectQuestion(index) {
+        this.$set(this, 'currentIndex', index)
+        if (this.questionCache[index]) {
+          this.$set(this, 'currentQuestion', this.questionCache[index])
+          return
+        }
+        // 假数据直接从 mockQuestions 取,对接真实接口时替换为下方注释的接口调用
+        const detail = this.mockQuestions[index] || null
+        this.$set(this.questionCache, index, detail)
+        this.$set(this, 'currentQuestion', detail)
+      },
+      // 判断选项是否被选中
+      isSelectedOption(q, key) {
+        if (!q.userAnswer) return false
+        return q.userAnswer.split(',').includes(key)
+      },
+      // 根据左侧题目列表的 status 字段获取题号样式
+      // status: 'correct' → 绿色,'wrong' → 红色,'unanswered' → 灰色
+      getNumClass(q) {
+        if (q.status === 'correct') return 'num-correct'
+        if (q.status === 'wrong') return 'num-wrong'
+        return 'num-unanswered'
+      }
+    }
+  }
+</script>
+
+<style scoped lang="scss">
+  .examDetails-addPage {
+    flex: 1;
+    display: flex;
+    overflow: hidden;
+    background: #f5f5f5;
+
+    /* ========== 左侧面板 ========== */
+    .left-panel {
+      width: 400px;
+      min-width: 400px;
+      background: #fff;
+      display: flex;
+      flex-direction: column;
+      overflow-y: auto;
+      padding: 20px 16px;
+      border-right: 1px solid #e8e8e8;
+
+      &::-webkit-scrollbar { width: 4px; }
+      &::-webkit-scrollbar-thumb { background: #ccc; border-radius: 4px; }
+
+      .exam-title {
+        font-size: 16px;
+        font-weight: 700;
+        color: #1890ff;
+        margin-bottom: 4px;
+      }
+
+      .exam-subtitle {
+        font-size: 13px;
+        color: #666;
+        margin-bottom: 16px;
+      }
+
+      .exam-name-box {
+        font-size: 13px;
+        color: #333;
+        margin-bottom: 12px;
+        line-height: 1.6;
+      }
+
+      .exam-info-box {
+        background: #f5f5f5;
+        border-radius: 6px;
+        padding: 12px;
+        margin-bottom: 20px;
+
+        .info-item {
+          font-size: 13px;
+          color: #333;
+          margin-bottom: 6px;
+          line-height: 1.5;
+          &:last-child { margin-bottom: 0; }
+
+          .label { font-weight: 500; }
+          .result-pass { color: #52c41a; font-weight: 700; }
+          .result-fail { color: #faad14; font-weight: 700; }
+        }
+      }
+
+      .question-group {
+        margin-bottom: 16px;
+
+        .group-header {
+          display: flex;
+          justify-content: space-between;
+          align-items: center;
+          margin-bottom: 8px;
+
+          .group-title { font-size: 14px; font-weight: 600; color: #333; }
+          .group-count { font-size: 12px; color: #999; }
+        }
+
+        .question-nums {
+          display: flex;
+          flex-wrap: wrap;
+          gap: 6px;
+
+          .num-item {
+            width: 36px;
+            height: 36px;
+            border-radius: 4px;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            font-size: 13px;
+            font-weight: 600;
+            cursor: pointer;
+            position: relative;
+            color: #fff;
+            transition: opacity 0.2s;
+
+            &:hover { opacity: 0.85; }
+
+            &.num-correct { background: #52c41a; }
+            &.num-wrong { background: #ff4d4f; }
+            &.num-unanswered { background: #999; color: #fff; }
+            &.num-active {
+              outline: 3px solid #1890ff;
+              outline-offset: 1px;
+            }
+          }
+        }
+      }
+    }
+
+    /* ========== 右侧内容区 ========== */
+    .right-panel {
+      flex: 1;
+      overflow-y: auto;
+      padding: 20px;
+
+      &::-webkit-scrollbar { width: 6px; }
+      &::-webkit-scrollbar-thumb { background: #ccc; border-radius: 4px; }
+
+      .question-card {
+        background: #fff;
+        border-radius: 8px;
+        padding: 20px;
+
+        .question-header {
+          display: flex;
+          align-items: center;
+          margin-bottom: 12px;
+
+          .question-index-box {
+            width: 28px;
+            height: 28px;
+            background: #1890ff;
+            color: #fff;
+            border-radius: 4px;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            font-size: 13px;
+            font-weight: 700;
+            margin-right: 10px;
+            flex-shrink: 0;
+          }
+
+          .question-type-score {
+            font-size: 15px;
+            font-weight: 600;
+            color: #333;
+            flex: 1;
+          }
+
+          .question-stars {
+            display: flex;
+            gap: 2px;
+
+            .star-icon {
+              font-size: 18px;
+              font-style: normal;
+              &.star-active { color: #fadb14; }
+              &.star-empty { color: #d9d9d9; }
+            }
+          }
+        }
+
+        .question-content {
+          font-size: 14px;
+          font-weight: 600;
+          color: #333;
+          margin-bottom: 16px;
+          line-height: 1.6;
+        }
+
+        .options-list {
+          display: flex;
+          flex-direction: column;
+          gap: 8px;
+          margin-bottom: 16px;
+
+          .option-item {
+            display: flex;
+            align-items: center;
+            border: 1px solid #e8e8e8;
+            border-radius: 6px;
+            padding: 10px 14px;
+            cursor: default;
+            transition: border-color 0.2s;
+
+            &.option-selected {
+              border-color: #1890ff;
+              background: #e6f7ff;
+
+              .option-key { color: #1890ff; }
+              .option-text { color: #1890ff; }
+            }
+
+            .option-radio {
+              width: 16px;
+              height: 16px;
+              border-radius: 50%;
+              border: 1px solid #d9d9d9;
+              margin-right: 10px;
+              display: flex;
+              align-items: center;
+              justify-content: center;
+              flex-shrink: 0;
+
+              .radio-checked { color: #1890ff; font-size: 12px; }
+              .radio-empty { display: block; width: 8px; height: 8px; }
+            }
+
+            .option-checkbox {
+              margin-right: 10px;
+              flex-shrink: 0;
+            }
+
+            .option-key {
+              font-size: 14px;
+              font-weight: 600;
+              color: #666;
+              margin-right: 8px;
+              width: 16px;
+              flex-shrink: 0;
+            }
+
+            .option-text {
+              font-size: 14px;
+              color: #333;
+              flex: 1;
+            }
+          }
+        }
+
+        .answer-result-box {
+          background: #f5f5f5;
+          border-radius: 6px;
+          padding: 14px 16px;
+
+          .answer-status {
+            margin-bottom: 10px;
+            font-size: 14px;
+            font-weight: 600;
+
+            .status-correct { color: #52c41a; }
+            .status-wrong { color: #ff4d4f; }
+          }
+
+          .answer-detail {
+            .answer-row {
+              font-size: 14px;
+              color: #333;
+              margin-bottom: 10px;
+
+              .answer-label { font-weight: 600; }
+            }
+
+            .analysis-title {
+              font-size: 14px;
+              font-weight: 600;
+              color: #333;
+              margin-bottom: 6px;
+            }
+
+            .analysis-content {
+              font-size: 13px;
+              color: #fa8c16;
+              line-height: 1.7;
+            }
+          }
+        }
+      }
+    }
+  }
+</style>

+ 1 - 49
src/views/safetyEducationExaminationNew/courseManagement/courseManagement/addPage.vue

@@ -1,7 +1,7 @@
 <!--新建课程-->
 <template>
   <div class="courseManagement-addPage">
-    <div class="top-max-big-box">
+    <div class="page-container-top-max-big-box">
       <p class="top-1-p">{{newData.id?'编辑课程':'新建课程'}}</p>
       <p class="top-2-p" @click="backPage()">返回</p>
       <p class="top-3-p" @click="resetButton()">重置</p>
@@ -95,54 +95,6 @@
     display: flex;
     flex-direction: column;
     overflow: hidden;
-    .top-max-big-box{
-      height:60px;
-      display: flex;
-      overflow: hidden;
-      border-radius:10px;
-      margin:5px 20px 10px 10px;
-      box-shadow: 0 0 8px 1px rgba(0, 0, 0, 0.1);
-      padding:0 20px;
-      .top-1-p{
-        flex:1;
-        line-height:60px;
-        font-size:16px;
-        font-weight:700;
-      }
-      .top-2-p{
-        height:35px;
-        line-height:33px;
-        margin:12px 0 0 20px;
-        padding:0 20px;
-        border-radius:4px;
-        background-color: #fff;
-        border:1px solid #999;
-        color:#999;
-        cursor: pointer;
-      }
-      .top-3-p{
-        height:35px;
-        line-height:33px;
-        margin:12px 0 0 20px;
-        padding:0 20px;
-        border-radius:4px;
-        background-color: #dedede;
-        border:1px solid #dedede;
-        color:#333;
-        cursor: pointer;
-      }
-      .top-4-p{
-        height:35px;
-        line-height:33px;
-        margin:12px 0 0 20px;
-        padding:0 20px;
-        border-radius:4px;
-        background-color: #0183fa;
-        border:1px solid #0183fa;
-        color:#fff;
-        cursor: pointer;
-      }
-    }
     .bottom-max-big-box{
       flex:1;
       display: flex;

+ 1 - 1
src/views/safetyEducationExaminationNew/courseManagement/courseManagement/drawerItem.vue

@@ -121,7 +121,7 @@
         },
         rightList:[],
         loading:false,
-        total:10,
+        total:0,
         multipleSelection: [], // 存储所有被勾选的数据
       }
     },

+ 1 - 0
src/views/safetyEducationExaminationNew/courseManagement/courseManagement/studyCourses.vue

@@ -136,6 +136,7 @@
             ],
           },
         ],
+        //拖拽相关
         dragInfo: {
           type: null, // 'big' 或 'small'
           bigIndex: null,

+ 1 - 49
src/views/safetyEducationExaminationNew/courseManagement/coursewareManagement/addPage.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="coursewareManagement-addPage">
-    <div class="top-max-big-box">
+    <div class="page-container-top-max-big-box">
       <p class="top-1-p">{{newData.id?'编辑课件':'添加课件'}}</p>
       <p class="top-2-p" @click="backPage()">返回</p>
       <p class="top-3-p" @click="resetButton()">重置</p>
@@ -511,54 +511,6 @@
     display: flex;
     flex-direction: column;
     overflow: hidden;
-    .top-max-big-box{
-      height:60px;
-      display: flex;
-      overflow: hidden;
-      border-radius:10px;
-      margin:5px 20px 10px 10px;
-      box-shadow: 0 0 8px 1px rgba(0, 0, 0, 0.1);
-      padding:0 20px;
-      .top-1-p{
-        flex:1;
-        line-height:60px;
-        font-size:16px;
-        font-weight:700;
-      }
-      .top-2-p{
-        height:35px;
-        line-height:33px;
-        margin:12px 0 0 20px;
-        padding:0 20px;
-        border-radius:4px;
-        background-color: #fff;
-        border:1px solid #999;
-        color:#999;
-        cursor: pointer;
-      }
-      .top-3-p{
-        height:35px;
-        line-height:33px;
-        margin:12px 0 0 20px;
-        padding:0 20px;
-        border-radius:4px;
-        background-color: #dedede;
-        border:1px solid #dedede;
-        color:#333;
-        cursor: pointer;
-      }
-      .top-4-p{
-        height:35px;
-        line-height:33px;
-        margin:12px 0 0 20px;
-        padding:0 20px;
-        border-radius:4px;
-        background-color: #0183fa;
-        border:1px solid #0183fa;
-        color:#fff;
-        cursor: pointer;
-      }
-    }
     .bottom-max-big-box{
       flex:1;
       display: flex;

+ 229 - 86
src/views/safetyEducationExaminationNew/examManagement/examinationArrange/addPage.vue

@@ -1,7 +1,7 @@
 <!-- 新建考试 -->
 <template>
   <div class="page-container examinationArrange-addPage">
-    <div class="top-max-big-box">
+    <div class="page-container-top-max-big-box">
       <p class="top-1-p">{{newData.id ? '编辑考试' : '新建考试'}}</p>
       <p class="top-2-p" @click="backPage()">返回</p>
       <p class="top-3-p" v-if="newData.id" @click="resetButton()">重置</p>
@@ -27,13 +27,19 @@
           <div v-show="activeTab === 'basic'">
             <el-form :model="newData" ref="basicForm" label-width="100px" :rules="rules">
               <div class="section-block">
+                <el-form-item label="分级考试" prop="placementTest">
+                  <div class="switch-row">
+                    <el-switch v-model="newData.placementTest" :active-value="true" :inactive-value="false"
+                               active-color="#0183FB" inactive-color="#AAAAAA" />
+                  </div>
+                </el-form-item>
                 <div class="flex-row-box">
-                  <el-form-item label="考试类型" prop="examType">
-                    <el-select v-model="newData.examType" placeholder="分级分类" style="width:200px;">
+                  <el-form-item label="考试类型" prop="examType" v-if="!newData.placementTest">
+                    <el-select v-model="newData.examType" placeholder="考试类型" style="width:200px;">
                       <el-option v-for="item in optionExamType" :key="item.value" :label="item.label" :value="item.value" />
                     </el-select>
                   </el-form-item>
-                  <el-form-item label="风险等级" prop="riskLevel">
+                  <el-form-item label="风险等级" prop="riskLevel" v-if="newData.placementTest">
                     <el-select v-model="newData.riskLevel" placeholder="I级" style="width:200px;">
                       <el-option v-for="item in optionRiskLevel" :key="item.value" :label="item.label" :value="item.value" />
                     </el-select>
@@ -41,26 +47,36 @@
                 </div>
 
                 <el-form-item label="考试名称" prop="examName">
-                  <el-input v-model="newData.examName" placeholder="请输入课程名称" style="width:100%;" />
+                  <el-input v-model="newData.examName" placeholder="请输入课程名称" style="width:600px;" />
                 </el-form-item>
 
                 <el-form-item label="答卷次数" prop="answerCount">
                   <div class="stepper-row">
                     <div class="number-stepper">
-                      <button class="stepper-btn" @click.prevent="stepChange('answerCount', -1)">-</button>
-                      <span class="stepper-val">{{ newData.answerCount }}</span>
-                      <button class="stepper-btn" @click.prevent="stepChange('answerCount', 1)">+</button>
+                      <el-input-number
+                        :disabled="newData.answerUnlimited"
+                        class="form-input-number"
+                        style="width:160px"
+                        :min="1"
+                        :max="9999"
+                        :step="1" :precision="0" step-strictly
+                        v-model="newData.answerCount"
+                      >
+                      </el-input-number>
                     </div>
                     <span class="unit-text">次</span>
-                    <el-radio v-model="newData.answerUnlimited" :label="true" style="margin-left:20px;">无限制</el-radio>
+                    <el-checkbox v-model="newData.answerUnlimited" :label="true" style="margin-left:20px;">无限制</el-checkbox>
                   </div>
                 </el-form-item>
 
                 <el-form-item label="是否共享">
-                  <el-switch v-model="newData.isShare" :active-value="true" :inactive-value="false"
-                    active-color="#0183FB" inactive-color="#AAAAAA" />
-                  <span class="tip-text" v-if="newData.isShare">共享开启,全校所有学院单位都可见</span>
-                </el-form-item>
+                  <div class="switch-row">
+                    <el-switch v-model="newData.isShare" :active-value="true" :inactive-value="false"
+                               active-color="#0183FB" inactive-color="#AAAAAA" />
+                    <p class="tip-text" style="margin:2px 0 0 10px;">共享开启,全校所有学院单位都可见</p>
+
+                  </div>
+               </el-form-item>
 
                 <el-form-item label="启用" prop="enabled">
                   <el-switch v-model="newData.enabled" :active-value="true" :inactive-value="false"
@@ -69,7 +85,7 @@
 
                 <el-form-item label="考卷" prop="paperId">
                   <div class="paper-select-row">
-                    <el-button type="primary" @click="openPaperDrawer">选择试卷</el-button>
+                    <p class="checkButton-p" @click="openPaperDrawer">选择试卷</p>
                     <div class="selected-tag" v-if="newData.paperName">
                       <span>{{ newData.paperName }}</span>
                       <i class="el-icon-close" @click="newData.paperId = null; newData.paperName = ''"></i>
@@ -95,9 +111,17 @@
                   <el-form-item label="考试时长" prop="examDuration">
                     <div class="stepper-row">
                       <div class="number-stepper">
-                        <button class="stepper-btn" @click.prevent="stepChange('examDuration', -1)">-</button>
-                        <span class="stepper-val">{{ newData.examDuration }}</span>
-                        <button class="stepper-btn" @click.prevent="stepChange('examDuration', 1)">+</button>
+                        <div class="number-stepper">
+                          <el-input-number
+                            class="form-input-number"
+                            style="width:160px"
+                            :min="0"
+                            :max="9999"
+                            :step="1" :precision="0" step-strictly
+                            v-model="newData.examDuration"
+                          >
+                          </el-input-number>
+                        </div>
                       </div>
                       <span class="unit-text">分钟</span>
                       <span class="tip-text" style="margin-left:12px;">限制答卷最大时长</span>
@@ -117,16 +141,30 @@
                       <i v-if="newData.examScope === item.value" class="el-icon-check" style="color:#0183fa;margin-right:4px;"></i>
                       {{ item.label }}
                     </button>
+                    <p class="checkButton-p" @click="openExamScopeDrawer" v-if="newData.examScope == 'scope' || newData.examScope == 'specify'">选择</p>
                   </div>
                   <!-- 考试范围表格(非全校时显示) -->
                   <div v-if="newData.examScope !== 'all'" class="scope-table-box">
-                    <div class="scope-table-header">已选取:{{ scopeList.length }}组</div>
-                    <el-table :data="scopeList" size="small" style="width:100%;">
+                    <div class="scope-table-header" v-if="newData.examScope == 'scope'">已选取:{{ scopeList.length }}组</div>
+                    <div class="scope-table-header" v-if="newData.examScope == 'specify'">已选取:{{ userList.length }}人</div>
+                    <el-table v-if="newData.examScope == 'scope'" class="table-box" :data="scopeList" size="small" style="width:100%;">
+                      <el-table-column label="学院" prop="college" />
+                      <el-table-column label="专业" prop="major" width="200" />
+                      <el-table-column label="角色" prop="role" width="180" />
+                      <el-table-column label="学年" prop="grade" width="180" />
+                      <el-table-column label="操作" width="140" align="center">
+                        <template slot-scope="{ $index }">
+                          <el-button size="mini" @click="scopeList.splice($index, 1)">移除</el-button>
+                        </template>
+                      </el-table-column>
+                    </el-table>
+                    <el-table v-if="newData.examScope == 'specify'" class="table-box" :data="userList" size="small" style="width:100%;">
+                      <el-table-column label="姓名" prop="major" width="200" />
                       <el-table-column label="学院" prop="college" />
-                      <el-table-column label="专业" prop="major" width="160" />
-                      <el-table-column label="角色" prop="role" width="120" />
-                      <el-table-column label="年级" prop="grade" width="120" />
-                      <el-table-column label="操作" width="80" align="center">
+                      <el-table-column label="专业" prop="major" width="200" />
+                      <el-table-column label="角色" prop="role" width="180" />
+                      <el-table-column label="学年" prop="grade" width="180" />
+                      <el-table-column label="操作" width="140" align="center">
                         <template slot-scope="{ $index }">
                           <el-button size="mini" @click="scopeList.splice($index, 1)">移除</el-button>
                         </template>
@@ -146,13 +184,19 @@
                   <div class="switch-row">
                     <el-switch v-model="newData.creditRequired" :active-value="true" :inactive-value="false"
                       active-color="#0183FB" inactive-color="#AAAAAA" />
-                    <div class="number-stepper" style="margin-left:12px;">
-                      <button class="stepper-btn" @click.prevent="stepChange('creditHours', -1)">-</button>
-                      <span class="stepper-val">{{ newData.creditHours }}</span>
-                      <button class="stepper-btn" @click.prevent="stepChange('creditHours', 1)">+</button>
+                    <div class="number-stepper" v-if="newData.creditRequired" style="margin-left:12px;">
+                      <el-input-number
+                        class="form-input-number"
+                        style="width:160px;margin-left:8px;"
+                        :min="0"
+                        :max="9999"
+                        :step="1" :precision="0" step-strictly
+                        v-model="newData.creditHours"
+                      >
+                      </el-input-number>
                     </div>
-                    <span class="unit-text">学时</span>
-                    <span class="tip-text" style="margin-left:12px;" v-if="newData.creditRequired">开启后,需完成相应学时才能参加考试</span>
+                    <span v-if="newData.creditRequired" class="unit-text" style="margin-top:2px;">学时</span>
+                    <span class="tip-text" style="margin-left:12px;">开启后,需完成相应学时才能参加考试</span>
                   </div>
                 </el-form-item>
 
@@ -160,24 +204,24 @@
                   <div class="switch-row">
                     <el-switch v-model="newData.preStudy" :active-value="true" :inactive-value="false"
                       active-color="#0183FB" inactive-color="#AAAAAA" />
-                    <el-button type="primary" style="margin-left:12px;" @click="openCourseDrawer">选择课程</el-button>
+                    <p class="checkButton-p" style="margin-left:20px;" v-if="newData.preStudy" @click="openCourseDrawer">选择课程</p>
+                    <div class="tip-text" style="margin-left:20px;">开启后,需完成学习才能参加考试</div>
                   </div>
-                  <div class="selected-tag" v-if="newData.preStudyCourseName" style="margin-top:8px;max-width:400px;">
+                  <div class="selected-tag" v-if="newData.preStudy&&newData.preStudyCourseName" style="margin-top:8px;max-width:400px;">
                     <span>{{ newData.preStudyCourseName }}</span>
                     <i class="el-icon-close" @click="newData.preStudyCourseId = null; newData.preStudyCourseName = ''"></i>
                   </div>
-                  <div class="tip-text" v-if="newData.preStudy" style="margin-top:6px;">开启后,需完成学习才能参加考试</div>
                 </el-form-item>
 
                 <el-form-item label="模拟练习">
                   <div class="switch-row">
                     <el-switch v-model="newData.mockPractice" :active-value="true" :inactive-value="false"
                       active-color="#0183FB" inactive-color="#AAAAAA" />
-                    <el-button type="primary" style="margin-left:12px;" @click="reuseExamPaper">复用试卷</el-button>
-                    <el-button style="margin-left:8px;" @click="openMockPaperDrawer">选择试题</el-button>
-                    <span class="tip-text" style="margin-left:12px;" v-if="newData.mockPractice">开启后,需完成模拟练习任务才能参加考试</span>
+                    <el-checkbox v-if="newData.mockPractice" v-model="newData.mockPracticeReuse" :label="true" style="margin-left:20px;">复用试卷</el-checkbox>
+                    <p class="checkButton-p" v-if="newData.mockPractice&&!newData.mockPracticeReuse" style="margin-left:20px;" @click="openMockPaperDrawer">选择试题</p>
+                    <span class="tip-text" style="margin-left:20px;" >开启后,需完成模拟练习任务才能参加考试</span>
                   </div>
-                  <div class="mock-stat-bar" v-if="newData.mockPaperId">
+                  <div class="mock-stat-bar" v-if="newData.mockPaperId&&newData.mockPractice&&!newData.mockPracticeReuse">
                     <span>总题目数:<b>{{ newData.mockTotalCount || 0 }}</b></span>
                     <span style="margin-left:20px;">总分:<b>{{ newData.mockTotalScore || 0 }}</b></span>
                     <span style="margin-left:20px;">及格分:<b>{{ newData.mockPassScore || 0 }}</b></span>
@@ -188,17 +232,18 @@
                   <div class="switch-row">
                     <el-switch v-model="newData.pledge" :active-value="true" :inactive-value="false"
                       active-color="#0183FB" inactive-color="#AAAAAA" />
-                    <span class="tip-text" style="margin-left:12px;" v-if="newData.pledge">开启后,考生需要阅读考试承诺内容并做出承诺才可开始考试</span>
+                    <span class="tip-text" style="margin-left:20px;">开启后,考生需要阅读考试承诺内容并做出承诺才可开始考试</span>
                     <span style="margin-left:30px;font-weight:600;color:#333;">承诺书打印</span>
                     <el-switch v-model="newData.pledgePrint" :active-value="true" :inactive-value="false"
                       active-color="#0183FB" inactive-color="#AAAAAA" style="margin-left:12px;" />
                   </div>
                   <div class="editor-box" v-if="newData.pledge" style="margin-top:12px;">
-                    <el-input
-                      type="textarea"
+                    <tinymce-text
                       v-model="newData.pledgeContent"
-                      :rows="10"
-                      placeholder="请输入考试承诺书内容"
+                      :height="500"
+                      :max-length="1000"
+                      :upload-url="editorUploadUrl"
+                      :upload-headers="editorUploadHeaders"
                     />
                   </div>
                 </el-form-item>
@@ -213,13 +258,19 @@
                 <el-form-item label="图形弹窗校验">
                   <div class="switch-row">
                     <el-switch v-model="newData.captcha" :active-value="true" :inactive-value="false"
-                      active-color="#0183FB" inactive-color="#AAAAAA" />
+                               active-color="#0183FB" inactive-color="#AAAAAA" />
                     <template v-if="newData.captcha">
-                      <span class="tip-text" style="margin-left:12px;">开启后,每隔</span>
+                      <p class="tip-text" style="margin-left:12px;">开启后,每隔</p>
                       <div class="number-stepper" style="margin-left:8px;">
-                        <button class="stepper-btn" @click.prevent="stepChange('captchaInterval', -1)">-</button>
-                        <span class="stepper-val">{{ newData.captchaInterval }}</span>
-                        <button class="stepper-btn" @click.prevent="stepChange('captchaInterval', 1)">+</button>
+                        <el-input-number
+                          class="form-input-number"
+                          style="width:160px;"
+                          :min="0"
+                          :max="9999"
+                          :step="1" :precision="0" step-strictly
+                          v-model="newData.captchaInterval"
+                        >
+                        </el-input-number>
                       </div>
                       <span class="tip-text" style="margin-left:8px;">分钟,提醒弹窗验证</span>
                     </template>
@@ -261,7 +312,7 @@
                   <div class="switch-row">
                     <el-switch v-model="newData.certificate" :active-value="true" :inactive-value="false"
                       active-color="#0183FB" inactive-color="#AAAAAA" />
-                    <el-button type="primary" style="margin-left:12px;" @click="openCertTemplateDrawer">选择模板</el-button>
+                    <p v-if="newData.certificate" class="checkButton-p" style="margin-left:20px;" @click="openCertTemplateDrawer">选择模板</p>
                   </div>
                 </el-form-item>
 
@@ -273,9 +324,15 @@
                 <el-form-item label="证书有效期">
                   <div class="stepper-row">
                     <div class="number-stepper">
-                      <button class="stepper-btn" @click.prevent="stepChange('certValidity', -1)">-</button>
-                      <span class="stepper-val">{{ newData.certValidity }}</span>
-                      <button class="stepper-btn" @click.prevent="stepChange('certValidity', 1)">+</button>
+                      <el-input-number
+                        class="form-input-number"
+                        style="width:160px;"
+                        :min="0"
+                        :max="9999"
+                        :step="1" :precision="0" step-strictly
+                        v-model="newData.certValidity"
+                      >
+                      </el-input-number>
                     </div>
                     <span class="unit-text">个月</span>
                   </div>
@@ -284,9 +341,15 @@
                 <el-form-item label="证书复审周期">
                   <div class="stepper-row">
                     <div class="number-stepper">
-                      <button class="stepper-btn" @click.prevent="stepChange('certReviewCycle', -1)">-</button>
-                      <span class="stepper-val">{{ newData.certReviewCycle }}</span>
-                      <button class="stepper-btn" @click.prevent="stepChange('certReviewCycle', 1)">+</button>
+                      <el-input-number
+                        class="form-input-number"
+                        style="width:160px;"
+                        :min="0"
+                        :max="9999"
+                        :step="1" :precision="0" step-strictly
+                        v-model="newData.certReviewCycle"
+                      >
+                      </el-input-number>
                     </div>
                     <span class="unit-text">个月</span>
                   </div>
@@ -298,17 +361,56 @@
         </div>
       </div>
     </div>
+    <el-drawer
+      size="100%"
+      custom-class="examinationArrange-addPage-drawer"
+      :append-to-body="true"
+      :title="drawerTitle"
+      ref="drawer"
+      :visible.sync="drawer"
+      direction="rtl"
+    >
+      <!--选择试卷/选择试题-->
+      <selectTestPaper v-if="drawerType === 1 || drawerType === 5"></selectTestPaper>
+      <!--选择课程-->
+      <selectCourse v-if="drawerType === 2"></selectCourse>
+      <!--选择用户-->
+      <selectUser v-if="drawerType === 3"></selectUser>
+      <!--选择范围-->
+      <selectScope v-if="drawerType === 4"></selectScope>
+      <!--选择证书-->
+      <selectCertificate v-if="drawerType === 6"></selectCertificate>
+    </el-drawer>
   </div>
 </template>
 
 <script>
+  import { getToken } from "@/utils/auth";
+  import tinymceText from "@/components/tinymceContainer/tinymceText.vue";
+  import selectTestPaper from "./selectTestPaper.vue";
+  import selectCourse from "./selectCourse.vue";
+  import selectUser from "./selectUser.vue";
+  import selectScope from "./selectScope.vue";
+  import selectCertificate from "./selectCertificate.vue";
   export default {
     name: 'addPage',
+    components: {
+      tinymceText,
+      selectTestPaper,
+      selectCourse,
+      selectUser,
+      selectScope,
+      selectCertificate,
+    },
     props: {
       propsData: {}
     },
     data() {
       return {
+        editorUploadUrl: this.uploadUrl(),
+        editorUploadHeaders: {
+          Authorization:getToken(),
+        },
         activeTab: 'basic',
         tabs: [
           { key: 'basic', label: '基本设置' },
@@ -330,12 +432,14 @@
           { label: 'III级', value: 3 },
         ],
         scopeList: [],
+        userList: [],
         newData: {
           id: null,
+          placementTest:false,
           examType: null,
           riskLevel: null,
           examName: '',
-          answerCount: 2,
+          answerCount: 1,
           answerUnlimited: false,
           isShare: true,
           enabled: true,
@@ -350,6 +454,7 @@
           preStudyCourseId: null,
           preStudyCourseName: '',
           mockPractice: false,
+          mockPracticeReuse: false,
           mockPaperId: null,
           mockTotalCount: 0,
           mockTotalScore: 0,
@@ -369,10 +474,19 @@
         },
         rules: {
           examType: [{ required: true, message: '请选择考试类型', trigger: 'change' }],
+          riskLevel: [{ required: true, message: '请选择风险等级', trigger: 'change' }],
           examName: [{ required: true, message: '请输入考试名称', trigger: 'blur' }],
+          answerCount: [{ required: true, message: '请输入答卷次数', trigger: 'blur' }],
           paperId: [{ required: true, message: '请选择考卷', trigger: 'change' }],
+          enabled: [{ required: true, message: '请选择启用停用', trigger: 'change' }],
+          examDuration: [{ required: true, message: '请选择考试时长', trigger: 'change' }],
+          examScope: [{ required: true, message: '请选择考试范围', trigger: 'change' }],
           examTimeRange: [{ required: true, message: '请选择考试时间', trigger: 'change' }],
         },
+        //弹窗相关
+        drawerTitle:'',
+        drawerType:'',
+        drawer: false,
       }
     },
     mounted() {
@@ -399,24 +513,46 @@
           // TODO: 调用保存接口
         })
       },
-      stepChange(field, delta) {
-        const val = (Number(this.newData[field]) || 0) + delta
-        this.newData[field] = Math.max(0, val)
-      },
       openPaperDrawer() {
         // TODO: 打开选择试卷弹窗
+        this.$set(this,'drawerTitle','选择试卷');
+        this.$set(this,'drawerType',1);
+        this.$set(this,'drawer',true);
       },
       openCourseDrawer() {
         // TODO: 打开选择课程弹窗
+        this.$set(this,'drawerTitle','选择课程');
+        this.$set(this,'drawerType',2);
+        this.$set(this,'drawer',true);
       },
-      reuseExamPaper() {
-        // TODO: 复用考试试卷
+      openExamScopeDrawer(){
+        if(this.newData.examScope == 'specify'){
+          // TODO: 打开选择用户弹窗
+          this.$set(this,'drawerTitle','选择用户');
+          this.$set(this,'drawerType',3);
+          this.$set(this,'drawer',true);
+        }else if(this.newData.examScope == 'scope'){
+          // TODO: 打开选择范围弹窗
+          this.$set(this,'drawerTitle','选择范围');
+          this.$set(this,'drawerType',4);
+          this.$set(this,'drawer',true);
+        }
       },
       openMockPaperDrawer() {
         // TODO: 打开选择试题弹窗
+        this.$set(this,'drawerTitle','选择试题');
+        this.$set(this,'drawerType',5);
+        this.$set(this,'drawer',true);
       },
       openCertTemplateDrawer() {
         // TODO: 打开选择证书模板弹窗
+        this.$set(this,'drawerTitle','选择证书');
+        this.$set(this,'drawerType',6);
+        this.$set(this,'drawer',true);
+      },
+      //弹窗关闭
+      drawerOffButton(){
+        this.$refs.drawer.closeDrawer();
       },
     }
   }
@@ -424,27 +560,6 @@
 
 <style scoped lang="scss">
   .examinationArrange-addPage {
-    .top-max-big-box {
-      height: 60px;
-      display: flex;
-      overflow: hidden;
-      padding: 0 20px;
-      border-bottom: 1px solid #dedede;
-      align-items: center;
-      .top-1-p { flex: 1; font-size: 16px; font-weight: 700; }
-      .top-2-p {
-        height: 35px; line-height: 33px; margin-left: 20px; padding: 0 20px;
-        border-radius: 4px; background-color: #fff; border: 1px solid #999; color: #999; cursor: pointer;
-      }
-      .top-3-p {
-        height: 35px; line-height: 33px; margin-left: 20px; padding: 0 20px;
-        border-radius: 4px; background-color: #dedede; border: 1px solid #dedede; color: #333; cursor: pointer;
-      }
-      .top-4-p {
-        height: 35px; line-height: 33px; margin-left: 20px; padding: 0 20px;
-        border-radius: 4px; background-color: #0183fa; border: 1px solid #0183fa; color: #fff; cursor: pointer;
-      }
-    }
 
     .content-box {
       flex: 1;
@@ -510,7 +625,6 @@
     .number-stepper {
       display: inline-flex;
       align-items: center;
-      border: 1px solid #dcdfe6;
       border-radius: 4px;
       overflow: hidden;
       .stepper-btn {
@@ -538,6 +652,15 @@
       flex-wrap: wrap;
     }
 
+    .checkButton-p{
+      background-color: #0183fa;
+      color: #ffffff;
+      line-height:40px;
+      border-radius:4px;
+      text-align: center;
+      width:80px;
+      cursor: pointer;
+    }
     .selected-tag {
       display: inline-flex;
       align-items: center;
@@ -569,9 +692,12 @@
       border: 1px solid #e8e8e8;
       border-radius: 4px;
       overflow: hidden;
+      height:400px;
+      display: flex;
+      flex-direction: column;
       .scope-table-header {
         background: #f5f5f5;
-        padding: 10px 16px;
+        padding: 4px 16px;
         font-size: 13px;
         color: #555;
         border-bottom: 1px solid #e8e8e8;
@@ -580,8 +706,15 @@
 
     .switch-row {
       display: flex;
-      align-items: center;
-      flex-wrap: wrap;
+      /*align-items: center;*/
+      /*flex-wrap: wrap;*/
+      .el-switch{
+        margin-top:10px;
+      }
+
+      .tip-text{
+        margin-top:2px;
+      }
     }
 
     .anti-cheat-group {
@@ -602,7 +735,17 @@
     }
 
     .editor-box {
-      max-width: 760px;
+      max-width: 1030px;
+    }
+  }
+</style>
+<style lang="scss">
+  .examinationArrange-addPage-drawer{
+    .el-drawer__body{
+      padding:0;
+    }
+    .el-drawer__header{
+      margin:0;
     }
   }
 </style>

+ 13 - 23
src/views/safetyEducationExaminationNew/examManagement/examinationArrange/index.vue

@@ -96,7 +96,7 @@
           <el-table-column label="可考次数" prop="content" width="120" show-overflow-tooltip/>
           <el-table-column label="考试范围" prop="content" width="140" show-overflow-tooltip/>
           <el-table-column label="状态" prop="content" width="200" show-overflow-tooltip/>
-          <el-table-column label="操作" width="200" show-overflow-tooltip>
+          <el-table-column label="操作" width="220" show-overflow-tooltip>
             <template slot-scope="scope">
               <div class="table-button-box">
                 <p class="table-button-null"></p>
@@ -107,10 +107,10 @@
                    @click="tableButton(3,scope.row)"
                 >编辑</p>
                 <p class="table-button-p"
-                   @click="tableButton(2,scope.row)"
+                   @click="tableButton(4,scope.row)"
                 >复制</p>
                 <p class="table-button-p"
-                   @click="tableButton(4,scope.row)"
+                   @click="tableButton(5,scope.row)"
                 >删除</p>
                 <p class="table-button-null"></p>
               </div>
@@ -127,6 +127,7 @@
       </div>
     </div>
     <add-page :propsData="propsData" v-if="pageType === 2"></add-page>
+    <students-taking-make-up-exams :propsData="propsData" v-if="pageType === 3"></students-taking-make-up-exams>
   </div>
 </template>
 <script>
@@ -134,11 +135,13 @@
   //import { systemUserSelect } from "@/api/commonality/permission";
   //import { getInfo } from "@/api/basicsModules/index";
   import addPage from "./addPage.vue";
+  import studentsTakingMakeUpExams from "./studentsTakingMakeUpExams.vue";
   import { examElDisciplineTypeList,examElExamTypeList,systemDeptCurrentDept, } from "@/api/safetyEducationExaminationNew/index";
   export default {
     name: 'index',
     components: {
-     addPage
+      addPage,
+      studentsTakingMakeUpExams
     },
     data () {
       return {
@@ -166,7 +169,7 @@
         //时间数据
         dateRange:[],
         //列表数据
-        dataList:[],
+        dataList:[{}],
         //数据数量
         total:0,
         //组件传参
@@ -229,18 +232,19 @@
           this.$set(this,'pageType',2);
           this.$set(this,'propsData',{});
         }else if(type == 2){
-          //详情
-          this.$set(this,'pageType',2);
+          //补考
+          this.$set(this,'pageType',3);
           let obj = JSON.parse(JSON.stringify(row))
-          obj.showType = true;
           this.$set(this,'propsData',obj);
         }else if(type == 3){
           //编辑
           this.$set(this,'pageType',2);
           let obj = JSON.parse(JSON.stringify(row))
-          obj.showType = false;
           this.$set(this,'propsData',obj);
         }else if(type == 4){
+          //复制
+
+        }else if(type == 5){
           //删除
           this.$confirm('是否确认删除?', "警告", {
             confirmButtonText: "确定",
@@ -253,20 +257,6 @@
               self.getList();
             });
           }).catch(() => {});
-        }else if(type == 5){
-          //启用&停用
-          let text = row.state  ? "停用" : "启用";
-          this.$confirm('是否确认' + text + '?', "警告", {
-            confirmButtonText: "确定",
-            cancelButtonText: "取消",
-            type: "warning"
-          }).then(function() {
-          }).then(() => {
-            stateFunction({id:row.id,state:!row.state,}).then(response => {
-              self.msgSuccess(response.message)
-              self.getList();
-            });
-          }).catch(() => {});
         }else if(type == 6){
           //返回并刷新
           this.$set(this,'pageType',1);

+ 171 - 0
src/views/safetyEducationExaminationNew/examManagement/examinationArrange/selectCertificate.vue

@@ -0,0 +1,171 @@
+<!-- 选择证书 -->
+<template>
+  <div class="page-container selectCertificate-addPage">
+    <div class="page-form-title-box">
+      <el-form :model="queryParams" class="form-box" ref="queryForm"
+               :inline="true" style="width:100%;">
+        <el-form-item label="" prop="queryParamsData1">
+          <el-select v-model="queryParams.queryParamsData1" placeholder="是否共享" style="width: 120px">
+            <el-option label="共享" value="1"/>
+            <el-option label="不共享" value="2"/>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="" prop="queryParamsData2">
+          <el-select v-model="queryParams.queryParamsData2" placeholder="考试类型" style="width: 140px">
+            <el-option
+              v-for="dict in optionListA"
+              :key="dict.id"
+              :label="dict.examTypeName"
+              :value="dict.id"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="" prop="queryParamsData3">
+          <el-select v-model="queryParams.queryParamsData3" placeholder="颁发单位" style="width: 120px">
+            <el-option
+              v-for="dict in optionListB"
+              :key="dict.deptId"
+              :label="dict.deptName"
+              :value="dict.deptId"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="" prop="queryParamsData4">
+          <el-input
+            maxLength="30"
+            v-model="queryParams.queryParamsData4"
+            placeholder="搜索名称"
+            style="width: 140px"
+          />
+        </el-form-item>
+        <p class="page-inquire-common-style-button" @click="handleQuery">查询</p>
+        <p class="page-reset-common-style-button" @click="resetQuery">重置</p>
+      </el-form>
+    </div>
+    <div class="page-content-box">
+      <el-table class="table-box" v-loading="loading" border :data="dataList">
+        <el-table-column label="证书模板名称" prop="name"  show-overflow-tooltip/>
+        <el-table-column label="适用考试类型" prop="content" width="200" show-overflow-tooltip/>
+        <el-table-column label="是否共享" prop="content" width="200" show-overflow-tooltip>
+          <template slot-scope="scope">
+            <span style="padding:2px 10px;border:1px solid rgb(1, 131, 251);border-radius:4px;color: rgb(1, 131, 251);background-color:rgba(1, 131, 251,0.2)">共享</span>
+            <span style="padding:2px 10px;border:1px solid rgb(245, 154, 35);border-radius:4px;color: rgb(245, 154, 35);background-color:rgba(245, 154, 35,0.2)">不共享</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="颁发单位" prop="content" width="200" show-overflow-tooltip/>
+        <el-table-column label="更新人" prop="content" width="200" show-overflow-tooltip/>
+        <el-table-column label="操作" width="200" show-overflow-tooltip>
+          <template slot-scope="scope">
+            <div class="table-button-box">
+              <p class="table-button-null"></p>
+              <p class="table-button-p"
+                 @click="checkButton(scope.row)"
+              >选择</p>
+              <p class="table-button-null"></p>
+            </div>
+          </template>
+        </el-table-column>
+      </el-table>
+      <pagination :page-sizes="[20, 30, 40, 50]"
+                  v-show="total>0"
+                  :total="total"
+                  :page.sync="queryParams.page"
+                  :limit.sync="queryParams.pageSize"
+                  @pagination="getList"
+      />
+    </div>
+  </div>
+</template>
+
+<script>
+  import { examElExamTypeList,systemDeptCurrentDept, } from "@/api/safetyEducationExaminationNew/index";
+  export default {
+    name: 'addPage',
+    props: {
+      propsData: {}
+    },
+    data() {
+      return {
+        //页面遮罩
+        loading:false,
+        //下拉列表数据
+        optionListA:[],
+        optionListB:[],
+        //查询条件
+        queryParams:{
+          page:1,
+          pageSize:20,
+          queryParamsData1:null,
+          queryParamsData2 :null,
+          queryParamsData3 :null,
+          queryParamsData4 :'',
+        },
+        //列表数据
+        dataList:[{}],
+        //数据数量
+        total:0,
+      }
+    },
+    created() {
+
+    },
+    mounted() {
+      this.examElExamTypeList();
+      this.systemDeptCurrentDept();
+    },
+    methods: {
+      // 提交按钮
+      checkButton(row) {
+        this.$parent.$parent.drawerOffButton()
+      },
+      //查询按钮
+      handleQuery(){
+        this.$set(this.queryParams,'page',1);
+        this.getList();
+      },
+      //重置按钮
+      resetQuery(){
+        this.$set(this,'queryParams',{
+          page:1,
+          pageSize:20,
+          queryParamsData1:null,
+          queryParamsData2 :null,
+          queryParamsData3 :null,
+          queryParamsData4 :'',
+        });
+        this.getList();
+      },
+      //获取数据列表
+      getList(){
+        this.$set(this,'loading',true);
+        let obj = JSON.parse(JSON.stringify(this.queryParams))
+        getListFunction(obj).then(response => {
+          this.$set(this,'loading',false);
+          this.$set(this,'dataList',response.data.records);
+          this.$set(this,'total',response.data.total);
+        });
+      },
+      //查询全部考试类型
+      examElExamTypeList(){
+        examElExamTypeList({}).then(response => {
+          this.$set(this,'optionListA',response.data);
+        });
+      },
+      //学院列表
+      systemDeptCurrentDept(){
+        systemDeptCurrentDept({}).then(response => {
+          this.$set(this,'optionListB',response.data);
+        });
+      },
+    }
+  }
+</script>
+
+<style scoped lang="scss">
+  .selectCertificate-addPage {
+    flex:1;
+    display: flex;
+    flex-direction: column;
+    overflow: hidden;
+  }
+</style>

+ 208 - 0
src/views/safetyEducationExaminationNew/examManagement/examinationArrange/selectCourse.vue

@@ -0,0 +1,208 @@
+<!-- 选择课程 -->
+<template>
+  <div class="page-container selectCourse-addPage">
+    <div class="page-form-title-box">
+      <el-form :model="queryParams" class="form-box" ref="queryForm"
+               :inline="true" style="width:100%;">
+        <el-form-item label="" prop="queryParamsData1">
+          <el-select v-model="queryParams.queryParamsData1" placeholder="是否共享" style="width: 120px">
+            <el-option label="共享" value="1"/>
+            <el-option label="不共享" value="2"/>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="" prop="queryParamsData2">
+          <el-cascader
+            style="width: 180px"
+            v-model="queryParams.queryParamsData2"
+            :options="optionListC"
+            :props="{
+                emitPath:false,
+                checkStrictly: true,
+                value: 'id',
+                label: 'knowledgePointName',
+                children: 'children',
+              }"
+            :show-all-levels="false"
+            filterable
+            placeholder="知识点"
+          ></el-cascader>
+        </el-form-item>
+        <el-form-item label="" prop="queryParamsData3">
+          <el-select v-model="queryParams.queryParamsData3" placeholder="专业类别" style="width: 140px">
+            <el-option
+              v-for="dict in optionListD"
+              :key="dict.id"
+              :label="dict.disciplineName"
+              :value="dict.id"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="" prop="queryParamsData4">
+          <el-select v-model="queryParams.queryParamsData4" placeholder="来源" style="width: 120px">
+            <el-option
+              v-for="dict in optionListB"
+              :key="dict.value"
+              :label="dict.label"
+              :value="dict.value"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="" prop="queryParamsData5">
+          <el-input
+            maxLength="30"
+            v-model="queryParams.queryParamsData5"
+            placeholder="搜索名称"
+            style="width: 140px"
+          />
+        </el-form-item>
+        <p class="page-inquire-common-style-button" @click="handleQuery">查询</p>
+        <p class="page-reset-common-style-button" @click="resetQuery">重置</p>
+      </el-form>
+    </div>
+    <div class="page-content-box">
+      <el-table class="table-box" v-loading="loading" border :data="dataList">
+        <el-table-column label="课程名称" prop="name"  show-overflow-tooltip/>
+        <el-table-column label="知识点" prop="content" width="200" show-overflow-tooltip/>
+        <el-table-column label="专业类别" prop="content" width="200" show-overflow-tooltip/>
+        <el-table-column label="来源" prop="content" width="200" show-overflow-tooltip/>
+        <el-table-column label="是否共享" prop="content" width="200" show-overflow-tooltip>
+          <template slot-scope="scope">
+            <span style="padding:2px 10px;border:1px solid rgb(1, 131, 251);border-radius:4px;color: rgb(1, 131, 251);background-color:rgba(1, 131, 251,0.2)">共享</span>
+            <span style="padding:2px 10px;border:1px solid rgb(245, 154, 35);border-radius:4px;color: rgb(245, 154, 35);background-color:rgba(245, 154, 35,0.2)">不共享</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="课件总数" prop="content" width="200" show-overflow-tooltip/>
+        <el-table-column label="总时长" prop="content" width="200" show-overflow-tooltip/>
+        <el-table-column label="可得学时" prop="content" width="200" show-overflow-tooltip/>
+        <el-table-column label="操作" width="200" show-overflow-tooltip>
+          <template slot-scope="scope">
+            <div class="table-button-box">
+              <p class="table-button-null"></p>
+              <p class="table-button-p"
+                 @click="checkButton(scope.row)"
+              >选择</p>
+              <p class="table-button-null"></p>
+            </div>
+          </template>
+        </el-table-column>
+      </el-table>
+      <pagination :page-sizes="[20, 30, 40, 50]"
+                  v-show="total>0"
+                  :total="total"
+                  :page.sync="queryParams.page"
+                  :limit.sync="queryParams.pageSize"
+                  @pagination="getList"
+      />
+    </div>
+  </div>
+</template>
+
+<script>
+  import { examElDisciplineTypeList,examElKnowledgePointTreeList, } from "@/api/safetyEducationExaminationNew/index";
+  export default {
+    name: 'addPage',
+    props: {
+      propsData: {}
+    },
+    data() {
+      return {
+        //页面遮罩
+        loading:false,
+        //下拉列表数据
+        optionListB:[],
+        optionListC:[],
+        optionListD:[],
+        //查询条件
+        queryParams:{
+          page:1,
+          pageSize:20,
+          queryParamsData1:null,
+          queryParamsData2 :null,
+          queryParamsData3 :null,
+          queryParamsData4 :null,
+          queryParamsData5 :'',
+        },
+        //列表数据
+        dataList:[{}],
+        //数据数量
+        total:0,
+      }
+    },
+    created() {
+
+    },
+    mounted() {
+      this.examElKnowledgePointTreeList();
+      this.examElDisciplineTypeList();
+    },
+    methods: {
+      // 提交按钮
+      checkButton(row) {
+        this.$parent.$parent.drawerOffButton()
+      },
+      //查询按钮
+      handleQuery(){
+        this.$set(this.queryParams,'page',1);
+        this.getList();
+      },
+      //重置按钮
+      resetQuery(){
+        this.$set(this,'queryParams',{
+          page:1,
+          pageSize:20,
+          queryParamsData1:null,
+          queryParamsData2 :null,
+          queryParamsData3 :null,
+          queryParamsData4 :null,
+          queryParamsData5 :'',
+        });
+        this.getList();
+      },
+      //获取数据列表
+      getList(){
+        this.$set(this,'loading',true);
+        let obj = JSON.parse(JSON.stringify(this.queryParams))
+        getListFunction(obj).then(response => {
+          this.$set(this,'loading',false);
+          this.$set(this,'dataList',response.data.records);
+          this.$set(this,'total',response.data.total);
+        });
+      },
+      //知识点
+      examElKnowledgePointTreeList(){
+        examElKnowledgePointTreeList({}).then(response => {
+          const list = response.data || [];
+          this.formatTreeData(list);
+          this.$set(this, 'optionListC', list);
+        });
+      },
+      //查询全部专业类别
+      examElDisciplineTypeList(){
+        examElDisciplineTypeList({}).then(response => {
+          this.$set(this,'optionListD',response.data);
+        });
+      },
+      formatTreeData(data) {
+        data.forEach(item => {
+          // 如果 children 存在,且是数组,且长度为 0(即 children: [])
+          if (Array.isArray(item.children) && item.children.length === 0) {
+            // 将 children 设为 undefined(或直接 delete item.children;)
+            item.children = undefined;
+          } else if (item.children && item.children.length > 0) {
+            // 如果还有下一级,继续递归处理
+            this.formatTreeData(item.children);
+          }
+        });
+      },
+    }
+  }
+</script>
+
+<style scoped lang="scss">
+  .selectCourse-addPage {
+    flex:1;
+    display: flex;
+    flex-direction: column;
+    overflow: hidden;
+  }
+</style>

+ 194 - 0
src/views/safetyEducationExaminationNew/examManagement/examinationArrange/selectScope.vue

@@ -0,0 +1,194 @@
+<!-- 选择范围 -->
+<template>
+  <div class="page-container selectScope-addPage">
+    <div class="page-form-title-box">
+      <el-form :model="queryParams" class="form-box" ref="queryForm"
+               :inline="true" style="width:100%;">
+        <el-form-item label="" prop="queryParamsData1">
+          <el-select v-model="queryParams.queryParamsData1" placeholder="是否共享" style="width: 120px">
+            <el-option
+              v-for="dict in optionListA"
+              :key="dict.value"
+              :label="dict.label"
+              :value="dict.value"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="" prop="queryParamsData2">
+          <el-select v-model="queryParams.queryParamsData2" placeholder="学院单位" style="width: 140px">
+            <el-option
+              v-for="dict in optionListB"
+              :key="dict.deptId"
+              :label="dict.deptName"
+              :value="dict.deptId"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="" prop="queryParamsData3">
+          <el-select v-model="queryParams.queryParamsData3" multiple placeholder="用户类别" style="width: 120px">
+            <el-option
+              v-for="dict in optionListC"
+              :key="dict.value"
+              :label="dict.label"
+              :value="dict.value"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="" prop="queryParamsData4">
+          <el-select v-model="queryParams.queryParamsData4" multiple placeholder="学年" style="width: 140px">
+            <el-option
+              v-for="dict in optionListD"
+              :key="dict.value"
+              :label="dict.label"
+              :value="dict.value"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="" prop="queryParamsData5">
+          <el-select v-model="queryParams.queryParamsData5" placeholder="专业" style="width: 140px">
+            <el-option
+              v-for="dict in optionListE"
+              :key="dict.id"
+              :label="dict.disciplineName"
+              :value="dict.id"
+            />
+          </el-select>
+        </el-form-item>
+        <p class="page-inquire-common-style-button" @click="handleQuery">查询</p>
+        <p class="page-reset-common-style-button" @click="resetQuery">重置</p>
+        <p class="page-submit-common-style-button"
+           style="float: right;"
+           @click="checkButton()"
+        >确定</p>
+      </el-form>
+    </div>
+    <div class="page-content-box">
+      <el-table class="table-box" v-loading="loading" border :data="dataList"
+                ref="multipleTable" :row-key="getRowKey" @selection-change="handleSelectionChange">
+        <el-table-column type="selection" width="55" :reserve-selection="true"></el-table-column>
+        <el-table-column label="学院单位" prop="name"  show-overflow-tooltip/>
+        <el-table-column label="角色" prop="content" width="200" show-overflow-tooltip/>
+        <el-table-column label="专业" prop="content" width="200" show-overflow-tooltip/>
+        <el-table-column label="学年" prop="content" width="200" show-overflow-tooltip/>
+      </el-table>
+      <pagination :page-sizes="[20, 30, 40, 50]"
+                  v-show="total>0"
+                  :total="total"
+                  :page.sync="queryParams.page"
+                  :limit.sync="queryParams.pageSize"
+                  @pagination="getList"
+      />
+    </div>
+  </div>
+</template>
+
+<script>
+  import { examElDisciplineTypeList,systemDeptCurrentDept, } from "@/api/safetyEducationExaminationNew/index";
+  export default {
+    name: 'addPage',
+    props: {
+      propsData: {}
+    },
+    data() {
+      return {
+        //页面遮罩
+        loading:false,
+        //下拉列表数据
+        optionListA:[],
+        optionListB:[],
+        optionListC:[],
+        optionListD:[],
+        optionListE:[],
+        //查询条件
+        queryParams:{
+          page:1,
+          pageSize:20,
+          queryParamsData1:null,
+          queryParamsData2 :null,
+          queryParamsData3 :null,
+          queryParamsData4 :null,
+          queryParamsData5 :null,
+        },
+        //列表数据
+        dataList:[{}],
+        //数据数量
+        total:0,
+        //存储所有被勾选的数据
+        multipleSelection: [],
+      }
+    },
+    created() {
+
+    },
+    mounted() {
+      this.examElDisciplineTypeList();
+      this.systemDeptCurrentDept();
+    },
+    methods: {
+      // 提交按钮
+      checkButton() {
+        if(!this.multipleSelection[0]){
+          this.msgError('请勾选范围')
+          return
+        }
+        this.$parent.$parent.drawerOffButton()
+      },
+      //查询按钮
+      handleQuery(){
+        this.$set(this.queryParams,'page',1);
+        this.getList();
+      },
+      //重置按钮
+      resetQuery(){
+        this.$set(this,'queryParams',{
+          page:1,
+          pageSize:20,
+          queryParamsData1:null,
+          queryParamsData2 :null,
+          queryParamsData3 :null,
+          queryParamsData4 :null,
+          queryParamsData5 :null,
+        });
+        this.getList();
+      },
+      //获取数据列表
+      getList(){
+        this.$set(this,'loading',true);
+        let obj = JSON.parse(JSON.stringify(this.queryParams))
+        getListFunction(obj).then(response => {
+          this.$set(this,'loading',false);
+          this.$set(this,'dataList',response.data.records);
+          this.$set(this,'total',response.data.total);
+        });
+      },
+      //查询全部专业类别
+      examElDisciplineTypeList(){
+        examElDisciplineTypeList({}).then(response => {
+          this.$set(this,'optionListE',response.data);
+        });
+      },
+      //学院列表
+      systemDeptCurrentDept(){
+        systemDeptCurrentDept({}).then(response => {
+          this.$set(this,'optionListB',response.data);
+        });
+      },
+      getRowKey(row) {
+        return row.id;
+      },
+      handleSelectionChange(val) {
+        this.multipleSelection = val;
+        console.log('当前所有选中的数据:', this.multipleSelection);
+      },
+    }
+  }
+</script>
+
+<style scoped lang="scss">
+  .selectScope-addPage {
+    flex:1;
+    display: flex;
+    flex-direction: column;
+    overflow: hidden;
+  }
+</style>

+ 214 - 0
src/views/safetyEducationExaminationNew/examManagement/examinationArrange/selectTestPaper.vue

@@ -0,0 +1,214 @@
+<!-- 选择试卷 -->
+<template>
+  <div class="page-container selectTestPaper-addPage">
+    <div class="page-form-title-box">
+      <el-form :model="queryParams" class="form-box" ref="queryForm"
+               :inline="true" style="width:100%;">
+        <el-form-item label="" prop="queryParamsData1">
+          <el-select v-model="queryParams.queryParamsData1" placeholder="是否共享" style="width: 120px">
+            <el-option label="共享" value="1"/>
+            <el-option label="不共享" value="2"/>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="" prop="queryParamsData2">
+          <el-cascader
+            style="width: 180px"
+            v-model="queryParams.queryParamsData2"
+            :options="optionListC"
+            :props="{
+                emitPath:false,
+                checkStrictly: true,
+                value: 'id',
+                label: 'knowledgePointName',
+                children: 'children',
+              }"
+            :show-all-levels="false"
+            filterable
+            placeholder="知识点"
+          ></el-cascader>
+        </el-form-item>
+        <el-form-item label="" prop="queryParamsData3">
+          <el-select v-model="queryParams.queryParamsData3" placeholder="专业类别" style="width: 140px">
+            <el-option
+              v-for="dict in optionListD"
+              :key="dict.id"
+              :label="dict.disciplineName"
+              :value="dict.id"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="" prop="queryParamsData4">
+          <el-select v-model="queryParams.queryParamsData4" placeholder="来源" style="width: 120px">
+            <el-option
+              v-for="dict in optionListB"
+              :key="dict.value"
+              :label="dict.label"
+              :value="dict.value"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="" prop="queryParamsData5">
+          <el-select v-model="queryParams.queryParamsData5" placeholder="组卷方式" style="width: 140px">
+            <el-option label="固定组卷" value="1"/>
+            <el-option label="随机组卷" value="2"/>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="" prop="queryParamsData6">
+          <el-input
+            maxLength="30"
+            v-model="queryParams.queryParamsData6"
+            placeholder="搜索名称"
+            style="width: 140px"
+          />
+        </el-form-item>
+        <p class="page-inquire-common-style-button" @click="handleQuery">查询</p>
+        <p class="page-reset-common-style-button" @click="resetQuery">重置</p>
+      </el-form>
+    </div>
+    <div class="page-content-box">
+      <el-table class="table-box" v-loading="loading" border :data="dataList">
+        <el-table-column label="试卷名称" prop="name"  show-overflow-tooltip/>
+        <el-table-column label="组卷方式" prop="content" width="200" show-overflow-tooltip/>
+        <el-table-column label="知识点" prop="content" width="200" show-overflow-tooltip/>
+        <el-table-column label="专业类别" prop="content" width="200" show-overflow-tooltip/>
+        <el-table-column label="是否共享" prop="content" width="200" show-overflow-tooltip>
+          <template slot-scope="scope">
+            <span style="padding:2px 10px;border:1px solid rgb(1, 131, 251);border-radius:4px;color: rgb(1, 131, 251);background-color:rgba(1, 131, 251,0.2)">共享</span>
+            <span style="padding:2px 10px;border:1px solid rgb(245, 154, 35);border-radius:4px;color: rgb(245, 154, 35);background-color:rgba(245, 154, 35,0.2)">不共享</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="来源" prop="content" width="200" show-overflow-tooltip/>
+        <el-table-column label="操作" width="200" show-overflow-tooltip>
+          <template slot-scope="scope">
+            <div class="table-button-box">
+              <p class="table-button-null"></p>
+              <p class="table-button-p"
+                 @click="checkButton(scope.row)"
+              >选择</p>
+              <p class="table-button-null"></p>
+            </div>
+          </template>
+        </el-table-column>
+      </el-table>
+      <pagination :page-sizes="[20, 30, 40, 50]"
+                  v-show="total>0"
+                  :total="total"
+                  :page.sync="queryParams.page"
+                  :limit.sync="queryParams.pageSize"
+                  @pagination="getList"
+      />
+    </div>
+  </div>
+</template>
+
+<script>
+  import { examElDisciplineTypeList,examElKnowledgePointTreeList, } from "@/api/safetyEducationExaminationNew/index";
+  export default {
+    name: 'addPage',
+    props: {
+      propsData: {}
+    },
+    data() {
+      return {
+        //页面遮罩
+        loading:false,
+        //下拉列表数据
+        optionListB:[],
+        optionListC:[],
+        optionListD:[],
+        //查询条件
+        queryParams:{
+          page:1,
+          pageSize:20,
+          queryParamsData1:null,
+          queryParamsData2 :null,
+          queryParamsData3 :null,
+          queryParamsData4 :null,
+          queryParamsData5 :null,
+          queryParamsData6 :'',
+        },
+        //列表数据
+        dataList:[{}],
+        //数据数量
+        total:0,
+      }
+    },
+    created() {
+
+    },
+    mounted() {
+      this.examElKnowledgePointTreeList();
+      this.examElDisciplineTypeList();
+    },
+    methods: {
+      // 提交按钮
+      checkButton(row) {
+        this.$parent.$parent.drawerOffButton()
+      },
+      //查询按钮
+      handleQuery(){
+        this.$set(this.queryParams,'page',1);
+        this.getList();
+      },
+      //重置按钮
+      resetQuery(){
+        this.$set(this,'queryParams',{
+          page:1,
+          pageSize:20,
+          queryParamsData1:null,
+          queryParamsData2 :null,
+          queryParamsData3 :null,
+          queryParamsData4 :null,
+          queryParamsData5 :null,
+          queryParamsData6 :'',
+        });
+        this.getList();
+      },
+      //获取数据列表
+      getList(){
+        this.$set(this,'loading',true);
+        let obj = JSON.parse(JSON.stringify(this.queryParams))
+        getListFunction(obj).then(response => {
+          this.$set(this,'loading',false);
+          this.$set(this,'dataList',response.data.records);
+          this.$set(this,'total',response.data.total);
+        });
+      },
+      //知识点
+      examElKnowledgePointTreeList(){
+        examElKnowledgePointTreeList({}).then(response => {
+          const list = response.data || [];
+          this.formatTreeData(list);
+          this.$set(this, 'optionListC', list);
+        });
+      },
+      //查询全部专业类别
+      examElDisciplineTypeList(){
+        examElDisciplineTypeList({}).then(response => {
+          this.$set(this,'optionListD',response.data);
+        });
+      },
+      formatTreeData(data) {
+        data.forEach(item => {
+          // 如果 children 存在,且是数组,且长度为 0(即 children: [])
+          if (Array.isArray(item.children) && item.children.length === 0) {
+            // 将 children 设为 undefined(或直接 delete item.children;)
+            item.children = undefined;
+          } else if (item.children && item.children.length > 0) {
+            // 如果还有下一级,继续递归处理
+            this.formatTreeData(item.children);
+          }
+        });
+      },
+    }
+  }
+</script>
+
+<style scoped lang="scss">
+  .selectTestPaper-addPage {
+    flex:1;
+    display: flex;
+    flex-direction: column;
+    overflow: hidden;
+  }
+</style>

+ 194 - 0
src/views/safetyEducationExaminationNew/examManagement/examinationArrange/selectUser.vue

@@ -0,0 +1,194 @@
+<!-- 选择用户 -->
+<template>
+  <div class="page-container selectUser-addPage">
+    <div class="page-form-title-box">
+      <el-form :model="queryParams" class="form-box" ref="queryForm"
+               :inline="true" style="width:100%;">
+        <el-form-item label="" prop="queryParamsData1">
+          <el-select v-model="queryParams.queryParamsData1" multiple placeholder="学年" style="width: 120px">
+            <el-option
+              v-for="dict in optionListA"
+              :key="dict.value"
+              :label="dict.label"
+              :value="dict.value"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="" prop="queryParamsData2">
+          <el-select v-model="queryParams.queryParamsData2" placeholder="学院单位" style="width: 140px">
+            <el-option
+              v-for="dict in optionListB"
+              :key="dict.deptId"
+              :label="dict.deptName"
+              :value="dict.deptId"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="" prop="queryParamsData3">
+          <el-select v-model="queryParams.queryParamsData3" multiple placeholder="用户类别" style="width: 120px">
+            <el-option
+              v-for="dict in optionListC"
+              :key="dict.value"
+              :label="dict.label"
+              :value="dict.value"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="" prop="queryParamsData5">
+          <el-select v-model="queryParams.queryParamsData5" placeholder="专业" style="width: 140px">
+            <el-option
+              v-for="dict in optionListE"
+              :key="dict.id"
+              :label="dict.disciplineName"
+              :value="dict.id"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="" prop="queryParamsData6">
+          <el-input
+            maxLength="30"
+            v-model="queryParams.queryParamsData6"
+            placeholder="姓名/学工号"
+            style="width: 140px"
+          />
+        </el-form-item>
+        <p class="page-inquire-common-style-button" @click="handleQuery">查询</p>
+        <p class="page-reset-common-style-button" @click="resetQuery">重置</p>
+        <p class="page-submit-common-style-button"
+           style="float: right;"
+           @click="checkButton()"
+        >确定</p>
+      </el-form>
+    </div>
+    <div class="page-content-box">
+      <el-table class="table-box" v-loading="loading" border :data="dataList"
+                ref="multipleTable" :row-key="getRowKey" @selection-change="handleSelectionChange">
+        <el-table-column type="selection" width="55" :reserve-selection="true"></el-table-column>
+        <el-table-column label="姓名" prop="name"  show-overflow-tooltip/>
+        <el-table-column label="学工号" prop="content" width="200" show-overflow-tooltip/>
+        <el-table-column label="专业" prop="content" width="200" show-overflow-tooltip/>
+        <el-table-column label="学年" prop="content" width="200" show-overflow-tooltip/>
+        <el-table-column label="学院单位" prop="content" width="200" show-overflow-tooltip/>
+      </el-table>
+      <pagination :page-sizes="[20, 30, 40, 50]"
+                  v-show="total>0"
+                  :total="total"
+                  :page.sync="queryParams.page"
+                  :limit.sync="queryParams.pageSize"
+                  @pagination="getList"
+      />
+    </div>
+  </div>
+</template>
+
+<script>
+  import { examElDisciplineTypeList,systemDeptCurrentDept, } from "@/api/safetyEducationExaminationNew/index";
+  export default {
+    name: 'addPage',
+    props: {
+      propsData: {}
+    },
+    data() {
+      return {
+        //页面遮罩
+        loading:false,
+        //下拉列表数据
+        optionListA:[],
+        optionListB:[],
+        optionListC:[],
+        optionListD:[],
+        optionListE:[],
+        //查询条件
+        queryParams:{
+          page:1,
+          pageSize:20,
+          queryParamsData1:null,
+          queryParamsData2 :null,
+          queryParamsData3 :null,
+          queryParamsData4 :null,
+          queryParamsData5 :null,
+          queryParamsData6 :'',
+        },
+        //列表数据
+        dataList:[{}],
+        //数据数量
+        total:0,
+        //存储所有被勾选的数据
+        multipleSelection: [],
+      }
+    },
+    created() {
+
+    },
+    mounted() {
+      this.examElDisciplineTypeList();
+      this.systemDeptCurrentDept();
+    },
+    methods: {
+      // 提交按钮
+      checkButton() {
+        if(!this.multipleSelection[0]){
+          this.msgError('请勾选用户')
+          return
+        }
+        this.$parent.$parent.drawerOffButton()
+      },
+      //查询按钮
+      handleQuery(){
+        this.$set(this.queryParams,'page',1);
+        this.getList();
+      },
+      //重置按钮
+      resetQuery(){
+        this.$set(this,'queryParams',{
+          page:1,
+          pageSize:20,
+          queryParamsData1:null,
+          queryParamsData2 :null,
+          queryParamsData3 :null,
+          queryParamsData4 :null,
+          queryParamsData5 :null,
+        });
+        this.getList();
+      },
+      //获取数据列表
+      getList(){
+        this.$set(this,'loading',true);
+        let obj = JSON.parse(JSON.stringify(this.queryParams))
+        getListFunction(obj).then(response => {
+          this.$set(this,'loading',false);
+          this.$set(this,'dataList',response.data.records);
+          this.$set(this,'total',response.data.total);
+        });
+      },
+      //查询全部专业类别
+      examElDisciplineTypeList(){
+        examElDisciplineTypeList({}).then(response => {
+          this.$set(this,'optionListE',response.data);
+        });
+      },
+      //学院列表
+      systemDeptCurrentDept(){
+        systemDeptCurrentDept({}).then(response => {
+          this.$set(this,'optionListB',response.data);
+        });
+      },
+      getRowKey(row) {
+        return row.id;
+      },
+      handleSelectionChange(val) {
+        this.multipleSelection = val;
+        console.log('当前所有选中的数据:', this.multipleSelection);
+      },
+    }
+  }
+</script>
+
+<style scoped lang="scss">
+  .selectUser-addPage {
+    flex:1;
+    display: flex;
+    flex-direction: column;
+    overflow: hidden;
+  }
+</style>

+ 373 - 0
src/views/safetyEducationExaminationNew/examManagement/examinationArrange/studentsTakingMakeUpExams.vue

@@ -0,0 +1,373 @@
+<!-- 补考人员 -->
+<template>
+  <div class="page-container studentsTakingMakeUpExams-addPage">
+    <div class="page-container-top-max-big-box">
+      <p class="top-1-p">补考安排</p>
+      <p class="top-2-p" @click="backPage()">返回</p>
+      <p class="top-4-p" @click="submitButton()">保存</p>
+    </div>
+    <div class="content-box">
+      <div class="max-add-form-box">
+        <el-form class="add-form-box" :model="newData" ref="form" label-width="140px" :rules="rules">
+          <div class="form-flex-box">
+            <el-form-item label="复用原试卷" prop="data1">
+              <el-switch
+                :active-value="true"
+                :inactive-value="false"
+                v-model="newData.data1"
+                active-color="#0183FB"
+                inactive-color="#AAAAAA"
+              >
+              </el-switch>
+            </el-form-item>
+            <p class="checkButton-p" v-if="!newData.data1" style="margin-left:20px;" @click="openPaperDrawer">选择试卷</p>
+            <div class="selected-tag" v-if="newData.paperName && !newData.data1">
+              <span>{{ newData.paperName }}</span>
+              <i class="el-icon-close" @click="newData.paperId = null; newData.paperName = ''"></i>
+            </div>
+          </div>
+          <div class="form-flex-box">
+            <el-form-item label="补考时间" prop="dateRange">
+              <el-date-picker
+                :clearable="false"
+                v-model="newData.dateRange"
+                size="small"
+                style="width: 220px"
+                value-format="yyyy-MM-dd"
+                type="daterange"
+                range-separator="-"
+                start-placeholder="开始日期"
+                end-placeholder="结束日期"
+              ></el-date-picker>
+            </el-form-item>
+          </div>
+          <div class="form-flex-box">
+            <el-form-item label="可补考次数" prop="data2">
+              <el-input-number
+                class="form-input-number"
+                style="width:200px"
+                :min="1"
+                :max="9999"
+                :step="1" :precision="0" step-strictly
+                v-model="newData.data2"
+              >
+              </el-input-number>
+            </el-form-item>
+          </div>
+        </el-form>
+      </div>
+      <div class="page-form-title-box">
+        <el-form :model="queryParams" class="form-box" ref="queryForm"
+                 :inline="true" style="width:100%;"
+        >
+          <el-form-item label="" prop="queryParamsData1">
+            <el-select v-model="queryParams.queryParamsData1" placeholder="学院单位" style="width: 140px">
+              <el-option
+                v-for="dict in optionListA"
+                :key="dict.deptId"
+                :label="dict.deptName"
+                :value="dict.deptId"
+              />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="" prop="queryParamsData2">
+            <el-select v-model="queryParams.queryParamsData2" multiple placeholder="学年" style="width: 140px">
+              <el-option
+                v-for="dict in optionListB"
+                :key="dict.value"
+                :label="dict.label"
+                :value="dict.value"
+              />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="" prop="queryParamsData3">
+            <el-select v-model="queryParams.queryParamsData3" placeholder="专业" style="width: 140px">
+              <el-option
+                v-for="dict in optionListC"
+                :key="dict.id"
+                :label="dict.disciplineName"
+                :value="dict.id"
+              />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="" prop="queryParamsData4">
+            <el-input
+              maxLength="30"
+              v-model="queryParams.queryParamsData4"
+              placeholder="姓名/学工号"
+              style="width: 140px"
+            />
+          </el-form-item>
+          <el-form-item style="float: right;">
+            <export-component :exportConfig="exportConfig"></export-component>
+          </el-form-item>
+          <p class="page-inquire-common-style-button" @click="handleQuery">查询</p>
+          <p class="page-reset-common-style-button" @click="resetQuery">重置</p>
+        </el-form>
+      </div>
+      <div class="page-content-box">
+        <el-table class="table-box" v-loading="loading" border :data="dataList">
+          <el-table-column label="姓名" prop="name" show-overflow-tooltip/>
+          <el-table-column label="学工号" prop="content" width="140" show-overflow-tooltip/>
+          <el-table-column label="专业" prop="content" width="120" show-overflow-tooltip/>
+          <el-table-column label="学年" prop="content" width="100" show-overflow-tooltip/>
+          <el-table-column label="学院单位" prop="content" width="200" show-overflow-tooltip/>
+          <el-table-column label="交卷时间" prop="content" width="140" show-overflow-tooltip>
+            <template slot-scope="scope">
+              <span>{{ parseTime(scope.row.createTime,'{y}-{m}-{d} {h}:{i}:{s}') }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column label="用时" prop="content" width="100" show-overflow-tooltip/>
+          <el-table-column label="成绩" prop="content" width="100" show-overflow-tooltip/>
+          <el-table-column label="结果" prop="content" width="100" show-overflow-tooltip/>
+          <el-table-column label="操作" width="200" show-overflow-tooltip>
+            <template slot-scope="scope">
+              <div class="table-button-box">
+                <p class="table-button-null"></p>
+                <p class="table-button-p"
+                   @click="lockExam(scope.row)"
+                >查看答卷</p>
+                <p class="table-button-null"></p>
+              </div>
+            </template>
+          </el-table-column>
+        </el-table>
+        <pagination :page-sizes="[20, 30, 40, 50]"
+                    v-show="total>0"
+                    :total="total"
+                    :page.sync="queryParams.page"
+                    :limit.sync="queryParams.pageSize"
+                    @pagination="getList"
+        />
+      </div>
+    </div>
+    <el-drawer
+      size="100%"
+      custom-class="examinationArrange-addPage-drawer"
+      :append-to-body="true"
+      :title="drawerTitle"
+      ref="drawer"
+      :visible.sync="drawer"
+      direction="rtl"
+    >
+      <!--选择试卷/选择试题-->
+      <selectTestPaper v-if="drawerType === 1"></selectTestPaper>
+      <examDetails v-if="drawerType === 2"></examDetails>
+    </el-drawer>
+  </div>
+</template>
+
+<script>
+  import exportComponent from '@/components/exportComponent/exportComponent.vue'
+  import examDetails from '@/views/safetyEducationExaminationNew/components/examDetails.vue'
+  import selectTestPaper from './selectTestPaper.vue'
+  import { examElDisciplineTypeList, systemDeptCurrentDept } from '@/api/safetyEducationExaminationNew/index'
+
+  export default {
+    name: 'addPage',
+    components: {
+      exportComponent,
+      selectTestPaper,
+      examDetails
+    },
+    props: {
+      propsData: {}
+    },
+    data() {
+      return {
+        //页面遮罩
+        loading: false,
+        newData: {
+          data1: true,
+          data2: 1,
+          paperName: '试卷名称',
+          paperId: '2',
+          dateRange: [],
+        },
+        rules:{
+          data1: [
+            { required: true, message: "是否复用原试卷", trigger: "blur" },
+            { required: true, message: "是否复用原试卷", validator: this.spaceJudgment, trigger: "blur" }
+          ],
+          data2: [
+            { required: true, message: "请选择可补考次数", trigger: "blur" },
+            { required: true, message: "请选择可补考次数", validator: this.spaceJudgment, trigger: "blur" }
+          ],
+          dateRange: [
+            { required: true, message: "请选择补考时间", trigger: "blur" },
+            { required: true, message: "请选择补考时间", validator: this.spaceJudgment, trigger: "blur" }
+          ],
+        },
+        //下拉列表数据
+        optionListA: [],
+        optionListB: [],
+        optionListC: [],
+        //查询条件
+        queryParams: {
+          page: 1,
+          pageSize: 20,
+          queryParamsData1: null,
+          queryParamsData2: null,
+          queryParamsData3: null,
+          queryParamsData4: ''
+        },
+        //列表数据
+        dataList: [{}],
+        //数据数量
+        total: 0,
+        exportConfig: {
+          api: 'system/user/student/export',              //导出接口地址
+          ids: '',                                        //勾选导出,勾选的IDS
+          fileName: '导出文件名',                         //导出文件的命名
+          customKey: null,                                //自定义参数KEY(非必填)
+          customValue: null,                               //自定义参数VALUE(非必填)
+          screenData: {}                                  //导出筛选数据(非必要-如有该参数组件会显示导出筛选数据按钮)
+        },
+        //弹窗相关
+        drawerTitle: '',
+        drawerType: '',
+        drawer: false
+      }
+    },
+    created() {
+
+    },
+    mounted() {
+      this.examElDisciplineTypeList()
+      this.systemDeptCurrentDept()
+    },
+    methods: {
+      //弹窗返回
+      drawerReturnButton(){
+        this.$refs.drawer.closeDrawer();
+      },
+      //弹窗确定
+      drawerConfirmButton(){
+        this.$refs.drawer.closeDrawer();
+      },
+      openPaperDrawer() {
+        // TODO: 打开选择试卷弹窗
+        this.$set(this, 'drawerTitle', '选择试卷')
+        this.$set(this, 'drawerType', 1)
+        this.$set(this, 'drawer', true)
+      },
+      // 返回按钮
+      backPage() {
+        this.$parent.tableButton(6)
+      },
+      // 提交按钮
+      submitButton() {
+
+      },
+      lockExam(){
+        this.$set(this, 'drawerTitle', '考试详情')
+        this.$set(this, 'drawerType', 2)
+        this.$set(this, 'drawer', true)
+      },
+      //查询按钮
+      handleQuery() {
+        this.$set(this.queryParams, 'page', 1)
+        this.getList()
+      },
+      //重置按钮
+      resetQuery() {
+        this.$set(this, 'queryParams', {
+          page: 1,
+          pageSize: 20,
+          queryParamsData1: null,
+          queryParamsData2: null,
+          queryParamsData3: null,
+          queryParamsData4: ''
+        })
+        this.getList()
+      },
+      //获取数据列表
+      getList() {
+        this.$set(this, 'loading', true)
+        let obj = JSON.parse(JSON.stringify(this.queryParams))
+        getListFunction(obj).then(response => {
+          this.$set(this, 'loading', false)
+          this.$set(this, 'dataList', response.data.records)
+          this.$set(this, 'total', response.data.total)
+        })
+      },
+      //查询全部专业类别
+      examElDisciplineTypeList() {
+        examElDisciplineTypeList({}).then(response => {
+          this.$set(this, 'optionListC', response.data)
+        })
+      },
+      //学院列表
+      systemDeptCurrentDept() {
+        systemDeptCurrentDept({}).then(response => {
+          this.$set(this, 'optionListA', response.data)
+        })
+      }
+    }
+  }
+</script>
+
+<style scoped lang="scss">
+  .studentsTakingMakeUpExams-addPage {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+    overflow: hidden;
+
+    .content-box {
+      flex: 1;
+      display: flex;
+      flex-direction: column;
+      overflow: hidden;
+
+      .max-add-form-box {
+        padding: 20px 0 0 0;
+        border-bottom:1px solid #dedede;
+
+        .form-flex-box {
+          display: flex;
+        }
+
+        .checkButton-p {
+          height: 40px;
+          background-color: #0183fa;
+          color: #ffffff;
+          line-height: 40px;
+          border-radius: 4px;
+          text-align: center;
+          width: 80px;
+          cursor: pointer;
+        }
+
+        .selected-tag {
+          height: 40px;
+          margin-left: 20px;
+          display: inline-flex;
+          align-items: center;
+          background: #e6f4ff;
+          border: 1px solid #91caff;
+          border-radius: 4px;
+          padding: 6px 12px;
+          color: #0183fa;
+          font-size: 13px;
+          min-width: 200px;
+          max-width: 500px;
+
+          span {
+            line-height: 28px;
+            flex: 1;
+          }
+
+          i {
+            margin-left: 8px;
+            cursor: pointer;
+
+            &:hover {
+              color: #e74c3c;
+            }
+          }
+        }
+      }
+    }
+  }
+</style>

+ 38 - 56
src/views/safetyEducationExaminationNew/examManagement/examinationResult/index.vue

@@ -6,7 +6,7 @@
         <el-form :model="queryParams" class="form-box" ref="queryForm"
                  :inline="true" style="width:100%;">
           <el-form-item label="" prop="queryParamsData1">
-            <el-select v-model="queryParams.queryParamsData1" placeholder="学年" style="width: 140px">
+            <el-select v-model="queryParams.queryParamsData1" multiple placeholder="学年" style="width: 140px">
               <el-option
                 v-for="dict in optionListA"
                 :key="dict.id"
@@ -67,18 +67,13 @@
           <el-form-item style="float: right;">
             <export-component :exportConfig="exportConfig"></export-component>
           </el-form-item>
-          <p class="page-submit-common-style-button"
-             style="float: right;"
-             @click="tableButton(1)"
-             v-hasPermiRouter="['demo:demo:add']"
-          >新增</p>
         </el-form>
       </div>
       <div class="page-content-box">
         <el-table class="table-box" v-loading="loading" border :data="dataList">
           <el-table-column label="考试名称" prop="name"  show-overflow-tooltip/>
           <el-table-column label="考试类型" prop="content" width="200" show-overflow-tooltip/>
-          <el-table-column label="创建时间" prop="createTime" width="200" show-overflow-tooltip>
+          <el-table-column label="考试时间" prop="createTime" width="200" show-overflow-tooltip>
             <template slot-scope="scope">
               <span>{{ parseTime(scope.row.createTime,"{y}-{m}-{d} {h}:{i}") }}至{{ parseTime(scope.row.createTime,"{y}-{m}-{d} {h}:{i}") }}</span>
             </template>
@@ -92,10 +87,10 @@
               <div class="table-button-box">
                 <p class="table-button-null"></p>
                 <p class="table-button-p"
-                   @click="tableButton(2,scope.row)"
+                   @click="tableButton(1,scope.row)"
                 >查看成绩</p>
                 <p class="table-button-p"
-                   @click="tableButton(3,scope.row)"
+                   @click="tableButton(2,scope.row)"
                 >学习情况</p>
                 <p class="table-button-null"></p>
               </div>
@@ -111,6 +106,17 @@
         />
       </div>
     </div>
+    <el-drawer
+      size="100%"
+      custom-class="examinationArrange-addPage-drawer"
+      :append-to-body="true"
+      :title="drawerTitle"
+      ref="drawer"
+      :visible.sync="drawer"
+      direction="rtl"
+    >
+      <viewGrades v-if="drawerType==1"></viewGrades>
+    </el-drawer>
     <!--<add-page :propsData="propsData" v-if="pageType === 2"></add-page>-->
   </div>
 </template>
@@ -118,13 +124,14 @@
   //import { getDicts } from "@/api/commonality/noPermission";
   //import { systemUserSelect } from "@/api/commonality/permission";
   //import { getInfo } from "@/api/basicsModules/index";
-  //import addPage from "./addPage.vue";
+  import viewGrades from "./viewGrades.vue";
   import { examElExamTypeList,systemDeptCurrentDept, } from "@/api/safetyEducationExaminationNew/index";
   import exportComponent from "@/components/exportComponent/exportComponent.vue";
   export default {
     name: 'index',
     components: {
       exportComponent,
+      viewGrades,
     },
     data () {
       return {
@@ -150,7 +157,7 @@
         //时间数据
         dateRange:[],
         //列表数据
-        dataList:[],
+        dataList:[{}],
         //数据数量
         total:0,
         //组件传参
@@ -164,6 +171,10 @@
           customValue:null,                               //自定义参数VALUE(非必填)
           screenData:{}                                  //导出筛选数据(非必要-如有该参数组件会显示导出筛选数据按钮)
         },
+        //弹窗相关
+        drawerTitle: '',
+        drawerType: '',
+        drawer: false
       }
     },
     created () {
@@ -175,6 +186,14 @@
       this.systemDeptCurrentDept();
     },
     methods: {
+      //弹窗返回
+      drawerReturnButton(){
+        this.$refs.drawer.closeDrawer();
+      },
+      //弹窗确定
+      drawerConfirmButton(){
+        this.$refs.drawer.closeDrawer();
+      },
       //查询按钮
       handleQuery(){
         this.$set(this.queryParams,'page',1);
@@ -215,52 +234,15 @@
       tableButton(type,row){
         let self = this;
         if(type == 1){
-          //新增
-          this.$set(this,'pageType',2);
-          this.$set(this,'propsData',{});
+          // TODO: 查看成绩
+          this.$set(this, 'drawerTitle', '查看成绩')
+          this.$set(this, 'drawerType', 1)
+          this.$set(this, 'drawer', true)
         }else if(type == 2){
-          //详情
-          this.$set(this,'pageType',2);
-          let obj = JSON.parse(JSON.stringify(row))
-          obj.showType = true;
-          this.$set(this,'propsData',obj);
-        }else if(type == 3){
-          //编辑
-          this.$set(this,'pageType',2);
-          let obj = JSON.parse(JSON.stringify(row))
-          obj.showType = false;
-          this.$set(this,'propsData',obj);
-        }else if(type == 4){
-          //删除
-          this.$confirm('是否确认删除?', "警告", {
-            confirmButtonText: "确定",
-            cancelButtonText: "取消",
-            type: "warning"
-          }).then(function() {
-          }).then(() => {
-            deleteFunction({id:row.id}).then(response => {
-              self.msgSuccess(response.message)
-              self.getList();
-            });
-          }).catch(() => {});
-        }else if(type == 5){
-          //启用&停用
-          let text = row.state  ? "停用" : "启用";
-          this.$confirm('是否确认' + text + '?', "警告", {
-            confirmButtonText: "确定",
-            cancelButtonText: "取消",
-            type: "warning"
-          }).then(function() {
-          }).then(() => {
-            stateFunction({id:row.id,state:!row.state,}).then(response => {
-              self.msgSuccess(response.message)
-              self.getList();
-            });
-          }).catch(() => {});
-        }else if(type == 6){
-          //返回并刷新
-          this.$set(this,'pageType',1);
-          this.getList();
+          // TODO: 学习情况
+          this.$set(this, 'drawerTitle', '学习情况')
+          this.$set(this, 'drawerType', 2)
+          this.$set(this, 'drawer', true)
         }
       },
       //查询全部考试类型

+ 54 - 0
src/views/safetyEducationExaminationNew/examManagement/examinationResult/learningProgress.vue

@@ -0,0 +1,54 @@
+<!-- 学习情况 -->
+<template>
+  <div class="page-container learningProgress-addPage">
+
+  </div>
+</template>
+
+<script>
+  //import { getDicts } from "@/api/commonality/noPermission";
+  //import { systemUserSelect } from "@/api/commonality/permission";
+  export default {
+    name: 'addPage',
+    props: {
+      propsData: {}
+    },
+    data() {
+      return {
+        newData: {}
+      }
+    },
+    created() {
+
+    },
+    mounted() {
+      this.initialize()
+    },
+    methods: {
+      // 初始化
+      initialize() {
+
+      },
+      // 返回按钮
+      backPage() {
+        this.$parent.tableButton(6)
+      },
+      // 提交按钮
+      submitForm() {
+
+      }
+    }
+  }
+</script>
+
+<style scoped lang="scss">
+  .learningProgress-addPage {
+    .content-box {
+      flex: 1;
+      display: flex;
+      flex-direction: column;
+      overflow: hidden;
+      padding: 20px;
+    }
+  }
+</style>

+ 254 - 0
src/views/safetyEducationExaminationNew/examManagement/examinationResult/viewGrades.vue

@@ -0,0 +1,254 @@
+<!-- 查看成绩 -->
+<template>
+  <div class="page-container viewGrades-addPage">
+    <div class="page-form-title-box">
+      <el-form :model="queryParams" class="form-box" ref="queryForm"
+               :inline="true" style="width:100%;">
+        <el-form-item label="" prop="queryParamsData1">
+          <el-select v-model="queryParams.queryParamsData1" placeholder="学院单位" style="width: 140px">
+            <el-option
+              v-for="dict in optionListA"
+              :key="dict.deptId"
+              :label="dict.deptName"
+              :value="dict.deptId"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="" prop="queryParamsData2">
+          <el-select v-model="queryParams.queryParamsData2" multiple placeholder="年级" style="width: 120px">
+            <el-option
+              v-for="dict in optionListB"
+              :key="dict.value"
+              :label="dict.label"
+              :value="dict.value"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="" prop="queryParamsData3">
+          <el-select v-model="queryParams.queryParamsData3" placeholder="专业" style="width: 140px">
+            <el-option
+              v-for="dict in optionListC"
+              :key="dict.id"
+              :label="dict.disciplineName"
+              :value="dict.id"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="" prop="queryParamsData4">
+          <el-select v-model="queryParams.queryParamsData4" multiple placeholder="用户类别" style="width: 120px">
+            <el-option
+              v-for="dict in optionListD"
+              :key="dict.value"
+              :label="dict.label"
+              :value="dict.value"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="" prop="queryParamsData5">
+          <el-select v-model="queryParams.queryParamsData5" multiple placeholder="是否通过" style="width: 120px">
+            <el-option label="通过" value="1"/>
+            <el-option label="不通过" value="2"/>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="" prop="queryParamsData6">
+          <el-input
+            maxLength="30"
+            v-model="queryParams.queryParamsData6"
+            placeholder="考试名称"
+            style="width: 140px"
+          />
+        </el-form-item>
+        <p class="page-inquire-common-style-button" @click="handleQuery">查询</p>
+        <p class="page-reset-common-style-button" @click="resetQuery">重置</p>
+        <el-form-item style="float: right;">
+          <export-component :exportConfig="exportConfig"></export-component>
+        </el-form-item>
+      </el-form>
+    </div>
+    <div class="page-content-box">
+      <el-table class="table-box" v-loading="loading" border :data="dataList">
+        <el-table-column label="姓名" prop="name"  show-overflow-tooltip/>
+        <el-table-column label="学工号" prop="content" width="200" show-overflow-tooltip/>
+        <el-table-column label="专业" prop="content" width="200" show-overflow-tooltip/>
+        <el-table-column label="学年" prop="content" width="200" show-overflow-tooltip/>
+        <el-table-column label="学院单位" prop="content" width="200" show-overflow-tooltip/>
+        <el-table-column label="交卷时间" prop="createTime" width="200" show-overflow-tooltip>
+          <template slot-scope="scope">
+            <span>{{ parseTime(scope.row.createTime,"{y}-{m}-{d} {h}:{i}") }}至{{ parseTime(scope.row.createTime,"{y}-{m}-{d} {h}:{i}") }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="用时" prop="content" width="200" show-overflow-tooltip/>
+        <el-table-column label="成绩" prop="content" width="200" show-overflow-tooltip/>
+        <el-table-column label="结果" prop="content" width="200" show-overflow-tooltip/>
+        <el-table-column label="操作" width="200" show-overflow-tooltip>
+          <template slot-scope="scope">
+            <div class="table-button-box">
+              <p class="table-button-null"></p>
+              <p class="table-button-p"
+                 @click="lockExam(scope.row)"
+              >查看答卷</p>
+              <p class="table-button-null"></p>
+            </div>
+          </template>
+        </el-table-column>
+      </el-table>
+      <pagination :page-sizes="[20, 30, 40, 50]"
+                  v-show="total>0"
+                  :total="total"
+                  :page.sync="queryParams.page"
+                  :limit.sync="queryParams.pageSize"
+                  @pagination="getList"
+      />
+    </div>
+    <el-drawer
+      size="100%"
+      custom-class="examinationArrange-addPage-drawer"
+      :append-to-body="true"
+      :title="drawerTitle"
+      ref="drawer"
+      :visible.sync="drawer"
+      direction="rtl"
+    >
+      <examDetails></examDetails>
+    </el-drawer>
+  </div>
+</template>
+
+<script>
+  import { examElDisciplineTypeList,systemDeptCurrentDept, } from "@/api/safetyEducationExaminationNew/index";
+  import examDetails from '@/views/safetyEducationExaminationNew/components/examDetails.vue'
+  import exportComponent from "@/components/exportComponent/exportComponent.vue";
+  export default {
+    name: 'addPage',
+    components: {
+      exportComponent,
+      examDetails
+    },
+    props: {
+      propsData: {}
+    },
+    data() {
+      return {
+        //页面遮罩
+        loading:false,
+        //下拉列表数据
+        optionListA:[],
+        optionListB:[],
+        optionListC:[],
+        optionListD:[],
+        optionListE:[],
+        //查询条件
+        queryParams:{
+          page:1,
+          pageSize:20,
+          queryParamsData1:null,
+          queryParamsData2 :null,
+          queryParamsData3 :null,
+          queryParamsData4 :null,
+          queryParamsData5 :null,
+          queryParamsData6 :'',
+        },
+        //列表数据
+        dataList:[{}],
+        //数据数量
+        total:0,
+        //导出配置
+        exportConfig:{
+          api:'system/user/student/export',              //导出接口地址
+          ids:'',                                        //勾选导出,勾选的IDS
+          fileName:'导出文件名',                         //导出文件的命名
+          customKey:null,                                //自定义参数KEY(非必填)
+          customValue:null,                               //自定义参数VALUE(非必填)
+          screenData:{}                                  //导出筛选数据(非必要-如有该参数组件会显示导出筛选数据按钮)
+        },
+        //弹窗相关
+        drawerTitle: '',
+        drawerType: '',
+        drawer: false
+      }
+    },
+    created() {
+
+    },
+    mounted() {
+      this.examElDisciplineTypeList();
+      this.systemDeptCurrentDept();
+    },
+    methods: {
+      lockExam(){
+        this.$set(this, 'drawerTitle', '考试详情')
+        this.$set(this, 'drawerType', 2)
+        this.$set(this, 'drawer', true)
+      },
+      //操作按钮
+      tableButton(type,row){
+        let self = this;
+        if(type == 1){
+          // TODO: 查看成绩
+          this.$set(this, 'drawerTitle', '查看成绩')
+          this.$set(this, 'drawerType', 1)
+          this.$set(this, 'drawer', true)
+        }else if(type == 2){
+          // TODO: 学习情况
+          this.$set(this, 'drawerTitle', '学习情况')
+          this.$set(this, 'drawerType', 2)
+          this.$set(this, 'drawer', true)
+        }
+      },
+      // 提交按钮
+      checkButton() {
+        this.$parent.$parent.drawerOffButton()
+      },
+      //查询按钮
+      handleQuery(){
+        this.$set(this.queryParams,'page',1);
+        this.getList();
+      },
+      //重置按钮
+      resetQuery(){
+        this.$set(this,'queryParams',{
+          page:1,
+          pageSize:20,
+          queryParamsData1:null,
+          queryParamsData2 :null,
+          queryParamsData3 :null,
+          queryParamsData4 :null,
+          queryParamsData5 :null,
+          queryParamsData6 :'',
+        });
+        this.getList();
+      },
+      //获取数据列表
+      getList(){
+        this.$set(this,'loading',true);
+        let obj = JSON.parse(JSON.stringify(this.queryParams))
+        getListFunction(obj).then(response => {
+          this.$set(this,'loading',false);
+          this.$set(this,'dataList',response.data.records);
+          this.$set(this,'total',response.data.total);
+        });
+      },
+      //查询全部专业类别
+      examElDisciplineTypeList(){
+        examElDisciplineTypeList({}).then(response => {
+          this.$set(this,'optionListC',response.data);
+        });
+      },
+      //学院列表
+      systemDeptCurrentDept(){
+        systemDeptCurrentDept({}).then(response => {
+          this.$set(this,'optionListA',response.data);
+        });
+      },
+    }
+  }
+</script>
+
+<style scoped lang="scss">
+  .viewGrades-addPage {
+    flex:1;
+    display: flex;
+    flex-direction: column;
+    overflow: hidden;
+  }
+</style>

+ 555 - 159
src/views/safetyEducationExaminationNew/examManagement/testPaperManagement/addPage.vue

@@ -1,39 +1,64 @@
 <!-- 新建试卷 -->
 <template>
   <div class="page-container testPaperManagement-addPage">
-    <div class="top-max-big-box">
+    <div class="page-container-top-max-big-box">
       <p class="top-1-p">{{newData.id?'编辑试卷':'新建试卷'}}</p>
       <p class="top-2-p" @click="backPage()">返回</p>
-      <p class="top-3-p" @click="resetButton()">重置</p>
+      <p class="top-3-p" v-if="newData.id" @click="resetButton()">重置</p>
       <p class="top-4-p" @click="submitButton()">保存</p>
     </div>
     <div class="content-box scrollbar-box">
       <el-form class="add-form-box" :model="newData" ref="form" label-width="100px" :rules="rules">
-
         <!-- 第一块:基本信息 -->
         <div class="section-box">
           <!-- 试卷名称 -->
-          <el-form-item label="试卷名称" prop="name" class="full-width-item">
-            <el-input v-model="newData.name" placeholder="请输入名称" />
+          <el-form-item label="试卷名称" prop="data1" class="full-width-item">
+            <el-input v-model="newData.data1" maxLength="30" placeholder="请输入名称"/>
           </el-form-item>
-
           <!-- 总分数 / 及格分数 / 答题时间 -->
           <div class="flex-row-box">
-            <el-form-item label="总分数" prop="totalScore">
+            <el-form-item label="总分数" prop="data2">
               <div class="input-unit-box">
-                <el-input v-model="newData.totalScore" placeholder="100" style="width:160px;" />
+                <el-input-number
+                  class="form-input-number"
+                  style="width:160px"
+                  :min="0"
+                  :max="9999"
+                  :step="1" :precision="0" step-strictly
+                  v-model="newData.data2"
+                  :controls="false"
+                >
+                </el-input-number>
                 <span class="unit-text">分</span>
               </div>
             </el-form-item>
-            <el-form-item label="及格分数" prop="passScore">
+            <el-form-item label="及格分数" prop="data3">
               <div class="input-unit-box">
-                <el-input v-model="newData.passScore" placeholder="90" style="width:160px;" />
+                <el-input-number
+                  class="form-input-number"
+                  style="width:160px"
+                  :min="0"
+                  :max="9999"
+                  :step="1" :precision="0" step-strictly
+                  v-model="newData.data3"
+                  :controls="false"
+                >
+                </el-input-number>
                 <span class="unit-text">分</span>
               </div>
             </el-form-item>
-            <el-form-item label="答题时间" prop="duration">
+            <el-form-item label="答题时间" prop="data4">
               <div class="input-unit-box">
-                <el-input v-model="newData.duration" placeholder="30" style="width:160px;" />
+                <el-input-number
+                  class="form-input-number"
+                  style="width:160px"
+                  :min="0"
+                  :max="9999"
+                  :step="1" :precision="0" step-strictly
+                  v-model="newData.data4"
+                  :controls="false"
+                >
+                </el-input-number>
                 <span class="unit-text">分钟</span>
               </div>
             </el-form-item>
@@ -41,25 +66,25 @@
 
           <!-- 专业类别 / 是否共享 -->
           <div class="flex-row-box">
-            <el-form-item label="专业类别" prop="disciplineId">
-              <el-select v-model="newData.disciplineId" placeholder="专业类别" style="width:200px;">
+            <el-form-item label="专业类别" prop="data5">
+              <el-select v-model="newData.data5" placeholder="专业类别" style="width:200px;">
                 <el-option
-                  v-for="item in optionListDiscipline"
+                  v-for="item in optionListA"
                   :key="item.id"
                   :label="item.disciplineName"
                   :value="item.id"
                 />
               </el-select>
             </el-form-item>
-            <el-form-item label="是否共享" prop="isShare">
+            <el-form-item label="是否共享" prop="data6">
               <el-switch
-                v-model="newData.isShare"
+                v-model="newData.data6"
                 :active-value="true"
                 :inactive-value="false"
                 active-color="#0183FB"
                 inactive-color="#AAAAAA"
               />
-              <span class="share-tip">{{newData.isShare ? '共享开启,全校所有学院单位都可见' : ''}}</span>
+              <span class="share-tip">{{newData.data6 ? '共享开启,全校所有学院单位都可见' : '不共享,仅本学院单位及上级都可见'}}</span>
             </el-form-item>
           </div>
         </div>
@@ -70,15 +95,15 @@
         <!-- 第二块:组卷方式 & 知识点 -->
         <div class="section-box">
           <!-- 组卷方式 -->
-          <el-form-item label="组卷方式" prop="groupType">
-            <el-radio-group v-model="newData.groupType">
+          <el-form-item label="组卷方式" prop="data7">
+            <el-radio-group v-model="newData.data7">
               <el-radio :label="1">固定组卷</el-radio>
               <el-radio :label="2">随机组卷</el-radio>
             </el-radio-group>
           </el-form-item>
 
           <!-- 选择知识点 -->
-          <el-form-item label="选择知识点" prop="knowledgePoints" class="knowledge-form-item">
+          <el-form-item label="选择知识点" prop="data8" class="knowledge-form-item">
             <!-- 统计信息条 -->
             <div class="stat-bar">
               <span>总题目数:<b>{{totalQuestionCount}}</b></span>
@@ -88,19 +113,12 @@
 
             <!-- 已选知识点标签 -->
             <div class="knowledge-tag-box">
-              <div
-                v-for="(item, index) in selectedKnowledgePoints"
-                :key="item.id"
-                class="knowledge-tag"
-              >
-                <span>{{item.name}}</span>
-                <i class="el-icon-close" @click="removeKnowledge(index)"></i>
-              </div>
               <el-cascader
                 ref="knowledgeCascader"
-                v-model="cascaderValue"
+                v-model="newData.data8"
                 :options="optionListKnowledge"
                 :props="{
+                  multiple :true,
                   emitPath: false,
                   checkStrictly: true,
                   value: 'id',
@@ -110,7 +128,7 @@
                 :show-all-levels="false"
                 filterable
                 placeholder="选择知识点"
-                style="width:160px;"
+                style="width:1420px;"
                 @change="onKnowledgeSelect"
               />
             </div>
@@ -121,32 +139,145 @@
                 <span class="col-name">知识点</span>
                 <span class="col-count">试题数量</span>
                 <span class="col-score">每题分数</span>
-                <span class="col-fixed">固定选题</span>
+                <span class="col-fixed">{{newData.data7==1?'固定选题':'随机选题'}}</span>
               </div>
               <div
                 class="table-row"
                 v-for="(item, index) in selectedKnowledgePoints"
-                :key="item.id"
+                :key="index"
+                draggable="true"
+                @dragstart="onBigDragStart($event, index)"
+                @dragover.prevent="onBigDragOver($event, index)"
+                @drop="onBigDrop($event, index)"
+                @dragend="onDragEnd"
               >
-                <span class="col-name link-text">{{item.name}}</span>
-                <span class="col-count link-text">{{item.questionCount || 0}}</span>
-                <span class="col-score">
-                  <div class="number-stepper">
-                    <button class="stepper-btn" @click="changeScore(index, -1)">-</button>
-                    <span class="stepper-val">{{item.scorePerQuestion || 0}}</span>
-                    <button class="stepper-btn" @click="changeScore(index, 1)">+</button>
-                  </div>
-                </span>
-                <span class="col-fixed">
-                  <el-input
-                    v-model="item.fixedCount"
-                    style="width:80px;"
-                    type="number"
-                    :min="0"
-                  />
-                  <el-button type="primary" circle icon="el-icon-plus" size="mini" style="margin-left:8px;" @click="addFixed(index)" />
-                  <el-button circle icon="el-icon-aim" size="mini" style="margin-left:4px;" @click="selectFixed(index)" />
-                </span>
+                <div class="table-big-row">
+                  <span class="col-name color-name">
+                    <span class="roller-span" v-show="newData.data7==2"
+                          @click="rollerButton(index)"
+                          :class="item.rollerType?'el-icon-arrow-up':'el-icon-arrow-down'"></span>
+                    {{item.name}}
+                  </span>
+                  <span class="col-count color-name">{{item.questionCount || 0}}</span>
+                  <span class="col-score">
+                    <span class="number-stepper">
+                      <el-input-number
+                        v-if="newData.data7==1"
+                        class="form-input-number"
+                        style="width:160px"
+                        :min="0"
+                        :max="9999"
+                        :step="1" :precision="0" step-strictly
+                        v-model="item.scorePerQuestion"
+                      >
+                      </el-input-number>
+                    </span>
+                  </span>
+                  <span class="col-fixed">
+                    <span class="item-num-span" v-if="newData.data7==1">{{item.fixedCount}}</span>
+                    <span class="el-icon-circle-plus" v-if="newData.data7==1" @click="addFixed(index)"></span>
+                    <span class="el-icon-rank"></span>
+                  </span>
+                </div>
+                <div  v-if="newData.data7==2 && item.rollerType" class="table-min-row">
+                  <span class="col-name">
+                    <span class="roller-span">单选题</span>
+                  </span>
+                  <span class="col-count color-name">{{item.maxNumA || 0}}</span>
+                  <span class="col-score">
+                    <span class="number-stepper">
+                      <el-input-number
+                        class="form-input-number"
+                        style="width:160px"
+                        :min="0"
+                        :max="9999"
+                        :step="1" :precision="0" step-strictly
+                        v-model="item.scoreA"
+                      >
+                      </el-input-number>
+                    </span>
+                  </span>
+                  <span class="col-fixed">
+                    <span class="number-stepper">
+                      <el-input-number
+                        class="form-input-number"
+                        style="width:80px"
+                        :min="0"
+                        :max="9999"
+                        :step="1" :precision="0" step-strictly
+                        v-model="item.checkNumA"
+                        :controls="false"
+                      >
+                      </el-input-number>
+                    </span>
+                  </span>
+                </div>
+                <div  v-if="newData.data7==2 && item.rollerType" class="table-min-row">
+                  <span class="col-name">
+                    <span class="roller-span">单选题</span>
+                  </span>
+                  <span class="col-count color-name">{{item.maxNumB || 0}}</span>
+                  <span class="col-score">
+                    <span class="number-stepper">
+                      <el-input-number
+                        class="form-input-number"
+                        style="width:160px"
+                        :min="0"
+                        :max="9999"
+                        :step="1" :precision="0" step-strictly
+                        v-model="item.scoreB"
+                      >
+                      </el-input-number>
+                    </span>
+                  </span>
+                  <span class="col-fixed">
+                    <span class="number-stepper">
+                      <el-input-number
+                        class="form-input-number"
+                        style="width:80px"
+                        :min="0"
+                        :max="9999"
+                        :step="1" :precision="0" step-strictly
+                        v-model="item.checkNumB"
+                        :controls="false"
+                      >
+                      </el-input-number>
+                    </span>
+                  </span>
+                </div>
+                <div v-if="newData.data7==2 && item.rollerType" class="table-min-row">
+                  <span class="col-name">
+                    <span class="roller-span">判断题</span>
+                  </span>
+                  <span class="col-count color-name">{{item.maxNumC || 0}}</span>
+                  <span class="col-score">
+                    <span class="number-stepper">
+                      <el-input-number
+                        class="form-input-number"
+                        style="width:160px"
+                        :min="0"
+                        :max="9999"
+                        :step="1" :precision="0" step-strictly
+                        v-model="item.scoreC"
+                      >
+                      </el-input-number>
+                    </span>
+                  </span>
+                  <span class="col-fixed">
+                    <span class="number-stepper">
+                      <el-input-number
+                        class="form-input-number"
+                        style="width:80px"
+                        :min="0"
+                        :max="9999"
+                        :step="1" :precision="0" step-strictly
+                        v-model="item.checkNumC"
+                        :controls="false"
+                      >
+                      </el-input-number>
+                    </span>
+                  </span>
+                </div>
               </div>
             </div>
           </el-form-item>
@@ -162,13 +293,14 @@
       ref="drawer"
       :visible.sync="drawer"
       direction="rtl"
-      :withHeader="false">
+      :withHeader="false"
+    >
       <drawer-item
         v-if="drawer"
         :knowledge-name="drawerKnowledgeName"
         :knowledge-id="drawerKnowledgeId"
         :score-per-question="drawerScorePerQuestion"
-        :total-score="Number(newData.totalScore) || 0"
+        :total-score="Number(newData.data2) || 0"
         :init-selected="drawerInitSelected"
         @confirm="onDrawerConfirm"
         @cancel="drawer = false"
@@ -178,7 +310,9 @@
 </template>
 
 <script>
+  import { examElKnowledgePointTreeList, examElDisciplineTypeList } from '@/api/safetyEducationExaminationNew/index'
   import drawerItem from './drawerItem.vue'
+
   export default {
     name: 'addPage',
     components: { drawerItem },
@@ -186,35 +320,63 @@
       propsData: {}
     },
     data() {
+      const validateData = (rule, value, callback) => {
+        if (value == 0) {
+          callback(new Error('总分数不能为0'))
+        } else if (this.newData.data3 > value) {
+          callback(new Error('总分数不能小于及格分数'))
+        } else {
+          callback()
+        }
+      }
       return {
-        drawer: false,
-        drawerKnowledgeName: '',
-        drawerKnowledgeId: null,
-        drawerScorePerQuestion: 1,
-        drawerInitSelected: [],
-        drawerCurrentIndex: -1,
         newData: {
-          id: null,
-          name: '',
-          totalScore: '',
-          passScore: '',
-          duration: '',
-          disciplineId: null,
-          isShare: true,
-          groupType: 1,
+          data1: '',
+          data2: 0,
+          data3: 0,
+          data4: 0,
+          data5: null,
+          data6: true,
+          data7: 1,
+          data8: []
         },
         rules: {
-          name: [{ required: true, message: '请输入试卷名称', trigger: 'blur' }],
-          totalScore: [{ required: true, message: '请输入总分数', trigger: 'blur' }],
-          passScore: [{ required: true, message: '请输入及格分数', trigger: 'blur' }],
-          duration: [{ required: true, message: '请输入答题时间', trigger: 'blur' }],
-          groupType: [{ required: true, message: '请选择组卷方式', trigger: 'change' }],
-          knowledgePoints: [{ required: true, message: '请选择知识点', trigger: 'change' }],
+          data1: [
+            { required: true, message: '请输入试卷名称', trigger: 'blur' }
+          ],
+          data2: [
+            { required: true, message: '请输入总分数', trigger: 'blur' },
+            { validator: validateData, trigger: 'blur' }
+          ],
+          data3: [
+            { required: true, message: '请输入及格分数', trigger: 'blur' }
+          ],
+          data4: [
+            { required: true, message: '请输入答题时间', trigger: 'blur' }
+          ],
+          data7: [
+            { required: true, message: '请选择组卷方式', trigger: 'change' }
+          ],
+          data8: [
+            { required: true, message: '请选择知识点', trigger: 'change' }
+          ]
         },
-        optionListDiscipline: [],
+        optionListA: [],
         optionListKnowledge: [],
         selectedKnowledgePoints: [],
-        cascaderValue: null,
+        //弹窗相关
+        drawer: false,
+        drawerKnowledgeName: '',
+        drawerKnowledgeId: null,
+        drawerScorePerQuestion: 1,
+        drawerInitSelected: [],
+        drawerCurrentIndex: -1,
+        //拖拽相关
+        dragInfo: {
+          type: null, // 'big' 或 'small'
+          bigIndex: null,
+          smallIndex: null
+        }
       }
     },
     computed: {
@@ -227,11 +389,13 @@
         }, 0)
       },
       remainScore() {
-        return Math.max(0, Number(this.newData.totalScore || 0) - this.totalScore)
-      },
+        return Math.max(0, Number(this.newData.data2 || 0) - this.totalScore)
+      }
     },
     mounted() {
       this.initialize()
+      this.examElKnowledgePointTreeList()
+      this.examElDisciplineTypeList()
     },
     methods: {
       initialize() {
@@ -243,7 +407,7 @@
         this.$parent.tableButton(6)
       },
       resetButton() {
-        this.$set(this,'drawer',true);
+        this.$set(this, 'drawer', true)
       },
       submitButton() {
         this.$refs.form.validate(valid => {
@@ -251,37 +415,68 @@
           // TODO: 调用保存接口
         })
       },
+      //收起展开
+      rollerButton(idnex){
+        this.$set(this.selectedKnowledgePoints[idnex],'rollerType',!this.selectedKnowledgePoints[idnex].rollerType);
+      },
+      //change触发过滤知识点列表
       onKnowledgeSelect(val) {
-        if (!val) return
-        // 防止重复添加
-        const exists = this.selectedKnowledgePoints.find(k => k.id === val)
-        if (!exists) {
-          // 从级联选项中找到对应节点名称
-          const node = this.$refs.knowledgeCascader.getCheckedNodes()
-          const name = node && node[0] ? node[0].label : String(val)
-          this.selectedKnowledgePoints.push({
-            id: val,
-            name,
-            questionCount: 0,
-            scorePerQuestion: 1,
-            fixedCount: 0,
-          })
+        if (!Array.isArray(val)) return
+        for (let i = this.selectedKnowledgePoints.length - 1; i >= 0; i--) {
+          const currentItem = this.selectedKnowledgePoints[i]
+          if (!val.includes(currentItem.id)) {
+            this.selectedKnowledgePoints.splice(i, 1)
+          }
         }
-        this.$nextTick(() => {
-          this.cascaderValue = null
+        val.forEach(id => {
+          const exists = this.selectedKnowledgePoints.some(item => item.id === id)
+          if (!exists) {
+            const node = this.$refs.knowledgeCascader.getCheckedNodes()
+            let name = ''
+            for (let i = 0; i < node.length; i++) {
+              if (id == node[i].value) {
+                name = node[i].label
+              }
+            }
+            this.selectedKnowledgePoints.push({
+              id: id,
+              name,
+              rollerType: true,
+              dataList: [],
+              questionCount: 0,
+              scorePerQuestion: 0,
+              fixedCount: 0,
+              maxNumA:0,
+              scoreA:0,
+              checkNumA:0,
+              maxNumB:0,
+              scoreB:0,
+              checkNumB:0,
+              maxNumC:0,
+              scoreC:0,
+              checkNumC:0,
+            })
+          }
         })
       },
-      removeKnowledge(index) {
-        this.selectedKnowledgePoints.splice(index, 1)
-      },
       changeScore(index, delta) {
         const val = (Number(this.selectedKnowledgePoints[index].scorePerQuestion) || 0) + delta
         this.selectedKnowledgePoints[index].scorePerQuestion = Math.max(0, val)
       },
-      addFixed(index) {
-        // TODO: 打开固定选题弹窗
+      minChangeScore(index, delta,type) {
+        let val = 0;
+        if(type == 1){
+          val = (Number(this.selectedKnowledgePoints[index].scoreA) || 0) + delta
+          this.selectedKnowledgePoints[index].scoreA = Math.max(0, val)
+        }else if(type == 2){
+          val = (Number(this.selectedKnowledgePoints[index].scoreB) || 0) + delta
+          this.selectedKnowledgePoints[index].scoreB = Math.max(0, val)
+        }else if(type == 3){
+          val = (Number(this.selectedKnowledgePoints[index].scoreC) || 0) + delta
+          this.selectedKnowledgePoints[index].scoreC = Math.max(0, val)
+        }
       },
-      selectFixed(index) {
+      addFixed(index) {
         const item = this.selectedKnowledgePoints[index]
         this.drawerCurrentIndex = index
         this.drawerKnowledgeName = item.name
@@ -299,59 +494,84 @@
         }
         this.drawer = false
       },
+      //数字过滤
+      handleInput(type, val) {
+        if (type == 1) {
+          this.newData.data2 = val.replace(/[^\d]/g, '')
+        } else if (type == 2) {
+          this.newData.data3 = val.replace(/[^\d]/g, '')
+        } else if (type == 3) {
+          this.newData.data4 = val.replace(/[^\d]/g, '')
+        }
+      },
+      //及格分判断
+      validateData2() {
+        this.$refs.form && this.$refs.form.validateField('data2')
+      },
+      // ========== 大项拖拽逻辑 ==========
+      onBigDragStart(e, index) {
+        this.dragInfo = { type: 'big', bigIndex: index, smallIndex: null }
+        // 给拖拽中的元素加个样式
+        e.target.classList.add('dragging')
+      },
+      onBigDragOver(e, index) {
+        // 只有当拖拽的是大项时才允许放置
+        if (this.dragInfo.type === 'big') {
+          e.currentTarget.classList.add('drag-over')
+        }
+      },
+      onBigDrop(e, targetIndex) {
+        if (this.dragInfo.type !== 'big') return
+        const sourceIndex = this.dragInfo.bigIndex
+        if (sourceIndex === targetIndex) return
+
+        // 核心:交换数组元素
+        const movedItem = this.selectedKnowledgePoints.splice(sourceIndex, 1)[0]
+        this.selectedKnowledgePoints.splice(targetIndex, 0, movedItem)
+      },
+      // 拖拽结束,清理样式
+      onDragEnd(e) {
+        e.target.classList.remove('dragging')
+        document.querySelectorAll('.drag-over').forEach(el => el.classList.remove('drag-over'))
+        this.dragInfo = { type: null, bigIndex: null, smallIndex: null }
+      },
+      // 辅助方法:获取当前鼠标悬停的大项索引
+      getCurrentBigIndex(e) {
+        const minBox = e.currentTarget.closest('.for-max-box')
+        return Array.from(this.$el.querySelectorAll('.for-max-box')).indexOf(minBox)
+      },
+      //查询全部专业类别
+      examElDisciplineTypeList() {
+        examElDisciplineTypeList({}).then(response => {
+          this.$set(this, 'optionListA', response.data)
+        })
+      },
+      //知识点
+      examElKnowledgePointTreeList() {
+        examElKnowledgePointTreeList({}).then(response => {
+          const list = response.data || []
+          this.formatTreeData(list)
+          this.$set(this, 'optionListKnowledge', list)
+        })
+      },
+      formatTreeData(data) {
+        data.forEach(item => {
+          // 如果 children 存在,且是数组,且长度为 0(即 children: [])
+          if (Array.isArray(item.children) && item.children.length === 0) {
+            // 将 children 设为 undefined(或直接 delete item.children;)
+            item.children = undefined
+          } else if (item.children && item.children.length > 0) {
+            // 如果还有下一级,继续递归处理
+            this.formatTreeData(item.children)
+          }
+        })
+      }
     }
   }
 </script>
 
 <style scoped lang="scss">
   .testPaperManagement-addPage {
-    .top-max-big-box {
-      height: 60px;
-      display: flex;
-      overflow: hidden;
-      padding: 0 20px;
-      border-bottom: 1px solid #dedede;
-      align-items: center;
-      .top-1-p {
-        flex: 1;
-        font-size: 16px;
-        font-weight: 700;
-      }
-      .top-2-p {
-        height: 35px;
-        line-height: 33px;
-        margin-left: 20px;
-        padding: 0 20px;
-        border-radius: 4px;
-        background-color: #fff;
-        border: 1px solid #999;
-        color: #999;
-        cursor: pointer;
-      }
-      .top-3-p {
-        height: 35px;
-        line-height: 33px;
-        margin-left: 20px;
-        padding: 0 20px;
-        border-radius: 4px;
-        background-color: #dedede;
-        border: 1px solid #dedede;
-        color: #333;
-        cursor: pointer;
-      }
-      .top-4-p {
-        height: 35px;
-        line-height: 33px;
-        margin-left: 20px;
-        padding: 0 20px;
-        border-radius: 4px;
-        background-color: #0183fa;
-        border: 1px solid #0183fa;
-        color: #fff;
-        cursor: pointer;
-      }
-    }
-
     .content-box {
       flex: 1;
       overflow-y: auto;
@@ -373,6 +593,7 @@
         ::v-deep .el-input {
           width: 100%;
         }
+
         ::v-deep .el-form-item__content {
           width: calc(100% - 100px);
         }
@@ -387,6 +608,7 @@
       .input-unit-box {
         display: flex;
         align-items: center;
+
         .unit-text {
           margin-left: 8px;
           color: #333;
@@ -413,6 +635,7 @@
         margin-bottom: 10px;
         font-size: 14px;
         color: #333;
+
         b {
           color: #0183fa;
         }
@@ -439,11 +662,13 @@
         border-radius: 4px;
         padding: 4px 10px;
         font-size: 13px;
+
         i {
           margin-left: 6px;
           cursor: pointer;
           color: #0183fa;
           font-size: 12px;
+
           &:hover {
             color: #e74c3c;
           }
@@ -456,15 +681,167 @@
         border-radius: 4px;
         overflow: hidden;
 
-        .table-header,
-        .table-row {
+        .table-header {
           display: flex;
           align-items: center;
           padding: 12px 16px;
-          .col-name  { flex: 3; }
-          .col-count { flex: 2; text-align: center; }
-          .col-score { flex: 2; text-align: center; }
-          .col-fixed { flex: 3; display: flex; align-items: center; }
+
+          .col-name {
+            flex: 3;
+          }
+
+          .col-count {
+            flex: 2;
+            text-align: center;
+          }
+
+          .col-score {
+            flex: 2;
+            text-align: center;
+          }
+
+          .col-fixed {
+            flex: 3;
+            display: flex;
+            align-items: center;
+          }
+
+          .color-name {
+            color: #0183fa;
+          }
+        }
+
+        .table-row {
+          flex: 1;
+          display: flex;
+          flex-direction: column;
+          overflow: hidden;
+          padding: 12px 0;
+
+          .table-big-row {
+            flex: 1;
+            display: flex;
+            align-items: center;
+            overflow: hidden;
+            /*padding: 0 16px 12px;*/
+            /*border-bottom:1px solid #dedede;*/
+            padding:0 16px;
+            .col-name {
+              flex: 3;
+            }
+
+            .col-count {
+              flex: 2;
+              text-align: center;
+            }
+
+            .col-score {
+              flex: 2;
+              text-align: center;
+            }
+
+            .col-fixed {
+              flex: 3;
+              display: flex;
+              align-items: center;
+            }
+
+            .roller-span {
+              color: #999;
+              font-size: 24px;
+              margin-right: 12px;
+              cursor: pointer;
+              display: inline-block;
+            }
+
+            .color-name {
+              color: #0183fa;
+            }
+
+            .item-num-span {
+              display: inline-block;
+              width: 80px;
+              line-height: 40px;
+              border-radius: 4px;
+              border: 1px solid #dedede;
+              text-align: center;
+            }
+
+            .el-icon-circle-plus {
+              font-size: 28px;
+              color: #0183fa;
+              cursor: pointer;
+              margin-left: 28px;
+            }
+
+            .el-icon-rank {
+              font-size: 28px;
+              color: #999;
+              cursor: pointer;
+              margin-left: 28px;
+            }
+          }
+          .table-min-row {
+            flex: 1;
+            display: flex;
+            align-items: center;
+            overflow: hidden;
+            padding: 12px 16px 0;
+            /*border-top:1px solid #dedede;*/
+            .col-name {
+              flex: 3;
+            }
+
+            .col-count {
+              flex: 2;
+              text-align: center;
+            }
+
+            .col-score {
+              flex: 2;
+              text-align: center;
+            }
+
+            .col-fixed {
+              flex: 3;
+              display: flex;
+              align-items: center;
+            }
+
+            .roller-span {
+              color: #333;
+              font-size: 16px;
+              cursor: pointer;
+              margin-left:200px;
+            }
+
+            .color-name {
+              color: #0183fa;
+            }
+
+            .item-num-span {
+              display: inline-block;
+              width: 80px;
+              line-height: 40px;
+              border-radius: 4px;
+              border: 1px solid #dedede;
+              text-align: center;
+            }
+
+            .el-icon-circle-plus {
+              font-size: 28px;
+              color: #0183fa;
+              cursor: pointer;
+              margin-left: 40px;
+            }
+
+            .el-icon-rank {
+              font-size: 28px;
+              color: #999;
+              cursor: pointer;
+              margin-left: 40px;
+            }
+          }
         }
 
         .table-header {
@@ -477,7 +854,10 @@
 
         .table-row {
           border-bottom: 1px solid #f0f0f0;
-          &:last-child { border-bottom: none; }
+
+          &:last-child {
+            border-bottom: none;
+          }
         }
 
         .link-text {
@@ -488,23 +868,25 @@
         .number-stepper {
           display: inline-flex;
           align-items: center;
-          border: 1px solid #dcdfe6;
           border-radius: 4px;
           overflow: hidden;
 
           .stepper-btn {
             width: 32px;
-            height: 32px;
+            height: 36px;
             background: #f5f7fa;
             border: none;
             cursor: pointer;
             font-size: 16px;
             color: #333;
-            &:hover { background: #e6f0fe; }
+
+            &:hover {
+              background: #e6f0fe;
+            }
           }
 
           .stepper-val {
-            min-width: 40px;
+            min-width: 80px;
             text-align: center;
             font-size: 14px;
             padding: 0 4px;
@@ -512,5 +894,19 @@
         }
       }
     }
+
+    .drag-handle {
+      cursor: move;
+      user-select: none; /* 防止拖拽时选中文字 */
+    }
+
+    .dragging {
+      opacity: 0.5;
+      background-color: #f5f7fa;
+    }
+
+    .drag-over {
+      border-top: 2px solid #409EFF !important; /* 放置位置的指示线 */
+    }
   }
 </style>

+ 1 - 38
src/views/safetyEducationExaminationNew/publicConfiguration/basicConfiguration/index.vue

@@ -1,7 +1,7 @@
 <!-- 基础配置 -->
 <template>
   <div class="basicConfiguration">
-    <div class="top-max-big-box">
+    <div class="page-container-top-max-big-box">
       <p class="top-1-p">基础配置</p>
       <p class="top-2-p" @click="resetButton()">重置</p>
       <p class="top-3-p" @click="submitButton()">保存</p>
@@ -101,43 +101,6 @@
     display: flex;
     flex-direction: column;
     overflow: hidden;
-    .top-max-big-box{
-      height:60px;
-      display: flex;
-      overflow: hidden;
-      border-radius:10px;
-      margin:5px 20px 10px 10px;
-      box-shadow: 0 0 8px 1px rgba(0, 0, 0, 0.1);
-      padding:0 20px;
-      .top-1-p{
-        flex:1;
-        line-height:60px;
-        font-size:16px;
-        font-weight:700;
-      }
-      .top-2-p{
-        height:35px;
-        line-height:33px;
-        margin:12px 0 0 20px;
-        padding:0 20px;
-        border-radius:4px;
-        background-color: #fff;
-        border:1px solid #999;
-        color:#999;
-        cursor: pointer;
-      }
-      .top-3-p{
-        height:35px;
-        line-height:33px;
-        margin:12px 0 0 20px;
-        padding:0 20px;
-        border-radius:4px;
-        background-color: #0183fa;
-        border:1px solid #0183fa;
-        color:#fff;
-        cursor: pointer;
-      }
-    }
     .bottom-max-big-box{
       flex:1;
       display: flex;

+ 1 - 1
src/views/safetyEducationExaminationNew/questionBankManagement/practiceSolvingProblems/index.vue

@@ -6,7 +6,7 @@
         <el-form :model="queryParams" class="form-box" ref="queryForm"
                  :inline="true" style="width:100%;">
           <el-form-item label="" prop="queryParamsData1">
-            <el-select v-model="queryParams.queryParamsData1" placeholder="学年" style="width: 200px">
+            <el-select v-model="queryParams.queryParamsData1" multiple placeholder="学年" style="width: 200px">
               <el-option
                 v-for="dict in optionListA"
                 :key="dict.value"

+ 1 - 47
src/views/safetyEducationExaminationNew/questionBankManagement/questionBankManagement/addPage.vue

@@ -1,7 +1,7 @@
 <!-- 新建试题 -->
 <template>
   <div class="page-container questionBankManagement-addPage">
-    <div class="top-max-big-box">
+    <div class="page-container-top-max-big-box">
       <p class="top-1-p">{{newData.id?'编辑试题':'添加试题'}}</p>
       <p class="top-2-p" @click="backPage()">返回</p>
       <p class="top-3-p" v-if="newData.id" @click="resetButton()">重置</p>
@@ -396,52 +396,6 @@
 
 <style scoped lang="scss">
   .questionBankManagement-addPage {
-    .top-max-big-box{
-      height:60px;
-      display: flex;
-      overflow: hidden;
-      padding:0 20px;
-      border-bottom:1px solid #dedede;
-      .top-1-p{
-        flex:1;
-        line-height:60px;
-        font-size:16px;
-        font-weight:700;
-      }
-      .top-2-p{
-        height:35px;
-        line-height:33px;
-        margin:12px 0 0 20px;
-        padding:0 20px;
-        border-radius:4px;
-        background-color: #fff;
-        border:1px solid #999;
-        color:#999;
-        cursor: pointer;
-      }
-      .top-3-p{
-        height:35px;
-        line-height:33px;
-        margin:12px 0 0 20px;
-        padding:0 20px;
-        border-radius:4px;
-        background-color: #dedede;
-        border:1px solid #dedede;
-        color:#333;
-        cursor: pointer;
-      }
-      .top-4-p{
-        height:35px;
-        line-height:33px;
-        margin:12px 0 0 20px;
-        padding:0 20px;
-        border-radius:4px;
-        background-color: #0183fa;
-        border:1px solid #0183fa;
-        color:#fff;
-        cursor: pointer;
-      }
-    }
     .content-box {
       flex: 1;
       display: flex;