QuestionnaireCommit.vue 40 KB

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