Browse Source

编辑题目默认给分分布

dengshaobo 1 month ago
parent
commit
54c21ffe7f

+ 71 - 6
src/components/AddExam.vue

@@ -22,14 +22,24 @@
         </el-radio-group>
       </el-form-item>
       <el-form-item label="班级类型:" prop="classType" v-if="showClassType">
-        <el-radio-group v-model="formData.classType" >
+        <el-radio-group v-model="formData.classType" @change="HandleClassTypeChange">
           <el-radio :value="subject.value"  v-for="subject in classTypeList" :key="subject.value">{{subject.label}}</el-radio>
         </el-radio-group>
       </el-form-item>
       <el-form-item label="选择班级:" prop="checkClassList">
-          <el-checkbox-group v-model="formData.checkClassList">
-            <el-checkbox :label="item.className" :value="item.classIdCode" v-for="item in classList" :key="item.classIdCode"  :disabled="item.cancelFlag==0?true:false"/>
-          </el-checkbox-group>
+          <div class="class_select_wrapper">
+            <div class="class_select_all" v-if="classList.length > 0">
+              <el-checkbox
+                :model-value="checkAll"
+                :indeterminate="isIndeterminate"
+                @change="HandleCheckAllChange"
+              >全选</el-checkbox>
+              <span class="selected_count">已选 {{ formData.checkClassList.length }} / {{ classList.length }} 个班级</span>
+            </div>
+            <el-checkbox-group v-model="formData.checkClassList">
+              <el-checkbox :label="item.className" :value="item.classIdCode" v-for="item in classList" :key="item.classIdCode"  :disabled="item.cancelFlag==0?true:false"/>
+            </el-checkbox-group>
+          </div>
       </el-form-item>
     </el-form>
     <template #footer>
@@ -134,7 +144,7 @@ const formData = reactive({
     courseCode:'',//科目code
     courseName:'',//科目名称
     classType:0,//0 行政班 1 教学班
-    checkClassList:[],//班级列表
+    checkClassList:[] as any[],//班级列表
 })
 
 // 重置表单
@@ -361,6 +371,45 @@ const HandleCourseChange=()=>{
   formData.classType=0;
 }
 
+// 可选班级(排除禁用项 cancelFlag==0,禁用班级不可被勾选/取消)
+const selectableClassList = computed(() => {
+  return classList.value.filter((item: any) => item.cancelFlag != 0)
+})
+
+// 班级全选状态:所有可选班级都已被选中
+const checkAll = computed(() => {
+  if (selectableClassList.value.length === 0) return false
+  const checked = formData.checkClassList as any[]
+  return selectableClassList.value.every((item: any) => checked.includes(item.classIdCode))
+})
+
+// 班级半选状态:选中了部分可选班级
+const isIndeterminate = computed(() => {
+  const checked = formData.checkClassList as any[]
+  const checkedCount = selectableClassList.value.filter((item: any) =>
+    checked.includes(item.classIdCode)
+  ).length
+  return checkedCount > 0 && checkedCount < selectableClassList.value.length
+})
+
+// 全选 / 取消全选
+const HandleCheckAllChange = (val: any) => {
+  const selectableIds = selectableClassList.value.map((item: any) => item.classIdCode)
+  const checked = formData.checkClassList as any[]
+  if (val) {
+    // 全选:合并所有可选班级(禁用班级保持原有选中状态)
+    formData.checkClassList = Array.from(new Set([...checked, ...selectableIds]))
+  } else {
+    // 取消全选:仅移除可选班级,保留禁用班级
+    formData.checkClassList = checked.filter((id: any) => !selectableIds.includes(id))
+  }
+}
+
+// 切换班级类型:清空已选班级,保证全选状态与当前班级列表一致
+const HandleClassTypeChange = () => {
+  formData.checkClassList = []
+}
+
 //获取选择的班级list
 const classDetailDTOS = computed(() => {
   if (!Array.isArray(formData.checkClassList) || !Array.isArray(classList.value)) return []
@@ -441,7 +490,23 @@ const HandleSubmit = async () => {
 </script>
 
 <style lang="scss" scoped>
-.exam-form {
+.exam_form {
   padding-right: 20px;
 }
+.class_select_wrapper {
+  width: 100%;
+}
+.class_select_all {
+  display: flex;
+  align-items: center;
+  gap: 16px;
+  margin-bottom: 8px;
+  padding-bottom: 8px;
+  border-bottom: 1px solid var(--el-border-color-lighter);
+
+  .selected_count {
+    font-size: 13px;
+    color: var(--el-text-color-secondary);
+  }
+}
 </style>

+ 1 - 1
src/views/exam/components/EditorQuestion.vue

@@ -160,7 +160,7 @@ const InitData = (item) => {
         examSubjectId: item.examSubjectId,
         fullScore: item.fullScore, // 满分
         id: item.id,
-        multipleRule: item.multipleRule,
+        multipleRule: String(item.multipleRule || '1'),//默认第一个
         multipleRuleDesc: item.multipleRuleDesc,
         optionsSize: item.optionsSize,
         standardAnswer: item.standardAnswer,

+ 62 - 3
src/views/exam/question.vue

@@ -42,6 +42,10 @@
                 <div class="search_name">分组:</div>
                 <el-input v-model="queryParams.groupName" placeholder="请输入"  class="select_width" ></el-input>
             </div>
+            <div class="item_search" v-if="queryParams.questionType == 2" >
+            (给分规则:少选得<el-input v-model="shaoxuan" @input="(val: any) => HandleScoreInput(null,'shaoxuan', val)"  style="width: 50px;" maxlength="4" @change="ScoreYanzheng()"></el-input>分
+            )
+            </div>
             <div class="item_search">
                 <el-button type="primary"  style="width: 68px;" @click="EnterSetQuestion()">确定</el-button>
             </div>
@@ -203,6 +207,9 @@ const queryParams= ref({
     groupName:'',//分组名称
 })
 
+//多选题「少选得分」(questionType==2 时使用),与模板 v-model="shaoxuan" 绑定
+const shaoxuan = ref('')//批量设置多选题给分分布的功能
+
 const templateName=ref('机读卡');
 const scanText=ref('开始扫描');
 const templateQuestionCount=ref(0);//模板题目总数量
@@ -288,6 +295,56 @@ const onScoreInput = (target: any, key: string, val: any) => {
     }
 };
 
+// 组件顶层分数输入 ref 映射:当 obj 传 null 时,按 prop 名写入对应的 ref
+const scoreRefMap: Record<string, any> = { shaoxuan }
+
+// 每小题分数输入验证(0.5 分制)
+// obj:目标对象,为 null 时表示组件顶层 ref(如 shaoxuan);prop:属性名;value:输入值
+const HandleScoreInput = (obj: any, prop: string, value: any) => {
+    // 1. 移除所有非数字和非小数点的字符
+    let newValue = String(value ?? '').replace(/[^\d.]/g, '')
+
+    // 2. 确保只有一个小数点
+    const parts = newValue.split('.')
+    if (parts.length > 2) {
+        newValue = parts[0] + '.' + parts.slice(1).join('')
+    }
+
+    // 3. 不允许以多个 0 开头(0.xx 的情况除外)
+    if (newValue.length > 1 && newValue.startsWith('0') && !newValue.startsWith('0.')) {
+        newValue = newValue.substring(1)
+    }
+
+    // 4. 校验是否为 0.5 的倍数:数值 ×2 应为整数,否则四舍五入到最近的 0.5 倍数
+    if (newValue && newValue !== '.' && !isNaN(Number(newValue))) {
+        const numValue = parseFloat(newValue)
+        if (numValue > 0) {
+            const doubled = numValue * 2
+            if (Math.abs(doubled - Math.round(doubled)) > 0.0001) {
+                newValue = (Math.round(doubled) / 2).toString()
+            }
+        }
+    }
+
+    // 5. 动态赋值:obj 为 null 时写入顶层 ref,否则写入 obj[prop]
+    if (obj == null) {
+        const targetRef = scoreRefMap[prop]
+        if (targetRef) targetRef.value = newValue
+    } else {
+        obj[prop] = newValue
+    }
+}
+
+// 少选得分校验(@change 触发):不能超过每题满分
+const ScoreYanzheng = () => {
+    const fullMark = parseFloat(String(queryParams.value.fullMark ?? ''))
+    const score = parseFloat(String(shaoxuan.value ?? ''))
+    if (!isNaN(score) && !isNaN(fullMark) && score >= fullMark) {
+        ElMessage.error('少选得分必须小于每题满分')
+        shaoxuan.value = ''
+    }
+}
+
 //打开选择模板弹窗
 const OpenChooseTemplate=()=>{
     showChooseTemplate.value=true;
@@ -862,7 +919,7 @@ onUnmounted(() => {
 {
     display: flex;
     align-items: center;
-    gap: 10px;
+
     margin-right: 10px;
     .search_name
     {
@@ -874,7 +931,9 @@ onUnmounted(() => {
     
     .select_width
     {
-        width: 100px;
+        margin-left: 5px;
+        margin-right: 5px;
+        width: 90px;
     }
 }
 .item_title
@@ -886,7 +945,7 @@ onUnmounted(() => {
     display: flex;
     justify-content: flex-start;
     align-items: center;
-    gap: 10px;
+    gap: 4px;
     position: relative;
 
     .preview_btn