ShowTestGradeForGrader.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504
  1. <template>
  2. <div class="ShowTestGradeForGrader"
  3. :style="localOptions.style">
  4. <q-card v-if="!answerBook.loading && answerBook.id">
  5. <q-card-section class="flex justify-between">
  6. <div>{{ answerBook.test_info.title }}</div>
  7. <q-btn v-if="!answerBook.loading && answerBook.ticket"
  8. outline
  9. color="primary"
  10. class="btn-need-installment"
  11. @click="scrollToObjectionBox">
  12. مشاهده اعتراض
  13. </q-btn>
  14. </q-card-section>
  15. <q-separator />
  16. <q-banner>
  17. وضعیت آزمون:
  18. {{ answerBook.status_info.label }}
  19. </q-banner>
  20. <q-btn v-if="!answerBook.grader && $route.query.read_only !== '1'"
  21. color="primary"
  22. class="full-width"
  23. @click="setGraderToMe">
  24. تصحیح توسط شما
  25. </q-btn>
  26. <q-separator />
  27. <q-banner>
  28. <div class="row">
  29. <div class="col-md-4 col-12">
  30. زمان شروع:
  31. {{ toShamsi(answerBook.attending_start_time) }}
  32. </div>
  33. <div class="col-md-4 col-12">
  34. {{ answerBook.owner_info.firstname }}
  35. {{ answerBook.owner_info.lastname }}
  36. (کدملی: {{ answerBook.owner_info.national_code }})
  37. </div>
  38. <div class="col-md-4 col-12">
  39. نمره نهایی
  40. <template v-if="currentUserIsGrader && answerBook.status === 'GRADING'">
  41. {{ totalScore }}
  42. </template>
  43. <template v-else>
  44. {{ answerBook.total_score }}
  45. </template>
  46. </div>
  47. </div>
  48. </q-banner>
  49. <q-list separator>
  50. <q-item v-for="(answerSheet, answerSheetIndex) in answerBook.answer_sheet_info.list"
  51. :key="answerSheetIndex"
  52. clickable>
  53. <div class="row full-width q-col-gutter-md">
  54. <div class="col-md-10 col-12">
  55. <div v-html="'<span>' + (answerSheetIndex + 1) + ' - </span>' + answerSheet.test_question_info.question_info.text" />
  56. <q-card class="q-mt-sm">
  57. <q-card-section>
  58. <div>
  59. پاسخ صحیح:
  60. </div>
  61. <div v-html="answerSheet.test_question_info.question_info.correct_answer" />
  62. </q-card-section>
  63. </q-card>
  64. <q-card class="q-mt-sm">
  65. <q-card-section>
  66. <div>
  67. متن پاسخ کاربر:
  68. </div>
  69. <div>
  70. {{ answerSheet.answer_text }}
  71. </div>
  72. <div v-if="answerSheet.answer_attachment">
  73. <q-separator class="q-my-md" />
  74. <q-btn outline
  75. color="primary"
  76. icon="attachment"
  77. label="فایل پیوست کاربر"
  78. :href="answerSheet.answer_attachment"
  79. target="_blank" />
  80. </div>
  81. </q-card-section>
  82. </q-card>
  83. </div>
  84. <div class="col-md-2 col-12">
  85. <div>
  86. <q-badge v-if="answerSheet.score !== null">
  87. {{ answerSheet.score }}
  88. از
  89. {{ answerSheet.test_question_info.mark }}
  90. نمره
  91. </q-badge>
  92. <q-badge v-else>
  93. {{ answerSheet.test_question_info.mark }}
  94. نمره
  95. </q-badge>
  96. </div>
  97. <div v-if="currentUserIsGrader && answerBook.status === 'GRADING' && $route.query.read_only !== '1'"
  98. class="q-mt-md input-grate-question">
  99. <q-input v-model="scores[answerSheetIndex].score"
  100. :loading="scores[answerSheetIndex].loading"
  101. dense>
  102. <template v-slot:append>
  103. <q-btn color="primary"
  104. label="ثبت نمره"
  105. outline
  106. :loading="scores[answerSheetIndex].loading"
  107. @click="submitScore(answerSheetIndex)" />
  108. </template>
  109. </q-input>
  110. </div>
  111. </div>
  112. </div>
  113. </q-item>
  114. </q-list>
  115. <q-separator class="q-my-lg" />
  116. <div class="q-pa-lg">
  117. <entity-show v-if="mounted"
  118. ref="entityShow"
  119. v-model:value="overallAnswerInput"
  120. :loaded-data="answerBook"
  121. :show-close-button="false"
  122. :show-edit-button="false"
  123. :show-expand-button="false"
  124. :show-save-button="false"
  125. :show-index-button="false"
  126. :show-reload-button="false"
  127. :default-layout="false" />
  128. </div>
  129. <template v-if="$route.query.send_objection && $route.query.send_objection.toString() === '1' && $route.query.read_only !== '1'">
  130. <q-separator class="q-my-xl" />
  131. <q-card-section>
  132. <entity-edit v-if="mounted"
  133. ref="objectionEntityEdit"
  134. v-model:value="objectionInputs"
  135. title="ثبت اعتراض"
  136. :api="objectionApi"
  137. :loaded-data="{}"
  138. :show-close-button="false"
  139. :show-edit-button="false"
  140. :show-expand-button="false"
  141. :show-save-button="false"
  142. :show-reload-button="false">
  143. <template #after-form-builder>
  144. <div class="flex justify-end q-mt-md">
  145. <q-btn color="primary"
  146. label="ثبت اعتراض"
  147. @click="sendObjection" />
  148. </div>
  149. </template>
  150. </entity-edit>
  151. </q-card-section>
  152. </template>
  153. <template v-else-if="answerBook.objection_request">
  154. <div>
  155. متن اعتراض:
  156. </div>
  157. <div v-html="answerBook.objection_request" />
  158. </template>
  159. <template v-if="answerBook.objection_result">
  160. <div>
  161. نتیجه اعتراض:
  162. </div>
  163. <div v-html="answerBook.objection_result" />
  164. </template>
  165. <q-banner v-if="currentUserIsGrader && answerBook.status === 'GRADING' && $route.query.read_only !== '1'">
  166. <q-input v-model="answerBook.grader_description"
  167. type="textarea"
  168. label="توضیحات مصحح"
  169. :loading="graderDescriptionLoading" />
  170. <q-btn color="primary"
  171. label="ثبت توضیحات مصحح"
  172. outline
  173. class="full-width"
  174. :loading="graderDescriptionLoading"
  175. @click="submitGraderDescription" />
  176. </q-banner>
  177. <q-card-actions v-if="answerBook.status === 'GRADING' && $route.query.read_only !== '1'">
  178. <q-btn color="primary"
  179. class="q-mr-lg"
  180. @click="confirmScores">
  181. تایید نهایی نمرات
  182. </q-btn>
  183. <q-btn color="red"
  184. @click="confirmUngradable">
  185. نیاز به رفع مشکل
  186. </q-btn>
  187. </q-card-actions>
  188. </q-card>
  189. <div v-else>
  190. کمی صبر کنید...
  191. </div>
  192. </div>
  193. <q-card v-if="mounted && !answerBook.loading && answerBook.id && answerBook.ticket"
  194. ref="objectionBox"
  195. class="q-mt-md">
  196. <q-card-section>
  197. <div class="row justify-center">
  198. <div class="full-width">
  199. <q-skeleton v-if="entityLoading"
  200. type="rect"
  201. width="100%"
  202. height="200px" />
  203. <template v-else>
  204. {{ answerBook.ticket_info.body }}
  205. <q-chat-message v-for="message in answerBook.ticket_info.replies_info.list"
  206. :key="message.id"
  207. :avatar="message.creator_info?.picture ? message.creator_info.picture : 'assets/images/web/sample-avatar.svg'"
  208. :name="message.creator_info?.firstname + ' ' + message.creator_info?.lastname"
  209. text-html
  210. :text="[message.body]"
  211. :sent="!isUserMessage(authenticatedUser, message)">
  212. <template v-slot:stamp>
  213. <div>
  214. {{ miladiToShamsi(message.creation_time) }}
  215. </div>
  216. <q-btn icon="clear"
  217. round
  218. color="red"
  219. flat
  220. size="xs"
  221. @click="removeMessage(message.id)" />
  222. <q-icon v-if="isUserMessage(authenticatedUser, message)"
  223. :name="message.read ? 'done_all' : 'done'" />
  224. </template>
  225. </q-chat-message>
  226. <div>
  227. <q-input v-if="false"
  228. v-model="replyText"
  229. bottom-slots
  230. autogrow
  231. :loading="entityLoading">
  232. <template v-slot:after>
  233. <q-btn round
  234. dense
  235. flat
  236. icon="send"
  237. :loading="entityLoading"
  238. @click="sendReply" />
  239. </template>
  240. </q-input>
  241. </div>
  242. </template>
  243. </div>
  244. </div>
  245. </q-card-section>
  246. </q-card>
  247. </template>
  248. <script>
  249. import Assist from 'src/assets/js/Assist.js'
  250. import { APIGateway } from 'src/api/APIGateway.js'
  251. import { EntityShow, EntityEdit } from 'quasar-crud'
  252. import { AnswerBook } from 'src/models/AnswerBook.js'
  253. import { mixinWidget, mixinAuth } from 'src/mixin/Mixins.js'
  254. import { User } from 'src/models/User.js'
  255. export default {
  256. name: 'ShowTestGradeForGrader',
  257. components: { EntityShow, EntityEdit },
  258. mixins: [mixinWidget, mixinAuth],
  259. data () {
  260. return {
  261. mounted: false,
  262. replyText: null,
  263. entityLoading: true,
  264. authenticatedUser: new User(),
  265. graderDescriptionLoading: false,
  266. currentUserIsGrader: false,
  267. overallAnswerInput: [
  268. // { type: 'input', name: 'overall_answer_text', responseKey: 'overall_answer_text', label: 'متن پاسخ جامع', placeholder: ' ', inputType: 'textarea', col: 'col-12' },
  269. { type: 'file', name: 'overall_answer_attachment', responseKey: 'overall_answer_attachment', label: 'فایل پیوست جامع', placeholder: ' ', col: 'col-12' }
  270. ],
  271. questionInput: [
  272. { type: 'input', name: 'answer_text', responseKey: 'answer_text', label: 'متن پاسخ', placeholder: ' ', inputType: 'textarea', col: 'col-12' },
  273. { type: 'file', name: 'answer_attachment', responseKey: 'answer_attachment', label: 'فایل پیوست', placeholder: ' ', col: 'col-12' }
  274. ],
  275. questionInputs: [],
  276. scores: [],
  277. objectionInputs: [
  278. { type: 'input', name: 'objection_request', responseKey: 'objection_request', label: 'متن اعتراض', placeholder: ' ', inputType: 'textarea', col: 'col-12' }
  279. ],
  280. objectionApi: APIGateway.answerBook.APIAdresses.submitObjectionRequest(this.$route.params.answer_book_id),
  281. participateType: null,
  282. participateTypeDialog: false,
  283. answerBook: new AnswerBook(),
  284. timerInterval: null
  285. }
  286. },
  287. computed: {
  288. totalScore () {
  289. let totalScore = 0
  290. this.scores.forEach(item => {
  291. if (item.score) {
  292. totalScore += parseFloat(item.score)
  293. }
  294. })
  295. return totalScore
  296. }
  297. },
  298. mounted () {
  299. this.loadAuthData()
  300. this.getAnswerBook()
  301. this.mounted = true
  302. },
  303. methods: {
  304. scrollToObjectionBox() {
  305. this.$nextTick(() => {
  306. if (this.$refs.objectionBox && this.$refs.objectionBox.$el) {
  307. this.$refs.objectionBox.$el.scrollIntoView({ behavior: 'smooth', block: 'end' })
  308. }
  309. })
  310. },
  311. loadAuthData () {
  312. this.authenticatedUser = this.$store.getters['Auth/user']
  313. },
  314. closeTicket () {
  315. this.$q.dialog({
  316. title: 'توجه',
  317. message: 'آیا از بستن تیکت اطمینان دارید؟',
  318. cancel: true,
  319. persistent: true
  320. }).onOk(() => {
  321. this.entityLoading = true
  322. APIGateway.ticket.close(this.$route.params.id)
  323. .then(() => {
  324. this.$refs.entityEdit.getData()
  325. })
  326. .catch(() => {
  327. this.entityLoading = false
  328. })
  329. })
  330. },
  331. miladiToShamsi (miladi) {
  332. return Assist.miladiToShamsi(miladi)
  333. },
  334. isUserMessage (authenticatedUser, message) {
  335. return authenticatedUser.id === message.creator_info.id
  336. },
  337. removeMessage (replyMessageId) {
  338. this.$q.dialog({
  339. title: 'توجه',
  340. message: 'آیا از حذف پیام اطمینان دارید؟',
  341. cancel: true,
  342. persistent: true
  343. }).onOk(() => {
  344. this.entityLoading = true
  345. APIGateway.ticket.deleteReplyMessage(replyMessageId)
  346. .then(() => {
  347. this.getAnswerBook()
  348. })
  349. .catch(() => {
  350. this.entityLoading = false
  351. })
  352. })
  353. },
  354. sendReply() {
  355. this.entityLoading = true
  356. const body = this.replyText.replace(/\n/g, '<br>\n')
  357. APIGateway.ticket.reply({
  358. id: this.answerBook.ticket,
  359. body
  360. })
  361. .then(() => {
  362. this.replyText = null
  363. this.getAnswerBook()
  364. })
  365. .catch(() => {
  366. this.getAnswerBook()
  367. })
  368. },
  369. sendObjection () {
  370. this.answerBook.loading = true
  371. this.$refs.objectionEntityEdit.editEntity(false)
  372. .then(() => {
  373. this.getAnswerBook()
  374. })
  375. .catch(() => {
  376. this.getAnswerBook()
  377. })
  378. },
  379. toShamsi (miladi) {
  380. return Assist.miladiToShamsi(miladi)
  381. },
  382. loadAnswerBook (answerBook) {
  383. this.scores = []
  384. this.answerBook = new AnswerBook(answerBook)
  385. this.answerBook.answer_sheet_info.list.forEach((answerSheetItem) => {
  386. this.questionInputs.push(this.questionInput)
  387. this.scores.push({
  388. answer_text: answerSheetItem.answer_text,
  389. answer_attachment: answerSheetItem.answer_attachment,
  390. score: answerSheetItem.score,
  391. loading: false
  392. })
  393. })
  394. this.currentUserIsGrader = this.answerBook.grader === this.user.id
  395. },
  396. setGraderToMe () {
  397. this.answerBook.loading = true
  398. APIGateway.answerBook.setGraderToMe(this.$route.params.answer_book_id)
  399. .then((answerBook) => {
  400. this.loadAnswerBook(answerBook)
  401. this.answerBook.loading = false
  402. })
  403. .catch(() => {
  404. this.getAnswerBook()
  405. this.answerBook.loading = false
  406. })
  407. },
  408. submitGraderDescription () {
  409. this.graderDescriptionLoading = true
  410. APIGateway.answerBook.submitGraderDescription(this.answerBook.id, this.answerBook.grader_description)
  411. .then(() => {
  412. this.graderDescriptionLoading = false
  413. this.getAnswerBook()
  414. })
  415. .catch(() => {
  416. this.graderDescriptionLoading = false
  417. })
  418. },
  419. submitScore (index) {
  420. this.scores[index].loading = true
  421. APIGateway.answerSheet.submitScore(this.answerBook.answer_sheet_info.list[index].id, this.scores[index].score)
  422. .then(() => {
  423. this.scores[index].loading = false
  424. this.getAnswerBook()
  425. })
  426. .catch(() => {
  427. this.scores[index].loading = false
  428. })
  429. },
  430. getAnswerBook () {
  431. this.answerBook.loading = true
  432. this.entityLoading = true
  433. APIGateway.answerBook.get(this.$route.params.answer_book_id)
  434. .then((answerBook) => {
  435. this.loadAnswerBook(answerBook)
  436. this.answerBook.loading = false
  437. this.entityLoading = false
  438. })
  439. .catch(() => {
  440. this.answerBook.loading = false
  441. this.entityLoading = false
  442. })
  443. },
  444. confirmScores () {
  445. this.answerBook.loading = true
  446. APIGateway.answerBook.confirmScores(this.$route.params.answer_book_id)
  447. .then(() => {
  448. this.getAnswerBook()
  449. this.answerBook.loading = false
  450. })
  451. .catch(() => {
  452. this.getAnswerBook()
  453. this.answerBook.loading = false
  454. })
  455. },
  456. confirmUngradable () {
  457. this.answerBook.loading = true
  458. APIGateway.answerBook.confirmUngradable(this.$route.params.answer_book_id)
  459. .then(() => {
  460. this.getAnswerBook()
  461. this.answerBook.loading = false
  462. })
  463. .catch(() => {
  464. this.getAnswerBook()
  465. this.answerBook.loading = false
  466. })
  467. }
  468. }
  469. }
  470. </script>
  471. <style scoped lang="scss">
  472. .ShowTestGradeForGrader {
  473. :deep(.q-banner) {
  474. background: #FCF9F4 0 0 no-repeat padding-box;
  475. padding: 20px;
  476. .q-banner__content {
  477. font-size: 16px;
  478. font-weight: normal;
  479. }
  480. }
  481. :deep(.q-list) {
  482. .q-item {
  483. .input-grate-question {
  484. .q-input {
  485. .q-field__inner .q-field__control {
  486. height: 40px;
  487. min-height: 40px;
  488. }
  489. }
  490. }
  491. .q-badge {
  492. background: #ACBCAE 0 0 no-repeat padding-box;
  493. border-radius: 18px;
  494. height: 32px;
  495. padding: 4px 16px;
  496. }
  497. }
  498. }
  499. }
  500. </style>