Browse Source

feat: 给分板操作控制

chenhao 2 years ago
parent
commit
9b30cf84eb

+ 30 - 16
src/components/shared/ScoringPanel.vue

@@ -12,6 +12,7 @@
       <template v-for="(question, index) in questionList" :key="question.mainNumber + question.subNumber">
         <scoring-panel-item
           v-model:score="scoreValues[index]"
+          v-model:scoreValidFail="scoreValidFail[index]"
           :active="activeIndex === index"
           :modal="dialogMode"
           :toggle-modal="props.toggleModal && index === 0"
@@ -29,7 +30,7 @@
 </template>
 
 <script setup lang="ts" name="ScoringPanel">
-import { watch, withDefaults, ref, defineComponent, useSlots, computed } from 'vue'
+import { watch, withDefaults, ref, defineComponent, useSlots, computed, nextTick } from 'vue'
 
 import { ElButton } from 'element-plus'
 import BaseDialog from '@/components/element/BaseDialog.vue'
@@ -78,20 +79,16 @@ const ScoringPanelContainer = computed(() => {
 
 const modalVisible = useVModel(props, 'visible')
 
-watch(modalVisible, () => {
-  if (!modalVisible.value) {
-    activeIndex.value = 0
-  }
-})
-
-const getClass = (val: string, callback?: string) => {
-  return dialogMode.value ? val : callback || ''
-}
-
 const scoreValues = useVModel(props, 'score')
 
 const activeIndex = ref<number>(0)
 
+const scoreValidFail = ref<boolean[]>([])
+
+watch(modalVisible, () => {
+  activeIndex.value ??= 0
+})
+
 const { fetch: getQuestionStruct, reset: resetQuestionStruct, result: questionStruct } = useFetch('getQuestionStruct')
 
 watch(
@@ -127,15 +124,32 @@ const allowSubmit = computed(() => {
   return questionList.value?.length && scoreValues.value?.length === questionList.value?.length
 })
 
+const getClass = (val: string, callback?: string) => {
+  return dialogMode.value ? val : callback || ''
+}
+
 const onSubmit = () => {
-  emits('submit', questionStruct.value)
+  if (!scoreValidFail.value.some((valid) => valid)) {
+    emits('submit', questionStruct.value)
+  }
 }
 
 const onEnter = (index: number) => {
-  activeIndex.value = index + 1
-  if (activeIndex.value >= questionList.value?.length) {
-    onSubmit()
-  }
+  nextTick(() => {
+    if (scoreValues.value.length >= questionList.value?.length) {
+      const nullScoreIndex = scoreValues.value?.findIndex((v) => !v)
+      const validFailIndexIndex = scoreValidFail.value?.findIndex((v) => !!v)
+      if (nullScoreIndex >= 0) {
+        activeIndex.value = nullScoreIndex
+      } else if (validFailIndexIndex >= 0) {
+        activeIndex.value = validFailIndexIndex
+      } else {
+        onSubmit()
+      }
+    } else {
+      activeIndex.value = index + 1
+    }
+  })
 }
 
 const onFocused = (index: number) => {

+ 66 - 24
src/components/shared/ScoringPanelItem.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="flex scoring-panel" :class="getClass('', 'sticky')">
+  <div class="flex scoring-panel" :class="getClass('modal-panel', 'sticky')">
     <toggle-dialog-render>
       <svg-icon name="question"></svg-icon>
       <span class="m-l-mini">{{ question.mainNumber }} - {{ question.subNumber }}{{ question.mainTitle }}</span>
@@ -23,7 +23,7 @@
         {{ scoreItem }}
       </div>
       <toggle-dialog-render>
-        <div class="flex items-center score-result">
+        <div class="flex items-center score-result" :class="{ 'score-valid-fail': scoreValidFail }">
           <div class="flex-1 text-center">给分</div>
           <input
             ref="refInput1"
@@ -38,15 +38,17 @@
       </toggle-dialog-render>
     </div>
     <toggle-dialog-render dialog>
-      <input
-        ref="refInput2"
-        class="grid radius-base score-input dialog-score"
-        :value="currentScore"
-        @focus="onInputFocus"
-        @blur="onBlur"
-        @keydown="onValidScore"
-        @input="scoreChange"
-      />
+      <div class="grid radius-base dialog-score" :class="{ 'score-valid-fail': scoreValidFail }">
+        <input
+          ref="refInput2"
+          class="score-input"
+          :value="currentScore"
+          @focus="onInputFocus"
+          @blur="onBlur"
+          @keydown="onValidScore"
+          @input="scoreChange"
+        />
+      </div>
     </toggle-dialog-render>
     <toggle-dialog-render>
       <svg-icon
@@ -63,7 +65,7 @@
 import { watch, computed, ref, nextTick, withDefaults, defineComponent, useSlots } from 'vue'
 import SvgIcon from '@/components/common/SvgIcon.vue'
 import useVModel from '@/hooks/useVModel'
-import { getNumbers } from '@/utils/common'
+import { getNumbers, isDefine } from '@/utils/common'
 
 interface QuestionInfo {
   mainNumber: number
@@ -83,9 +85,10 @@ const props = withDefaults(
     score?: number | string | undefined
     /** active 当前正在评分 */
     active: boolean
+    scoreValidFail: boolean | undefined
     question: QuestionInfo
   }>(),
-  { modal: false, toggleModal: true, score: void 0 }
+  { modal: false, toggleModal: true, score: void 0, scoreValidFail: false }
 )
 
 const emits = defineEmits(['focused', 'toggle-click', 'enter', 'update:score'])
@@ -94,6 +97,8 @@ const dialogMode = ref<boolean>(props.modal)
 
 const currentScore = useVModel(props, 'score')
 
+const scoreValidFail = useVModel(props, 'scoreValidFail')
+
 const ToggleDialogRender = defineComponent({
   name: 'DialogHideNode',
   inheritAttrs: false,
@@ -111,10 +116,6 @@ const ToggleDialogRender = defineComponent({
   },
 })
 
-const getClass = (val: string, callback?: string) => {
-  return dialogMode.value ? val : callback || ''
-}
-
 const question = computed(() => {
   return props.question || ({} as QuestionInfo)
 })
@@ -143,6 +144,10 @@ watch(
   { immediate: true, deep: true }
 )
 
+const getClass = (val: string, callback?: string) => {
+  return dialogMode.value ? val : callback || ''
+}
+
 const onSetScore = (v: number | string) => {
   onInputFocus()
   currentScore.value = v
@@ -153,8 +158,10 @@ const onInputFocus = () => {
   emits('focused')
 }
 
-const onBlur = () => {
+const onBlur = (e: Event) => {
   focused.value = false
+  const target = e.target as HTMLInputElement
+  target.value && scoreStrictValidFail(target.value)
 }
 
 const inputFocus = () => {
@@ -183,6 +190,11 @@ const validScore = (score: number | string) => {
   return scoreList.value.some((s) => `${s}`.startsWith(`${score}`))
 }
 
+const scoreStrictValidFail = (score: number | string) => {
+  scoreValidFail.value = !scoreList.value.some((s) => `${s}` === `${score}`)
+  return scoreValidFail.value
+}
+
 const deleteStringChart = (str: string, index: number) => {
   return str.substring(0, index) + str.substring(index + 1)
 }
@@ -206,8 +218,10 @@ const onValidScore = (e: KeyboardEvent) => {
   }
 
   if ('Enter' === e.key) {
-    if (oldScore) {
-      emits('enter')
+    if (oldScore && !scoreStrictValidFail(oldScore)) {
+      nextTick(() => {
+        emits('enter')
+      })
     }
     e.preventDefault()
     return
@@ -250,10 +264,13 @@ const onToggleClick = () => {
   background-color: $MainLayoutHeaderBg;
   font-size: $MediumFont;
   margin-bottom: 6px;
+
+  &.modal-panel {
+    padding: 10px 0;
+  }
   &.sticky {
-    // height: $MainLayoutHeaderHeight;
     align-items: center;
-    padding: 10px 20px;
+    padding: 12px 20px;
   }
 
   .dialog-name {
@@ -294,7 +311,6 @@ const onToggleClick = () => {
       height: 32px;
       line-height: 32px;
       min-width: 84px;
-      overflow: hidden;
       margin-bottom: 8px;
 
       .score-num {
@@ -312,17 +328,43 @@ const onToggleClick = () => {
   .dialog-score {
     width: 80px;
     place-items: center;
-    text-align: center;
     background: $color--primary;
     color: $color--white;
     font-size: 32px;
     outline: none;
+    .score-input {
+      background: inherit;
+      color: inherit;
+      font-size: inherit;
+      width: 100%;
+    }
   }
 
   .score-input {
     border: none;
     outline: none;
     font-weight: bold;
+    text-align: center;
+  }
+
+  .score-valid-fail {
+    position: relative;
+    // background-color: $DangerColor !important;
+    border: 1px solid $DangerColor;
+    box-shadow: 0 0 4px $DangerColor;
+    &:before {
+      content: '无效分值';
+      position: absolute;
+      width: 100%;
+      height: 1em;
+      line-height: 1;
+      text-align: center;
+      top: -1.2em;
+      left: 50%;
+      transform: translateX(-50%);
+      font-size: 10px;
+      color: $DangerColor;
+    }
   }
 
   .toggle-icon {

+ 1 - 2
src/components/shared/ScoringPanelWithConfirm.vue

@@ -77,8 +77,7 @@ watch(submitModalVisible, () => {
 
 /** 总分 */
 const totalScore = computed(() => {
-  let value = modelScore.value[0] as any
-  return value === '' || value === undefined ? add(0) : add(...modelScore.value)
+  return add(...modelScore.value.map((v) => v || 0))
 })
 
 const questionInfo = ref<ExtractApiResponse<'getQuestionStruct'>>()

+ 9 - 4
src/modules/analysis/group-monitoring-detail/index.vue

@@ -113,7 +113,7 @@ const onPreview = () => {
 }
 
 /** 抽查详情列表 */
-const { pagination, currentPage, total, data } = useTable(
+const { pagination, currentPage, total, data, fetchTable } = useTable(
   'getGroupMonitorDetail',
   {
     operateType: query.operateType as 'VIEW' | 'MARK',
@@ -122,7 +122,7 @@ const { pagination, currentPage, total, data } = useTable(
   { pageSize: 4 }
 )
 
-const { tableRef, current, onCurrentChange, tableData } = useTableCheck(data)
+const { tableRef, current, onCurrentChange, tableData, next } = useTableCheck(data)
 
 /** 抽查详情表格配置 */
 const tableColumn: EpTableColumn<ExtractMultipleApiResponse<'getGroupMonitorDetail'>>[] = [
@@ -177,8 +177,10 @@ const getXAxisData = <K extends keyof ExtractArrayValue<StatisticObjectiveByMark
 
 const markerSubjectiveChartsOption = computed<EChartsOption>(() => {
   return {
+    grid: {
+      bottom: 30,
+    },
     legend: {
-      right: 0,
       itemWidth: 14,
       data: ['评卷员主观分布', '小组主观分布', '题组主观分布'],
     },
@@ -234,8 +236,10 @@ const markerSubjectiveChartsOption = computed<EChartsOption>(() => {
 
 const markerObjectiveChartsOption = computed<EChartsOption>(() => {
   return {
+    grid: {
+      bottom: 30,
+    },
     legend: {
-      right: 0,
       itemWidth: 14,
       data: ['评卷员客观分布', '小组客观分布', '题组客观分布'],
     },
@@ -295,6 +299,7 @@ const onSubmit = async () => {
       return
     }
     await useFetch('markMonitorDetailTask').fetch({ taskId: current.value.taskId, scores: modelScore.value })
+    fetchTable()
   } catch (error) {
     console.error(error)
   }