Explorar el Código

卡增加模拟次数
paper组卷

jinxia.mo hace 3 semanas
padre
commit
faa8dfca30

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

@@ -252,3 +252,21 @@ export function updateCardUser(data) {
     data: data,
   });
 }
+
+// 获取用户模拟考试列表
+export function getUserSimulateList(query) {
+  return request({
+    url: "/dz/cards/getUserSimulateList",
+    method: "get",
+    params: query,
+  });
+}
+
+// 更新用户模拟考试次数
+export function updateUserEvalCounts(data) {
+  return request({
+    url: "/dz/cards/updateUserEvalCounts",
+    method: "post",
+    data: data,
+  });
+}

+ 127 - 0
back-ui/src/views/dz/cards/components/CardTable.vue

@@ -138,6 +138,11 @@
             <span>{{ scope.row.payTime ? parseTime(scope.row.payTime, '{y}-{m}-{d}') : '-' }}</span>
           </template>
         </el-table-column>
+        <el-table-column label="模考次数" prop="simulate" align="center" min-width="100" :fixed="hideActions ? 'right' : undefined">
+          <template #default="scope">
+            <el-button type="primary" link @click="handleShowSimulate(scope.row.cardId)">查看</el-button>
+          </template>
+        </el-table-column>
         <el-table-column v-if="!hideActions" label="操作" min-width="110" fixed="right" align="center">
           <template #default="scope">
             <el-button type="danger" text icon="Delete" @click="handleDelete(scope.row)">删除</el-button>
@@ -164,11 +169,48 @@
         <el-table-column label="专业类" prop="majorAncestors" min-width="200" align="center"></el-table-column>
       </el-table>
     </el-dialog>
+    
+    <!-- 模拟考试弹窗 -->
+    <el-dialog v-model="simulateDialogVisible" title="模考次数" width="600px">
+      <el-table :data="simulateList" class="w-full" style="width: 100%" v-loading="simulateLoading">
+        <el-table-column label="科目ID" prop="subjectId" width="100" align="center"></el-table-column>
+        <el-table-column label="科目名称" prop="subjectName" min-width="150" align="center"></el-table-column>
+        <el-table-column label="次数" prop="count" width="100" align="center"></el-table-column>
+        <el-table-column label="操作" width="100" align="center">
+          <template #default="scope">
+            <el-button type="primary" link @click="handleEditSimulateCount(scope.row)">修改</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+      <div v-if="!simulateLoading && simulateList.length === 0" style="text-align: center; color: #909399; padding: 20px;">
+        暂无模拟考试记录
+      </div>
+    </el-dialog>
+    
+    <!-- 修改次数弹窗 -->
+    <el-dialog v-model="editCountDialogVisible" title="修改次数" width="400px">
+      <el-form :model="editCountForm" label-width="100px">
+        <el-form-item label="科目">
+          <el-input v-model="editCountForm.subjectName" readonly></el-input>
+        </el-form-item>
+        <el-form-item label="次数" prop="count">
+          <el-input-number v-model="editCountForm.count" :min="0" :precision="0" style="width: 100%"></el-input-number>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="editCountDialogVisible = false">取 消</el-button>
+          <el-button type="primary" @click="handleSaveCount" :loading="savingCount">保 存</el-button>
+        </div>
+      </template>
+    </el-dialog>
   </div>
 </template>
 <script setup>
 import DictTag from '@/components/DictTag/index.vue';
 import { getCurrentInstance, ref } from 'vue';
+import { getUserSimulateList, updateUserEvalCounts } from '@/api/dz/cards';
+import { ElMessage } from 'element-plus';
 const { proxy } = getCurrentInstance();
 const { card_type, exam_type, card_distribute_status, card_status, card_settlement_status, card_time_status, card_pay_status } = 
 proxy.useDict("card_type", "exam_type", "card_distribute_status", "card_status", "card_settlement_status", "card_time_status", "card_pay_status");
@@ -292,6 +334,91 @@ const handleShowDirectedStudy = (row) => {
   directedStudyList.value = getDirectedStudyList(row);
   directedStudyDialogVisible.value = true;
 };
+
+// 模拟考试弹窗相关
+const simulateDialogVisible = ref(false);
+const simulateList = ref([]);
+const simulateLoading = ref(false);
+const currentCardId = ref(null);
+
+// 修改次数弹窗相关
+const editCountDialogVisible = ref(false);
+const editCountForm = ref({
+  userId: null,
+  subjectId: null,
+  subjectName: '',
+  count: 0
+});
+const savingCount = ref(false);
+
+// 显示模拟考试弹窗
+const handleShowSimulate = async (cardId) => {
+  if (!cardId) {
+    ElMessage.warning('卡号不存在')
+    return
+  }
+  
+  currentCardId.value = cardId
+  simulateDialogVisible.value = true
+  simulateLoading.value = true
+  simulateList.value = []
+  
+  try {
+    const response = await getUserSimulateList({ cardId })
+    if (response && response.data) {
+      simulateList.value = response.data
+    } else {
+      simulateList.value = []
+    }
+  } catch (error) {
+    console.error('获取模拟考试数据失败:', error)
+    ElMessage.error('获取模拟考试数据失败')
+    simulateList.value = []
+  } finally {
+    simulateLoading.value = false
+  }
+};
+
+// 显示修改次数弹窗
+const handleEditSimulateCount = (row) => {
+  editCountForm.value = {
+    userId: row.userId,
+    subjectId: row.subjectId,
+    subjectName: row.subjectName,
+    count: row.count || 0
+  };
+  editCountDialogVisible.value = true;
+};
+
+// 保存次数
+const handleSaveCount = async () => {
+  if (editCountForm.value.count < 0) {
+    ElMessage.warning('次数不能为负数')
+    return
+  }
+  
+  savingCount.value = true
+  try {
+    await updateUserEvalCounts({
+      userId: editCountForm.value.userId,
+      subjectId: editCountForm.value.subjectId,
+      count: editCountForm.value.count
+    })
+    ElMessage.success('修改成功')
+    editCountDialogVisible.value = false
+    
+    // 更新本地列表数据
+    const item = simulateList.value.find(item => item.subjectId === editCountForm.value.subjectId)
+    if (item) {
+      item.count = editCountForm.value.count
+    }
+  } catch (error) {
+    console.error('修改次数失败:', error)
+    ElMessage.error('修改次数失败')
+  } finally {
+    savingCount.value = false
+  }
+};
 </script>
 <style lang="scss" scoped>
 /* 在弹窗中使用时的样式 */

+ 20 - 1
back-ui/src/views/dz/cards/components/EditDialog.vue

@@ -173,6 +173,7 @@ const selectedExamMajor = computed({
 })
 
 const {
+  area: schoolArea,
   schoolList,
   selectedSchool,
   classList,
@@ -181,6 +182,9 @@ const {
   selectedCampus,
   campusClassList,
   selectedCampusClass,
+  getAreaList: getSchoolAreaList,
+  getSchoolList,
+  getCampusList,
   reset: resetSchool
 } = useSchool({ autoLoad: false, loadClass: true, loadCampus: true, loadCampusClass: true });
 
@@ -259,7 +263,22 @@ const getUserInfo = (cardInfo) => {
       subjectId: res.data.examMajor
     })
     
-    // 设置其他字段
+    // 加载学校相关的省份列表,并设置省份选择
+    await getSchoolAreaList()
+    // 根据 location 找到对应的 areaId
+    if (area.selectedItem && area.selectedItem.areaId) {
+      schoolArea.selected = area.selectedItem.areaId
+    } else if (area.selected) {
+      schoolArea.selected = Array.isArray(area.selected) ? area.selected[area.selected.length - 1] : area.selected
+    }
+    
+    // 加载学校列表和培训学校列表
+    if (schoolArea.selected) {
+      await getSchoolList()
+      await getCampusList()
+    }
+    
+    // 设置其他字段(在加载完列表后再设置,确保下拉框有数据)
     selectedCampus.value = res.data.campusSchoolId;
     selectedCampusClass.value = res.data.campusClassId;
     selectedSchool.value = res.data.schoolId;

+ 239 - 2
back-ui/src/views/dz/papers/components/list-paper-records.vue

@@ -65,6 +65,8 @@
             </template>
             <template #download="{row}">
                 <el-button type="primary" link @click="handleDownload(row.paperId, row.paperName)" v-hasPermi="['learn:paper:download']">下载</el-button>
+                <!-- <el-button type="success" link @click="handleShowPaperQuestion(row.paperId)">题目</el-button> -->
+                <el-button type="warning" link @click="handleSendPaper(row)">发送</el-button>
             </template>
         </Table>
         
@@ -108,12 +110,87 @@
                 </div>
             </div>
         </el-dialog>
+        
+        <!-- 试卷题目弹窗 -->
+        <el-dialog v-model="paperQuestionDialogVisible" title="试卷题目" width="90%" destroy-on-close>
+            <div style="height: 70vh; overflow: auto;">
+                <QuestionsComponent v-if="paperQuestionDialogVisible" :paper-id="currentPaperId" :hide-actions="true" />
+            </div>
+        </el-dialog>
+        
+        <!-- 发送试卷弹窗 -->
+        <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>
+                <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>
+                        </el-checkbox-group>
+                        <div v-if="!studentListLoading && studentList.length === 0" style="text-align: center; color: #909399; padding: 20px;">
+                            请先选择学校和班级
+                        </div>
+                    </div>
+                </el-form-item>
+            </el-form>
+            <template #footer>
+                <div class="dialog-footer">
+                    <el-button @click="sendPaperDialogVisible = false">取 消</el-button>
+                    <el-button type="primary" @click="handleConfirmSend" :loading="sending">确 定</el-button>
+                </div>
+            </template>
+        </el-dialog>
 </template>
 
 <script setup name="ListPaperRecords">
-import { ref, computed, getCurrentInstance, onMounted } from 'vue'
+import { ref, computed, getCurrentInstance, onMounted, markRaw } from 'vue'
 import { useInjectGlobalLoading } from "@/views/hooks/useGlobalLoading.js"
 import { getPapers, getFrontPaperDetail } 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 { ElMessage } from 'element-plus'
 import Table from '@/components/Table/index.vue'
 import DictTag from '@/components/DictTag/index.vue'
@@ -122,6 +199,8 @@ import { listToMap } from "@/utils/index.js"
 import { useProvidePaperBatchCondition } from "@/views/dz/papers/hooks/usePaperBatchCondition.js"
 import { usePaperDownload } from "@/views/dz/papers/hooks/usePaperDownload.js"
 import { parseTime } from "@/utils/ruoyi.js"
+import Pagination from '@/components/Pagination/index.vue'
+import QuestionsComponent from '@/views/learn/questions/index.vue'
 
 const { proxy } = getCurrentInstance()
 const { exam_type } = proxy.useDict("exam_type")
@@ -165,6 +244,24 @@ const questionList = ref([])
 const currentPaperName = ref('')
 const currentSubjectName = ref('')
 
+// 试卷题目弹窗
+const paperQuestionDialogVisible = ref(false)
+const currentPaperId = ref(null)
+
+// 发送试卷弹窗
+const sendPaperDialogVisible = ref(false)
+const sendPaperForm = ref({
+    schoolId: null,
+    classId: null
+})
+const schoolList = ref([])
+const classList = ref([])
+const studentList = ref([])
+const selectedStudentIds = ref([])
+const studentListLoading = ref(false)
+const sending = ref(false)
+const currentPaperRow = ref(null)
+
 
 // 获取题数/总分显示
 const getQuestionInfo = (row) => {
@@ -263,6 +360,146 @@ const getOptionLabel = (index) => {
     return labels[index] || String(index + 1)
 }
 
+// 显示试卷题目
+const handleShowPaperQuestion = (paperId) => {
+    if (!paperId) {
+        ElMessage.warning('试卷ID不存在')
+        return
+    }
+    
+    currentPaperId.value = paperId
+    paperQuestionDialogVisible.value = true
+}
+
+// 显示发送试卷弹窗
+const handleSendPaper = (row) => {
+    if (!row || !row.paperId) {
+        ElMessage.warning('试卷信息不存在')
+        return
+    }
+    
+    currentPaperRow.value = row
+    sendPaperForm.value = {
+        schoolId: 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 = []
+    }
+}
+
+// 学校变化时加载班级列表
+const handleSchoolChange = async (schoolId) => {
+    sendPaperForm.value.classId = null
+    classList.value = []
+    studentList.value = []
+    selectedStudentIds.value = []
+    
+    if (!schoolId) {
+        return
+    }
+    
+    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 = []
+    }
+}
+
+// 班级变化时加载学生列表
+const handleClassChange = async (classId) => {
+    studentList.value = []
+    selectedStudentIds.value = []
+    
+    if (!classId || !sendPaperForm.value.schoolId) {
+        return
+    }
+    
+    studentListLoading.value = true
+    try {
+        const response = await listStudent({
+            schoolId: sendPaperForm.value.schoolId,
+            classId: classId,
+            pageNum: 1,
+            pageSize: 1000
+        })
+        if (response && response.rows) {
+            studentList.value = response.rows
+        } else {
+            studentList.value = []
+        }
+    } catch (error) {
+        console.error('加载学生列表失败:', error)
+        ElMessage.error('加载学生列表失败')
+        studentList.value = []
+    } finally {
+        studentListLoading.value = false
+    }
+}
+
+// 确认发送
+const handleConfirmSend = async () => {
+    if (!currentPaperRow.value || !currentPaperRow.value.paperId) {
+        ElMessage.warning('试卷信息不存在')
+        return
+    }
+    
+    if (!sendPaperForm.value.schoolId) {
+        ElMessage.warning('请选择学校')
+        return
+    }
+    
+    if (!sendPaperForm.value.classId) {
+        ElMessage.warning('请选择班级')
+        return
+    }
+    
+    if (!selectedStudentIds.value || selectedStudentIds.value.length === 0) {
+        ElMessage.warning('请至少选择一个学生')
+        return
+    }
+    
+    sending.value = true
+    try {
+        // TODO: 调用发送试卷的API
+        // 这里需要根据实际的API接口来实现
+        ElMessage.success(`试卷已发送给 ${selectedStudentIds.value.length} 个学生`)
+        sendPaperDialogVisible.value = false
+    } catch (error) {
+        console.error('发送试卷失败:', error)
+        ElMessage.error('发送试卷失败')
+    } finally {
+        sending.value = false
+    }
+}
+
 // 表格列定义
 const columns = [
     { label: 'ID', prop: 'id', width: 80 },
@@ -276,7 +513,7 @@ const columns = [
     { label: '题数/总分', prop: 'questionInfo', width: 100, type: 'slot', slotName: 'questionInfo' },
     { label: '时长(分钟)', prop: 'duration', width: 120, type: 'slot', slotName: 'duration' },
     { label: '创建时间', prop: 'createTime', width: 160, type: 'slot', slotName: 'createTime' },
-    { label: '操作', prop: 'download', width: 80, type: 'slot', slotName: 'download' }
+    { label: '操作', prop: 'download', width: 150, type: 'slot', slotName: 'download' }
 ]
 
 // 操作按钮

+ 5 - 4
back-ui/src/views/dz/papers/components/paper-knowledge-hand.vue

@@ -5,7 +5,7 @@
         </el-col>
         <el-col :span="18">
             <el-form label-width="68px">
-<!--                <BatchYearSelect v-model:batch-id="batchId" :batch-list="batchList" />-->
+                <BatchYearSelect v-model:batch-id="batchId" :batch-list="batchList" />
                 <el-row :gutter="20">
                     <el-col :span="8">
                         <el-form-item label="省份">
@@ -101,11 +101,12 @@ const hasBuiltPaper = computed(() => built.value?.hasPaper)
 
 const handleSubmit = async (qTypes) => {
     // validation
-    if (!batchId.value) return ElMessage.error('请选择批次')
+    // 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('请填写题量')
-    const classIds = selectedClasses.value.map(c => c.classId)
-    if (!classIds.length) return ElMessage.error('请选择班级')
+    // 知识点组卷不校验班级
+    const classIds = selectedClasses.value?.map(c => c.classId) || []
 
     // build
     const commit = {

+ 31 - 6
back-ui/src/views/learn/questions/index.vue

@@ -1,6 +1,6 @@
 <template>
     <div class="app-container">
-        <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch" label-width="68px">
+        <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch && !hideActions" label-width="68px">
             <el-form-item label="题目ID" prop="id">
                 <el-input v-model.number="queryParams.id" type="number" placeholder="请输入题目ID" clearable
                           @keyup.enter="handleQuery"/>
@@ -61,7 +61,7 @@
             </el-form-item>
         </el-form>
 
-        <el-row :gutter="10" class="mb8">
+        <el-row :gutter="10" class="mb8" v-if="!hideActions">
             <el-col :span="1.5">
                 <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['learn:questions:add']">新增
                 </el-button>
@@ -228,7 +228,7 @@
                 </template>
             </el-table-column>
             <el-table-column label="操作" align="center" class-name="small-padding fixed-width" min-width="140"
-                             fixed="right">
+                             fixed="right" v-if="!hideActions">
                 <template #default="scope">
                     <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)"
                                v-hasPermi="['learn:questions:edit']">修改
@@ -541,7 +541,7 @@
 
         <!-- 知识点列表管理弹窗 -->
         <el-dialog v-model="showKnowledgeTreesDialog" title="知识点管理" width="80%" append-to-body class="knowledge-trees-dialog">
-            <el-row :gutter="10" class="mb8">
+            <el-row :gutter="10" class="mb8" v-if="!hideActions">
                 <el-col :span="1.5">
                     <el-button type="primary" plain icon="Plus" @click="handleAddKnowledgeQuestion">新增</el-button>
                 </el-col>
@@ -565,7 +565,7 @@
                         <span v-else>-</span>
                     </template>
                 </el-table-column>
-                <el-table-column label="操作" align="center" class-name="small-padding fixed-width" min-width="100">
+                <el-table-column label="操作" align="center" class-name="small-padding fixed-width" min-width="100" v-if="!hideActions">
                     <template #default="scope">
                         <el-button link type="primary" icon="Edit" @click="handleUpdateKnowledgeQuestion(scope.row)">修改</el-button>
                     </template>
@@ -620,9 +620,21 @@ import {listKnowledgeQuestion, getKnowledgeQuestion, delKnowledgeQuestion, addKn
 import {ElMessage, ElLoading} from "element-plus";
 import DictTag from '@/components/DictTag/index.vue'
 import Editor from '@/components/Editor/index.vue'
-import { computed } from 'vue'
+import { computed, watch } from 'vue'
 import { getImageProxyUrl, formatContentWithImages } from '@/utils/imageHelper'
 
+// 定义 props
+const props = defineProps({
+    paperId: {
+        type: [Number, String],
+        default: null
+    },
+    hideActions: {
+        type: Boolean,
+        default: false
+    }
+})
+
 const {proxy} = getCurrentInstance()
 const {question_type} = proxy.useDict('question_type')
 const {paper_type} = proxy.useDict('paper_type')
@@ -1307,8 +1319,21 @@ function submitKnowledgeQuestionForm() {
     })
 }
 
+// 监听 paperId 变化,自动设置查询参数并查询
+watch(() => props.paperId, (newPaperId) => {
+    if (newPaperId) {
+        queryParams.value.paperId = newPaperId
+        queryParams.value.pageNum = 1
+        getList()
+    }
+}, { immediate: true })
+
 getKnowledgeTreeList()
 getSubjectList()
+// 如果传入了 paperId,则设置查询参数
+if (props.paperId) {
+    queryParams.value.paperId = props.paperId
+}
 getList()
 </script>
 

+ 139 - 1
ie-admin/src/main/java/com/ruoyi/web/controller/dz/DzCardsController.java

@@ -41,6 +41,7 @@ import com.ruoyi.common.core.domain.AjaxResult;
 import com.ruoyi.common.enums.BusinessType;
 import com.ruoyi.dz.domain.DzCards;
 import com.ruoyi.dz.service.IDzCardsService;
+import com.ruoyi.dz.service.IDzSubjectService;
 import com.ruoyi.common.utils.poi.ExcelUtil;
 import com.ruoyi.common.core.page.TableDataInfo;
 
@@ -65,6 +66,8 @@ public class DzCardsController extends BaseController
     private SysLoginService sysLoginService;
     @Autowired
     private SysRegisterService sysRegisterService;
+    @Autowired
+    private IDzSubjectService dzSubjectService;
 
     /**
      * 查询学习卡列表
@@ -166,7 +169,7 @@ public class DzCardsController extends BaseController
      */
     @PreAuthorize("@ss.hasPermi('dz:cards:remove')")
     @Log(title = "学习卡", businessType = BusinessType.DELETE)
-	@DeleteMapping("/{cardIds}")
+    @DeleteMapping("/{cardIds}")
     public AjaxResult remove(@PathVariable Long[] cardIds)
     {
         return toAjax(dzCardsService.deleteDzCardsByCardIds(cardIds));
@@ -361,6 +364,141 @@ public class DzCardsController extends BaseController
         return success(cardUserBody);
     }
 
+    /**
+     * 获取用户模拟考试列表
+     */
+    @GetMapping("/getUserSimulateList")
+    @ApiOperation("获取用户模拟考试列表")
+    @PreAuthorize("@ss.hasPermi('dz:cards:list')")
+    public AjaxResult getUserSimulateList(@RequestParam("cardId") Long cardId)
+    {
+        // 根据cardId查询userId
+        List<SysUser> sysUserList = sysUserService.selectUserByCardIds(Lists.newArrayList(cardId));
+        if(CollectionUtils.isEmpty(sysUserList)) {
+            return AjaxResult.success(new java.util.ArrayList<>());
+        }
+        SysUser sysUser = sysUserList.get(0);
+
+        // 获取eval_counts字段
+        String evalCounts = sysUser.getEvalCounts();
+        if(StringUtils.isBlank(evalCounts)) {
+            return AjaxResult.success(new java.util.ArrayList<>());
+        }
+
+        // 解析JSON
+        JSONObject evalCountsJson = JSONObject.parseObject(evalCounts);
+        if(evalCountsJson == null || evalCountsJson.isEmpty()) {
+            return AjaxResult.success(new java.util.ArrayList<>());
+        }
+
+        // 获取所有subjectId
+        java.util.Set<String> subjectIdSet = evalCountsJson.keySet();
+        if(subjectIdSet.isEmpty()) {
+            return AjaxResult.success(new java.util.ArrayList<>());
+        }
+
+        // 转换为Long数组
+        Long[] subjectIds = subjectIdSet.stream()
+                .filter(StringUtils::isNotBlank)
+                .map(Long::parseLong)
+                .toArray(Long[]::new);
+
+        // 查询科目信息
+        List<com.ruoyi.dz.domain.DzSubject> subjectList = dzSubjectService.selectDzSubjectBySubjectIds(subjectIds);
+        java.util.Map<Long, String> subjectNameMap = subjectList.stream()
+                .collect(java.util.stream.Collectors.toMap(
+                    com.ruoyi.dz.domain.DzSubject::getSubjectId,
+                    com.ruoyi.dz.domain.DzSubject::getSubjectName,
+                    (v1, v2) -> v1
+                ));
+
+        // 构建返回结果
+        List<java.util.Map<String, Object>> resultList = new java.util.ArrayList<>();
+        for(String subjectIdStr : subjectIdSet) {
+            try {
+                Long subjectId = Long.parseLong(subjectIdStr);
+                Integer count = evalCountsJson.getInteger(subjectIdStr);
+                if(count == null) {
+                    count = 0;
+                }
+
+                java.util.Map<String, Object> item = new java.util.HashMap<>();
+                item.put("subjectId", subjectId);
+                item.put("subjectName", subjectNameMap.getOrDefault(subjectId, "未知科目"));
+                item.put("count", count);
+                item.put("userId", sysUser.getUserId());
+                resultList.add(item);
+            } catch(NumberFormatException e) {
+                // 忽略无效的subjectId
+                continue;
+            }
+        }
+
+        // 按subjectId排序
+        resultList.sort((a, b) -> {
+            Long idA = (Long) a.get("subjectId");
+            Long idB = (Long) b.get("subjectId");
+            return idA.compareTo(idB);
+        });
+
+        return AjaxResult.success(resultList);
+    }
+
+    /**
+     * 更新用户模拟考试次数
+     */
+    @PostMapping("/updateUserEvalCounts")
+    @ApiOperation("更新用户模拟考试次数")
+    @PreAuthorize("@ss.hasPermi('dz:cards:list')")
+    public AjaxResult updateUserEvalCounts(@RequestBody Map<String, Object> params)
+    {
+        // 参数验证
+        if (params == null || !params.containsKey("userId") || params.get("userId") == null) {
+            throw new ValidationException("用户ID不能为空");
+        }
+        if (!params.containsKey("subjectId") || params.get("subjectId") == null) {
+            throw new ValidationException("科目ID不能为空");
+        }
+        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();
+        } else {
+            // 解析JSON
+            evalCountsJson = JSONObject.parseObject(evalCounts);
+            if(evalCountsJson == null) {
+                evalCountsJson = new JSONObject();
+            }
+        }
+        
+        // 更新对应subjectId的值
+        evalCountsJson.put(String.valueOf(subjectId), count);
+        
+        // 更新到数据库(使用updateUserInfo只更新基本信息,不影响角色和岗位)
+        sysUser.setEvalCounts(evalCountsJson.toJSONString());
+        sysUserService.updateUserInfo(sysUser);
+        
+        return AjaxResult.success();
+    }
+
     /**
      * 统计学习卡数据
      */

+ 9 - 0
ie-admin/src/main/java/com/ruoyi/web/controller/learn/LearnTeacherController.java

@@ -30,6 +30,7 @@ import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 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.util.CollectionUtils;
 import org.springframework.web.bind.annotation.*;
@@ -321,6 +322,14 @@ public class LearnTeacherController extends BaseController {
         return AjaxResult.success(learnTeacherService.buildPapersFullByKnowledge(req));
     }
 
+    @PreAuthorize("@ss.hasPermi('learn:test_paper:send')")
+    @PostMapping("/build/sendPaper")
+    @ApiOperation("发送试卷")
+    public AjaxResult buildFullKnowledgeIntelligent(@Param("paperId") Long testPaperId)
+    {
+        return AjaxResult.success(learnTeacherService.sendPaper(testPaperId));
+    }
+
     @GetMapping("/universities")
     @ApiOperation("院校列表")
     public AjaxResult universities(@ApiParam("批次ID") Long batchId, @RequestParam @ApiParam("考生类型") ExamType examType)

+ 2 - 2
ie-admin/src/main/java/com/ruoyi/web/controller/learn/LearnTestPaperController.java

@@ -25,7 +25,7 @@ import com.ruoyi.common.core.page.TableDataInfo;
 
 /**
  * 批次测试卷Controller
- * 
+ *
  * @author ruoyi
  * @date 2025-09-18
  */
@@ -67,7 +67,7 @@ public class LearnTestPaperController extends BaseController
      */
     @PreAuthorize("@ss.hasPermi('learn:test_paper:query')")
     @GetMapping(value = "/{id}")
-    public AjaxResult getInfo(@PathVariable("id") String id)
+    public AjaxResult getInfo(@PathVariable("id") Long id)
     {
         return success(learnTestPaperService.selectLearnTestPaperById(id));
     }

+ 22 - 5
ie-admin/src/main/java/com/ruoyi/web/service/LearnTeacherService.java

@@ -734,7 +734,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 "";
@@ -746,19 +746,36 @@ public class LearnTeacherService {
      * @param paperId
      * @return
      */
-    public String sendPaper(TestPaperVO.TestPaperBuildReq req,Long paperId) {
+    public String sendPaper(Long testPaperId) {
+        LearnTestPaper testPaper = learnTestPaperMapper.selectLearnTestPaperById(testPaperId);
+        Long paperId = testPaper.getPaperId();
+
+        TestPaperVO.TestPaperBuildReq req = new TestPaperVO.TestPaperBuildReq();
         List<LearnStudent> studentList = learnStudentMapper.selectLearnStudentsByMap(req.toMap());
         if(CollectionUtils.isEmpty(studentList)) {
             throw new ValidationException("无学生可发布");
         }
 
+        // 查询已发布的学生ID列表(根据paperId)
+        LearnTestStudent queryCondition = new LearnTestStudent();
+        queryCondition.setPaperId(paperId);
+        List<LearnTestStudent> publishedStudents = learnTestStudentMapper.selectLearnTestStudentList(queryCondition);
+        Set<Long> publishedStudentIds = publishedStudents.stream()
+                .map(LearnTestStudent::getStudentId)
+                .collect(Collectors.toSet());
+
 //        LearnTestPaper learnTestPaper = learnTestPaperMapper.selectLearnTestPaperByPaperId(paperId);
         for(LearnStudent student : studentList) {
+            // 如果学生已经发布过,跳过
+            if (publishedStudentIds.contains(student.getStudentId())) {
+                continue;
+            }
+
             LearnTestStudent lts = new LearnTestStudent();
-            lts.setBatchId(req.getBatchId());
+            lts.setBatchId(testPaper.getBatchId());
             lts.setStudentId(student.getStudentId());
-            lts.setBuildType(req.getBuildType());
-            lts.setSubjectId(req.getSubjectId());
+            lts.setBuildType(testPaper.getBuildType());
+            lts.setSubjectId(testPaper.getSubjectId());
             lts.setDirectKey("");
             lts.setPaperId(paperId);
             lts.setCampusId(student.getCampusId());

+ 8 - 8
ie-system/src/main/java/com/ruoyi/learn/domain/LearnTestPaper.java

@@ -7,7 +7,7 @@ import com.ruoyi.common.core.domain.BaseEntity;
 
 /**
  * 批次测试卷对象 learn_test_paper
- * 
+ *
  * @author ruoyi
  * @date 2025-09-18
  */
@@ -16,7 +16,7 @@ public class LearnTestPaper extends BaseEntity
     private static final long serialVersionUID = 1L;
 
     /** 批次卷id */
-    private String id;
+    private Long id;
 
     /** 批次id */
     @Excel(name = "批次id")
@@ -58,12 +58,12 @@ public class LearnTestPaper extends BaseEntity
     @Excel(name = "创建人")
     private Long creatorId;
 
-    public void setId(String id) 
+    public void setId(Long id)
     {
         this.id = id;
     }
 
-    public String getId() 
+    public Long getId()
     {
         return id;
     }
@@ -115,17 +115,17 @@ public class LearnTestPaper extends BaseEntity
         this.directKey = directKey;
     }
 
-    public String getDirectKey() 
+    public String getDirectKey()
     {
         return directKey;
     }
 
-    public void setPaperId(Long paperId) 
+    public void setPaperId(Long paperId)
     {
         this.paperId = paperId;
     }
 
-    public Long getPaperId() 
+    public Long getPaperId()
     {
         return paperId;
     }
@@ -143,7 +143,7 @@ public class LearnTestPaper extends BaseEntity
         this.creatorId = creatorId;
     }
 
-    public Long getCreatorId() 
+    public Long getCreatorId()
     {
         return creatorId;
     }

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

@@ -50,6 +50,8 @@ public class TestPaperVO {
         List<Long> knowledgeIds;
         List<TypeDef2> types;
 
+        List<Long> studentIds;
+
         // 定向手动+全量手动
         @ApiModelProperty("名称")
         String name;

+ 1 - 1
ie-system/src/main/java/com/ruoyi/learn/mapper/LearnTestPaperMapper.java

@@ -21,7 +21,7 @@ public interface LearnTestPaperMapper
      * @param id 批次测试卷主键
      * @return 批次测试卷
      */
-    public LearnTestPaper selectLearnTestPaperById(String id);
+    public LearnTestPaper selectLearnTestPaperById(Long id);
     public LearnTestPaper selectLearnTestPaperByPaperId(Long paperId);
 
     /**

+ 1 - 1
ie-system/src/main/java/com/ruoyi/learn/service/ILearnTestPaperService.java

@@ -17,7 +17,7 @@ public interface ILearnTestPaperService
      * @param id 批次测试卷主键
      * @return 批次测试卷
      */
-    public LearnTestPaper selectLearnTestPaperById(String id);
+    public LearnTestPaper selectLearnTestPaperById(Long id);
     public LearnTestPaper selectLearnTestPaperByPaperId(Long paperId);
 
     /**

+ 1 - 1
ie-system/src/main/java/com/ruoyi/learn/service/impl/LearnTestPaperServiceImpl.java

@@ -27,7 +27,7 @@ public class LearnTestPaperServiceImpl implements ILearnTestPaperService
      * @return 批次测试卷
      */
     @Override
-    public LearnTestPaper selectLearnTestPaperById(String id)
+    public LearnTestPaper selectLearnTestPaperById(Long id)
     {
         return learnTestPaperMapper.selectLearnTestPaperById(id);
     }

+ 3 - 1
ie-system/src/main/resources/mapper/learn/LearnQuestionsMapper.xml

@@ -71,7 +71,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="answer1 != null  and answer1 != ''"> and answer1 = #{answer1}</if>
             <if test="answer2 != null  and answer2 != ''"> and answer2 = #{answer2}</if>
             <if test="qtpye != null  and qtpye != ''"> and qtpye = #{qtpye}</if>
-            <if test="paperId != null "> and paperId = #{paperId}</if>
+            <if test="paperId != null ">
+                and EXISTS ( SELECT 1 FROM learn_paper_question pq WHERE pq.question_id = learn_questions.id AND pq.paper_id = #{paperId} )
+            </if>
             <if test="subjectId != null or knowledgeId != null ">
                 and EXISTS ( SELECT 1 FROM learn_knowledge_question lkq WHERE lkq.question_id = learn_questions.id AND (
                           EXISTS (SELECT 1 FROM learn_knowledge_tree lkt

+ 1 - 1
ie-system/src/main/resources/mapper/learn/LearnTestPaperMapper.xml

@@ -47,7 +47,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         </where>
     </select>
 
-    <select id="selectLearnTestPaperById" parameterType="String" resultMap="LearnTestPaperResult">
+    <select id="selectLearnTestPaperById" parameterType="Long" resultMap="LearnTestPaperResult">
         <include refid="selectLearnTestPaperVo"/>
         where id = #{id}
     </select>