QuestionnaireCommit.vue 38 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070
  1. <template>
  2. <div class="main_container">
  3. <div class="page_item top_header">
  4. <el-button style="margin-right: 92px;" class="button_default_background" @click="router.go(-1)">
  5. <el-icon class="img"><ArrowLeftBold /></el-icon>
  6. 返回
  7. </el-button>
  8. <!-- 如该任务勾选了所有单科统一模板后,则会出现"整卷填写”和“按题填写”的页面,默认按照整卷填写 -->
  9. <TabSearch v-if="isUnifyTemplate" tabType="box"
  10. :disabled="QuestionnaireModeDisabled"
  11. :defaultTabKey="defaultQuestionnaireMode" :tabList="[
  12. {
  13. label:'整卷填写',
  14. key:'full',
  15. },
  16. {
  17. label:'按题填写',
  18. key:'split',
  19. }]"
  20. @tabChange="HandleQuestionnaireModeChange"
  21. />
  22. <div class="info_tip">
  23. <span class="process_tip">总进度:{{ questionnaireProgress }}</span>
  24. <span class="time_tip">
  25. <img :src="redTimeIcon" alt="">
  26. {{
  27. deadlineCountDown?.abort ? '已结束' :
  28. `剩余时间:${deadlineCountDown?.day}天${deadlineCountDown?.hours}时${deadlineCountDown?.minutes}分`
  29. }}
  30. </span>
  31. </div>
  32. </div>
  33. <div class="page_jg_20"> </div>
  34. <div class="center_content">
  35. <!-- 左侧 教师问卷列表 -->
  36. <div class="page_item left_teacher_nav">
  37. <div class="page_search">
  38. <div class="search_content">
  39. <div class="content_left">
  40. <div class="grade_title" style="color: #000;">授课老师</div>
  41. </div>
  42. </div>
  43. </div>
  44. <ul class="teacher_list">
  45. <li v-for="(teacher,index) in teacherList" :key="teacher.id"
  46. :class="['teacher_card',{active:currentActiveTeacherId === teacher.id,},
  47. {disabled:defaultQuestionnaireMode == 'split' || !isTeacherEdit(teacher,index)}]"
  48. @click="(defaultQuestionnaireMode == 'split' || !isTeacherEdit(teacher,index)) ? '' : HandleSubjectTeacherChange(index,teacher)">
  49. <div class="teacher_tag" :style="{backgroundColor:subjectToColor(teacher.evaluationSubjectName)?.color}">
  50. {{ teacher.evaluationSubjectName }}
  51. </div>
  52. <div class="info_text">
  53. <div class="teacher_name">{{ teacher.teacherName }}</div>
  54. <div class="status"
  55. :style="{color:fillOutStatusToColor(teacher.evaluationStatus)?.color}"
  56. >{{ fillOutStatusToColor(teacher.evaluationStatus)?.label }}</div>
  57. </div>
  58. </li>
  59. </ul>
  60. </div>
  61. <!-- 中间 问卷内容 -->
  62. <div class="center_questionnaire">
  63. <!-- 问卷头部 -->
  64. <div class="questionnaire_head">
  65. <h1 class="head_title">{{ questionaireMsg?.assessmentTitle }}</h1>
  66. <p class="head_desc">{{ questionaireMsg?.assessmentIllustrate }}</p>
  67. </div>
  68. <!-- 整卷填写问卷内容 -->
  69. <template v-if="defaultQuestionnaireMode == 'full'">
  70. <QuestionnaireQuestionList
  71. :allowEdit="!isCurrentQuestionnaireTaskFinish"
  72. :questionaireData="questionaireMsg.questionData"
  73. v-model:questionListRef="questionListRef"
  74. @formListAnswerChange="HandleQuestionnaireFormChange"/>
  75. <div class="operation">
  76. <el-button :class="[isCurrentQuestionnaireTaskFinish ? 'is-disabled' : 'button_background']"
  77. @click="isCurrentQuestionnaireTaskFinish ? '' : HandleFinishReview()"
  78. >评价完成</el-button>
  79. </div>
  80. </template>
  81. <!-- 按题填写问卷内容 -->
  82. <template v-if="defaultQuestionnaireMode == 'split'" >
  83. <!-- 单选 0 评分单选 3-->
  84. <SingleSelectQuestionBatchFill
  85. v-if="currentEditQuestion?.questionType === 0 || currentEditQuestion?.questionType === 3"
  86. v-model:isFinishEdit="isFinishEdit"
  87. :titleIndex="currentSelectedQuestionIndex"
  88. :titleMsg="currentEditQuestion"
  89. @questionAnswerChange="HandleQuestionAnswerChange"
  90. :allowEdit="!isCurrentQuestionnaireTaskFinish"
  91. :customTableColumns="questionTableColumns"
  92. :defaultTableData="questionTableData"
  93. />
  94. <!-- 多选 1 -->
  95. <BatchSelectQuestionBatchFill
  96. v-if="currentEditQuestion?.questionType === 1"
  97. v-model:isFinishEdit="isFinishEdit"
  98. :titleIndex="currentSelectedQuestionIndex"
  99. :titleMsg="currentEditQuestion"
  100. @questionAnswerChange="HandleQuestionAnswerChange"
  101. :allowEdit="!isCurrentQuestionnaireTaskFinish"
  102. :customTableColumns="questionTableColumns"
  103. :defaultTableData="questionTableData"
  104. />
  105. <!-- 评分题 5-->
  106. <template v-if="currentEditQuestion?.questionType === 5" >
  107. <!-- 数字评分 -->
  108. <ScoreQuestionBatchFill
  109. v-if="currentEditQuestion?.questionAnswerClass === 0"
  110. v-model:isFinishEdit="isFinishEdit"
  111. :titleIndex="currentSelectedQuestionIndex"
  112. :titleMsg="currentEditQuestion"
  113. @questionAnswerChange="HandleQuestionAnswerChange"
  114. :allowEdit="!isCurrentQuestionnaireTaskFinish"
  115. :defaultTableData="questionTableData"
  116. />
  117. <!-- 星星评分 -->
  118. <StarRationQuestionBatchFill
  119. v-if="currentEditQuestion?.questionAnswerClass === 1"
  120. v-model:isFinishEdit="isFinishEdit"
  121. :titleIndex="currentSelectedQuestionIndex"
  122. :titleMsg="currentEditQuestion"
  123. :allowEdit="!isCurrentQuestionnaireTaskFinish"
  124. @questionAnswerChange="HandleQuestionAnswerChange"
  125. :defaultTableData="questionTableData"
  126. />
  127. </template>
  128. <!-- 问答题 2-->
  129. <QuestionAndAnswerBatchFill
  130. v-if="currentEditQuestion?.questionType === 2"
  131. v-model:isFinishEdit="isFinishEdit"
  132. :titleIndex="currentSelectedQuestionIndex"
  133. :titleMsg="currentEditQuestion"
  134. :allowEdit="!isCurrentQuestionnaireTaskFinish"
  135. @questionAnswerChange="HandleQuestionAnswerChange"
  136. :defaultTableData="questionTableData"
  137. />
  138. <div class="operation">
  139. <el-button :class="[!isCurrentQuestionnaireTaskFinish && isFinishEdit ? 'button_background' : 'is-disabled']"
  140. @click="!isCurrentQuestionnaireTaskFinish && isFinishEdit ? HandleFinishCurrentQuestion() : ''">
  141. {{(currentSelectedQuestionIndex + 1) < questionnaireSplitData?.length ? '下一题' : '提交'}}
  142. </el-button>
  143. </div>
  144. </template>
  145. </div>
  146. <!-- 右侧 问卷题目列表 -->
  147. <div class="page_item right_title_nav">
  148. <div class="page_search">
  149. <div class="search_content">
  150. <div class="content_left">
  151. <div class="grade_title">问卷目录</div>
  152. </div>
  153. </div>
  154. </div>
  155. <ul v-if="questionaireMsg?.questionData?.length > 0" class="question_list_brief">
  156. <li v-for="(question,index) in questionaireMsg.questionData" :key="question.id">
  157. <span @click="defaultQuestionnaireMode=='full' ?
  158. ScrollToItem(question.id) :
  159. (!isQuestionTitleEdit(question,index) ? '' : HandleChangeQuestion(index,question)) "
  160. :title="question.questionTitleName"
  161. class="one_line_ellipsis"
  162. :class="[{active:defaultQuestionnaireMode=='split' && currentSelectedQuestionIndex == index,
  163. disabled:defaultQuestionnaireMode=='split' && !isQuestionTitleEdit(question,index)}]">
  164. {{ index + 1 }}.{{ question.questionTitleName }}
  165. </span>
  166. <img v-if="IsSplitQuestionnaireFinish(question.studentAnswerStatus)" :src="finishIcon" alt="">
  167. </li>
  168. </ul>
  169. </div>
  170. </div>
  171. </div>
  172. </template>
  173. <script setup lang="ts">
  174. import { ref,onMounted, reactive, computed,watch, nextTick } from 'vue';
  175. import { useRouter } from 'vue-router';
  176. import { ElMessage } from 'element-plus';
  177. import TabSearch from '@/baseComponents/TabSearch.vue';
  178. import QuestionnaireQuestionList from '@/components/QuestionnaireQuestionList.vue';
  179. import SingleSelectQuestionBatchFill from '@/components/singleSelectQuestionBatchFill/SingleSelectQuestionBatchFill.vue';
  180. import ScoreQuestionBatchFill from '@/components/scoreQuestionBatchFill/ScoreQuestionBatchFill.vue';
  181. import StarRationQuestionBatchFill from '@/components/starRationQuestionBatchFill/StarRationQuestionBatchFill.vue';
  182. import QuestionAndAnswerBatchFill from '@/components/questionAndAnswerBatchFill/QuestionAndAnswerBatchFill.vue';
  183. import BatchSelectQuestionBatchFill from '@/components/batchSelectQuestionBatchFill/BatchSelectQuestionBatchFill.vue';
  184. import { subjectToColor,fillOutStatusToColor,
  185. IsSplitQuestionnaireFinish
  186. } from '@/components/commonDictConvert';
  187. import { useTimeCountDown } from '@/utils/dateTransform';
  188. import {getExcelColumn} from '@/utils/numberConvertExcelColumn'
  189. import { getEvaluationQuestionDataApi, getEvaluationQuestionDataSplitApi,
  190. saveEvaluationQuestionDataApi, saveEvaluationQuestionDataSplitApi
  191. } from '@/api/questionnaireCommit'
  192. import {getEvaluationProcessMsgApi} from '@/api/selfQuestionnaireProcess'
  193. import redTimeIcon from '@/assets/icon/red_time_icon.svg'
  194. import finishIcon from '@/assets/studentQuestionnaire/select_icon.png'
  195. interface TeacherItm {
  196. id: string|number
  197. evaluationClassName: string //班级
  198. evaluationStatus: number //评价状态 0-待评价 1-进行中 2-已评价
  199. subjectType: number // 科目类型 0-班主任 1 科目(单科)
  200. teacherName: string //教师名称
  201. evaluationSubjectName:string //科目名称
  202. }
  203. const router = useRouter()
  204. // 是否 所有单科统一模板
  205. const isUnifyTemplate = computed(()=>{
  206. // 统一模板状态,0-否(未勾选),1-是(已勾选)
  207. let unifyTemplateStatus = JSON.parse(localStorage.getItem('userInfo') || '{}')?.evaluationBO?.unifyTemplateStatus
  208. return unifyTemplateStatus ? true : false
  209. })
  210. // 倒计时相关参数
  211. const {
  212. DestoryInterval,
  213. deadlineCountDown,
  214. StartCounDown
  215. } = useTimeCountDown()
  216. /**
  217. * 1. 左侧教师相关变量
  218. */
  219. // 待评价教师列表
  220. const teacherList = ref<TeacherItm[]>([])
  221. // evaluationStatus 0-待评价 1-进行中 2-已评价
  222. // 问卷教师是否可以切换
  223. const isTeacherEdit = computed(() => {
  224. // 除了已评价(2)状态可编辑外,
  225. // 如果有进行中状态,第一个进行中(1)状态可编辑
  226. // 没有进行中状态,第一个待评价(0)状态可编辑;
  227. const list = teacherList.value;
  228. // 获取第一个 进行中
  229. const firstActiveIndex = list.findIndex(item => item.evaluationStatus === 1);
  230. const hasActive = firstActiveIndex !== -1;
  231. // 如果没有进行中的,才去寻找第一个待评价的
  232. const firstPendingIndex = hasActive
  233. ? -1
  234. : list.findIndex(item => item.evaluationStatus === 0);
  235. // 2. 返回一个函数,供 v-for 循环调用
  236. return (teacher, index) => {
  237. // 已评价(2) 始终可编辑
  238. if (teacher.evaluationStatus === 2) return true;
  239. // 进行中(1):判断当前索引是否等于缓存的第一个进行中索引
  240. if (teacher.evaluationStatus === 1) {
  241. return index === firstActiveIndex;
  242. }
  243. // 待评价(0):判断当前索引是否等于缓存的第一个待评价索引
  244. if (teacher.evaluationStatus === 0) {
  245. return index === firstPendingIndex;
  246. }
  247. // 兜底:其他未知状态不可编辑
  248. return false;
  249. };
  250. });
  251. // 问卷标题是否可以切换
  252. const isQuestionTitleEdit = computed(()=>{
  253. const list = questionaireMsg.value?.questionData || []
  254. // 获取第一个 未答完的题
  255. const firstUnfinishIndex = list.findIndex(item => item.studentAnswerStatus === 0);
  256. return (question, index) => {
  257. // 已答题(1) 始终可编辑
  258. if (question.studentAnswerStatus === 1) return true;
  259. // 未答题(0):判断当前索引是否等于缓存的第一个未答题索引
  260. if (question.studentAnswerStatus === 0) {
  261. return index === firstUnfinishIndex;
  262. }
  263. // 兜底:其他未知状态不可编辑
  264. return false;
  265. };
  266. })
  267. /**
  268. * 2. 问卷整体数据
  269. */
  270. // 当前问卷任务是否全部完成 ———— 以及问卷是否结束
  271. const isCurrentQuestionnaireTaskFinish = computed(()=>{
  272. // 判断教师列表是否全部完成
  273. return !(teacherList.value.find(teacher=>{
  274. return (teacher.evaluationStatus === 0 || teacher.evaluationStatus === 1)
  275. }))
  276. })
  277. // 问卷填写截止时间
  278. const deadlineTime = ref('')
  279. // 每个问卷的进度
  280. const questionnaireProgress= computed(()=>{
  281. if(defaultQuestionnaireMode.value == 'full'){
  282. return questionaireMsg.value?.progress
  283. }else if(defaultQuestionnaireMode.value == 'split'){
  284. let totalQuestion = questionaireMsg.value?.questionData || []
  285. let finishLength = 0
  286. totalQuestion.map(item=>{
  287. if(IsSplitQuestionnaireFinish(item.studentAnswerStatus)){
  288. finishLength += 1
  289. }
  290. })
  291. if(totalQuestion?.length > 0){
  292. return Math.floor((finishLength / totalQuestion.length) * 100) + '%'
  293. }else{
  294. return '0%'
  295. }
  296. }
  297. })
  298. // 【按题填写】时候的全部数据内容
  299. const questionnaireSplitData = ref<any[]>([])
  300. // 按题填写时候的题目列表
  301. const questionnaireSplitTitleList = computed(()=>{
  302. return questionnaireSplitData.value?.map((item:any)=>{
  303. return {
  304. id:item.questionId,
  305. studentAnswerStatus:item.studentAnswerStatus,
  306. questionTitleName:item.questionTitleName,
  307. }
  308. })
  309. })
  310. // 【整卷填写】 —— 全部数据内容
  311. const teacherQuestionnaireData = ref([])
  312. // 当前选择的教师问卷(整卷填写) 或者 题目问卷(按题填写) 全部信息
  313. const currentSelectedQuestionnaireData = computed(()=>{
  314. // 整卷填写
  315. if(defaultQuestionnaireMode.value == 'full'){
  316. return teacherQuestionnaireData.value.find((item:any)=>{
  317. return item.evaluationCodeRelationId == currentActiveTeacherId.value
  318. })
  319. // 按题填写
  320. }else if(defaultQuestionnaireMode.value == 'split'){
  321. return questionnaireSplitData.value?.[currentSelectedQuestionIndex.value]
  322. }
  323. })
  324. // 最终渲染的整体问卷数据
  325. const questionaireMsg = computed(()=>{
  326. if(defaultQuestionnaireMode.value == 'full'){
  327. let addedQuestionList = currentSelectedQuestionnaireData.value?.questionData?.map(item=>{
  328. let options = item.answerData.map(opt=>{
  329. return{
  330. questionSortCode:String(opt.questionSortCode), //选项的序号
  331. answerCode: opt.answerCode,
  332. content: opt.answerName,
  333. score: opt.answerScore,
  334. id: opt.answerId
  335. }
  336. })
  337. let extraConfig = []
  338. if(Number(item.questionAnswerType) === 1){
  339. extraConfig.push('required')
  340. }
  341. if(Number(item.questionSortRole) === 1){
  342. extraConfig.push('isHorizontal')
  343. }
  344. // 初始化默认值
  345. let defaultAnswer = undefined
  346. // 单选 | 评分单选
  347. if(item.questionType === 0 || item.questionType === 3){
  348. item.answerData.map(opt=>{
  349. if(opt.answerCode){
  350. defaultAnswer = opt.answerCode
  351. }
  352. })
  353. // 多选
  354. }else if(item.questionType === 1){
  355. defaultAnswer = []
  356. item.answerData.map(opt=>{
  357. if(opt.answerCode){
  358. defaultAnswer.push(opt.answerCode)
  359. }
  360. })
  361. //问答
  362. }else if(item.questionType === 2){
  363. defaultAnswer = item.studentAnswer
  364. //评分
  365. } else if(item.questionType === 5){
  366. defaultAnswer = item.studentAnswerScore
  367. }
  368. console.log('defaultAnswer',defaultAnswer)
  369. return {
  370. ...item,
  371. id:item.questionId,
  372. extraConfig, // 是否必答 required, 是否横排 isHorizontal
  373. options,
  374. answer:defaultAnswer, //获取默认answer
  375. }
  376. })
  377. console.log('默认初始赋值——————addedQuestionList',addedQuestionList)
  378. return{
  379. assessmentTitle:currentSelectedQuestionnaireData.value?.assessmentName, //问卷标题
  380. assessmentIllustrate:currentSelectedQuestionnaireData.value?.assessmentIllustrate, //问卷简介
  381. // 问卷试题列表
  382. questionData:addedQuestionList,
  383. progress:currentSelectedQuestionnaireData.value?.progress,
  384. deadlineTime:currentSelectedQuestionnaireData.value?.endDateTime,
  385. }
  386. }else if(defaultQuestionnaireMode.value == 'split'){
  387. return{
  388. assessmentTitle:currentSelectedQuestionnaireData.value?.assessmentName, //问卷标题
  389. assessmentIllustrate:currentSelectedQuestionnaireData.value?.assessmentIllustrate, //问卷简介
  390. // 问卷试题列表
  391. questionData:questionnaireSplitTitleList.value,
  392. // progress:currentSelectedQuestionnaireData.value?.progress,
  393. deadlineTime:currentSelectedQuestionnaireData.value?.endDateTime,
  394. }
  395. }
  396. })
  397. // 历史选择的问卷填写方式
  398. // //无 '' , full 整卷填写 , split 按题填写
  399. const HistoryQuestionnaireFillMode = ref('')
  400. // 默认的问卷填写模式
  401. const defaultQuestionnaireMode = ref('full') // split full
  402. // 问卷填写模式是否允许修改
  403. const QuestionnaireModeDisabled = ref(false)
  404. // 问卷填写模式变更
  405. const HandleQuestionnaireModeChange = async (val)=>{
  406. console.log('当前选中的值',val)
  407. defaultQuestionnaireMode.value = val
  408. if(val == 'split'){
  409. await GetSplitQuestionnaireData()
  410. }else if(val == 'full'){
  411. await GetFullQuestionnaireData()
  412. }
  413. }
  414. // 监听从接口获取到的 历史问卷填写方式,变更当前页面的模式和模式的禁选状态
  415. watch(()=>HistoryQuestionnaireFillMode.value,(newVal)=>{
  416. if(newVal){
  417. defaultQuestionnaireMode.value = newVal
  418. QuestionnaireModeDisabled.value = true
  419. }
  420. })
  421. // 获取整卷填写问卷数据
  422. const GetFullQuestionnaireData = async ()=>{
  423. let teacherIds = teacherList.value.map(item=>{
  424. return item.id
  425. })
  426. let res = await getEvaluationQuestionDataApi(teacherIds)
  427. if(res?.code == 200){
  428. teacherQuestionnaireData.value = res?.data
  429. }
  430. // 如果当前没有选中的教师问卷,则默认选中第一个教师问卷
  431. if(currentActiveTeacherId.value){
  432. return
  433. }
  434. currentActiveTeacherId.value = teacherList.value?.[0]?.id || ''
  435. }
  436. // 获取按题填写问卷数据
  437. const GetSplitQuestionnaireData = async ()=>{
  438. let teacherIds = teacherList.value.map(item=>{
  439. return item.id
  440. })
  441. let res = await getEvaluationQuestionDataSplitApi(teacherIds)
  442. if(res?.code == 200){
  443. questionnaireSplitData.value = res.data
  444. }
  445. }
  446. /**
  447. * 3. 整卷填写相关变量 和操作
  448. */
  449. // 问卷试题 dom
  450. const questionListRef = ref()
  451. // 当前选中的待评价科目教师
  452. const currentActiveTeacherId = ref<number|string>('')
  453. // 整卷填写 教师问卷的答案
  454. const fullQuestionnaireAnswer = ref({})
  455. // 切换 评价的科目教师
  456. const HandleSubjectTeacherChange = (index,teacher)=>{
  457. currentActiveTeacherId.value = teacher.id
  458. currentSelectedQuestionIndex.value = 0
  459. }
  460. // 问卷试题定位—— 滚动定位到试题中的某一题
  461. const ScrollToItem = (id)=>{
  462. const targetElement = questionListRef.value.querySelector(`[id="${id}"]`);
  463. if (targetElement) {
  464. // 平滑滚动到目标元素,并使其居中显示
  465. targetElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
  466. }
  467. }
  468. // 接收问卷表单填写信息的变更
  469. const HandleQuestionnaireFormChange = (val)=>{
  470. fullQuestionnaireAnswer.value = val
  471. }
  472. // 问卷整卷提交
  473. const HandleFinishReview = async ()=>{
  474. console.log('整卷提交的问卷答案--',fullQuestionnaireAnswer.value)
  475. let params = {
  476. evaluationCodeRelationId: currentSelectedQuestionnaireData.value.evaluationCodeRelationId,
  477. assessmentId: currentSelectedQuestionnaireData.value.assessmentId,
  478. answerData: Object.values(fullQuestionnaireAnswer.value)
  479. }
  480. let res = await saveEvaluationQuestionDataApi(params)
  481. if(res?.code == 200){
  482. ElMessage.success('问卷评价提交成功')
  483. // true 表示当前问卷全部填写完成,跳到下一个问卷( false 没有完成的留在当前问卷页)
  484. if(res.data){
  485. fullQuestionnaireAnswer.value = {}
  486. // 重新查询教师状态和问卷状态
  487. await ReloadPageData()
  488. // 重查和索引切换顺序不能颠倒
  489. // 找到当前选中的教师
  490. let currentIndex = teacherList.value.findIndex(item=>{
  491. return item.id == currentActiveTeacherId.value
  492. })
  493. // 如果不是最后一卷,跳转到下一卷
  494. if((currentIndex + 1) >= teacherList.value.length ){
  495. return
  496. }
  497. // 跳转到下一个教师的问卷
  498. currentActiveTeacherId.value = teacherList.value?.[currentIndex + 1]?.id || ''
  499. }else{
  500. // 重新查询教师状态和问卷状态
  501. await ReloadPageData()
  502. }
  503. }
  504. }
  505. /**
  506. * 4. 按题填写相关变量 和操作
  507. */
  508. const isFinishEdit = ref(false) // 当前试题是否已经编辑完成
  509. const currentSelectedQuestionIndex = ref(0) // 当前选中的题目索引
  510. // 当前问卷题目的 教师评价答案列表
  511. const singleQuestionAnswer = ref([])
  512. // 当前编辑的试题
  513. const currentEditQuestion = computed(()=>{
  514. let currentQuestion = questionnaireSplitData.value?.[currentSelectedQuestionIndex.value]
  515. let extraConfig = []
  516. if(Number(currentQuestion?.questionAnswerType) === 1){
  517. extraConfig.push('required')
  518. }
  519. if(Number(currentQuestion?.questionSortRole) === 1){
  520. extraConfig.push('isHorizontal')
  521. }
  522. return {...currentQuestion,extraConfig}
  523. })
  524. // 按题填写的表格数据渲染 _ 不同题目类型渲染不同的内容
  525. const questionTableColumns = ref<any[]>([]) //按题填写的表列数据
  526. const questionTableData = ref<any[]>([]) //按题填写的表行数据
  527. // 监听【按题填写】时候的 表格数据渲染
  528. watch(()=>currentEditQuestion.value?.questionType,(newVal)=>{
  529. console.log('teacherList',teacherList.value)
  530. console.log('questionaireMsg',questionaireMsg.value)
  531. switch(newVal){
  532. case 0:
  533. case 3:
  534. { //单选
  535. let initRowKeys = {} //初始化的行数据
  536. let singleScoreObj = {}
  537. // 表列(头)获取
  538. questionTableColumns.value = currentEditQuestion.value?.answerData?.map((item,index)=>{
  539. initRowKeys['sort_'+item.questionSortCode] = false
  540. singleScoreObj[item.questionSortCode] = item.answerScore
  541. return {
  542. name:'sort_'+item.questionSortCode,
  543. label:`${getExcelColumn(item.questionSortCode - 1)}. `+item.answerName,
  544. width:'200',
  545. fixed:'',
  546. align:'left',
  547. minWdth:'',
  548. custom:true,
  549. }
  550. }) || []
  551. // 表行数据获取
  552. questionTableData.value = currentEditQuestion.value?.studentAnswerData?.map((stuAnswer)=>{
  553. let rowKeys = {
  554. ...initRowKeys,
  555. evaluationCodeRelationId: stuAnswer.evaluationCodeRelationId,
  556. "answerCode": "",
  557. "studentAnswer": "",
  558. studentQuestionScore: currentEditQuestion.value?.questionScore,
  559. "studentAnswerScore": '',
  560. // 评分单选时候,每个选项对应的分值 obj
  561. "answerScoreObj": singleScoreObj
  562. }
  563. let answers = []
  564. if(stuAnswer.answerCode){
  565. answers = stuAnswer.answerCode?.split('|') || []
  566. }
  567. // 回显已经填写完的信息
  568. answers.map((ans)=>{
  569. rowKeys['sort_'+ans] = true
  570. })
  571. return {
  572. teacher:stuAnswer.teacherName,
  573. ...rowKeys
  574. }
  575. })
  576. break
  577. }
  578. case 1:{ //多选
  579. let initRowKeys = {} //初始化的行数据
  580. questionTableColumns.value = currentEditQuestion.value?.answerData?.map((item,index)=>{
  581. initRowKeys['sort_'+item.questionSortCode] = false
  582. return {
  583. name:'sort_'+item.questionSortCode,
  584. label:`${getExcelColumn(item.questionSortCode - 1)}. `+item.answerName,
  585. width:'200',
  586. fixed:'',
  587. align:'left',
  588. minWdth:'',
  589. custom:true,
  590. }
  591. }) || []
  592. // 表行数据获取
  593. questionTableData.value = currentEditQuestion.value?.studentAnswerData?.map((stuAnswer)=>{
  594. let rowKeys = {
  595. ...initRowKeys,
  596. evaluationCodeRelationId: stuAnswer.evaluationCodeRelationId,
  597. "answerCode": "",
  598. "studentAnswer": "",
  599. studentQuestionScore: '',
  600. "studentAnswerScore": '',
  601. "answerScore": ''
  602. }
  603. let answers = []
  604. if(stuAnswer.answerCode){
  605. answers = stuAnswer.answerCode?.split('|') || []
  606. }
  607. // 回显已经填写完的信息
  608. answers.map((ans)=>{
  609. rowKeys['sort_'+ans] = true
  610. })
  611. return {
  612. teacher:stuAnswer.teacherName,
  613. ...rowKeys
  614. }
  615. })
  616. break
  617. }
  618. case 5:{
  619. //评分题
  620. questionTableData.value = currentEditQuestion.value?.studentAnswerData?.map((stuAnswer)=>{
  621. let answers = stuAnswer.studentAnswerScore
  622. // 回显已经填写完的信息
  623. return {
  624. teacher:stuAnswer.teacherName,
  625. score:answers,
  626. fullScore:currentEditQuestion.value?.questionScore,
  627. evaluationCodeRelationId: stuAnswer.evaluationCodeRelationId,
  628. }
  629. })
  630. break
  631. }
  632. case 2:{ //问答题
  633. // 表行数据获取
  634. questionTableData.value = currentEditQuestion.value?.studentAnswerData?.map((stuAnswer)=>{
  635. let answers = stuAnswer.studentAnswer
  636. // 回显已经填写完的信息
  637. return {
  638. teacher:stuAnswer.teacherName,
  639. content:answers,
  640. evaluationCodeRelationId: stuAnswer.evaluationCodeRelationId,
  641. }
  642. })
  643. break
  644. }
  645. }
  646. },{immediate:true})
  647. // 切换当前选中的试题索引
  648. const HandleChangeQuestion = (index,question)=>{
  649. console.log('当前索引--',index)
  650. isFinishEdit.value = false
  651. currentSelectedQuestionIndex.value = index
  652. }
  653. // 接收问卷试题答案的变更
  654. const HandleQuestionAnswerChange = (val)=>{
  655. singleQuestionAnswer.value = val
  656. }
  657. // 当前题目 有关所有教师的评价数据提交
  658. const HandleFinishCurrentQuestion = async ()=>{
  659. console.log('当前题目的教师评价数据--',singleQuestionAnswer.value )
  660. let finalAnswerData:any[] = []
  661. switch(currentEditQuestion.value?.questionType){
  662. // 单选
  663. case 0:
  664. // 多选
  665. case 1:{
  666. finalAnswerData = singleQuestionAnswer.value.map(item=>{
  667. let keys = Object.keys(item).filter(key=>{
  668. return key.includes('sort_') && item[key] === true
  669. })
  670. let answerCode = keys.map(ans=>{
  671. return ans.split('_')[1]
  672. }).join('|')
  673. return {
  674. evaluationCodeRelationId: item.evaluationCodeRelationId,
  675. answerCode: answerCode,
  676. studentAnswer: '',
  677. studentQuestionScore: '',
  678. studentAnswerScore: '',
  679. answerScore: ''
  680. }
  681. })
  682. break
  683. }
  684. case 3:{ //评分单选
  685. finalAnswerData = singleQuestionAnswer.value.map(item=>{
  686. let keys = Object.keys(item).find(key=>{
  687. return key.includes('sort_') && item[key] === true
  688. })
  689. let answerCode = keys?.split('_')[1]
  690. return {
  691. evaluationCodeRelationId: item.evaluationCodeRelationId,
  692. answerCode: answerCode,
  693. studentAnswer: '',
  694. studentQuestionScore: '',
  695. studentAnswerScore: '',
  696. answerScore: item.answerScoreObj?.[answerCode]
  697. }
  698. })
  699. break
  700. }
  701. case 5:{ //评分题
  702. finalAnswerData = singleQuestionAnswer.value.map(item=>{
  703. return {
  704. evaluationCodeRelationId: item.evaluationCodeRelationId,
  705. answerCode: '',
  706. studentAnswer: '',
  707. studentQuestionScore: item.fullScore,
  708. studentAnswerScore: item.score,
  709. answerScore: ''
  710. }
  711. })
  712. break
  713. }
  714. case 2:{ //问答题
  715. finalAnswerData = singleQuestionAnswer.value.map(item=>{
  716. return {
  717. evaluationCodeRelationId: item.evaluationCodeRelationId,
  718. answerCode: '',
  719. studentAnswer: item.content,
  720. studentQuestionScore: '',
  721. studentAnswerScore: '',
  722. answerScore: ''
  723. }
  724. })
  725. break
  726. }
  727. }
  728. let params = {
  729. questionId: currentSelectedQuestionnaireData.value.questionId,
  730. lastQuestion: currentSelectedQuestionIndex.value + 1 >= questionnaireSplitData.value?.length ? true : false,
  731. answerData:finalAnswerData
  732. }
  733. let res = await saveEvaluationQuestionDataSplitApi(params)
  734. if(res?.code == 200){
  735. ElMessage.success('问卷评价提交成功')
  736. // 不是最后一卷 ,要跳转到下一卷
  737. if((currentSelectedQuestionIndex.value + 1) < questionaireMsg.value.questionData?.length){
  738. singleQuestionAnswer.value = []
  739. isFinishEdit.value = false
  740. questionTableData.value = []
  741. questionTableColumns.value = []
  742. // 重新查询 问卷内容和页面内容
  743. await ReloadPageData()
  744. currentSelectedQuestionIndex.value += 1
  745. }else{
  746. // 重新查询 问卷内容和页面内容
  747. await ReloadPageData()
  748. }
  749. }
  750. }
  751. //
  752. // 处理页面的倒计时
  753. const HandleTimeDeadline = ()=>{
  754. DestoryInterval()
  755. // 获取到剩余时间后,开始执行倒计时
  756. StartCounDown(deadlineTime.value,1000)
  757. }
  758. // 获取评价教师列表
  759. const GetEvaluationProcessMsg = async ()=>{
  760. let res = await getEvaluationProcessMsgApi()
  761. if(res?.code == 200){
  762. teacherList.value = res.data.dataList
  763. }
  764. }
  765. // 重新加载页面数据
  766. const ReloadPageData = async ()=>{
  767. await GetEvaluationProcessMsg()
  768. if(defaultQuestionnaireMode.value == 'full'){
  769. await GetFullQuestionnaireData()
  770. }else if(defaultQuestionnaireMode.value == 'split'){
  771. await GetSplitQuestionnaireData()
  772. }
  773. }
  774. watch(()=>questionaireMsg?.value?.deadlineTime,async (newVal)=>{
  775. deadlineTime.value = newVal
  776. await nextTick()
  777. HandleTimeDeadline()
  778. })
  779. onMounted( async ()=>{
  780. await ReloadPageData()
  781. })
  782. </script>
  783. <style lang="scss" scoped>
  784. .main_container{
  785. > *{
  786. flex-shrink: 0;
  787. }
  788. }
  789. .top_header{
  790. display: flex;
  791. align-items: center;
  792. justify-content: space-between;
  793. padding:10px 20px;
  794. .info_tip{
  795. margin-right: 240px;
  796. margin-left: 10px;
  797. flex: 1 1;
  798. display: flex;
  799. justify-content: flex-end;
  800. gap:40px;
  801. align-items: center;
  802. font-size: 14px;
  803. line-height: 24px;
  804. .process_tip{
  805. color: #2E64FA;
  806. }
  807. .time_tip{
  808. display: flex;
  809. align-items: center;
  810. gap:4px;
  811. color:#f56C6c;
  812. }
  813. }
  814. :deep(.button_default_background){
  815. padding:8px 10px;
  816. >span{
  817. gap:4px;
  818. }
  819. }
  820. }
  821. .center_content{
  822. display: flex;
  823. gap:20px;
  824. align-items: flex-start;
  825. height: calc(100% - 56px - 20px);
  826. overflow: auto;
  827. .left_teacher_nav{
  828. width: 160px;
  829. max-height: 100%;
  830. overflow: auto;
  831. .teacher_list{
  832. display: flex;
  833. flex-direction: column;
  834. gap:16px;
  835. margin-top: 16px;
  836. .teacher_card{
  837. display: flex;
  838. gap:8px;
  839. cursor: pointer;
  840. &.disabled{
  841. cursor:not-allowed
  842. }
  843. &.active{
  844. .status,
  845. .teacher_name{
  846. color:#2E64FA !important;
  847. }
  848. .teacher_name{
  849. font-weight: 600;
  850. }
  851. }
  852. .teacher_tag{
  853. display: flex;
  854. justify-content: center;
  855. align-items: center;
  856. width: 44px;
  857. height: 44px;
  858. border-radius: 4px 4px 4px 4px;
  859. font-weight: 600;
  860. font-size: 12px;
  861. color: #FFFFFF;
  862. }
  863. .info_text{
  864. display: flex;
  865. flex-direction: column;
  866. justify-content: space-between;
  867. .teacher_name{
  868. font-size: 14px;
  869. color: #333333;
  870. }
  871. .status{
  872. font-size: 12px;
  873. }
  874. }
  875. }
  876. }
  877. }
  878. .right_title_nav{
  879. width: 240px;
  880. max-height: 100%;
  881. overflow: auto;
  882. padding:20px 10px;
  883. .question_list_brief{
  884. display: flex;
  885. flex-direction: column;
  886. font-size: 14px;
  887. li{
  888. display: flex;
  889. align-items: center;
  890. justify-content: space-between;
  891. gap:8px;
  892. padding:10px 0;
  893. cursor: pointer;
  894. .disabled{
  895. cursor: not-allowed;
  896. }
  897. .active{
  898. color: #2E64FA;
  899. }
  900. img{
  901. width:16px;
  902. height:16px;
  903. }
  904. }
  905. }
  906. }
  907. .center_questionnaire {
  908. flex: 1 1;
  909. display: flex;
  910. flex-direction: column;
  911. border-radius: 10px;
  912. background-color: white;
  913. overflow: auto;
  914. height: 100%;
  915. padding:20px 0 116px 0;
  916. /* --- 头部样式 --- */
  917. .questionnaire_head {
  918. text-align: center;
  919. margin-bottom: 30px;
  920. .head_title {
  921. font-size: 24px;
  922. line-height: 34px;
  923. font-weight: bold;
  924. color: #000;
  925. margin-bottom: 10px;
  926. }
  927. .head_desc {
  928. font-size: 14px;
  929. color: #999999;
  930. line-height: 1.6;
  931. margin: 0;
  932. padding:0 60px;
  933. }
  934. }
  935. .operation{
  936. flex: 1 1;
  937. display: flex;
  938. align-items: flex-end;
  939. justify-content: center;
  940. .el-button{
  941. margin-top: 60px;
  942. width: 160px;
  943. height: 48px;
  944. }
  945. }
  946. }
  947. }
  948. </style>