Przeglądaj źródła

Merge branch 'master' of http://49.234.186.218:9000/root/ieplus

month-red-love 1 miesiąc temu
rodzic
commit
46ca9f2a29

+ 84 - 1
back-ui/src/api/dz/papers.js

@@ -55,4 +55,87 @@ export function getPaperMajors(query) {
         method: 'get',
         params: query
     })
-}
+}
+
+/// 试卷批次
+export function getPaperBatches() {
+    return request({
+        url: '/learn/test/list',
+        method: 'get'
+    })
+}
+
+/// 试卷题型
+export function getPaperQuestionTypes(query) {
+    // query: {subjectId, majorPlanId}
+    return request({
+        url: '/learn/teaching/questionTypes',
+        method: 'get',
+        params: query
+    })
+}
+
+// 试卷试题
+export function getPaperQuestions(query) {
+    // query: {...}
+    return request({
+        url: '/learn/questions/list',
+        method: 'get',
+        params: query
+    })
+}
+
+// 班级
+export function getPaperClasses(query) {
+    return request({
+        url: '/learn/teaching/classes',
+        method: 'get',
+        params: query
+    })
+}
+
+// 学生
+export function getPaperStudents(query) {
+    return request({
+        url: '/learn/teaching/students',
+        method: 'get',
+        params: query
+    })
+}
+
+// 自动组卷
+export function buildPaperAuto(data) {
+    return request({
+        url: '/learn/teaching/build/auto',
+        method: 'post',
+        data
+    })
+}
+
+// 手工组卷
+export function buildPaperManual(data) {
+    return request({
+        url: '/learn/teaching/build/manual',
+        method: 'post',
+        data
+    })
+}
+
+// 发布试卷
+export function publishPaper(data) {
+    return request({
+        url: '/learn/teaching/publish/paper',
+        method: 'post',
+        data
+    })
+}
+
+// 批量发布试卷
+export function publishPapers(data) {
+    return request({
+        url: '/learn/teaching/publish/papers',
+        method: 'post',
+        data
+    })
+}
+

+ 36 - 0
back-ui/src/views/components/question-content.vue

@@ -0,0 +1,36 @@
+<template>
+    <div v-html="question.title"/>
+    <div v-if="!hiddenOptions" class="flex flex-wrap justify-between mt-2">
+        <div v-for="op in options" v-html="op"/>
+    </div>
+    <div class="px-5">
+        <el-divider border-style="dashed"/>
+    </div>
+    <div class="flex items-center gap-10 px-5">
+        <div class="text-sm">ID:{{ question.id }}</div>
+        <div class="text-sm">题型:{{ question.qtpye }}</div>
+        <el-link type="primary" @click="emits('parse')">查看解析</el-link>
+        <slot/>
+    </div>
+</template>
+
+<script setup name="QuestionContent">
+const emits = defineEmits(['parse'])
+const props = defineProps({
+    question: Object,
+    hiddenOptions: Boolean
+})
+const options = computed(() => {
+    const results = []
+    const codes = ['A', 'B', 'C', 'D', 'E', 'F', 'G']
+    codes.forEach(code => {
+        const op = props.question['option' + code]
+        if (op) results.push(code + '、' + op)
+    })
+    return results
+})
+</script>
+
+<style scoped>
+
+</style>

+ 16 - 2
back-ui/src/views/dz/cards/components/ApplyCardDialog.vue

@@ -44,7 +44,7 @@
             style="width: 100%"
           >
             <el-option
-              v-for="school in schoolList"
+              v-for="school in campusList"
               :key="school.id"
               :label="school.name"
               :value="school.id"
@@ -74,6 +74,7 @@
 import { ref, computed, watch } from "vue";
 import { requestOpenCard } from "@/api/dz/cards";
 import { listUniversity } from "@/api/dz/school";
+import { associateCampus, getCampusList } from "@/api/dz/cards";
 
 const props = defineProps({
   modelValue: {
@@ -96,6 +97,7 @@ const visible = computed({
 const applyCardFormRef = ref();
 const loading = ref(false);
 const schoolList = ref([]);
+const campusList = ref([]);
 
 const form = ref({
   beginCardNo: "",
@@ -111,6 +113,18 @@ const rules = {
   schoolId: [{ required: true, message: "学校不能为空", trigger: "change" }],
 };
 
+// 获取校区列表
+async function getCampusListData() {
+  try {
+    const response = await getCampusList({ pageNum: 1, pageSize: 1000 });
+    if (response.code === 200) {
+      campusList.value = response.data || [];
+    }
+  } catch (error) {
+    console.error("获取校区列表失败:", error);
+  }
+}
+
 // 获取所有学校列表
 async function getAllSchools() {
   try {
@@ -221,7 +235,7 @@ watch(visible, (newVal) => {
   if (!newVal) {
     resetForm();
   } else {
-    getAllSchools();
+    getCampusListData();
     autoFillCardRange();
   }
 });

+ 5 - 6
back-ui/src/views/dz/cards/index.vue

@@ -103,7 +103,7 @@
         <el-button
           type="primary"
           plain
-          :disabled="multiple"
+
           @click="handleAssociateCampus"
           v-hasPermi="['dz:cards:add']"
           style="border-color: #1890ff; color: #1890ff; font-weight: 500"
@@ -116,7 +116,6 @@
         <el-button
           type="success"
           plain
-          :disabled="multiple"
           @click="handleApplyCard"
           v-hasPermi="['dz:cards:add']"
           style="border-color: #52c41a; color: #52c41a; font-weight: 500"
@@ -805,8 +804,8 @@ function handleRefundConfirm(cardNo) {
 /** 关联校区按钮操作 */
 function handleAssociateCampus() {
   if (selectedRows.value.length === 0) {
-    proxy.$modal.msgWarning("请选择要关联校区的卡片");
-    return;
+    // proxy.$modal.msgWarning("请选择要关联校区的卡片");
+    // return;
   }
   associateCampusOpen.value = true;
 }
@@ -841,8 +840,8 @@ async function getAgentListData() {
 /** 申请开卡按钮操作 */
 function handleApplyCard() {
   if (selectedRows.value.length === 0) {
-    proxy.$modal.msgWarning("请选择要申请开卡的卡片");
-    return;
+    // proxy.$modal.msgWarning("请选择要申请开卡的卡片");
+    // return;
   }
   applyCardOpen.value = true;
 }

+ 7 - 0
back-ui/src/views/dz/papers/components/paper-exact-conditions.vue

@@ -11,6 +11,12 @@
             <el-option v-for="t in examTypes" :label="t.dictLabel" :value="t.dictValue"/>
         </el-select>
     </div>
+    <div class="flex flex-row items-center gap-2 mt-5">
+        <span :class="labelClass">批次:</span>
+        <el-select v-model="batchId" style="width: 280px">
+            <el-option v-for="b in batchList" :label="b.name" :value="b.batchId"/>
+        </el-select>
+    </div>
     <div class="flex flex-row items-center gap-2 mt-5">
         <span :class="labelClass">院校:</span>
         <el-select v-model="universityId" style="width: 280px">
@@ -37,6 +43,7 @@ import {useInjectPaperExactCondition} from "@/views/dz/papers/hooks/usePaperExac
 const labelClass = 'break-keep text-sm font-bold w-[80px]'
 const {location, provinces,
     examType, examTypes,
+    batchId, batchList,
     universityId, universities,
     majorPlanId, majors,
     subjectId, subjects} = useInjectPaperExactCondition()

+ 7 - 1
back-ui/src/views/dz/papers/components/paper-full-conditions.vue

@@ -5,12 +5,18 @@
             <el-radio-button v-for="s in subjects" :label="s.subjectName" :value="s.subjectId"/>
         </el-radio-group>
     </div>
+    <div class="flex flex-row items-center gap-2 mt-5">
+        <span class="break-keep text-sm font-bold">批次:</span>
+        <el-select v-model="batchId" style="width: 280px">
+            <el-option v-for="b in batchList" :label="b.name" :value="b.batchId"/>
+        </el-select>
+    </div>
 </template>
 
 <script setup name="PaperFullConditions">
 import {useInjectPaperFullCondition} from "@/views/dz/papers/hooks/usePaperFullCondition.js";
 
-const {subjectId, subjects} = useInjectPaperFullCondition()
+const {subjectId, subjects, batchId, batchList} = useInjectPaperFullCondition()
 </script>
 
 <style scoped>

+ 4 - 1
back-ui/src/views/dz/papers/components/paper-hand-exact.vue

@@ -4,13 +4,16 @@
         <el-aside width="350px">
             <paper-knowledge-tree exact-mode/>
         </el-aside>
-        <el-main></el-main>
+        <el-main>
+            <paper-question-hand exact-mode/>
+        </el-main>
     </el-container>
 </template>
 
 <script setup name="PaperHandExact">
 import PaperExactConditions from "@/views/dz/papers/components/paper-exact-conditions.vue";
 import PaperKnowledgeTree from "@/views/dz/papers/components/paper-knowledge-tree.vue";
+import PaperQuestionHand from "@/views/dz/papers/components/paper-question-hand.vue";
 import {useProvidePaperExactCondition} from "@/views/dz/papers/hooks/usePaperExactCondition.js";
 
 useProvidePaperExactCondition()

+ 6 - 3
back-ui/src/views/dz/papers/components/paper-hand-full.vue

@@ -1,10 +1,12 @@
 <template>
-    <paper-full-conditions />
+    <paper-full-conditions/>
     <el-container class="mt-5">
         <el-aside width="350px">
             <paper-knowledge-tree/>
         </el-aside>
-        <el-main></el-main>
+        <el-main>
+            <paper-question-hand/>
+        </el-main>
     </el-container>
 </template>
 
@@ -12,8 +14,9 @@
 import PaperFullConditions from "@/views/dz/papers/components/paper-full-conditions.vue";
 import {useProvidePaperFullCondition} from "@/views/dz/papers/hooks/usePaperFullCondition.js";
 import PaperKnowledgeTree from "@/views/dz/papers/components/paper-knowledge-tree.vue";
+import PaperQuestionHand from "@/views/dz/papers/components/paper-question-hand.vue";
 
-const {subjectId, knowledgeId} = useProvidePaperFullCondition()
+useProvidePaperFullCondition()
 </script>
 
 <style scoped>

+ 5 - 6
back-ui/src/views/dz/papers/components/paper-knowledge-tree.vue

@@ -1,9 +1,9 @@
 <template>
     <el-input v-model="keyword" placeholder="请输入知识点名称" clearable prefix-icon="Search"
               style="margin-bottom: 20px"/>
-    <el-tree :data="knowledges" :props="{ label: 'name', children: 'children' }" :expand-on-click-node="false"
-             :filter-node-method="filterNode" node-key="id" highlight-current default-expand-all
-             :show-checkbox="allowMultiple" @node-click="handleNodeClick" @check="handleNodeCheck"/>
+    <el-tree v-if="knowledges.length" :data="knowledges" :props="{ label: 'name', children: 'children' }"
+             :expand-on-click-node="false" :filter-node-method="filterNode" node-key="id" highlight-current
+             default-expand-all :show-checkbox="allowMultiple" @node-click="handleNodeClick" @check="handleNodeCheck"/>
 </template>
 
 <script setup name="PaperKnowledgeTree">
@@ -16,7 +16,7 @@ const props = defineProps({
 })
 
 const keyword = ref('')
-const {knowledgeId, knowledges, knowledgeCheckNodes} = props.exactMode
+const {knowledges, knowledgeNode, knowledgeCheckNodes} = props.exactMode
     ? useInjectPaperExactCondition()
     : useInjectPaperFullCondition()
 
@@ -26,13 +26,12 @@ const filterNode = function (value, data) {
 }
 
 const handleNodeClick = function (data) {
-    knowledgeId.value = data.id
+    knowledgeNode.value = data
 }
 
 const handleNodeCheck = function (data, {checkedNodes}) {
     // 多选时,只保留叶子节点
     knowledgeCheckNodes.value = checkedNodes.filter(k => !!!k.children?.length)
-    console.log('knowledgeCheckNodes', knowledgeCheckNodes.value)
 }
 </script>
 

+ 97 - 0
back-ui/src/views/dz/papers/components/paper-question-hand.vue

@@ -0,0 +1,97 @@
+<template>
+    <div v-if="knowledgeNode" class="text-xs mb-2">当前查询知识点:<strong>{{ knowledgeNode.name }}</strong></div>
+    <div class="flex flex-row items-center gap-3">
+        <el-input v-model="keywordLocal" clearable prefix-icon="search" placeholder="输入题目关键字-回车触发搜索"
+                  style="width: 250px" @keydown.enter="confirmKeyword" @clear="confirmKeyword"/>
+        <el-select v-model="qtpye" clearable style="width: 220px">
+            <el-option v-for="t in qTypes" :value="t.dictValue" :label="t.dictLabel"></el-option>
+        </el-select>
+        <el-popover width="250">
+            <span v-if="currentSubject" class="text-sm font-bold">科目:{{ currentSubject.subjectName }}</span>
+            <div v-for="g in groupedQuestions" class="flex flex-col">
+                <el-divider style="margin: 10px 0"/>
+                <span>{{ g.qtpye }}:{{ g.questions.length }}道</span>
+                <el-link type="danger" plain icon="delete" class="self-end" @click="removeQuestionGroup(g.qtpye)">清除
+                </el-link>
+            </div>
+            <el-divider style="margin: 10px 0"/>
+            <div class="flex items-center justify-between">
+                <el-link type="danger" plain icon="delete" @click="clearCart">全部清除</el-link>
+                <el-button type="primary">生成试卷</el-button>
+            </div>
+            <template #reference>
+                <el-button type="primary" size="large" icon="shopping-cart" class="ml-auto">试题篮({{
+                        cart.length
+                    }})
+                </el-button>
+            </template>
+        </el-popover>
+    </div>
+    <el-divider/>
+    <el-empty v-if="total==0"/>
+    <template v-else v-for="q in questionList">
+        <question-content :question="q" @parse="showParseQuestion=q,showParse=true">
+            <el-button v-if="!hasQuestion(q)" type="primary" icon="plus" class="ml-auto" @click="addQuestion(q)">
+                加入试题篮
+            </el-button>
+            <el-button v-else type="danger" plain icon="delete" class="ml-auto" @click="removeQuestion(q)">移出试题篮
+            </el-button>
+        </question-content>
+        <el-divider/>
+    </template>
+    <pagination v-show="total>0" :total="total" v-model:page="pageNum" v-model:limit="pageSize"
+                @pagination="getQuestionList"/>
+
+    <el-dialog v-model="showParse" append-to-body show-close @close="showParse=false">
+        <template #title>ID:{{ showParseQuestion.id }} 试题解析</template>
+        <div v-if="showParseQuestion.answer0" v-html="`【正确答案】` + showParseQuestion.answer0"/>
+        <div v-if="showParseQuestion.parse" v-html="`【解析】` + showParseQuestion.parse" class="mt-5"></div>
+        <div v-if="showParseQuestion.parse0" v-html="`【解析】` + showParseQuestion.parse0" class="mt-5"></div>
+        <template #footer>
+            <el-button type="primary" @click="showParse=false">确 定</el-button>
+        </template>
+    </el-dialog>
+</template>
+
+<script setup name="PaperQuestionHand">
+
+import {useProvidePaperQuestionCondition} from "@/views/dz/papers/hooks/usePaperQuestionCondition.js";
+import {useInjectPaperExactCondition} from "@/views/dz/papers/hooks/usePaperExactCondition.js";
+import {useInjectPaperFullCondition} from "@/views/dz/papers/hooks/usePaperFullCondition.js";
+import QuestionContent from "@/views/components/question-content.vue";
+
+const props = defineProps({
+    allowMultiple: Boolean,
+    exactMode: Boolean
+})
+
+const showParse = ref(false)
+const showParseQuestion = ref(null)
+
+const {knowledgeNode} = props.exactMode ? useInjectPaperExactCondition() : useInjectPaperFullCondition()
+const {
+    keyword,
+    qtpye,
+    qTypes,
+    pageNum,
+    pageSize,
+    total,
+    questionList,
+    getQuestionList,
+    cart,
+    groupedQuestions,
+    currentSubject,
+    hasQuestion,
+    addQuestion,
+    removeQuestion,
+    removeQuestionGroup,
+    clearCart
+} = useProvidePaperQuestionCondition(props.exactMode, props.allowMultiple)
+
+const keywordLocal = ref('')
+const confirmKeyword = (val) => keyword.value = keywordLocal.value
+</script>
+
+<style scoped>
+
+</style>

+ 27 - 13
back-ui/src/views/dz/papers/hooks/usePaperExactCondition.js

@@ -1,5 +1,6 @@
 import {injectLocal, provideLocal} from "@vueuse/core";
 import {
+    getPaperBatches,
     getPaperExamTypes,
     getPaperKnowledges,
     getPaperMajors,
@@ -11,6 +12,9 @@ import {
 const key = Symbol('PaperExactCondition')
 
 export const useProvidePaperExactCondition = function () {
+    const batchId = ref('')
+    const batchList = ref([])
+
     const location = ref('')
     const provinces = ref([])
     const examType = ref('')
@@ -23,13 +27,15 @@ export const useProvidePaperExactCondition = function () {
     const subjects = ref([])
     const subjectId = ref('')
     const knowledges = ref([])
-    const knowledgeId = ref('') // 单选
+    const knowledgeNode = ref(null) // 单选节点
     const knowledgeCheckNodes = ref([]) // 多选的节点
     const knowledgeIds = computed(() => knowledgeCheckNodes.value.map(k => k.id))
+    const knowledgeId = computed(() => knowledgeNode.value?.id || '') // 单选
 
     const payload = {
-        location, provinces, examType, examTypes, universityId, universities, majorPlanId, majors,
-        subjects, subjectId, knowledges, knowledgeId, knowledgeCheckNodes, knowledgeIds
+        location, provinces, examType, examTypes, universityId, universities,
+        batchId, batchList, majorPlanId, majors, subjects, subjectId,
+        knowledges, knowledgeNode, knowledgeId, knowledgeCheckNodes, knowledgeIds
     }
     provideLocal(key, payload)
 
@@ -38,27 +44,35 @@ export const useProvidePaperExactCondition = function () {
         const res = await getPaperProvinces()
         provinces.value = res.data
     })
-    watch(location, async () => {
+    onMounted(async () => {
+        const res = await getPaperBatches()
+        batchList.value = res.data
+    })
+    watch(location, async (location) => {
         // clean
         examType.value = ''
         examTypes.value = []
-        universityId.value = ''
-        universities.value = []
 
-        if (!location.value) return
-        const resT = await getPaperExamTypes({location: toValue(location)})
+        if (!location) return
+        const resT = await getPaperExamTypes({location})
         examTypes.value = resT.data
+    })
+    watch([location, batchId], async ([location, batchId]) => {
+        // clean
+        universityId.value = ''
+        universities.value = []
 
-        const resU = await getPaperUniversities({location: toValue(location)})
+        if (!location || !batchId) return
+        const resU = await getPaperUniversities({location, batchId})
         universities.value = resU.data
     })
-    watch([examType, universityId], async ([examType, universityId]) => {
+    watch([location, examType, batchId, universityId], async ([location, examType, batchId, universityId]) => {
         // clean
         majorPlanId.value = ''
         majors.value = []
 
-        if (!examType || !universityId) return
-        const res = await getPaperMajors({location: toValue(location), examType, universityId})
+        if (!location || !examType || !batchId || !universityId) return
+        const res = await getPaperMajors({location, examType, batchId, universityId})
         majors.value = res.data
         if (res.data.length) majorPlanId.value = res.data[0].id
     })
@@ -75,7 +89,7 @@ export const useProvidePaperExactCondition = function () {
     watch([majorPlanId, subjectId], async ([majorPlanId, subjectId]) => {
         // clean
         knowledges.value = []
-        knowledgeId.value = '' // 单选的情况
+        knowledgeNode.value = null // 单选的情况
         knowledgeCheckNodes.value = [] // 多选的情况
 
         if (!subjectId || !majorPlanId) return

+ 17 - 6
back-ui/src/views/dz/papers/hooks/usePaperFullCondition.js

@@ -1,18 +1,25 @@
 import {injectLocal, provideLocal} from "@vueuse/core";
-import {getPaperKnowledges, getPaperSubjects} from "@/api/dz/papers.js";
+import {getPaperBatches, getPaperKnowledges, getPaperSubjects} from "@/api/dz/papers.js";
 import {watch} from "vue";
 
 const key = Symbol('PaperFullCondition')
 
 export const useProvidePaperFullCondition = function () {
+    const batchId = ref('')
+    const batchList = ref([])
+
     const subjects = ref([])
     const subjectId = ref('')
     const knowledges = ref([])
-    const knowledgeId = ref('') // 单选
+    const knowledgeNode = ref(null) // 单选节点
     const knowledgeCheckNodes = ref([]) // 多选的节点
     const knowledgeIds = computed(() => knowledgeCheckNodes.value.map(k => k.id))
+    const knowledgeId = computed(() => knowledgeNode.value?.id || '')
 
-    const payload = {subjectId, subjects, knowledgeId, knowledges, knowledgeCheckNodes, knowledgeIds}
+    const payload = {
+        batchId, batchList, subjectId, subjects,
+        knowledges, knowledgeId, knowledgeNode, knowledgeCheckNodes, knowledgeIds
+    }
     provideLocal(key, payload)
 
     onMounted(async () => {
@@ -21,14 +28,18 @@ export const useProvidePaperFullCondition = function () {
         // 给一个默认值
         if (res.data.length) subjectId.value = res.data[0].subjectId
     })
-    watch(subjectId, async () => {
+    onMounted(async () => {
+        const res = await getPaperBatches()
+        batchList.value = res.data
+    })
+    watch(subjectId, async (subjectId) => {
         // 先清空以前的知识点
         knowledges.value = []
-        knowledgeId.value = '' // 单选的情况
+        knowledgeNode.value = null // 单选的情况
         knowledgeCheckNodes.value = [] // 多选的情况
 
         // 获取知识点数据
-        const res = await getPaperKnowledges({subjectId: toValue(subjectId)})
+        const res = await getPaperKnowledges({subjectId})
         knowledges.value = res.data
     })
     return payload

+ 114 - 0
back-ui/src/views/dz/papers/hooks/usePaperQuestionCondition.js

@@ -0,0 +1,114 @@
+import {useInjectPaperExactCondition} from "@/views/dz/papers/hooks/usePaperExactCondition.js";
+import {useInjectPaperFullCondition} from "@/views/dz/papers/hooks/usePaperFullCondition.js";
+import {injectLocal, provideLocal} from "@vueuse/core";
+import {useInjectLoading} from "@/views/hooks/useLoading.js";
+import {getPaperQuestions, getPaperQuestionTypes} from "@/api/dz/papers.js";
+import {ElMessage} from "element-plus";
+
+const key = Symbol('PaperQuestionCondition')
+
+export const useProvidePaperQuestionCondition = function (exactMode, allowMultiple) {
+    // exactMode: exact condition & full condition
+    // allowMultiple: multiple knowledge selection
+
+    const keyword = ref('')
+    const qtpye = ref('') // 历史遗留拼写错误
+    const qTypes = ref([])
+
+    const pageNum = ref(1)
+    const pageSize = ref(10)
+    const total = ref(0)
+    const questionList = ref([])
+
+    const {
+        batchId, subjectId, subjects, knowledgeId, knowledgeIds
+    } = exactMode ? useInjectPaperExactCondition() : useInjectPaperFullCondition()
+    const loading = useInjectLoading()
+
+    // question cart
+    const cart = ref([])
+    const currentSubject = computed(() => {
+        const demoId = cart.value[0]?.subjectId
+        return subjects.value.find(s => s.subjectId == demoId)
+    })
+    const groupedQuestions = computed(() => {
+        const results = {}
+        cart.value.forEach(q => {
+            if (!results[q.qtpye]) {
+                results[q.qtpye] = []
+            }
+            results[q.qtpye].push(q)
+        })
+        return Object.keys(results).map(k => ({qtpye: k, questions: results[k]}))
+    })
+    const hasQuestion = (q) => {
+        return cart.value.some(c => c.id == q.id)
+    }
+    const removeQuestion = (q) => {
+        const idx = cart.value.findIndex(c => c.id == q.id)
+        if (idx > -1) cart.value.splice(idx, 1)
+    }
+    const addQuestion = (q) => {
+        if (currentSubject.value && currentSubject.value.subjectId != q.subjectId) {
+            return ElMessage.error(`当前科目【${currentSubject.value.subjectName}】冲突`)
+        }
+        cart.value.push(q)
+    }
+    const removeQuestionGroup = (qtpye) => {
+        const ls = groupedQuestions.value.find(g => g.qtpye == qtpye)?.questions
+        ls?.forEach(q => removeQuestion(q))
+    }
+    const clearCart = () => cart.value = []
+
+    // hooks
+    watch([subjectId, knowledgeId, () => knowledgeIds.value.toString()], async ([subjectId, knowledgeId, knowledgeIds]) => {
+        // clean
+        qtpye.value = ''
+        qTypes.value = []
+
+        if (!subjectId) return
+        if (!knowledgeId && !knowledgeIds) return
+        const query = {subjectId, knowledgeIds: knowledgeId || knowledgeIds}
+        const res = await getPaperQuestionTypes(query)
+        qTypes.value = res.data
+    })
+
+    const questionQuery = computed(() => ({
+        pageNum: pageNum.value,
+        pageSize: pageSize.value,
+        title: keyword.value,
+        qtpye: qtpye.value,
+        knowledgeId: knowledgeId.value,
+        knowledges: knowledgeIds.value.toString()
+    }))
+    const getQuestionList = async function () {
+        loading.value = true
+        try {
+            const res = await getPaperQuestions(questionQuery.value)
+            total.value = res.total
+            questionList.value = res.rows
+        } finally {
+            loading.value = false
+        }
+    }
+    watch([keyword, qtpye, knowledgeId, () => knowledgeIds.value.toString()], async ([title, qtpye, knowledgeId, knowledges]) => {
+        pageNum.value = 1
+        questionList.value = []
+        total.value = 0
+
+        if (!knowledgeId && !knowledges) return // 有知识点即可以查询题库
+        await getQuestionList(pageNum.value, pageSize.value)
+    })
+
+    const payload = {
+        keyword, qtpye, qTypes, pageNum, pageSize, total, questionList, getQuestionList,
+        cart, currentSubject, groupedQuestions,
+        hasQuestion, addQuestion, removeQuestion, removeQuestionGroup, clearCart
+    }
+    provideLocal(key, payload)
+    return payload
+}
+
+export const useInjectPaperQuestionCondition = function () {
+    return injectLocal(key)
+}

+ 13 - 10
back-ui/src/views/dz/papers/index.vue

@@ -1,19 +1,22 @@
 <template>
-  <div class="app-container">
-    <el-tabs>
-      <el-tab-pane label="手动组卷">
-        <paper-by-hand />
-      </el-tab-pane>
-      <el-tab-pane label="智能组卷">
-        <paper-by-intelligent />
-      </el-tab-pane>
-    </el-tabs>
-  </div>
+    <div class="app-container" v-loading="loading">
+        <el-tabs>
+            <el-tab-pane label="手动组卷">
+                <paper-by-hand />
+            </el-tab-pane>
+            <el-tab-pane label="智能组卷">
+                <paper-by-intelligent />
+            </el-tab-pane>
+        </el-tabs>
+    </div>
 </template>
 
 <script setup name="PaperIndex">
 import PaperByHand from "@/views/dz/papers/components/paper-by-hand.vue";
 import PaperByIntelligent from "@/views/dz/papers/components/paper-by-intelligent.vue";
+import {useProvideLoading} from "@/views/hooks/useLoading.js";
+
+const {loading} = useProvideLoading()
 </script>
 
 <style scoped></style>

+ 14 - 0
back-ui/src/views/hooks/useLoading.js

@@ -0,0 +1,14 @@
+import {injectLocal, provideLocal} from "@vueuse/core";
+
+const key = Symbol('GlobalLoading')
+
+export const useProvideLoading = function () {
+    const loading = ref(false)
+    const payload = {loading}
+    provideLocal(key, payload)
+    return payload
+}
+
+export const useInjectLoading = function () {
+    return injectLocal(key)
+}

+ 3 - 3
ie-admin/src/main/java/com/ruoyi/web/controller/front/FrontUniversitiesController.java

@@ -141,7 +141,7 @@ public class FrontUniversitiesController extends BaseController {
         startPage();
         List<BBusiWishUniversities> arr;
         Integer planYear = voluntaryService.getPlanYear(user);
-        if(Constant.EXAM_TYPE_ZG.equals(user.getExamType())) {
+        if(Constant.EXAM_TYPE_ZG.equals(user.getExamType().title())) {
             //职高对口
             arr = universitiesService.selectMajorWishUniversitiesListSimpleByMap2(cond.toCondMap(planYear, user.getExamType().title(), user.getSelectSubject(), user.getLocation())); // TODO MF
         } else {
@@ -174,7 +174,7 @@ public class FrontUniversitiesController extends BaseController {
         data.getBaseInfo().setCollected(CollectionUtils.isNotEmpty(list));
         this.saveUniversitiesClicks(code);
 
-        if(Constant.EXAM_TYPE_ZG.equals(sysUser.getExamType())) {
+        if(Constant.EXAM_TYPE_ZG.equals(sysUser.getExamType().title())) {
             Integer examMajor = sysUser.getSelectSubject(); // TODO MF
 
             BBusiWishUniversitySubmitRecruitPlan planCond = new BBusiWishUniversitySubmitRecruitPlan();
@@ -190,7 +190,7 @@ public class FrontUniversitiesController extends BaseController {
             submitCond.setLiberalScience(examMajor);
             List<UniversityDetailDTO.WishSubmit> submitList = wishUniversitySubmitMarjorsService.selectBBusiWishUniversitySubmitMarjorsList(submitCond).stream().map(t -> new UniversityDetailDTO.WishSubmit(t)).collect(Collectors.toList());
             data.setEnrollHistories(submitList);
-        } else if(Constant.EXAM_TYPE_PG.equals(sysUser.getExamType()) || Constant.EXAM_TYPE_ZZ.equals(sysUser.getExamType())) {
+        } else if(Constant.EXAM_TYPE_PG.equals(sysUser.getExamType().title()) || Constant.EXAM_TYPE_ZZ.equals(sysUser.getExamType().title())) {
             Pair<List<UniversityDetailDTO.WishPlan>, List<UniversityDetailDTO.WishSubmit>> pair = voluntaryService.getUniversityHistory(universityId, sysUser.getLocation(), sysUser.getExamType().title());
             data.setPlanHistories(pair.getLeft());
             data.setEnrollHistories(pair.getRight());