QuestionsCreateAndAppend.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505
  1. <template>
  2. <div class="ContentsSelector q-mt-md">
  3. <q-table :rows="testQuestionList"
  4. :columns="table.columns"
  5. row-key="name">
  6. <template #top>
  7. <q-banner class="full-width">
  8. <div class="flex justify-between items-center">
  9. <span>
  10. سوالات
  11. </span>
  12. <q-btn color="primary"
  13. class="q-mr-md"
  14. outline
  15. :loading="test.loading"
  16. @click="openDialogCreateNewQuestion">
  17. سوال جدید
  18. </q-btn>
  19. </div>
  20. </q-banner>
  21. </template>
  22. <template #bottom />
  23. <template #body-cell-number="props">
  24. <q-td>
  25. {{ props.rowIndex + 1 }}
  26. </q-td>
  27. </template>
  28. <template #body-cell-mark="props">
  29. <q-td auto-width>
  30. <q-input v-model="props.row.mark"
  31. style="width: 80px;"
  32. :loading="test.loading" />
  33. </q-td>
  34. </template>
  35. <template #body-cell-is_extra_mark="props">
  36. <q-td auto-width>
  37. <q-toggle v-model="props.row.is_extra_mark"
  38. :loading="test.loading" />
  39. </q-td>
  40. </template>
  41. <template #body-cell-action="props">
  42. <q-td auto-width>
  43. <q-btn color="primary"
  44. class="q-mr-md"
  45. size="sm"
  46. outline
  47. :loading="test.loading"
  48. @click="openDialogEditQuestion(props.row.question_info.id)">
  49. ویرایش سوال
  50. </q-btn>
  51. <q-btn v-if="questionType === 'QUESTION_BANK'"
  52. color="primary"
  53. class="q-mr-lg"
  54. size="sm"
  55. outline
  56. @click="editTestQuestion(props.row)">
  57. ویرایش نمره و حالت امتیازی سوال
  58. </q-btn>
  59. <q-btn color="red"
  60. class="q-mr-md"
  61. size="sm"
  62. :loading="test.loading"
  63. @click="deleteQuestion(props.row.question_info.id)">
  64. حذف سوال
  65. </q-btn>
  66. </q-td>
  67. </template>
  68. </q-table>
  69. <div class="q-mt-lg">
  70. <template v-if="questionType === 'QUESTION_BANK'">
  71. <div class="row">
  72. <div class="col-md-4 col-12">
  73. تعداد سوالات اجباری:
  74. {{ countOfForceQuestions }}
  75. </div>
  76. <div class="col-md-4 col-12">
  77. مجموع بارم‌ها:
  78. {{ sumOfTotalMarks }}
  79. </div>
  80. <div class="col-12" />
  81. <div class="col-md-4 col-12">
  82. تعداد سوالات اختیاری:
  83. {{ countOfExtraQuestions }}
  84. </div>
  85. <div class="col-md-4 col-12">
  86. مجموع بارم سوالات اختیاری:
  87. {{ sumOfExtraMarks }}
  88. </div>
  89. </div>
  90. </template>
  91. <template v-else-if="questionType === 'EVENT' && localValue">
  92. <div class="row">
  93. <div class="col-md-4 col-12">
  94. تعداد سوالات انتخاب شده:
  95. {{ localValue.length }}
  96. </div>
  97. </div>
  98. </template>
  99. </div>
  100. <q-dialog v-model="editQuestionDialog"
  101. maximized
  102. transition-show="slide-up"
  103. transition-hide="slide-down">
  104. <edit-question-form :question-id="selectedQuestionIdForEdit"
  105. :question-type="questionType"
  106. @onQuestionEdited="onQuestionEdited" />
  107. </q-dialog>
  108. <q-dialog v-model="createNewQuestionDialog"
  109. maximized
  110. transition-show="slide-up"
  111. transition-hide="slide-down">
  112. <create-question-form :test-id="testId"
  113. :test-set-id="testSetId"
  114. :question-type="questionType"
  115. @onQuestionCreated="onQuestionCreated" />
  116. </q-dialog>
  117. </div>
  118. </template>
  119. <script>
  120. import { Test } from 'src/models/Test.js'
  121. import { APIGateway } from 'src/api/APIGateway.js'
  122. import { FormBuilderAssist } from 'quasar-form-builder'
  123. import EditQuestionForm from './EditQuestionForm.vue'
  124. import CreateQuestionForm from './CreateQuestionForm.vue'
  125. import NormalizeNumber from 'src/assets/js/NormalizeNumber.js'
  126. import { Question, QuestionList } from 'src/models/Question.js'
  127. import { SessionTemplateList } from 'src/models/SessionTemplate.js'
  128. export default {
  129. name: 'QuestionsSelector',
  130. components: {
  131. EditQuestionForm,
  132. CreateQuestionForm
  133. },
  134. props: {
  135. value: {
  136. default: () => [],
  137. type: [Array, String, Number, Boolean]
  138. },
  139. readonly: {
  140. default: false,
  141. type: Boolean
  142. },
  143. showPageLink: {
  144. default: true,
  145. type: Boolean
  146. },
  147. showDownloadLink: {
  148. default: false,
  149. type: Boolean
  150. },
  151. questionType: {
  152. default: 'QUESTION_BANK',
  153. type: String
  154. },
  155. testId: {
  156. default: null,
  157. type: [Number, String, Boolean]
  158. },
  159. testSetId: {
  160. type: [Number, String, Boolean],
  161. default: null
  162. },
  163. unitId: {
  164. default: null,
  165. type: [Number, String, Boolean]
  166. }
  167. },
  168. emits: ['update:value'],
  169. data () {
  170. return {
  171. mounted: false,
  172. selectedQuestionIdForEdit: null,
  173. editQuestionDialog: false,
  174. createNewQuestionDialog: false,
  175. selectedSession: null,
  176. test: new Test(),
  177. testQuestionList: [],
  178. sessions: new SessionTemplateList(),
  179. sessionQuestions: new QuestionList(),
  180. loadedQuestions: {},
  181. tableKeys: {
  182. data: 'results',
  183. total: 'count',
  184. currentPage: 'current',
  185. perPage: 'per_page',
  186. pageKey: 'page'
  187. },
  188. inputs: [
  189. { type: 'hidden', name: 'type', value: this.questionType },
  190. { type: 'hidden', name: 'session_template_questions__session_template', value: null }
  191. // { type: 'hidden', name: 'session_template_questions__session_template__unit', value: this.unitId }
  192. ],
  193. table: {
  194. columns: []
  195. }
  196. }
  197. },
  198. computed: {
  199. selectedSessionTitle () {
  200. if (!this.selectedSession?.title) {
  201. return ''
  202. }
  203. return this.selectedSession?.title
  204. },
  205. countOfForceQuestions () {
  206. return this.testQuestionList.filter(item => !item.is_extra_mark).length
  207. },
  208. countOfExtraQuestions () {
  209. return this.testQuestionList.filter(item => item.is_extra_mark).length
  210. },
  211. sumOfExtraMarks () {
  212. let sum = 0
  213. this.testQuestionList.filter(item => item.is_extra_mark).forEach(item => {
  214. sum += parseInt(NormalizeNumber.toEnglish(item.mark.toString()))
  215. })
  216. return sum
  217. },
  218. sumOfTotalMarks () {
  219. let sum = 0
  220. this.testQuestionList.forEach(item => {
  221. sum += parseInt(NormalizeNumber.toEnglish(item.mark.toString()))
  222. })
  223. return sum
  224. },
  225. localValue: {
  226. get () {
  227. return this.value
  228. },
  229. set (newValue) {
  230. this.$emit('update:value', newValue)
  231. }
  232. },
  233. sessionOptions () {
  234. return this.sessions.list.map(item => {
  235. return {
  236. value: item,
  237. label: item.title
  238. }
  239. })
  240. },
  241. selectedSessionId () {
  242. if (!this.selectedSession) {
  243. return null
  244. }
  245. return this.selectedSession.id
  246. }
  247. },
  248. watch: {
  249. value (newValue) {
  250. this.reFreshTestQuestionList()
  251. }
  252. },
  253. created () {
  254. if (this.questionType === 'QUESTION_BANK') {
  255. this.table.columns = [
  256. {
  257. name: 'number',
  258. required: true,
  259. label: 'شماره',
  260. align: 'left',
  261. field: () => ''
  262. },
  263. {
  264. name: 'id',
  265. required: true,
  266. label: 'شناسه',
  267. align: 'left',
  268. field: row => row.question_info.id
  269. },
  270. {
  271. name: 'text',
  272. required: true,
  273. label: 'عنوان سوال',
  274. align: 'left',
  275. field: row => row.question_info.text
  276. },
  277. {
  278. name: 'level',
  279. required: true,
  280. label: 'سطح سوال',
  281. align: 'left',
  282. field: row => new Question(row.question_info).level_info.label
  283. },
  284. {
  285. name: 'mark',
  286. required: true,
  287. label: 'بارم',
  288. align: 'left',
  289. field: row => row.question_info.mark
  290. },
  291. {
  292. name: 'is_extra_mark',
  293. required: true,
  294. label: 'سوال امتیازی',
  295. align: 'left',
  296. field: row => ''
  297. },
  298. {
  299. name: 'action',
  300. required: true,
  301. label: 'عملیات',
  302. align: 'left',
  303. field: row => ''
  304. }
  305. ]
  306. } else if (this.questionType === 'EVENT') {
  307. this.table.columns = [
  308. {
  309. name: 'number',
  310. required: true,
  311. label: 'شماره',
  312. align: 'left',
  313. field: () => ''
  314. },
  315. {
  316. name: 'id',
  317. required: true,
  318. label: 'شناسه',
  319. align: 'left',
  320. field: row => row.question_info.id
  321. },
  322. {
  323. name: 'text',
  324. required: true,
  325. label: 'عنوان سوال',
  326. align: 'left',
  327. field: row => row.question_info.text
  328. },
  329. {
  330. name: 'action',
  331. required: true,
  332. label: 'عملیات',
  333. align: 'left',
  334. field: row => ''
  335. }
  336. ]
  337. }
  338. this.reFreshTestQuestionList()
  339. },
  340. mounted () {
  341. if (this.questionType === 'QUESTION_BANK') {
  342. this.getSessions()
  343. } else {
  344. this.mounted = true
  345. }
  346. },
  347. methods: {
  348. onQuestionEdited () {
  349. this.editQuestionDialog = false
  350. this.refreshTestQuestions()
  351. },
  352. onQuestionCreated () {
  353. this.createNewQuestionDialog = false
  354. this.refreshTestQuestions()
  355. },
  356. openDialogEditQuestion (questionId) {
  357. this.selectedQuestionIdForEdit = questionId
  358. this.editQuestionDialog = true
  359. },
  360. openDialogCreateNewQuestion () {
  361. this.createNewQuestionDialog = true
  362. },
  363. getShowQuestionRoute (questionId, unitId, selectedSessionId) {
  364. if (this.questionType === 'QUESTION_BANK') {
  365. return { name: 'Admin.Unit.Questions.Show', params: { unit_id: unitId, session_template_id: selectedSessionId, id: questionId } }
  366. } else if (this.questionType === 'EVENT') {
  367. return { name: 'Admin.Event.TestSet.Questions.Show', params: { question_id: questionId } }
  368. }
  369. },
  370. emitValues () {
  371. },
  372. reFreshTestQuestionList () {
  373. if (!this.localValue || !Array.isArray(this.localValue)) {
  374. return
  375. }
  376. this.testQuestionList = JSON.parse(JSON.stringify(this.localValue))
  377. },
  378. editTestQuestion (testQuestion) {
  379. this.test.loading = true
  380. APIGateway.test.editTestQuestion(testQuestion)
  381. .then(() => {
  382. this.test.loading = false
  383. this.refreshTestQuestions()
  384. })
  385. .catch(() => {
  386. this.test.loading = false
  387. })
  388. },
  389. deleteQuestion (questionId) {
  390. this.$q.dialog({
  391. title: 'حذف سوال',
  392. message: 'آیا از حذف سوال اطمینان دارید؟',
  393. cancel: true,
  394. persistent: true
  395. }).onOk(() => {
  396. this.test.loading = true
  397. APIGateway.question.delete({ data: { id: questionId } })
  398. .then(() => {
  399. this.test.loading = false
  400. this.refreshTestQuestions()
  401. })
  402. .catch(() => {
  403. this.test.loading = false
  404. })
  405. }).onCancel(() => {})
  406. },
  407. refreshTestQuestions () {
  408. this.test.loading = true
  409. if (this.testId) {
  410. APIGateway.test.get(this.testId)
  411. .then((test) => {
  412. this.test.loading = false
  413. this.localValue = test.inputData.test_questions_info
  414. this.reFreshTestQuestionList()
  415. })
  416. .catch(() => {
  417. this.test.loading = false
  418. })
  419. } else if (this.testSetId) {
  420. APIGateway.testSet.get(this.testSetId)
  421. .then((testSet) => {
  422. this.test.loading = false
  423. this.localValue = testSet.inputData.test_set_questions
  424. this.reFreshTestQuestionList()
  425. })
  426. .catch(() => {
  427. this.test.loading = false
  428. })
  429. }
  430. },
  431. filterQuestionList () {
  432. if (this.selectedSessionId) {
  433. FormBuilderAssist.setAttributeByName(this.inputs, 'session_template_questions__session_template', 'value', this.selectedSessionId)
  434. }
  435. this.$refs.entityIndexQuestions.search()
  436. },
  437. getSessions () {
  438. this.sessions.loading = true
  439. APIGateway.sessionTemplate.index({ unit: this.unitId || null, per_page: 999999 })
  440. .then((data) => {
  441. this.sessions = new SessionTemplateList(data.list)
  442. if (this.sessions.list.length > 0) {
  443. // this.selectedSession = this.sessions.list[0]
  444. }
  445. this.filterQuestionList()
  446. this.sessions.loading = false
  447. this.mounted = true
  448. })
  449. .catch(() => {
  450. this.sessions.loading = false
  451. this.mounted = true
  452. })
  453. },
  454. getSessionQuestions () {
  455. this.sessions.loading = true
  456. APIGateway.sessionTemplate.index({ unit: this.unitId || null, per_page: 999999 })
  457. .then((data) => {
  458. this.sessions = new SessionTemplateList(data.list)
  459. if (this.sessionOptions.length > 0) {
  460. this.selectedSession = this.sessionOptions[0]
  461. }
  462. this.sessions.loading = false
  463. })
  464. .catch(() => {
  465. this.sessions.loading = false
  466. })
  467. },
  468. onChangeQuestionListPage (response) {
  469. this.updateloadedQuestionsByArray(response.data.results)
  470. },
  471. updateloadedQuestionsByArray (array, allSelected = false) {
  472. if (!array || !Array.isArray(array)) {
  473. return
  474. }
  475. array.forEach(item => {
  476. const questionId = item.question || item.id
  477. const mark = item.mark || 0
  478. const isExtraMark = item.is_extra_mark || false
  479. const selected = allSelected || this.value.includes(questionId)
  480. const target = this.loadedQuestions[questionId]
  481. if (!target) {
  482. this.loadedQuestions[questionId] = {
  483. question: questionId,
  484. mark,
  485. is_extra_mark: isExtraMark,
  486. selected
  487. }
  488. }
  489. })
  490. }
  491. }
  492. }
  493. </script>
  494. <style lang="scss" scoped>
  495. .ContentsSelector {
  496. }
  497. </style>