ScoringPanel.vue 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. <template>
  2. <scoring-panel-container
  3. v-model="modalVisible"
  4. title="键盘给分"
  5. modal-class="no-mask"
  6. :width="'388px'"
  7. :modal="false"
  8. :can-resize="'can-resize'"
  9. class="keybord-dialog"
  10. @close="onToggleClick"
  11. >
  12. <div v-loading="props.loading" class="scoring-panel-box" :class="getClass('modal-box')">
  13. <template v-for="(question, index) in questionList" :key="question.mainNumber + question.subNumber">
  14. <div v-if="dialogMode" class="flex dialog-name items-center">
  15. <p>{{ question.mainNumber }} - {{ question.subNumber }}</p>
  16. <p class="main-title">{{ question.mainTitle }}</p>
  17. </div>
  18. <scoring-panel-item
  19. :id="props.id"
  20. v-model:score="scoreValues[index]"
  21. v-model:scoreValidFail="scoreValidFail[index]"
  22. :active="activeIndex === index"
  23. :modal="dialogMode"
  24. :toggle-modal="props.toggleModal && index === questionList.length - 1"
  25. :show-confirm-btn="index == questionList.length - 1"
  26. :show-all-paper-btn="props.showAllPaperBtn"
  27. :question="question"
  28. :large="props.large"
  29. :allow-submit="allowSubmit"
  30. :cannot-toggle="props.cannotToggle"
  31. :loading="props.loading"
  32. @blur="() => onBlur(index)"
  33. @enter="() => onEnter(index)"
  34. @focused="() => onFocused(index)"
  35. @toggle-click="onToggleClick"
  36. @view-papers="() => onViewPapers()"
  37. ></scoring-panel-item>
  38. </template>
  39. </div>
  40. <template #footer>
  41. <el-button type="primary" class="full-w" :disabled="buttonDisabled" @click="onEnter(0)">{{
  42. buttonText
  43. }}</el-button>
  44. </template>
  45. </scoring-panel-container>
  46. </template>
  47. <script setup lang="ts" name="ScoringPanel">
  48. import { watch, withDefaults, ref, defineComponent, useSlots, computed, nextTick, onMounted, onUnmounted } from 'vue'
  49. import { ElButton } from 'element-plus'
  50. import BaseDialog from '@/components/element/BaseDialog.vue'
  51. import ScoringPanelItem from './ScoringPanelItem.vue'
  52. import useVModel from '@/hooks/useVModel'
  53. import useVW from '@/hooks/useVW'
  54. import useFetch from '@/hooks/useFetch'
  55. import { sessionStorage } from '@/plugins/storage'
  56. import bus from '@/utils/bus'
  57. import useMarkStore from '@/store/mark'
  58. const markStore = useMarkStore()
  59. const props = withDefaults(
  60. defineProps<{
  61. /** 弹窗模式? */
  62. modal?: boolean
  63. /** 是否可以切换显示模式 */
  64. toggleModal?: boolean
  65. /** 显示隐藏 */
  66. visible?: boolean
  67. /** 分值 */
  68. score: (number | string)[]
  69. // mainNumber?: number | null
  70. mainNumber?: any
  71. subjectCode?: any
  72. id?: any
  73. autoVisible?: boolean | undefined
  74. large?: boolean
  75. cannotToggle?: boolean
  76. loading?: boolean
  77. showAllPaperBtn?: boolean
  78. }>(),
  79. {
  80. modal: false,
  81. toggleModal: true,
  82. score: () => [],
  83. mainNumber: null,
  84. subjectCode: null,
  85. id: null,
  86. autoVisible: true,
  87. large: true,
  88. cannotToggle: false,
  89. loading: false,
  90. showAllPaperBtn: false,
  91. }
  92. )
  93. const emits = defineEmits(['submit', 'update:score', 'update:visible', 'update:modal', 'view-papers'])
  94. const dialogModeBeforeSubmit = ref<boolean>(false)
  95. const dialogMode = ref<boolean>(props.modal)
  96. const LessRenderComponent = defineComponent({
  97. name: 'LessRender',
  98. inheritAttrs: false,
  99. props: {
  100. modelValue: {
  101. type: Boolean,
  102. default: false,
  103. },
  104. },
  105. render() {
  106. return this.modelValue ? useSlots()?.default?.() : null
  107. },
  108. })
  109. const ScoringPanelContainer = computed(() => {
  110. return dialogMode.value ? BaseDialog : LessRenderComponent
  111. })
  112. const modalVisible = useVModel(props, 'visible')
  113. const scoreValues = useVModel(props, 'score')
  114. const activeIndex = ref<number | null>(0)
  115. const scoreValidFail = ref<boolean[]>([])
  116. watch(modalVisible, (val) => {
  117. activeIndex.value = 0
  118. let sessionKeyboardShowType = sessionStorage.get('dialogModeBeforeSubmit')
  119. dialogMode.value =
  120. typeof sessionKeyboardShowType === 'boolean' ? sessionKeyboardShowType : dialogModeBeforeSubmit.value
  121. })
  122. onMounted(() => {
  123. let sessionKeyboardShowType = sessionStorage.get('dialogModeBeforeSubmit')
  124. dialogMode.value =
  125. typeof sessionKeyboardShowType === 'boolean' ? sessionKeyboardShowType : dialogModeBeforeSubmit.value
  126. })
  127. const { fetch: getQuestionStruct, reset: resetQuestionStruct, result: questionStruct } = useFetch('getQuestionStruct')
  128. watch([() => props.id, () => props.autoVisible], () => {
  129. if (props.autoVisible) {
  130. modalVisible.value = !!props.id
  131. }
  132. scoreValues.value = []
  133. })
  134. watch(
  135. () => props.mainNumber,
  136. () => {
  137. /** reset scores */
  138. scoreValues.value = []
  139. if (props.mainNumber) {
  140. resetQuestionStruct()
  141. getQuestionStruct({ mainNumber: props.mainNumber, subjectCode: props.subjectCode })
  142. }
  143. },
  144. { immediate: true }
  145. )
  146. const questionList = computed(() => {
  147. if (!questionStruct.value) {
  148. return []
  149. }
  150. const { mainNumber, mainTitle, questionList = [] } = questionStruct.value
  151. // 更新分数端评卷时间
  152. const markDelay = questionStruct.value.markDelay ? JSON.parse(questionStruct.value.markDelay) : []
  153. const getMarkLevelSpeedLimit = (score: number): number => {
  154. if (markDelay.length) {
  155. const speedLimit = markDelay.find((limit) => {
  156. return limit.endScore >= score && limit.startScore <= score
  157. })
  158. return speedLimit?.minMarkTime || 0
  159. } else {
  160. return 0
  161. }
  162. }
  163. return questionList.map((q) => ({
  164. ...q,
  165. mainNumber,
  166. mainTitle,
  167. markSpeedLimit: getMarkLevelSpeedLimit(q.totalScore),
  168. }))
  169. })
  170. const allowSubmit = computed(() => {
  171. let filterArr = scoreValues.value.filter((score) => score != undefined && score !== '')
  172. // return scoreValues.value?.length === questionList.value?.length
  173. return filterArr.length === questionList.value?.length
  174. })
  175. // 倒计时相关计算属性
  176. const isCountingDown = computed(() => markStore.isCountingDown)
  177. const buttonText = computed(() => markStore.buttonText)
  178. const buttonDisabled = computed(() => {
  179. return isCountingDown.value || !allowSubmit.value
  180. })
  181. // 启动倒计时
  182. const startCountdown = () => {
  183. const markSpeedLimit = questionList.value[activeIndex.value]?.markSpeedLimit
  184. markStore.startCountdown(markSpeedLimit)
  185. }
  186. const currentQuestion = computed(() => {
  187. return questionList.value[activeIndex.value]
  188. })
  189. // 监听questionList变化,启动倒计时
  190. watch(
  191. () => currentQuestion.value,
  192. () => {
  193. startCountdown()
  194. },
  195. { immediate: true, deep: true }
  196. )
  197. const getClass = (val: string, callback?: string) => {
  198. return dialogMode.value ? val : callback || ''
  199. }
  200. const onSubmit = () => {
  201. if (!scoreValidFail.value.some((valid) => valid)) {
  202. emits('submit', questionStruct.value)
  203. }
  204. }
  205. const onEnter = (index: number) => {
  206. dialogModeBeforeSubmit.value = dialogMode.value
  207. sessionStorage.set('dialogModeBeforeSubmit', dialogModeBeforeSubmit.value)
  208. nextTick(() => {
  209. // console.log('index:', index)
  210. // console.log('scoreValues.value.length:', scoreValues.value.length)
  211. // console.log('questionList.value?.length:', questionList.value?.length)
  212. let filterArr = scoreValues.value.filter((score) => score != undefined)
  213. // if (scoreValues.value.length >= questionList.value?.length) {
  214. if (filterArr.length >= questionList.value?.length) {
  215. const nullScoreIndex = scoreValues.value?.findIndex((v) => !`${v}`)
  216. const validFailIndexIndex = scoreValidFail.value?.findIndex((v) => !!v)
  217. if (nullScoreIndex >= 0) {
  218. activeIndex.value = nullScoreIndex
  219. } else if (validFailIndexIndex >= 0) {
  220. activeIndex.value = validFailIndexIndex
  221. } else {
  222. onSubmit()
  223. }
  224. } else {
  225. activeIndex.value = index + 1
  226. }
  227. })
  228. }
  229. const onFocused = (index: number) => {
  230. activeIndex.value = index
  231. }
  232. const onBlur = (index: number) => {
  233. if (activeIndex.value === index) {
  234. // activeIndex.value = null
  235. }
  236. }
  237. const onViewPapers = () => {
  238. emits('view-papers')
  239. }
  240. const onToggleClick = () => {
  241. if (modalVisible.value) {
  242. dialogModeBeforeSubmit.value = dialogMode.value ? false : true
  243. sessionStorage.set('dialogModeBeforeSubmit', dialogModeBeforeSubmit.value)
  244. }
  245. dialogMode.value = props.toggleModal ? !dialogMode.value : dialogMode.value
  246. if (!props.toggleModal) {
  247. modalVisible.value = false
  248. }
  249. }
  250. bus.on('mark-method-toggle', () => {
  251. onToggleClick()
  252. })
  253. bus.on('openScoreDialogByMouseRight', () => {
  254. if (!dialogMode.value) {
  255. onToggleClick()
  256. }
  257. })
  258. // 组件卸载时清除倒计时
  259. onUnmounted(() => {
  260. markStore.clearCountdown()
  261. })
  262. </script>
  263. <style scoped lang="scss">
  264. .scoring-panel-box {
  265. padding-bottom: 4px;
  266. background-color: #fff;
  267. }
  268. .modal-box {
  269. max-height: 50vh;
  270. min-height: 8vw;
  271. .dialog-name {
  272. color: $color--primary;
  273. font-weight: bold;
  274. padding-left: 10px;
  275. }
  276. }
  277. </style>