Преглед изворни кода

后台知识点自由组卷并发送

jinxia.mo пре 3 недеља
родитељ
комит
2574889e97
20 измењених фајлова са 1108 додато и 169 уклоњено
  1. 9 0
      back-ui/src/api/dz/cards.js
  2. 22 0
      back-ui/src/api/dz/papers.js
  3. 311 0
      back-ui/src/views/dz/components/SchoolClassSelect.vue
  4. 180 0
      back-ui/src/views/dz/hooks/useSchoolClassSelect.js
  5. 200 115
      back-ui/src/views/dz/papers/components/list-paper-records.vue
  6. 2 1
      back-ui/src/views/dz/papers/components/paper-full-intelligent.vue
  7. 13 7
      back-ui/src/views/dz/papers/components/paper-knowledge-hand.vue
  8. 14 9
      back-ui/src/views/dz/papers/components/plugs/question-intelligent.vue
  9. 14 7
      back-ui/src/views/dz/papers/hooks/usePaperQuestionCondition.js
  10. 7 7
      back-ui/src/views/dz/papers/list.vue
  11. 48 7
      ie-admin/src/main/java/com/ruoyi/web/controller/dz/DzCardsController.java
  12. 29 5
      ie-admin/src/main/java/com/ruoyi/web/controller/learn/LearnTeacherController.java
  13. 26 11
      ie-admin/src/main/java/com/ruoyi/web/service/LearnTeacherService.java
  14. 12 0
      ie-system/src/main/java/com/ruoyi/dz/domain/DzCards.java
  15. 176 0
      ie-system/src/main/java/com/ruoyi/dz/dto/CardStudentDTO.java
  16. 1 0
      ie-system/src/main/java/com/ruoyi/dz/mapper/DzCardsMapper.java
  17. 1 0
      ie-system/src/main/java/com/ruoyi/dz/service/IDzCardsService.java
  18. 6 0
      ie-system/src/main/java/com/ruoyi/dz/service/impl/DzCardsServiceImpl.java
  19. 1 0
      ie-system/src/main/java/com/ruoyi/learn/domain/TestPaperVO.java
  20. 36 0
      ie-system/src/main/resources/mapper/dz/DzCardsMapper.xml

+ 9 - 0
back-ui/src/api/dz/cards.js

@@ -9,6 +9,15 @@ export function listCards(query) {
   });
 }
 
+// 查询学习卡学生列表(简化版)
+export function listCards2(query) {
+  return request({
+    url: "/dz/cards/list2",
+    method: "get",
+    params: query,
+  });
+}
+
 // 查询学习卡详细
 export function getCards(cardId) {
   return request({

+ 22 - 0
back-ui/src/api/dz/papers.js

@@ -167,6 +167,15 @@ export function buildPaperFullIntelligent(data) {
     })
 }
 
+export function buildPaperFullKnowledgeIntelligent(data) {
+    // {buildType, batchId, examType, subjectId, knowledgeIds, types, classIds}
+    return request({
+        url: '/learn/teaching/build/fullKnowledgeIntelligent',
+        method: 'post',
+        data
+    })
+}
+
 export function buildPaperExactHand(data) {
     // {buildType, batchId, universityId, majorId, majorPlanId, classIds, questions}
     return request({
@@ -185,6 +194,19 @@ export function buildPaperFullHand(data) {
     })
 }
 
+export function sendPaper(testPaperId, cardIds) {
+    // testPaperId: 测试试卷ID
+    // cardIds: 选中的学生卡片ID列表
+    return request({
+        url: '/learn/teaching/build/sendPaper',
+        method: 'get',
+        params: {
+            paperId: testPaperId,
+            cardIds: cardIds && cardIds.length > 0 ? cardIds.join(',') : null
+        }
+    })
+}
+
 export function getBuiltPaper(params) {
     // 与上面的4个build接口参数一样,只不过走1个接口,返回试卷详情
     // // TODO: remove test code

+ 311 - 0
back-ui/src/views/dz/components/SchoolClassSelect.vue

@@ -0,0 +1,311 @@
+<template>
+  <div>
+    <!-- 所在学校 -->
+    <el-form-item :label="schoolLabel" :prop="schoolProp">
+      <el-select 
+        v-model="formData.schoolId" 
+        :placeholder="schoolPlaceholder" 
+        style="width: 100%" 
+        filterable 
+        clearable 
+        :disabled="schoolDisabled"
+        @change="handleSchoolChange"
+      >
+        <el-option
+          v-for="item in schoolOptions"
+          :key="item.id"
+          :label="item.name"
+          :value="item.id"
+        />
+      </el-select>
+    </el-form-item>
+    
+    <!-- 学校班级 -->
+    <el-form-item :label="schoolClassLabel" :prop="schoolClassProp" v-if="enableCampus">
+      <el-select 
+        v-model="formData.schoolClassIds" 
+        :placeholder="schoolClassPlaceholder" 
+        style="width: 100%" 
+        filterable 
+        clearable 
+        :disabled="!formData.schoolId || schoolClassDisabled || (!!formData.campusId)"
+        :multiple="multipleClass"
+        @change="handleClassChange"
+      >
+        <el-option
+          v-for="item in schoolClassOptions"
+          :key="item.classId"
+          :label="item.name"
+          :value="item.classId"
+        />
+      </el-select>
+    </el-form-item>
+    
+    <!-- 学校班级(当不启用校区时显示) -->
+    <el-form-item :label="schoolClassLabel" :prop="schoolClassProp" v-if="!enableCampus">
+      <el-select 
+        v-model="formData.schoolClassIds" 
+        :placeholder="schoolClassPlaceholder" 
+        style="width: 100%" 
+        filterable 
+        clearable 
+        :disabled="!formData.schoolId || schoolClassDisabled"
+        :multiple="multipleClass"
+        @change="handleClassChange"
+      >
+        <el-option
+          v-for="item in schoolClassOptions"
+          :key="item.classId"
+          :label="item.name"
+          :value="item.classId"
+        />
+      </el-select>
+    </el-form-item>
+    
+    <!-- 培训校区 -->
+    <el-form-item :label="campusLabel" :prop="campusProp" v-if="enableCampus">
+      <el-select 
+        v-model="formData.campusId" 
+        :placeholder="campusPlaceholder" 
+        style="width: 100%" 
+        filterable 
+        clearable 
+        :disabled="campusDisabled || (!!formData.schoolId)"
+        @change="handleCampusChange"
+      >
+        <el-option
+          v-for="item in campusOptions"
+          :key="item.id"
+          :label="item.name"
+          :value="item.id"
+        />
+      </el-select>
+    </el-form-item>
+
+    <!-- 校区班级 -->
+    <el-form-item :label="campusClassLabel" :prop="campusClassProp" v-if="enableCampus">
+      <el-select 
+        v-model="formData.campusClassIds" 
+        :placeholder="campusClassPlaceholder" 
+        style="width: 100%" 
+        filterable 
+        clearable 
+        :disabled="!formData.campusId || campusClassDisabled || (!!formData.schoolId)"
+        :multiple="multipleClass"
+        @change="handleClassChange"
+      >
+        <el-option
+          v-for="item in campusClassOptions"
+          :key="item.classId"
+          :label="item.name"
+          :value="item.classId"
+        />
+      </el-select>
+    </el-form-item>
+  </div>
+</template>
+
+<script setup>
+import { watch, onMounted, nextTick } from 'vue'
+import { getClassesBySchoolId, getClassesByCampusId } from "@/api/dz/classes"
+import useSchoolClassSelect from '../hooks/useSchoolClassSelect.js'
+
+const props = defineProps({
+  // 配置选项
+  enableCampus: {
+    type: Boolean,
+    default: false
+  },
+  multipleClass: {
+    type: Boolean,
+    default: false
+  },
+  autoLoad: {
+    type: Boolean,
+    default: true
+  },
+  // 标签文本
+  schoolLabel: {
+    type: String,
+    default: '所在学校'
+  },
+  schoolClassLabel: {
+    type: String,
+    default: '学校班级'
+  },
+  campusLabel: {
+    type: String,
+    default: '培训校区'
+  },
+  campusClassLabel: {
+    type: String,
+    default: '校区班级'
+  },
+  // 占位符
+  schoolPlaceholder: {
+    type: String,
+    default: '请选择所在学校'
+  },
+  schoolClassPlaceholder: {
+    type: String,
+    default: '请选择学校班级'
+  },
+  campusPlaceholder: {
+    type: String,
+    default: '请选择培训校区'
+  },
+  campusClassPlaceholder: {
+    type: String,
+    default: '请选择校区班级'
+  },
+  // 表单属性名
+  schoolProp: {
+    type: String,
+    default: 'schoolId'
+  },
+  schoolClassProp: {
+    type: String,
+    default: 'schoolClassIds'
+  },
+  campusProp: {
+    type: String,
+    default: 'campusId'
+  },
+  campusClassProp: {
+    type: String,
+    default: 'campusClassIds'
+  },
+  // 禁用状态
+  schoolDisabled: {
+    type: Boolean,
+    default: false
+  },
+  schoolClassDisabled: {
+    type: Boolean,
+    default: false
+  },
+  campusDisabled: {
+    type: Boolean,
+    default: false
+  },
+  campusClassDisabled: {
+    type: Boolean,
+    default: false
+  },
+  // 外部传入的初始值
+  modelValue: {
+    type: Object,
+    default: () => ({})
+  }
+})
+
+const emit = defineEmits(['update:modelValue', 'change'])
+
+// 使用 hook
+const {
+  schoolOptions,
+  schoolClassOptions,
+  campusOptions,
+  campusClassOptions,
+  formData,
+  loadSchoolList,
+  loadCampusList,
+  loadClassesBySchoolId: hookLoadClassesBySchoolId,
+  loadClassesByCampusId: hookLoadClassesByCampusId,
+  handleSchoolChange: hookHandleSchoolChange,
+  handleCampusChange: hookHandleCampusChange
+} = useSchoolClassSelect({
+  enableCampus: props.enableCampus,
+  multipleClass: props.multipleClass,
+  autoLoad: props.autoLoad
+})
+
+// 同步外部传入的值到内部 formData
+watch(() => props.modelValue, (newVal) => {
+  if (newVal && typeof newVal === 'object') {
+    if (newVal.schoolId !== undefined) {
+      formData.value.schoolId = newVal.schoolId
+      // 如果有学校ID,加载班级
+      if (newVal.schoolId) {
+        hookLoadClassesBySchoolId(newVal.schoolId)
+      }
+    }
+    if (newVal.schoolClassIds !== undefined) {
+      formData.value.schoolClassIds = newVal.schoolClassIds
+    }
+    if (newVal.campusId !== undefined) {
+      formData.value.campusId = newVal.campusId
+      // 如果有校区ID,加载班级
+      if (newVal.campusId && props.enableCampus) {
+        hookLoadClassesByCampusId(newVal.campusId)
+      }
+    }
+    if (newVal.campusClassIds !== undefined) {
+      formData.value.campusClassIds = newVal.campusClassIds
+    }
+  }
+}, { immediate: true, deep: true })
+
+// 监听内部 formData 变化,同步到外部
+watch(() => formData.value, (newVal) => {
+  const updateData = {
+    schoolId: newVal.schoolId,
+    schoolClassIds: newVal.schoolClassIds,
+    campusId: newVal.campusId,
+    campusClassIds: newVal.campusClassIds
+  }
+  emit('update:modelValue', updateData)
+  emit('change', updateData)
+}, { deep: true })
+
+// 学校变化处理
+async function handleSchoolChange(schoolId) {
+  await hookHandleSchoolChange(schoolId)
+  emit('change', { ...formData.value })
+}
+
+// 校区变化处理
+async function handleCampusChange(campusId) {
+  await hookHandleCampusChange(campusId)
+  emit('change', { ...formData.value })
+}
+
+// 班级变化处理
+function handleClassChange() {
+  emit('change', { ...formData.value })
+}
+
+// 加载学校班级的方法(包装 hook 的方法)
+const loadClassesBySchoolId = (schoolId) => {
+  return hookLoadClassesBySchoolId(schoolId)
+}
+
+// 加载校区班级的方法(包装 hook 的方法)
+const loadClassesByCampusId = (campusId) => {
+  return hookLoadClassesByCampusId(campusId)
+}
+
+// 重置方法
+const reset = () => {
+  formData.value = {
+    schoolId: null,
+    schoolClassIds: props.multipleClass ? [] : null,
+    campusId: null,
+    campusClassIds: props.multipleClass ? [] : null
+  }
+  schoolClassOptions.value = []
+  campusClassOptions.value = []
+}
+
+// 暴露方法供外部调用
+defineExpose({
+  formData,
+  loadSchoolList,
+  loadCampusList,
+  loadClassesBySchoolId,
+  loadClassesByCampusId,
+  reset
+})
+
+</script>
+

+ 180 - 0
back-ui/src/views/dz/hooks/useSchoolClassSelect.js

@@ -0,0 +1,180 @@
+import { ref } from 'vue'
+import { listAllSchool, getAllSchool } from "@/api/dz/school"
+import { listCampus } from "@/api/dz/campus"
+import { getClassesBySchoolId, getClassesByCampusId } from "@/api/dz/classes"
+
+/**
+ * 通用的学校、班级、校区、校区班级选择 Hook
+ * @param {Object} options 配置选项
+ * @param {Boolean} options.enableCampus 是否启用校区选择(默认false)
+ * @param {Boolean} options.multipleClass 班级是否多选(默认false)
+ * @param {Boolean} options.autoLoad 是否自动加载学校列表(默认false)
+ */
+export default function useSchoolClassSelect(options = {}) {
+  const {
+    enableCampus = false,
+    multipleClass = false,
+    autoLoad = false
+  } = options
+
+  // 学校相关
+  const schoolOptions = ref([])
+  const schoolClassOptions = ref([])
+  
+  // 校区相关
+  const campusOptions = ref([])
+  const campusClassOptions = ref([])
+
+  // 表单数据
+  const formData = ref({
+    schoolId: null,
+    schoolClassIds: multipleClass ? [] : null,
+    campusId: null,
+    campusClassIds: multipleClass ? [] : null
+  })
+
+  /** 加载学校列表 */
+  function loadSchoolList(useGetAll = false) {
+    if (useGetAll) {
+      return getAllSchool().then(response => {
+        schoolOptions.value = response.data || []
+        return schoolOptions.value
+      })
+    }
+    return listAllSchool({
+      pageNum: 1,
+      pageSize: 9999,
+    }).then(response => {
+      schoolOptions.value = response.data || []
+      return schoolOptions.value
+    })
+  }
+
+  /** 加载校区列表 */
+  function loadCampusList() {
+    if (!enableCampus) {
+      return Promise.resolve([])
+    }
+    return listCampus().then(response => {
+      campusOptions.value = response.rows || []
+      return campusOptions.value
+    })
+  }
+
+  /** 根据学校ID加载班级列表 */
+  function loadClassesBySchoolId(schoolId) {
+    if (!schoolId) {
+      schoolClassOptions.value = []
+      return Promise.resolve([])
+    }
+    return getClassesBySchoolId({ schoolId }).then(response => {
+      schoolClassOptions.value = response.data || []
+      return schoolClassOptions.value
+    })
+  }
+
+  /** 根据校区ID加载班级列表 */
+  function loadClassesByCampusId(campusId) {
+    if (!enableCampus || !campusId) {
+      campusClassOptions.value = []
+      return Promise.resolve([])
+    }
+    return getClassesByCampusId({ campusId }).then(response => {
+      campusClassOptions.value = response.data || []
+      return campusClassOptions.value
+    })
+  }
+
+  /** 学校变化处理 */
+  function handleSchoolChange(schoolId) {
+    // 如果选择了学校,清空校区(如果启用校区)
+    if (schoolId && enableCampus) {
+      formData.value.campusId = null
+      formData.value.campusClassIds = multipleClass ? [] : null
+      campusClassOptions.value = []
+    } else if (!schoolId) {
+      // 如果清空了学校,清空学校班级
+      formData.value.schoolClassIds = multipleClass ? [] : null
+      schoolClassOptions.value = []
+      return
+    }
+    
+    // 清空学校班级选择
+    formData.value.schoolClassIds = multipleClass ? [] : null
+    schoolClassOptions.value = []
+    
+    // 加载学校班级
+    if (schoolId) {
+      return loadClassesBySchoolId(schoolId)
+    }
+    return Promise.resolve([])
+  }
+
+  /** 校区变化处理 */
+  function handleCampusChange(campusId) {
+    if (!enableCampus) {
+      return Promise.resolve([])
+    }
+    
+    // 如果选择了校区,清空学校
+    if (campusId) {
+      formData.value.schoolId = null
+      formData.value.schoolClassIds = multipleClass ? [] : null
+      schoolClassOptions.value = []
+    } else {
+      // 如果清空了校区,清空校区班级
+      formData.value.campusClassIds = multipleClass ? [] : null
+      campusClassOptions.value = []
+      return Promise.resolve([])
+    }
+    
+    // 清空校区班级选择
+    formData.value.campusClassIds = multipleClass ? [] : null
+    campusClassOptions.value = []
+    
+    // 加载校区班级
+    if (campusId) {
+      return loadClassesByCampusId(campusId)
+    }
+    return Promise.resolve([])
+  }
+
+  /** 重置所有数据 */
+  function reset() {
+    formData.value = {
+      schoolId: null,
+      schoolClassIds: multipleClass ? [] : null,
+      campusId: null,
+      campusClassIds: multipleClass ? [] : null
+    }
+    schoolClassOptions.value = []
+    campusClassOptions.value = []
+  }
+
+  // 自动加载
+  if (autoLoad) {
+    loadSchoolList()
+    if (enableCampus) {
+      loadCampusList()
+    }
+  }
+
+  return {
+    // 数据
+    schoolOptions,
+    schoolClassOptions,
+    campusOptions,
+    campusClassOptions,
+    formData,
+    
+    // 方法
+    loadSchoolList,
+    loadCampusList,
+    loadClassesBySchoolId,
+    loadClassesByCampusId,
+    handleSchoolChange,
+    handleCampusChange,
+    reset
+  }
+}
+

+ 200 - 115
back-ui/src/views/dz/papers/components/list-paper-records.vue

@@ -121,55 +121,43 @@
         <!-- 发送试卷弹窗 -->
         <el-dialog v-model="sendPaperDialogVisible" title="发送试卷" width="60%" destroy-on-close>
             <el-form :model="sendPaperForm" label-width="100px">
-                <el-form-item label="学校">
-                    <el-select 
-                        v-model="sendPaperForm.schoolId" 
-                        placeholder="请选择学校" 
-                        clearable 
-                        filterable
-                        style="width: 100%"
-                        @change="handleSchoolChange"
-                    >
-                        <el-option 
-                            v-for="school in schoolList" 
-                            :key="school.id" 
-                            :label="school.name" 
-                            :value="school.id"
-                        />
-                    </el-select>
-                </el-form-item>
-                <el-form-item label="班级">
-                    <el-select 
-                        v-model="sendPaperForm.classId" 
-                        placeholder="请先选择学校" 
-                        clearable 
-                        filterable
-                        style="width: 100%"
-                        :disabled="!sendPaperForm.schoolId"
-                        @change="handleClassChange"
-                    >
-                        <el-option 
-                            v-for="cls in classList" 
-                            :key="cls.classId" 
-                            :label="cls.name" 
-                            :value="cls.classId"
-                        />
-                    </el-select>
-                </el-form-item>
+                <SchoolClassSelect
+                    ref="schoolClassSelectRef"
+                    v-model="sendPaperForm"
+                    :enable-campus="true"
+                    :multiple-class="true"
+                    :auto-load="false"
+                    school-label="所在学校"
+                    school-class-label="学校班级"
+                    campus-label="培训校区"
+                    campus-class-label="校区班级"
+                    school-placeholder="请选择所在学校"
+                    school-class-placeholder="请选择学校班级"
+                    campus-placeholder="请选择培训校区"
+                    campus-class-placeholder="请选择校区班级"
+                    @change="handleSchoolClassChange"
+                />
                 <el-form-item label="学生列表">
                     <div style="max-height: 400px; overflow-y: auto; border: 1px solid #dcdfe6; border-radius: 4px; padding: 10px;">
                         <el-checkbox-group v-model="selectedStudentIds" v-loading="studentListLoading">
-                            <el-checkbox 
-                                v-for="student in studentList" 
-                                :key="student.studentId" 
-                                :label="student.studentId"
-                                style="display: block; margin-bottom: 8px;"
-                            >
-                                {{ student.studentName || `学生ID: ${student.studentId}` }}
-                            </el-checkbox>
+                            <div class="student-checkbox-container">
+                                <el-checkbox 
+                                    v-for="student in studentList" 
+                                    :key="student.cardId" 
+                                    :label="student.userId || student.cardId"
+                                    :disabled="student.isSend === true"
+                                    :class="{ 'is-sent': student.isSend === true }"
+                                    class="student-checkbox-item"
+                                >
+                                    <span :class="{ 'text-gray': student.isSend === true }">
+                                        {{ student.nickName || student.userName || `卡片${student.cardNo || student.cardId}` }}
+                                        <span v-if="student.isSend === true" class="sent-tag">(已发送)</span>
+                                    </span>
+                                </el-checkbox>
+                            </div>
                         </el-checkbox-group>
                         <div v-if="!studentListLoading && studentList.length === 0" style="text-align: center; color: #909399; padding: 20px;">
-                            请先选择学校和班级
+                            请先选择学校和班级或校区和校区班级
                         </div>
                     </div>
                 </el-form-item>
@@ -184,13 +172,12 @@
 </template>
 
 <script setup name="ListPaperRecords">
-import { ref, computed, getCurrentInstance, onMounted, markRaw } from 'vue'
+import { ref, computed, getCurrentInstance, onMounted, markRaw, nextTick } from 'vue'
 import { useInjectGlobalLoading } from "@/views/hooks/useGlobalLoading.js"
-import { getPapers, getFrontPaperDetail } from "@/api/dz/papers.js"
+import { getPapers, getFrontPaperDetail, sendPaper } from "@/api/dz/papers.js"
 import { listPaper_question } from "@/api/learn/paper_question.js"
-import { getAllSchool } from "@/api/dz/school.js"
-import { getClassesBySchoolId } from "@/api/dz/classes.js"
-import { listStudent } from "@/api/learn/student.js"
+import { listCards2 } from "@/api/dz/cards.js"
+import SchoolClassSelect from '@/views/dz/components/SchoolClassSelect.vue'
 import { ElMessage } from 'element-plus'
 import Table from '@/components/Table/index.vue'
 import DictTag from '@/components/DictTag/index.vue'
@@ -252,10 +239,11 @@ const currentPaperId = ref(null)
 const sendPaperDialogVisible = ref(false)
 const sendPaperForm = ref({
     schoolId: null,
-    classId: null
+    schoolClassIds: null,
+    campusId: null,
+    campusClassIds: null,
+    classId: null // 用于兼容原有逻辑
 })
-const schoolList = ref([])
-const classList = ref([])
 const studentList = ref([])
 const selectedStudentIds = ref([])
 const studentListLoading = ref(false)
@@ -371,8 +359,11 @@ const handleShowPaperQuestion = (paperId) => {
     paperQuestionDialogVisible.value = true
 }
 
+// 学校班级选择组件的引用
+const schoolClassSelectRef = ref(null)
+
 // 显示发送试卷弹窗
-const handleSendPaper = (row) => {
+const handleSendPaper = async (row) => {
     if (!row || !row.paperId) {
         ElMessage.warning('试卷信息不存在')
         return
@@ -381,77 +372,111 @@ const handleSendPaper = (row) => {
     currentPaperRow.value = row
     sendPaperForm.value = {
         schoolId: null,
+        schoolClassIds: null,
+        campusId: null,
+        campusClassIds: null,
         classId: null
     }
-    classList.value = []
     studentList.value = []
     selectedStudentIds.value = []
     sendPaperDialogVisible.value = true
     
-    // 加载学校列表
-    loadSchoolList()
-}
-
-// 加载学校列表
-const loadSchoolList = async () => {
-    try {
-        const response = await getAllSchool()
-        if (response && response.data) {
-            schoolList.value = response.data
-        } else {
-            schoolList.value = []
-        }
-    } catch (error) {
-        console.error('加载学校列表失败:', error)
-        ElMessage.error('加载学校列表失败')
-        schoolList.value = []
+    // 等待弹窗打开后,再加载学校列表和校区列表
+    await nextTick()
+    if (schoolClassSelectRef.value) {
+        await Promise.all([
+            schoolClassSelectRef.value.loadSchoolList(true),
+            schoolClassSelectRef.value.loadCampusList()
+        ])
     }
 }
 
-// 学校变化时加载班级列表
-const handleSchoolChange = async (schoolId) => {
-    sendPaperForm.value.classId = null
-    classList.value = []
-    studentList.value = []
-    selectedStudentIds.value = []
+// 学校班级变化处理
+const handleSchoolClassChange = async (data) => {
+    // 更新表单数据
+    sendPaperForm.value.schoolId = data.schoolId
+    sendPaperForm.value.schoolClassIds = data.schoolClassIds
+    sendPaperForm.value.campusId = data.campusId
+    sendPaperForm.value.campusClassIds = data.campusClassIds
     
-    if (!schoolId) {
-        return
-    }
+    // 判断是否有学校班级或校区班级
+    const hasSchoolClass = data.schoolClassIds && (
+        (Array.isArray(data.schoolClassIds) && data.schoolClassIds.length > 0) ||
+        (!Array.isArray(data.schoolClassIds) && data.schoolClassIds)
+    )
+    const hasCampusClass = data.campusClassIds && (
+        (Array.isArray(data.campusClassIds) && data.campusClassIds.length > 0) ||
+        (!Array.isArray(data.campusClassIds) && data.campusClassIds)
+    )
     
-    try {
-        const response = await getClassesBySchoolId({ schoolId })
-        if (response && response.data) {
-            classList.value = response.data
-        } else {
-            classList.value = []
-        }
-    } catch (error) {
-        console.error('加载班级列表失败:', error)
-        ElMessage.error('加载班级列表失败')
-        classList.value = []
+    // 如果选择了班级,加载学生列表
+    if ((hasSchoolClass && data.schoolId) || (hasCampusClass && data.campusId)) {
+        await loadStudentList()
+    } else {
+        studentList.value = []
+        selectedStudentIds.value = []
     }
 }
 
-// 班级变化时加载学生列表
-const handleClassChange = async (classId) => {
-    studentList.value = []
-    selectedStudentIds.value = []
-    
-    if (!classId || !sendPaperForm.value.schoolId) {
-        return
-    }
-    
+// 加载学生列表
+const loadStudentList = async () => {
     studentListLoading.value = true
     try {
-        const response = await listStudent({
-            schoolId: sendPaperForm.value.schoolId,
-            classId: classId,
+        const params = {
             pageNum: 1,
-            pageSize: 1000
-        })
+            pageSize: 10000 // 获取所有学生
+        }
+        
+        // 传递 paperId,用于判断学生是否已发送
+        // 从表格数据看,row 中有 paperId 字段(对应 learn_paper.id)
+        if (currentPaperRow.value) {
+            if (currentPaperRow.value.paperId) {
+                params.paperId = currentPaperRow.value.paperId
+            } else {
+                console.warn('paperId 不存在,无法判断学生是否已发送', currentPaperRow.value)
+            }
+        }
+        
+        // 优先使用学校班级,如果没有则使用校区班级
+        if (sendPaperForm.value.schoolId && sendPaperForm.value.schoolClassIds) {
+            // 使用学校班级
+            const classIds = Array.isArray(sendPaperForm.value.schoolClassIds) 
+                ? sendPaperForm.value.schoolClassIds 
+                : [sendPaperForm.value.schoolClassIds]
+            
+            // 如果有多个班级,需要分别查询或使用第一个班级
+            // 这里先使用第一个班级,如果需要支持多班级,可以循环查询
+            params.schoolId = sendPaperForm.value.schoolId
+            params.classId = classIds[0]
+        } else if (sendPaperForm.value.campusId && sendPaperForm.value.campusClassIds) {
+            // 使用校区班级
+            const classIds = Array.isArray(sendPaperForm.value.campusClassIds) 
+                ? sendPaperForm.value.campusClassIds 
+                : [sendPaperForm.value.campusClassIds]
+            
+            params.campusId = sendPaperForm.value.campusId
+            params.campusClassId = classIds[0]
+        } else {
+            studentList.value = []
+            selectedStudentIds.value = []
+            studentListLoading.value = false
+            return
+        }
+        
+        const response = await listCards2(params)
         if (response && response.rows) {
+            // 从卡片数据中提取学生信息
+            // 只保留已关联用户的卡片(有nickName表示已关联用户)
             studentList.value = response.rows
+                .filter(card => card.nickName || card.cardId) // 保留有昵称或卡片ID的记录
+                .map(card => ({
+                    userId: card.userId || null, // userId可能不存在,使用cardId作为标识
+                    cardId: card.cardId,
+                    nickName: card.nickName || card.cardNo || `卡片${card.cardId}`,
+                    userName: card.userName || null,
+                    cardNo: card.cardNo,
+                    isSend: card.isSend || false // 保留 isSend 字段
+                }))
         } else {
             studentList.value = []
         }
@@ -471,13 +496,18 @@ const handleConfirmSend = async () => {
         return
     }
     
-    if (!sendPaperForm.value.schoolId) {
-        ElMessage.warning('请选择学校')
-        return
-    }
+    // 检查学校班级或校区班级(至少选择一组)
+    const hasSchoolClass = sendPaperForm.value.schoolId && sendPaperForm.value.schoolClassIds && (
+        (Array.isArray(sendPaperForm.value.schoolClassIds) && sendPaperForm.value.schoolClassIds.length > 0) ||
+        (!Array.isArray(sendPaperForm.value.schoolClassIds) && sendPaperForm.value.schoolClassIds)
+    )
+    const hasCampusClass = sendPaperForm.value.campusId && sendPaperForm.value.campusClassIds && (
+        (Array.isArray(sendPaperForm.value.campusClassIds) && sendPaperForm.value.campusClassIds.length > 0) ||
+        (!Array.isArray(sendPaperForm.value.campusClassIds) && sendPaperForm.value.campusClassIds)
+    )
     
-    if (!sendPaperForm.value.classId) {
-        ElMessage.warning('请选择班级')
+    if (!hasSchoolClass && !hasCampusClass) {
+        ElMessage.warning('请选择学校班级或校区班级')
         return
     }
     
@@ -488,13 +518,39 @@ const handleConfirmSend = async () => {
     
     sending.value = true
     try {
-        // TODO: 调用发送试卷的API
-        // 这里需要根据实际的API接口来实现
-        ElMessage.success(`试卷已发送给 ${selectedStudentIds.value.length} 个学生`)
+        // 获取 testPaperId(表格中的 id 字段,对应 learn_test_paper.id)
+        // 从 SQL 查询看,返回的字段是 t1.id,应该就是 id
+        // 如果 id 不存在,打印调试信息
+        const testPaperId = currentPaperRow.value.id
+        
+        if (!testPaperId) {
+            // 打印完整的数据结构,帮助调试
+            console.error('当前行数据:', JSON.stringify(currentPaperRow.value, null, 2))
+            console.error('可用的字段:', Object.keys(currentPaperRow.value))
+            ElMessage.error('试卷ID不存在,请检查数据。已打印调试信息到控制台')
+            return
+        }
+        
+        // 获取选中的学生的 cardId 列表
+        const selectedCardIds = studentList.value
+            .filter(student => selectedStudentIds.value.includes(student.userId || student.cardId))
+            .map(student => student.cardId)
+            .filter(cardId => cardId != null)
+        
+        if (selectedCardIds.length === 0) {
+            ElMessage.warning('请至少选择一个有效的学生')
+            return
+        }
+        
+        // 调用发送试卷的API
+        await sendPaper(testPaperId, selectedCardIds)
+        ElMessage.success(`试卷已发送给 ${selectedCardIds.length} 个学生`)
         sendPaperDialogVisible.value = false
+        // 刷新列表
+        await getList()
     } catch (error) {
         console.error('发送试卷失败:', error)
-        ElMessage.error('发送试卷失败')
+        ElMessage.error(error?.msg || error?.message || '发送试卷失败')
     } finally {
         sending.value = false
     }
@@ -727,5 +783,34 @@ onMounted(() => {
     color: #303133;
     flex: 1;
 }
+
+/* 学生复选框容器 */
+.student-checkbox-container {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 12px;
+}
+
+/* 学生复选框项 - 一行显示3个 */
+.student-checkbox-item {
+    flex: 0 0 calc(33.333% - 8px);
+    min-width: 200px;
+    max-width: calc(33.333% - 8px);
+}
+
+/* 已发送的学生样式 */
+.is-sent {
+    opacity: 0.6;
+}
+
+.text-gray {
+    color: #909399;
+}
+
+.sent-tag {
+    color: #909399;
+    font-size: 12px;
+    margin-left: 4px;
+}
 </style>
 

+ 2 - 1
back-ui/src/views/dz/papers/components/paper-full-intelligent.vue

@@ -115,7 +115,8 @@ const handleSubmit = async (qTypes) => {
         types: qTypes.value.map(t => ({
             type: t.dictValue,
             title: t.dictLabel,
-            count: t.count
+            count: t.count,
+            score: t.score || 0
         })),
         classIds
     }

+ 13 - 7
back-ui/src/views/dz/papers/components/paper-knowledge-hand.vue

@@ -55,7 +55,7 @@ import KnowledgeTree from "@/views/dz/papers/components/plugs/knowledge-tree.vue
 import {useProvidePaperKnowledgeCondition} from "@/views/dz/papers/hooks/usePaperKnowledgeCondition.js";
 import QuestionIntelligent from "@/views/dz/papers/components/plugs/question-intelligent.vue";
 import {ElMessage} from "element-plus";
-import {buildPaperFullIntelligent} from "@/api/dz/papers.js";
+import {buildPaperFullKnowledgeIntelligent} from "@/api/dz/papers.js";
 import BuiltPaperList from "@/views/dz/papers/components/plugs/built-paper-list.vue";
 import IeSelect from '@/components/IeSelect/index.vue'
 import useProvinceExamTypeSubject from '@/views/dz/hooks/useProvinceExamTypeSubject.js'
@@ -104,25 +104,31 @@ const handleSubmit = async (qTypes) => {
     // if (!batchId.value) return ElMessage.error('请选择批次')
     if (!batchId.value) batchId.value = '1'
     if (!knowledgeCheckNodes.value.length) return ElMessage.error('请选择知识点')
-    if (!qTypes.value.length || qTypes.value.every(t => !t.count)) return ElMessage.error('请填写题量')
+    // qTypes 已经是过滤后的数组(count > 0),不需要再过滤
+    if (!qTypes.length || qTypes.every(t => !t.count)) return ElMessage.error('请填写题量')
     // 知识点组卷不校验班级
     const classIds = selectedClasses.value?.map(c => c.classId) || []
 
     // build
+    // 如果 paperExamType 或 paperSubjectId 为空,使用页面中选择的值
+    const finalExamType = toValue(paperExamType) || selectedExamType.value || ''
+    const finalSubjectId = toValue(paperSubjectId) || selectedSubjectId.value || null
+    
     const commit = {
         buildType: type,
         batchId: toValue(batchId),
-        examType: toValue(paperExamType),
-        subjectId: toValue(paperSubjectId),
+        examType: finalExamType,
+        subjectId: finalSubjectId,
         knowledgeIds: knowledgeCheckNodes.value.map(k => k.id),
-        types: qTypes.value.map(t => ({
+        types: qTypes.map(t => ({
             type: t.dictValue,
             title: t.dictLabel,
-            count: t.count
+            count: t.count,
+            score: t.score || 1
         })),
         classIds
     }
-    await buildPaperFullIntelligent(commit)
+    await buildPaperFullKnowledgeIntelligent(commit)
     ElMessage.success('生成成功')
     _loadClassStatistic()
 }

+ 14 - 9
back-ui/src/views/dz/papers/components/plugs/question-intelligent.vue

@@ -11,14 +11,14 @@
     <div class="flex flex-row flex-wrap gap-3">
         <el-tag v-for="k in knowledgeCheckNodes" type="primary" round closable @close="removeKnowledge(k)">{{k.name}}</el-tag>
     </div>
-    
+
     <!-- 批量添加知识点弹窗 -->
     <el-dialog v-model="showBatchDialog" title="批量添加知识点" width="500px">
         <el-form :model="batchForm" label-width="100px">
             <el-form-item label="知识点ID">
-                <el-input 
-                    v-model="batchForm.knowledgeIds" 
-                    type="textarea" 
+                <el-input
+                    v-model="batchForm.knowledgeIds"
+                    type="textarea"
                     :rows="4"
                     placeholder="请输入知识点ID,多个ID用逗号分隔,例如:1,2,3,4"
                 />
@@ -38,7 +38,10 @@
     <div class="flex flex-row flex-wrap gap-10">
         <div v-for="t in qTypes" class="flex items-center gap-3">
             <el-text class="font-bold break-keep">{{t.dictLabel}}</el-text>
-            <el-input-number v-model="t.count" :min="0"/>
+            <el-input-number v-model="t.count" :min="0" :precision="0" style="width: 120px;"/>
+            <el-text class="text-gray-500">题</el-text>
+            <el-input-number v-model="t.score" :min="0" :precision="1" style="width: 120px;" placeholder="分数"/>
+            <el-text class="text-gray-500">分</el-text>
         </div>
     </div>
     <el-divider />
@@ -73,7 +76,7 @@ const handleClearAll = async () => {
         ElMessage.warning('当前没有已选知识点')
         return
     }
-    
+
     try {
         await ElMessageBox.confirm(
             `确定要清空所有 ${knowledgeCheckNodes.value.length} 个已选知识点吗?`,
@@ -101,7 +104,7 @@ const handleBatchSave = async () => {
         ElMessage.warning('请输入知识点ID')
         return
     }
-    
+
     batchLoading.value = true
     try {
         await batchAddKnowledge(batchForm.value.knowledgeIds)
@@ -113,10 +116,12 @@ const handleBatchSave = async () => {
 }
 
 const buildPaper = function () {
-    emits('submit', qTypes)
+    // 过滤掉 count=0 的项
+    const filteredQTypes = qTypes.value.filter(t => t.count > 0)
+    emits('submit', filteredQTypes)
 }
 </script>
 
 <style scoped>
 
-</style>
+</style>

+ 14 - 7
back-ui/src/views/dz/papers/hooks/usePaperQuestionCondition.js

@@ -107,25 +107,32 @@ export const useProvidePaperQuestionCondition = function (exactMode, handMode) {
             const res = await getPaperQuestionTypes(query)
             const newQTypes = res.data || []
             
-            // 保留已有的题型设置(特别是 count 值),只更新题型列表
-            // 如果已有题型设置,则合并新旧题型,保留已有的 count
+            // 保留已有的题型设置(特别是 count 和 score 值),只更新题型列表
+            // 如果已有题型设置,则合并新旧题型,保留已有的 count 和 score
             if (qTypes.value.length > 0) {
                 const existingTypesMap = new Map()
                 qTypes.value.forEach(qt => {
-                    existingTypesMap.set(qt.dictValue, qt.count || 0)
+                    existingTypesMap.set(qt.dictValue, {
+                        count: qt.count || 0,
+                        score: qt.score !== undefined ? qt.score : 0
+                    })
                 })
                 
                 // 合并新题型和已有设置
                 qTypes.value = newQTypes.map(newQt => {
-                    const existingCount = existingTypesMap.get(newQt.dictValue)
+                    const existing = existingTypesMap.get(newQt.dictValue)
                     return {
                         ...newQt,
-                        count: existingCount !== undefined ? existingCount : (newQt.count || 0)
+                        count: existing ? existing.count : (newQt.count || 0),
+                        score: existing ? existing.score : (newQt.score !== undefined ? newQt.score : 0)
                     }
                 })
             } else {
-                // 如果没有已有设置,直接使用新的题型列表
-                qTypes.value = newQTypes
+                // 如果没有已有设置,直接使用新的题型列表,初始化 score 为 0
+                qTypes.value = newQTypes.map(qt => ({
+                    ...qt,
+                    score: qt.score !== undefined ? qt.score : 0
+                }))
             }
             
             // 清空选中的题型

+ 7 - 7
back-ui/src/views/dz/papers/list.vue

@@ -20,10 +20,15 @@ import PaperRecords from "@/views/dz/papers/components/list-paper-records.vue";
 const {loading} = useProvideGlobalLoading()
 
 const tabs = ref([{
+    name: 'PaperRecords',
+    label: '组卷记录',
+    page: markRaw(PaperRecords),
+    visited: true
+}, {
     name: 'ExactIntelligent',
     label: '定向智能',
     page: markRaw(ListExactIntelligent) ,
-    visited: true
+    visited: false
 }, {
     name: 'FullIntelligent',
     label: '全量智能',
@@ -39,13 +44,8 @@ const tabs = ref([{
     label: '全量手动',
     page: markRaw(ListFullHand),
     visited: false
-}, {
-    name: 'PaperRecords',
-    label: '组卷记录',
-    page: markRaw(PaperRecords),
-    visited: false
 }])
-const currentTab = ref('ExactIntelligent')
+const currentTab = ref('PaperRecords')
 
 watch(currentTab, tabName => {
     // 通过visited=true 延迟渲染

+ 48 - 7
ie-admin/src/main/java/com/ruoyi/web/controller/dz/DzCardsController.java

@@ -3,6 +3,7 @@ package com.ruoyi.web.controller.dz;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.stream.Collectors;
 import javax.servlet.http.HttpServletResponse;
 import javax.validation.ValidationException;
@@ -17,10 +18,13 @@ import com.ruoyi.common.utils.NumberUtils;
 import com.ruoyi.common.utils.SecurityUtils;
 import com.ruoyi.common.utils.bean.BeanUtils;
 import com.ruoyi.dz.domain.DzAgent;
+import com.ruoyi.dz.dto.CardStudentDTO;
 import com.ruoyi.dz.service.IDzAgentService;
 import com.ruoyi.enums.CardAction;
 import com.ruoyi.enums.CardType;
 import com.ruoyi.enums.UserTypeEnum;
+import com.ruoyi.learn.domain.LearnTestStudent;
+import com.ruoyi.learn.service.ILearnTestStudentService;
 import com.ruoyi.system.service.ISysUserService;
 import com.ruoyi.web.domain.CardUserBody;
 import com.ruoyi.web.domain.DzCardExport;
@@ -68,6 +72,8 @@ public class DzCardsController extends BaseController
     private SysRegisterService sysRegisterService;
     @Autowired
     private IDzSubjectService dzSubjectService;
+    @Autowired
+    private ILearnTestStudentService learnTestStudentService;
 
     /**
      * 查询学习卡列表
@@ -83,6 +89,41 @@ public class DzCardsController extends BaseController
         return getDataTable(list);
     }
 
+    /**
+     * 查询学习卡学生列表(简化版),发送试卷使用
+     */
+    @PreAuthorize("@ss.hasPermi('dz:cards:list')")
+    @GetMapping("/list2")
+    @ApiOperation("学生列表")
+    public TableDataInfo list2(DzCards dzCards,Long paperId)
+    {
+        prepare(dzCards);
+        startPage();
+        List<CardStudentDTO> list = dzCardsService.selectDzCardsList3(dzCards);
+
+        // 查询已发布的学生ID列表(根据paperId)
+        Set<Long> publishedStudentIds = new java.util.HashSet<>();
+        if (paperId != null) {
+            LearnTestStudent queryCondition = new LearnTestStudent();
+            queryCondition.setPaperId(paperId);
+            List<LearnTestStudent> publishedStudents = learnTestStudentService.selectLearnTestStudentList(queryCondition);
+            publishedStudentIds = publishedStudents.stream()
+                    .map(LearnTestStudent::getStudentId)
+                    .collect(Collectors.toSet());
+        }
+
+        // 设置 isSend 字段
+        for (CardStudentDTO dto : list) {
+            if (dto.getUserId() != null && publishedStudentIds.contains(dto.getUserId())) {
+                dto.setIsSend(true);
+            } else {
+                dto.setIsSend(false);
+            }
+        }
+
+        return getDataTable(list);
+    }
+
     private DzCards prepare(DzCards dzCards) {
         SysUser sysUser = SecurityUtils.getLoginUser().getUser();
         String userType = sysUser.getUserType();
@@ -462,22 +503,22 @@ public class DzCardsController extends BaseController
         if (!params.containsKey("count") || params.get("count") == null) {
             throw new ValidationException("次数不能为空");
         }
-        
+
         // 获取参数
         Long userId = Long.valueOf(params.get("userId").toString());
         Long subjectId = Long.valueOf(params.get("subjectId").toString());
         Integer count = Integer.valueOf(params.get("count").toString());
-        
+
         // 根据userId查询用户
         SysUser sysUser = sysUserService.selectUserById(userId);
         if(sysUser == null) {
             throw new ValidationException("用户不存在");
         }
-        
+
         // 获取eval_counts字段
         String evalCounts = sysUser.getEvalCounts();
         JSONObject evalCountsJson;
-        
+
         if(StringUtils.isBlank(evalCounts)) {
             // 如果为空,创建新的JSON对象
             evalCountsJson = new JSONObject();
@@ -488,14 +529,14 @@ public class DzCardsController extends BaseController
                 evalCountsJson = new JSONObject();
             }
         }
-        
+
         // 更新对应subjectId的值
         evalCountsJson.put(String.valueOf(subjectId), count);
-        
+
         // 更新到数据库(使用updateUserInfo只更新基本信息,不影响角色和岗位)
         sysUser.setEvalCounts(evalCountsJson.toJSONString());
         sysUserService.updateUserInfo(sysUser);
-        
+
         return AjaxResult.success();
     }
 

+ 29 - 5
ie-admin/src/main/java/com/ruoyi/web/controller/learn/LearnTeacherController.java

@@ -32,6 +32,7 @@ import io.swagger.annotations.ApiParam;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.ibatis.annotations.Param;
 import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.util.CollectionUtils;
 import org.springframework.web.bind.annotation.*;
 
@@ -302,7 +303,7 @@ public class LearnTeacherController extends BaseController {
         req.setBuildType("FullHand");
         req.setDirectType(false);
         if(null == req.getSubjectId()) {
-            AjaxResult.error("未选择科目");
+            return AjaxResult.error("未选择科目");
         }
         req.setTeacherId(getTeacherId());
         return AjaxResult.success(learnTeacherService.buildPapersFull(req));
@@ -316,18 +317,41 @@ public class LearnTeacherController extends BaseController {
         req.setBuildType("FullIntelligent");
         req.setDirectType(false);
         if(null == req.getSubjectId()) {
-            AjaxResult.error("未选择科目");
+            req.setSubjectId(11L);
+//            return AjaxResult.error("未选择科目");
         }
         req.setTeacherId(getTeacherId());
         return AjaxResult.success(learnTeacherService.buildPapersFullByKnowledge(req));
     }
 
     @PreAuthorize("@ss.hasPermi('learn:test_paper:send')")
-    @PostMapping("/build/sendPaper")
+    @GetMapping("/build/sendPaper")
     @ApiOperation("发送试卷")
-    public AjaxResult buildFullKnowledgeIntelligent(@Param("paperId") Long testPaperId)
+    public AjaxResult sendPaper(@RequestParam("paperId") Long testPaperId, @RequestParam(value = "cardIds", required = false) String cardIdsStr)
     {
-        return AjaxResult.success(learnTeacherService.sendPaper(testPaperId));
+        if (testPaperId == null) {
+            return AjaxResult.error("试卷ID不能为空");
+        }
+
+        // 解析 cardIds 字符串为 List<Long>
+        List<Long> cardIds = new java.util.ArrayList<>();
+        if (cardIdsStr != null && !cardIdsStr.isEmpty()) {
+            cardIds = Arrays.stream(cardIdsStr.split(","))
+                    .map(String::trim)
+                    .filter(cardId -> !cardId.isEmpty())
+                    .map(cardId -> {
+                        try {
+                            return Long.parseLong(cardId);
+                        } catch (NumberFormatException e) {
+                            return null;
+                        }
+                    })
+                    .filter(Objects::nonNull)
+                    .collect(Collectors.toList());
+        }
+
+        // Service 方法接收的是 testPaperId(learn_test_paper.id),不是 paperId
+        return AjaxResult.success(learnTeacherService.sendPaper(testPaperId, cardIds));
     }
 
     @GetMapping("/universities")

+ 26 - 11
ie-admin/src/main/java/com/ruoyi/web/service/LearnTeacherService.java

@@ -7,18 +7,18 @@ import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 import com.ruoyi.common.annotation.Excel;
+import com.ruoyi.common.core.domain.AjaxResult;
 import com.ruoyi.common.core.domain.entity.SysUser;
 import com.ruoyi.common.enums.ExamType;
 import com.ruoyi.common.enums.SubjectType;
 import com.ruoyi.common.utils.CommonUtils;
 import com.ruoyi.common.utils.NumberUtils;
 import com.ruoyi.common.utils.SecurityUtils;
-import com.ruoyi.dz.domain.DzClasses;
-import com.ruoyi.dz.domain.DzControl;
-import com.ruoyi.dz.domain.DzSubject;
-import com.ruoyi.dz.domain.DzTeacher;
+import com.ruoyi.dz.domain.*;
+import com.ruoyi.dz.dto.CardStudentDTO;
 import com.ruoyi.dz.mapper.DzClassesMapper;
 import com.ruoyi.dz.mapper.DzTeacherMapper;
+import com.ruoyi.dz.service.IDzCardsService;
 import com.ruoyi.dz.service.IDzControlService;
 import com.ruoyi.dz.service.IDzSubjectService;
 import com.ruoyi.enums.ExamineeStatus;
@@ -39,12 +39,14 @@ import org.apache.commons.lang3.ArrayUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.tuple.Pair;
 import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.CollectionUtils;
 
 import javax.validation.ValidationException;
 import java.util.*;
+import java.util.Objects;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
@@ -69,6 +71,8 @@ public class LearnTeacherService {
     private final DzTeacherMapper dzTeacherMapper;
     private final SysUserMapper sysUserMapper;
     private final IDzControlService dzControlService;
+    @Autowired
+    private IDzCardsService dzCardsService;
 
     public LearnTeacherService(DzClassesMapper dzClassesMapper, LearnKnowledgeTreeMapper learnKnowledgeTreeMapper, LearnStudentMapper learnStudentMapper, AMarjorPlanMapper marjorPlanMapper, BBusiWishUniversitiesMapper busiWishUniversitiesMapper, LearnDirectedKnowledgeMapper learnDirectedKnowledgeMapper, PaperService paperService, CommService commService, LearnTestPaperMapper learnTestPaperMapper, LearnQuestionsMapper learnQuestionsMapper, LearnKnowledgeCourseMapper learnKnowledgeCourseMapper, LearnTestStudentMapper learnTestStudentMapper, IDzSubjectService dzSubjectService, ILearnTestService learnTestService, LearnAnswerMapper learnAnswerMapper, DzTeacherMapper dzTeacherMapper, SysUserMapper sysUserMapper, LearnCultureKnowledgeMapper learnCultureKnowledgeMapper, IDzControlService dzControlService) {
         this.dzClassesMapper = dzClassesMapper;
@@ -737,6 +741,7 @@ public class LearnTeacherService {
         paper.setDirectKey("");
         paper.setStatus(PaperStatus.Valid.getVal());
         paper.setRelateId(req.getBatchId().longValue());
+        paper.setPaperInfo(JSONObject.toJSONString(req));
         paper.setFenshu(0);
         if("FullHand".equals(req.getBuildType())) {
             saveHandPaper(paper, req.getQuestions());
@@ -768,7 +773,7 @@ public class LearnTeacherService {
         DzSubject dzSubject = dzSubjectService.selectDzSubjectBySubjectId(req.getSubjectId());
         LearnTest learnTest = learnTestService.selectLearnTestByBatchId(req.getBatchId());
         // 生成试卷
-        LearnTestPaper testPaper = getPaper( req, dzSubject, learnTest);
+        LearnTestPaper testPaper = getPaper( req, dzSubject, learnTest);
 
         updateBuildInfo(req.getTeacherId(), new TestRecordCond(req.getBuildType(), req.getBatchId(), req.getClassId(), req.getSubjectId(), null, null));
         return "";
@@ -780,12 +785,22 @@ public class LearnTeacherService {
      * @param paperId
      * @return
      */
-    public String sendPaper(Long testPaperId) {
+    public String sendPaper(Long testPaperId,List<Long> cardIds) {
         LearnTestPaper testPaper = learnTestPaperMapper.selectLearnTestPaperById(testPaperId);
+        if (null == testPaper){
+            throw new ValidationException("试卷不存在");
+        }
         Long paperId = testPaper.getPaperId();
 
-        TestPaperVO.TestPaperBuildReq req = new TestPaperVO.TestPaperBuildReq();
-        List<LearnStudent> studentList = learnStudentMapper.selectLearnStudentsByMap(req.toMap());
+        if (CollectionUtils.isEmpty(cardIds)) {
+            throw new ValidationException("未选择发送的学生");
+        }
+
+        // 通过 cardIds 查询用户,然后通过 userId 查询学生
+        DzCards dzCards = new DzCards();
+        dzCards.setCardIds(cardIds);
+        List<CardStudentDTO> studentList = dzCardsService.selectDzCardsList3(dzCards);
+
         if(CollectionUtils.isEmpty(studentList)) {
             throw new ValidationException("无学生可发布");
         }
@@ -799,15 +814,15 @@ public class LearnTeacherService {
                 .collect(Collectors.toSet());
 
 //        LearnTestPaper learnTestPaper = learnTestPaperMapper.selectLearnTestPaperByPaperId(paperId);
-        for(LearnStudent student : studentList) {
+        for(CardStudentDTO student : studentList) {
             // 如果学生已经发布过,跳过
-            if (publishedStudentIds.contains(student.getStudentId())) {
+            if (publishedStudentIds.contains(student.getUserId())) {
                 continue;
             }
 
             LearnTestStudent lts = new LearnTestStudent();
             lts.setBatchId(testPaper.getBatchId());
-            lts.setStudentId(student.getStudentId());
+            lts.setStudentId(student.getUserId());
             lts.setBuildType(testPaper.getBuildType());
             lts.setSubjectId(testPaper.getSubjectId());
             lts.setDirectKey("");

+ 12 - 0
ie-system/src/main/java/com/ruoyi/dz/domain/DzCards.java

@@ -1,6 +1,8 @@
 package com.ruoyi.dz.domain;
 
 import java.util.Date;
+import java.util.List;
+
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import org.apache.commons.lang3.builder.ToStringBuilder;
@@ -21,6 +23,8 @@ public class DzCards extends BaseEntity
     /** ID */
     private Long cardId;
 
+    private List<Long> cardIds;
+
     /** 账号 */
     @Excel(name = "账号")
     private String cardNo;
@@ -188,6 +192,14 @@ public class DzCards extends BaseEntity
     /** 考生专业类别 **/
     private String examMajorName;
 
+    public List<Long> getCardIds() {
+        return cardIds;
+    }
+
+    public void setCardIds(List<Long> cardIds) {
+        this.cardIds = cardIds;
+    }
+
     public Integer getExamMajor() {
         return examMajor;
     }

+ 176 - 0
ie-system/src/main/java/com/ruoyi/dz/dto/CardStudentDTO.java

@@ -0,0 +1,176 @@
+package com.ruoyi.dz.dto;
+
+import java.io.Serializable;
+
+/**
+ * 学习卡学生信息DTO
+ * 用于 selectDzCardsList3 查询结果
+ * 
+ * @author ruoyi
+ */
+public class CardStudentDTO implements Serializable
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 卡片ID */
+    private Long cardId;
+
+    /** 卡号 */
+    private String cardNo;
+
+    /** 校区ID */
+    private Long campusId;
+
+    /** 校区班级ID */
+    private Long campusClassId;
+
+    /** 学校ID */
+    private Long schoolId;
+
+    /** 班级ID */
+    private Long classId;
+
+    /** 用户ID */
+    private Long userId;
+
+    /** 昵称 */
+    private String nickName;
+
+    /** 用户名 */
+    private String userName;
+
+    /** 手机号 */
+    private String phonenumber;
+
+    /** 是否已发送 */
+    private Boolean isSend;
+
+    public Long getCardId()
+    {
+        return cardId;
+    }
+
+    public void setCardId(Long cardId)
+    {
+        this.cardId = cardId;
+    }
+
+    public String getCardNo()
+    {
+        return cardNo;
+    }
+
+    public void setCardNo(String cardNo)
+    {
+        this.cardNo = cardNo;
+    }
+
+    public Long getCampusId()
+    {
+        return campusId;
+    }
+
+    public void setCampusId(Long campusId)
+    {
+        this.campusId = campusId;
+    }
+
+    public Long getCampusClassId()
+    {
+        return campusClassId;
+    }
+
+    public void setCampusClassId(Long campusClassId)
+    {
+        this.campusClassId = campusClassId;
+    }
+
+    public Long getSchoolId()
+    {
+        return schoolId;
+    }
+
+    public void setSchoolId(Long schoolId)
+    {
+        this.schoolId = schoolId;
+    }
+
+    public Long getClassId()
+    {
+        return classId;
+    }
+
+    public void setClassId(Long classId)
+    {
+        this.classId = classId;
+    }
+
+    public Long getUserId()
+    {
+        return userId;
+    }
+
+    public void setUserId(Long userId)
+    {
+        this.userId = userId;
+    }
+
+    public String getNickName()
+    {
+        return nickName;
+    }
+
+    public void setNickName(String nickName)
+    {
+        this.nickName = nickName;
+    }
+
+    public String getUserName()
+    {
+        return userName;
+    }
+
+    public void setUserName(String userName)
+    {
+        this.userName = userName;
+    }
+
+    public String getPhonenumber()
+    {
+        return phonenumber;
+    }
+
+    public void setPhonenumber(String phonenumber)
+    {
+        this.phonenumber = phonenumber;
+    }
+
+    public Boolean getIsSend()
+    {
+        return isSend;
+    }
+
+    public void setIsSend(Boolean isSend)
+    {
+        this.isSend = isSend;
+    }
+
+    @Override
+    public String toString()
+    {
+        return "CardStudentDTO{" +
+                "cardId=" + cardId +
+                ", cardNo='" + cardNo + '\'' +
+                ", campusId=" + campusId +
+                ", campusClassId=" + campusClassId +
+                ", schoolId=" + schoolId +
+                ", classId=" + classId +
+                ", userId=" + userId +
+                ", nickName='" + nickName + '\'' +
+                ", userName='" + userName + '\'' +
+                ", phonenumber='" + phonenumber + '\'' +
+                ", isSend=" + isSend +
+                '}';
+    }
+}
+

+ 1 - 0
ie-system/src/main/java/com/ruoyi/dz/mapper/DzCardsMapper.java

@@ -32,6 +32,7 @@ public interface DzCardsMapper
      */
     public List<DzCards> selectDzCardsList(DzCards dzCards);
     public List<DzCards> selectDzCardsList2(DzCards dzCards);
+    public List<com.ruoyi.dz.dto.CardStudentDTO> selectDzCardsList3(DzCards dzCards);
 
     public DzCards selectOneECard();
     /**

+ 1 - 0
ie-system/src/main/java/com/ruoyi/dz/service/IDzCardsService.java

@@ -38,6 +38,7 @@ public interface IDzCardsService
      */
     public List<DzCards> selectDzCardsList(DzCards dzCards);
     public List<DzCards> selectDzCardsList2(DzCards dzCards);
+    public List<com.ruoyi.dz.dto.CardStudentDTO> selectDzCardsList3(DzCards dzCards);
 
     /**
      * 新增学习卡

+ 6 - 0
ie-system/src/main/java/com/ruoyi/dz/service/impl/DzCardsServiceImpl.java

@@ -161,6 +161,12 @@ public class DzCardsServiceImpl implements IDzCardsService
         return list;
     }
 
+    @Override
+    public List<com.ruoyi.dz.dto.CardStudentDTO> selectDzCardsList3(DzCards dzCards)
+    {
+        return dzCardsMapper.selectDzCardsList3(dzCards);
+    }
+
     /**
      * 查询学习卡列表
      *

+ 1 - 0
ie-system/src/main/java/com/ruoyi/learn/domain/TestPaperVO.java

@@ -83,6 +83,7 @@ public class TestPaperVO {
                     TestPaperVO.TypeDef typeDef = new TestPaperVO.TypeDef();
                     BeanUtils.copyProperties(typeDef2, typeDef);
                     typeDef.setType(typeDef.getTitle());
+                    typeDef.setScore(typeDef2.getScore().intValue());
                     if(null == typeDef.getScore()) {
                         typeDef.setScore(1);
                     }

+ 36 - 0
ie-system/src/main/resources/mapper/dz/DzCardsMapper.xml

@@ -53,6 +53,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <include refid="selectDzCardsVo"/>
         <where>
             <if test="cardNo != null  and cardNo != ''"> and card_no = #{cardNo}</if>
+            <if test="cardIds != null and cardIds.size() > 0"> and card_id in 
+                <foreach item="cardId" collection="cardIds" open="(" separator="," close=")">
+                    #{cardId}
+                </foreach>
+            </if>
             <if test="password != null  and password != ''"> and password = #{password}</if>
             <if test="type != null "> and type = #{type}</if>
             <if test="status != null "> and status = #{status}</if>
@@ -112,6 +117,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         from dz_cards c left join sys_user u on c.`card_id` = u.`card_id`
         <where>
             <if test="cardNo != null  and cardNo != ''"> and c.card_no = #{cardNo}</if>
+            <if test="cardIds != null and cardIds.size() > 0"> and c.card_id in 
+                <foreach item="cardId" collection="cardIds" open="(" separator="," close=")">
+                    #{cardId}
+                </foreach>
+            </if>
             <if test="password != null  and password != ''"> and c.password = #{password}</if>
             <if test="type != null and type == 1 "> and c.type in (6, 2) </if>
             <if test="type != null and type != 1 "> and c.type = #{type}</if>
@@ -175,6 +185,27 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         order by card_id desc
     </select>
 
+    <select id="selectDzCardsList3" parameterType="DzCards" resultType="com.ruoyi.dz.dto.CardStudentDTO">
+        select c.card_id as cardId, c.card_no as cardNo, c.campus_id as campusId, c.campus_class_id as campusClassId, 
+               c.school_id as schoolId, c.class_id as classId, u.user_id as userId, 
+               u.nick_name as nickName, u.user_name as userName, u.phonenumber
+        from dz_cards c left join sys_user u on c.`card_id` = u.`card_id`
+        <where>
+            <if test="cardNo != null  and cardNo != ''"> and c.card_no = #{cardNo}</if>
+            <if test="cardIds != null and cardIds.size() > 0"> and c.card_id in 
+                <foreach item="cardId" collection="cardIds" open="(" separator="," close=")">
+                    #{cardId}
+                </foreach>
+            </if>
+            <if test="leafAgentId != null "> and c.leaf_agent_id = #{leafAgentId}</if>
+            <if test="campusId != null "> and c.campus_id = #{campusId}</if>
+            <if test="campusClassId != null "> and c.campus_class_id = #{campusClassId}</if>
+            <if test="schoolId != null "> and c.school_id = #{schoolId}</if>
+            <if test="classId != null "> and c.class_id = #{classId}</if>
+        </where>
+        order by c.card_id desc
+    </select>
+
     <select id="selectDzCardsByCardId" parameterType="Long" resultMap="DzCardsResult">
         <include refid="selectDzCardsVo"/>
         where card_id = #{cardId}
@@ -383,6 +414,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="startNo != null  and startNo != ''"> and card_no &gt;= #{startNo}</if>
             <if test="endNo != null  and endNo != ''"> and card_no &lt;= #{endNo}</if>
             <if test="cardNo != null  and cardNo != ''"> and card_no = #{cardNo}</if>
+            <if test="cardIds != null and cardIds.size() > 0"> and card_id in 
+                <foreach item="cardId" collection="cardIds" open="(" separator="," close=")">
+                    #{cardId}
+                </foreach>
+            </if>
             <if test="password != null  and password != ''"> and password = #{password}</if>
             <if test="type != null "> and type = #{type}</if>
             <if test="status != null "> and status = #{status}</if>