Pārlūkot izejas kodu

Merge branch 'master' into jenkins

mo 2 gadi atpakaļ
vecāks
revīzija
8050d006ee

+ 6 - 6
src/constant/index.ts

@@ -136,16 +136,16 @@ export const sendType = {
 
 // 发送状态
 export const snedStatus = {
-  WAIT: "待发送",
-  SEND: "已发送",
-  DISABLE: "已停用",
-  EXPIRE: "已失效",
+  WAIT: '待发送',
+  SEND: '已发送',
+  DISABLE: '已停用',
+  EXPIRE: '已失效'
 }
 
 // 测验状态
 export const unitTestStatus = {
-  D_NO_SUBMIT: '未完成',
+  D_NO_SUBMIT: '未提交',
   C_ING: '进行中',
   B_NO_PASS: '不合格',
   A_PASS: '合格'
-}
+}

+ 9 - 0
src/school/approval-manage/batch-adjust.tsx

@@ -3,6 +3,7 @@ import OPopup from '@/components/o-popup'
 import OSticky from '@/components/o-sticky'
 import { postMessage } from '@/helpers/native-message'
 import request from '@/helpers/request'
+import { verifiyNumberInteger } from '@/helpers/toolsValidate'
 import { browser } from '@/helpers/utils'
 import router from '@/router'
 import { state } from '@/state'
@@ -73,6 +74,13 @@ export default defineComponent({
       }
     }
 
+    const onFormatterInt = (val: any) => {
+      if (val && val >= 1) {
+        return verifiyNumberInteger(val)
+      } else {
+        return ''
+      }
+    }
     onMounted(() => {
       getOrchestra()
     })
@@ -167,6 +175,7 @@ export default defineComponent({
             type="number"
             v-model={forms.adjustDay}
             placeholder="请输入调整天数"
+            formatter={onFormatterInt}
             v-slots={{ extra: () => <span style={{ paddingLeft: '6px' }}>天</span> }}
           />
         </CellGroup>

+ 12 - 8
src/school/attendance/components/teacher-attendDetail.tsx

@@ -200,16 +200,18 @@ export default defineComponent({
                     alt=""
                   />
                 </div>
-                <div class={styles.attRang}>
+                <div
+                  class={styles.attRang}
+                  onClick={() => {
+                    setAddress(teacherAttInfo.value.signInLongitudeLatitude)
+                  }}
+                >
                   <p>
                     {teacherAttInfo.value?.signInLongitudeLatitudeStatus === 'NORMAL'
                       ? '考勤范围内'
                       : '考勤范围外'}
                   </p>
                   <p
-                    onClick={() => {
-                      setAddress(teacherAttInfo.value.signInLongitudeLatitude)
-                    }}
                     class={[
                       styles.locP,
                       teacherAttInfo.value?.signInLongitudeLatitudeStatus === 'NORMAL'
@@ -268,7 +270,12 @@ export default defineComponent({
                     alt=""
                   />
                 </div>
-                <div class={styles.attRang}>
+                <div
+                  class={styles.attRang}
+                  onClick={() => {
+                    setAddress(teacherAttInfo.value.signOutLongitudeLatitude)
+                  }}
+                >
                   <p>
                     {' '}
                     {teacherAttInfo.value?.signOutLongitudeLatitudeStatus === 'NORMAL'
@@ -276,9 +283,6 @@ export default defineComponent({
                       : '考勤范围外'}
                   </p>
                   <p
-                    onClick={() => {
-                      setAddress(teacherAttInfo.value.signOutLongitudeLatitude)
-                    }}
                     class={[
                       styles.locP,
                       teacherAttInfo.value?.signOutLongitudeLatitudeStatus === 'NORMAL'

+ 6 - 1
src/school/companion-teacher/companion-teacher-register.tsx

@@ -200,6 +200,12 @@ export default defineComponent({
 
       // t: route.query.t, // 过期时间
       try {
+        // await request.post('/api-school/open/schoolTeacherStudent/queryQrCodeStatus', {
+        //   data: {
+        //     schoolId: state.id,
+        //     qrCodeEffectiveStartTime: state.t ? dayjs(state.t).format('YYYY-MM-DD HH:mm:ss') : null
+        //   }
+        // })
         if (state.t) {
           const { data } = await request.get('/api-school/open/paramConfig/queryByParamName', {
             requestType: 'form',
@@ -207,7 +213,6 @@ export default defineComponent({
               paramName: 'qr_code_expire_hours'
             }
           })
-
           if (dayjs(Number(state.t)).add(data.paramValue, 'hour').isBefore(dayjs())) {
             showDialog({
               title: '提示',

+ 1 - 1
src/school/manage-teacher/index.tsx

@@ -26,10 +26,10 @@ import { useRouter } from 'vue-router'
 import request from '@/helpers/request'
 import { state } from '@/state'
 import OEmpty from '@/components/o-empty'
+import OFullRefresh from '@/components/o-full-refresh'
 import { manageTeacherType } from '@/constant'
 import html2canvas from 'html2canvas'
 import { promisefiyPostMessage, postMessage } from '@/helpers/native-message'
-import OFullRefresh from '@/components/o-full-refresh'
 
 export default defineComponent({
   name: 'companion-teacher',

+ 7 - 0
src/school/school-detail/eidt-school.tsx

@@ -62,6 +62,13 @@ export default defineComponent({
       //   showToast('请选择省市')
       //   return
       // }
+      if (forms.email) {
+        const r = /^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\\.[a-zA-Z0-9-]+)*\.[a-zA-Z0-9]{2,6}$/
+        if (!forms.email.match(r)) {
+          showToast('请输入正确的邮箱')
+          return
+        }
+      }
       if (!forms.address) {
         showToast('请输入详细地址')
         return

+ 3 - 3
src/school/train-planning/component/practice/index.module.less

@@ -4,13 +4,13 @@
   margin: 12px 13px;
   background: #ffebdd;
   border-radius: 10px;
-  padding: 7px 19px;
-  font-size: 14px;
+  padding: 7px 12px;
+  font-size: 13px;
   color: #f67146;
   line-height: 20px;
 
   .icon {
-    font-size: 24px;
+    font-size: 20px;
     margin-right: 6px;
   }
 }

+ 1 - 1
src/school/train-planning/component/practice/index.tsx

@@ -124,7 +124,7 @@ export default defineComponent({
       <div class={styles.practice}>
         <div class={styles.tips}>
           <Icon name="warning" class={styles.icon} />
-          乐团加练可对任意班级进行排课
+          乐团加练可对任意班级进行排课,课程类型可多选
         </div>
 
         <CellGroup inset class={styles.cellGroup}>

+ 2 - 2
src/school/train-planning/component/standard/index.module.less

@@ -8,13 +8,13 @@
   margin: 12px 13px;
   background: #ffebdd;
   border-radius: 10px;
-  padding: 7px 19px;
+  padding: 7px 12px;
   font-size: 13px;
   color: #f67146;
   line-height: 20px;
 
   .icon {
-    font-size: 24px;
+    font-size: 20px;
     margin-right: 6px;
   }
 }

+ 1 - 0
src/student/payment-result/index.module.less

@@ -5,6 +5,7 @@
 
   .orderType {
     padding-top: 35px;
+    padding-bottom: 90px;
     display: flex;
     align-items: center;
     justify-content: center;

+ 17 - 3
src/student/payment-result/index.tsx

@@ -1,5 +1,5 @@
 import OHeader from '@/components/o-header'
-import { defineComponent, onMounted, reactive } from 'vue'
+import { defineComponent, onMounted, reactive, ref } from 'vue'
 import { Button, Cell, CellGroup, Image, showConfirmDialog, Tag } from 'vant'
 import styles from './index.module.less'
 import iconRefunding from './images/icon_refunding.png'
@@ -11,6 +11,7 @@ import { useRoute } from 'vue-router'
 import { orderStatus } from '@/constant'
 import { browser, moneyFormat } from '@/helpers/utils'
 import { useEventListener, useWindowScroll } from '@vueuse/core'
+import { useRect } from '@vant/use'
 
 export default defineComponent({
   name: 'payment-result',
@@ -19,7 +20,9 @@ export default defineComponent({
     const state = reactive({
       orders: {} as any,
       goodsInfos: [] as any,
-      background: 'transparent'
+      background: 'transparent',
+      color: '#fff',
+      backIconColor: 'white' as any
     })
     const getDetails = async () => {
       try {
@@ -88,15 +91,26 @@ export default defineComponent({
         const { y } = useWindowScroll()
         if (y.value > 52) {
           state.background = '#fff'
+          state.color = '#323333'
+          state.backIconColor = 'black'
         } else {
           state.background = 'transparent'
+          state.color = '#fff'
+          state.backIconColor = 'white'
         }
       })
     })
     return () => (
       <div class={styles.paymentResult}>
         <div class={styles.paymentTitle}>
-          {browser().isApp && <OHeader border={false} background={state.background} />}
+          {browser().isApp && (
+            <OHeader
+              border={false}
+              background={state.background}
+              color={state.color}
+              backIconColor={state.backIconColor}
+            />
+          )}
 
           {state.orders.id && (
             <>

+ 146 - 32
src/views/unit-test/examination-mode/index.tsx

@@ -47,12 +47,14 @@ export default defineComponent({
       examDetail: {} as any,
       visiableAnswer: false,
       currentIndex: 0,
-      questionList: [5],
-      answerList: {},
+      questionList: [],
       time: 0,
       visiableSure: false,
       visiableResult: false,
-      visiableExam: false // 考试已结束
+      resultInfo: {} as any,
+      resultStatusType: 'SUCCESS', // 'SUCCESS' | 'FAIL'
+      visiableExam: false, // 考试已结束
+      nextStatus: false
     })
 
     const getExamDetails = async () => {
@@ -66,21 +68,25 @@ export default defineComponent({
             }
           }
         )
-
         const { questionJson, studentAnswerJson, ...res } = data
-        ;(questionJson || []).forEach((item: any) => {
-          if (item.questionTypeCode === 'RADIO') {
-          }
+        const temp = questionJson || []
+        temp.forEach((item: any) => {
+          item.userAnswer = formatUserAnswers(item, studentAnswerJson)
         })
-        state.questionList = state.examDetail = { ...res } || {}
+        console.log(temp)
+        state.questionList = temp
+
+        state.examDetail = { ...res } || {}
 
         calcTime()
       } catch {
         //
-        state.visiableExam = true
       }
     }
 
+    /**
+     * @description 计算考试时间剩余时间
+     */
     const calcTime = () => {
       const examDetail = state.examDetail || {}
       const startTime = examDetail.startTime
@@ -88,11 +94,8 @@ export default defineComponent({
       const timeMinutes = examDetail.timeMinutes || 0 // 测验时间
       // 返回秒
       const minu = dayjs(startTime).add(timeMinutes, 'minute').diff(dayjs(nowTime))
-
-      // console.log(dayjs(startTime).add(timeMinutes, 'minute').valueOf() - dayjs(nowTime).valueOf())
-      // console.log(minu, dayjs(startTime).add(timeMinutes, 'minute'), dayjs(nowTime))
       if (minu <= 0) {
-        console.log('考试已结束')
+        state.visiableExam = true
       } else {
         state.time = Math.ceil(minu / 1000) * 1000
         setTimeout(() => {
@@ -101,6 +104,95 @@ export default defineComponent({
       }
     }
 
+    /**
+     * @description 初始化用户答案
+     */
+    const formatUserAnswers = (item: any, userAnswer: any) => {
+      // 判断是否有结果
+      if (!userAnswer) return []
+      const answers = JSON.parse(userAnswer) || []
+
+      const questionItem = answers.find((child: any) => child.questionId === item.id)
+      return questionItem ? questionItem.details : []
+    }
+
+    /**
+     * @description 下一题 | 测试完成
+     */
+    const onNextQuestion = async () => {
+      try {
+        const questionList = state.questionList || []
+        const userAnswerList: any = [] // 所有题目的答案
+
+        let currentResult = false // 当前题目是否已经答题
+
+        questionList.forEach((question: any, index: number) => {
+          // 格式化所有题目的答案
+          if (question.userAnswer && question.userAnswer.length > 0) {
+            userAnswerList.push({
+              questionId: question.id,
+              details: question.userAnswer
+            })
+          }
+
+          if (index === state.currentIndex) {
+            currentResult = question.userAnswer && question.userAnswer.length > 0 ? true : false
+          }
+        })
+
+        // 判断是否答题了
+        if (!currentResult) {
+          swipeRef.value?.next()
+          return
+        }
+
+        state.nextStatus = true
+        // 判断是否是最后一题
+        if (state.questionList.length === state.currentIndex + 1) {
+          // api-student/studentUnitExamination/completionExamination
+          const { data } = await request.post(
+            '/api-student/studentUnitExamination/completionExamination',
+            {
+              data: {
+                answers: userAnswerList,
+                studentUnitExaminationId: state.id
+              }
+            }
+          )
+          if (data.score >= state.examDetail.passScore) {
+            state.resultStatusType = 'SUCCESS'
+            state.resultInfo = {
+              tips: '恭喜你,测验通过!',
+              score: data.score,
+              examName: data.unitExaminationName
+            }
+          } else {
+            state.resultStatusType = 'FAIL'
+            state.resultInfo = {
+              tips: '本次测验不合格!',
+              score: data.score,
+              examName: data.unitExaminationName
+            }
+          }
+          state.visiableResult = true
+        } else {
+          await request.post('/api-student/studentUnitExamination/submitAnswer', {
+            hideLoading: true,
+            data: {
+              answers: userAnswerList,
+              studentUnitExaminationId: state.id
+            }
+          })
+          swipeRef.value?.next()
+        }
+
+        state.nextStatus = false
+      } catch {
+        //
+        state.nextStatus = false
+      }
+    }
+
     onMounted(() => {
       getExamDetails()
     })
@@ -114,8 +206,11 @@ export default defineComponent({
               <div class={styles.unitCount}>
                 <div class={styles.qNums}>
                   <Icon class={styles.icon} name={iconQuestionNums} />
-                  题目数量 <span class={styles.num}>{state.currentIndex + 1}</span>/
-                  {state.examDetail.questionNum}
+                  题目数量{' '}
+                  <span class={styles.num} style={{ paddingLeft: '6px' }}>
+                    {state.currentIndex + 1}
+                  </span>
+                  /{state.examDetail.questionNum}
                 </div>
                 <div class={styles.qNums}>
                   <Icon class={styles.icon} name={iconCountDown} />
@@ -145,14 +240,33 @@ export default defineComponent({
           }}
         >
           {state.questionList.map((item: any, index: number) => (
+            // item.questionTypeCode === QuestionType.LINK && (
+            //   <SwipeItem>
+            //     <KeepLookQuestion v-model:value={item.userAnswer} data={item} index={index + 1} />
+            //   </SwipeItem>
+            // )
             <SwipeItem>
               {item.questionTypeCode === QuestionType.RADIO && (
-                <ChoiceQuestion index={index + 1} data={item} type="radio" />
+                <ChoiceQuestion
+                  v-model:value={item.userAnswer}
+                  index={index + 1}
+                  data={item}
+                  type="radio"
+                />
               )}
-              {item.questionTypeCode === 'CHECKBOX' && <ChoiceQuestion type="checkbox" />}
-              {item.questionTypeCode === 'SORT' && <DragQuestion />}
-              {item.questionTypeCode === 'LINK' && <KeepLookQuestion />}
-              {item.questionTypeCode === 'PLAY' && <PlayQuestion />}
+              {item.questionTypeCode === QuestionType.CHECKBOX && (
+                <ChoiceQuestion
+                  v-model:value={item.userAnswer}
+                  index={index + 1}
+                  data={item}
+                  type="checkbox"
+                />
+              )}
+              {item.questionTypeCode === QuestionType.SORT && <DragQuestion />}
+              {item.questionTypeCode === QuestionType.LINK && (
+                <KeepLookQuestion v-model:value={item.userAnswer} data={item} index={index + 1} />
+              )}
+              {item.questionTypeCode === QuestionType.PLAY && <PlayQuestion />}
             </SwipeItem>
           ))}
         </Swipe>
@@ -176,16 +290,11 @@ export default defineComponent({
               block
               round
               type="primary"
-              onClick={() => {
-                // if (state.questionList.length - 1 === state.currentIndex) {
-                //   state.visiableSure = true
-                // } else {
-                swipeRef.value?.next()
-                // }
-              }}
+              onClick={onNextQuestion}
+              loading={state.nextStatus}
+              disabled={state.nextStatus}
             >
-              下一题
-              {/* {state.questionList.length === state.currentIndex + 1 ? '测试完成' : '下一题'} */}
+              {state.questionList.length === state.currentIndex + 1 ? '测试完成' : '下一题'}
             </Button>
             <Image
               src={iconButtonList}
@@ -198,7 +307,7 @@ export default defineComponent({
         {/* 题目集合 */}
         <ActionSheet v-model:show={state.visiableAnswer} title="题目列表" safeAreaInsetBottom>
           <AnswerList
-            value={[1, 3, 4]}
+            value={state.questionList}
             onSelect={(item: any) => {
               // 跳转,并且跳过动画
               swipeRef.value?.swipeTo(item, {
@@ -215,13 +324,18 @@ export default defineComponent({
           style={{ background: 'transparent', width: '96%' }}
         >
           <ResultFinish
-            // status="FAIL"
+            status={state.resultStatusType as any}
+            result={state.resultInfo}
             confirmButtonText="我知道了"
             cancelButtonText="去练习"
-            onClose={() => (state.visiableResult = false)}
+            onClose={() => {
+              state.visiableResult = false
+              router.back()
+            }}
             onConform={() => {
               console.log('Success')
               state.visiableResult = false
+              router.back()
             }}
           />
         </Popup>

+ 5 - 7
src/views/unit-test/model/answer-list/index.tsx

@@ -9,10 +9,6 @@ export default defineComponent({
       type: Array,
       default: () => []
     },
-    answerList: {
-      type: Array,
-      default: () => []
-    },
     statusList: {
       type: Array,
       default: () => [
@@ -42,9 +38,11 @@ export default defineComponent({
         </div>
 
         <Grid class={styles.aList} columnNum={6} border={false}>
-          {[1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 10].map((item: any) => (
-            <GridItem onClick={() => emit('select', item)}>
-              <span class={props.value.includes(item) && styles.answered}>{item}</span>
+          {props.value.map((item: any, index: number) => (
+            <GridItem onClick={() => emit('select', index)}>
+              <span class={item.userAnswer && item.userAnswer.length > 0 && styles.answered}>
+                {index + 1}
+              </span>
             </GridItem>
           ))}
         </Grid>

+ 27 - 19
src/views/unit-test/model/choice-question/index.tsx

@@ -8,8 +8,8 @@ export default defineComponent({
   name: 'choice-question',
   props: {
     value: {
-      type: [String, Number, Array],
-      default: ''
+      type: Array,
+      default: () => []
     },
     type: {
       type: String as PropType<'radio' | 'checkbox'>,
@@ -33,22 +33,38 @@ export default defineComponent({
   setup(props, { emit }) {
     const onSelect = (item: any) => {
       if (props.readOnly) return
+      const value: any = props.value || []
+      const result = {
+        answerId: item.examinationQuestionAnswerId,
+        answer: item.questionAnswer,
+        extra: item.questionExtra
+      }
       if (props.type === 'checkbox') {
-        // 判断是否已选过
-        const value: any = props.value
-        if (value.includes(item.index)) {
-          const index = value.findIndex((v: any) => v === item.index)
-          value.splice(index, 1)
+        const tempIndex = value.findIndex(
+          (c: any) => c.answerId === item.examinationQuestionAnswerId
+        )
+        if (tempIndex >= 0) {
+          value.splice(tempIndex, 1)
           emit('update:value', [...value])
         } else {
-          emit('update:value', [item.index, ...value])
+          emit('update:value', [...value, result])
         }
       } else {
-        emit('update:value', item.index)
+        emit('update:value', [result])
       }
     }
 
-    const answers = computed(() => props.data.answers || [])
+    const answers = computed(() => {
+      const list: any = props.data.answers || []
+      const value: any = props.value || []
+      list.forEach((item: any) => {
+        const tempIndex = value.findIndex(
+          (c: any) => c.answerId === item.examinationQuestionAnswerId
+        )
+        item.checked = tempIndex !== -1 ? true : false
+      })
+      return list
+    })
     const mediaUrls = computed(() => (props.data.mediaUrls ? props.data.mediaUrls.split(',') : ''))
 
     return () => (
@@ -68,15 +84,7 @@ export default defineComponent({
           {/* styles.active */}
           {answers.value.map((item: any, index: number) => (
             <div
-              class={[
-                styles.unitAnswer,
-                props.type === 'radio' &&
-                  props.value === item.examinationQuestionAnswerId &&
-                  styles.active,
-                props.type === 'checkbox' &&
-                  (props.value as any).includes(item.examinationQuestionAnswerId) &&
-                  styles.active
-              ]}
+              class={[styles.unitAnswer, item.checked && styles.active]}
               onClick={() => onSelect(item)}
             >
               <span class={styles.option}>{labelOptions[index + 1]}.</span>

+ 1 - 0
src/views/unit-test/model/keep-look-question/index.module.less

@@ -63,6 +63,7 @@
     font-weight: 500;
     color: #333333;
     line-height: 22px;
+    text-align: center;
   }
 
   .active {

+ 55 - 63
src/views/unit-test/model/keep-look-question/index.tsx

@@ -8,16 +8,17 @@ export default defineComponent({
   name: 'keep-look-question',
   props: {
     value: {
-      type: [String, Number, Array],
-      default: ''
+      type: Array,
+      default: () => []
     },
-    type: {
-      type: String as PropType<'radio' | 'checkbox'>,
-      default: 'radio'
+    index: {
+      // 题目是第几道
+      type: Number,
+      default: 1
     },
-    answers: {
+    data: {
       type: Object,
-      default: {}
+      default: () => ({})
     },
     /* 只读 */
     readOnly: {
@@ -31,43 +32,9 @@ export default defineComponent({
     const state = reactive({
       answerDomId: 'answer' + +new Date(),
       answerRect: {} as any,
-
       sortable: null as any,
       list: [] as any,
-      options: [
-        {
-          index: 1,
-          value: 'Sol',
-          left: false,
-          right: false,
-          leftLocked: false, // 是否已经连线
-          rightLocked: false // 是否已经连线
-        },
-        {
-          index: 2,
-          value: 'Sal',
-          left: false,
-          right: false,
-          leftLocked: false, // 是否已经连线
-          rightLocked: false // 是否已经连线
-        },
-        {
-          index: 3,
-          value: 'La',
-          left: false,
-          right: false,
-          leftLocked: false, // 是否已经连线
-          rightLocked: false // 是否已经连线
-        },
-        {
-          index: 4,
-          value: 'Si',
-          left: false,
-          right: false,
-          leftLocked: false, // 是否已经连线
-          rightLocked: false // 是否已经连线
-        }
-      ],
+      options: [] as any,
       drawLineList: [] as any,
       selectItem: [] as any
     })
@@ -133,6 +100,8 @@ export default defineComponent({
           renderDrawLine(canvasRef.value)
         }
       }
+
+      onSelect()
     }
 
     const onRightClick = (e: any, item: any) => {
@@ -196,6 +165,8 @@ export default defineComponent({
           renderDrawLine(canvasRef.value)
         }
       }
+
+      onSelect()
     }
 
     /**
@@ -292,23 +263,46 @@ export default defineComponent({
       ctx.stroke()
     }
 
-    // const onSelect = (item: any) => {
-    //   if (props.type === 'checkbox') {
-    //     // 判断是否已选过
-    //     const value: any = props.value
-    //     if (value.includes(item.index)) {
-    //       const index = value.findIndex((v: any) => v === item.index)
-    //       value.splice(index, 1)
-    //       emit('update:value', [...value])
-    //     } else {
-    //       emit('update:value', [item.index, ...value])
-    //     }
-    //   } else {
-    //     emit('update:value', item.index)
-    //   }
-    // }
+    // 返回选中的结果
+    const onSelect = () => {
+      const options = state.options || []
+      const drawLineList = state.drawLineList || []
+      const result: any = []
+      drawLineList.forEach((item: any) => {
+        const leftOption = options.find((child: any) => child.index === item.leftIndex)
+        const rightOption = options.find((child: any) => child.index === item.rightIndex)
+        result.push({
+          answerId: leftOption.index,
+          answer: leftOption.leftValue,
+          extra: rightOption.rightValue
+        })
+      })
+      emit('update:value', result)
+    }
+
+    const initOptions = () => {
+      const answers = props.data.answers || []
+      answers.forEach((answer: any) => {
+        const tmp = {
+          index: answer.examinationQuestionAnswerId,
+          leftValue: answer.questionAnswer,
+          rightValue: answer.questionExtra,
+          leftType: answer.questionAnswerTypeCode || 'TXT',
+          rightType: answer.questionExtraTypeCode || 'TXT',
+          left: false,
+          right: false,
+          leftLocked: false, // 是否已经连线
+          rightLocked: false // 是否已经连线
+        }
+
+        state.options.push(tmp)
+      })
+
+      console.log(state.options)
+    }
 
     onMounted(() => {
+      initOptions()
       // 获取canvas 对象
       nextTick(() => {
         // 获取canvas元素定位
@@ -343,17 +337,15 @@ export default defineComponent({
                 class={[styles.unitItem, item.left && styles.active]}
                 onClick={(e: any) => onLeftClick(e, item)}
               >
-                <Image
-                  src="https://lanhu-dds-backend.oss-cn-beijing.aliyuncs.com/merge_image/imgs/dbb27307d428424c8efb9f26032cfa1a_mergeImage.png"
-                  class={styles.img}
-                />
+                {item.leftType === 'TXT' && item.leftValue}
+                {item.leftType === 'IMAGE' && <Image src={item.leftValue} class={styles.img} />}
               </div>
               <div
                 class={[styles.unitItem, item.right && styles.active]}
                 onClick={(e: any) => onRightClick(e, item)}
               >
-                {item.value}
-                {item.index}
+                {item.rightType === 'TXT' && item.rightValue}
+                {item.rightType === 'IMAGE' && <Image src={item.rightValue} class={styles.img} />}
               </div>
             </div>
           ))}

+ 11 - 6
src/views/unit-test/model/result-finish/index.tsx

@@ -18,9 +18,13 @@ export default defineComponent({
       type: String as PropType<'SUCCESS' | 'FAIL'>,
       default: 'SUCCESS'
     },
-    resultString: {
-      type: String,
-      default: '恭喜你,测验通过!'
+    result: {
+      type: Object,
+      default: () => ({
+        tips: '恭喜你,测验通过!',
+        score: 0,
+        examName: ''
+      })
     }
   },
   emits: ['close', 'conform'],
@@ -30,10 +34,11 @@ export default defineComponent({
         <div class={styles.finishContainer}>
           <div class={styles.finishContent}>
             <div class={styles.scoreNums}>
-              98<span>分</span>
+              {props.result.score}
+              <span>分</span>
             </div>
-            <div class={styles.scoreName}>《长笛level上册测验一》</div>
-            <div class={styles.scoreResult}>{props.resultString}</div>
+            <div class={styles.scoreName}>{props.result.examName}</div>
+            <div class={styles.scoreResult}>{props.result.tips}</div>
           </div>
         </div>
 

+ 9 - 0
src/views/unit-test/unit-list/index.module.less

@@ -112,6 +112,15 @@
     }
   }
   :global {
+    .van-tabs__nav {
+      padding-right: 0;
+      padding-left: 0;
+      .van-tab {
+        &:nth-child(1) {
+          padding-left: 0;
+        }
+      }
+    }
     .van-tab__text {
       font-size: 16px;
     }

+ 4 - 1
src/views/unit-test/unit-list/models/unit-list-item.tsx

@@ -26,8 +26,11 @@ export default defineComponent({
         }
       })
     }
+    const gotoDetail = () => {
+      router.push({ path: '/unitDetail', query: { id: props.item.id } })
+    }
     return () => (
-      <div class={styles.unitItem}>
+      <div class={styles.unitItem} onClick={gotoDetail}>
         <div class={styles.unitItemTop}>
           <div class={styles.unitItemLeft}>
             <p>

+ 64 - 0
src/views/unit-test/unit-list/models/unit-student-item.tsx

@@ -0,0 +1,64 @@
+import { ActionSheet, Button, Cell, CellGroup, Icon, Image, List } from 'vant'
+import { defineComponent, reactive } from 'vue'
+import styles from './unit-student.module'
+import iconEdit from '../../images/icon-edit.png'
+import iconMessage from '@common/images/icon-muit-message.png'
+
+import { postMessage } from '@/helpers/native-message'
+import { useRouter } from 'vue-router'
+import dayjs from 'dayjs'
+export default defineComponent({
+  name: 'unit-student-item',
+  props: {
+    item: {
+      type: Object,
+      default: () => {}
+    }
+  },
+  setup(props) {
+    const router = useRouter()
+    const gotoMsg = () => {
+      postMessage({
+        api: 'joinChatGroup',
+        content: {
+          type: 'multi', // single 单人 multi 多人
+          id: props.item?.imGroupId
+        }
+      })
+    }
+    return () => (
+      <div class={styles.unitItem}>
+        <div class={styles.unitItemTop}>
+          <div class={styles.unitItemLeft}>
+            <p>
+              {' '}
+              <Image src={iconEdit} class={styles.editImg} />
+              {props.item?.classGroupName || '--'}
+            </p>
+            <span>
+              {dayjs(props.item?.createTime).format('YYYY-MM-DD HH:mm')} 至{' '}
+              {dayjs(props.item?.expiryDate).format('YYYY-MM-DD HH:mm')}
+            </span>
+          </div>
+          <div class={styles.unitItemICon} onClick={gotoMsg}>
+            <Image src={iconMessage} class={styles.msgImg}></Image>
+          </div>
+        </div>
+        <div class={[styles.unitItemInfo]}>
+          <div class={[styles.unitItemInfoWrap, styles.unitItemInfoWrapLine]}>
+            <p class={styles.unitItemInfoMain}>{props.item?.commitNum || 0}</p>
+            <p class={styles.unitItemInfosub}>提交人数</p>
+          </div>
+          <div class={[styles.unitItemInfoWrap]}>
+            <p class={[styles.unitItemInfoMain, styles.orange]}>{props.item?.passNum || 0}</p>
+            <p class={styles.unitItemInfosub}>达标人数</p>
+          </div>
+        </div>
+        <div class={styles.unitItemOrchestra}>
+          <p>{props.item?.orchestraName || '--'}</p>
+          <Icon class={styles.arrow} name="arrow"></Icon>
+        </div>
+      </div>
+    )
+  }
+})

+ 183 - 0
src/views/unit-test/unit-list/models/unit-student-list.tsx

@@ -0,0 +1,183 @@
+import {
+  ActionSheet,
+  Button,
+  Cell,
+  CellGroup,
+  Radio,
+  CheckboxGroup,
+  Icon,
+  Image,
+  List,
+  RadioGroup,
+  Tag
+} from 'vant'
+import { defineComponent, onMounted, reactive } from 'vue'
+import styles from './unit-student.module.less'
+import iconEdit from '../../images/icon-edit.png'
+import iconMessage from '@common/images/icon-muit-message.png'
+import iconTeacher from '@common/images/icon_teacher.png'
+import { postMessage } from '@/helpers/native-message'
+import { useRoute, useRouter } from 'vue-router'
+import OEmpty from '@/components/o-empty'
+import OFullRefresh from '@/components/o-full-refresh'
+import dayjs from 'dayjs'
+import request from '@/helpers/request'
+import { unitTestStatus } from '@/constant'
+export default defineComponent({
+  name: 'unit-student-list',
+  props: {
+    item: {
+      type: Object,
+      default: () => {}
+    }
+  },
+  setup(props) {
+    const router = useRouter()
+    const route = useRoute()
+    const searchList = reactive({
+      classGroupUnitExaminationId: route.query.id,
+      status: '',
+      page: 1
+    })
+    const form = reactive({
+      list: [],
+      listState: {
+        dataShow: true, // 判断是否有数据
+        loading: false,
+        finished: false,
+        refreshing: false,
+        height: 0 // 页面头部高度,为了处理下拉刷新用的
+      }
+    })
+    const gotoMsg = () => {
+      postMessage({
+        api: 'joinChatGroup',
+        content: {
+          type: 'multi', // single 单人 multi 多人
+          id: props.item?.imGroupId
+        }
+      })
+    }
+    onMounted(() => {
+      getList()
+    })
+    const getList = async () => {
+      try {
+        const res = await request.post('/api-teacher/studentUnitExamination/studentDetail', {
+          data: {
+            ...searchList
+          },
+          requestType: 'form'
+        })
+        form.listState.loading = false
+        form.listState.refreshing = false
+        const result = res.data || {}
+        // 处理重复请求数据
+        if (form.list.length > 0 && result.current === 1) {
+          return
+        }
+        form.list = form.list.concat(result[1] || [])
+        form.listState.finished = result.current >= result.pages
+        searchList.page = result.current + 1
+        form.listState.dataShow = form.list.length > 0
+      } catch {
+        form.listState.dataShow = false
+        form.listState.finished = true
+        form.listState.refreshing = false
+      }
+    }
+
+    const onSearch = () => {
+      searchList.page = 1
+      form.list = []
+      form.listState.dataShow = true // 判断是否有数据
+      form.listState.loading = false
+      form.listState.finished = false
+      getList()
+    }
+    return () => (
+      <div class={styles.unitList}>
+        <RadioGroup checked-color="#FF8057" v-model={searchList.status} direction="horizontal">
+          <Tag
+            size="large"
+            type="primary"
+            color={searchList.status !== 'A_PASS' ? '#EAEAEA' : '#FF8057'}
+            textColor={searchList.status !== 'A_PASS' ? '#AAA' : '#FFF'}
+            class={styles.radioSection}
+          >
+            <Radio class={styles.radioItem} name={'A_PASS'}></Radio>
+            {unitTestStatus['A_PASS']}
+          </Tag>
+          <Tag
+            size="large"
+            type="primary"
+            color={searchList.status !== 'B_NO_PASS' ? '#EAEAEA' : '#FF8057'}
+            textColor={searchList.status !== 'B_NO_PASS' ? '#AAA' : '#FFF'}
+            class={styles.radioSection}
+          >
+            <Radio class={styles.radioItem} name={'B_NO_PASS'}></Radio>{' '}
+            {unitTestStatus['B_NO_PASS']}
+          </Tag>
+          <Tag
+            size="large"
+            type="primary"
+            color={searchList.status !== 'D_NO_SUBMIT' ? '#EAEAEA' : '#FF8057'}
+            textColor={searchList.status !== 'D_NO_SUBMIT' ? '#AAA' : '#FFF'}
+            class={styles.radioSection}
+          >
+            <Radio class={styles.radioItem} name={'D_NO_SUBMIT'}></Radio>
+            {unitTestStatus['D_NO_SUBMIT']}
+          </Tag>
+        </RadioGroup>
+        {form.listState.dataShow ? (
+          <OFullRefresh
+            v-model:modelValue={form.listState.refreshing}
+            onRefresh={onSearch}
+            style={{
+              minHeight: `calc(100vh - ${form.listState.height}px)`
+            }}
+          >
+            <List
+              v-model:loading={form.listState.loading}
+              finished={form.listState.finished}
+              finishedText=" "
+              class={[styles.liveList]}
+              onLoad={getList}
+              immediateCheck={false}
+            >
+              {form.list.map((item: any) => (
+                <Cell center class={styles.manageCell}>
+                  {{
+                    icon: () => (
+                      <Image class={styles.img} src={item.avatar ? item.avatar : iconTeacher} />
+                    ),
+                    title: () => (
+                      <div class={styles.content}>
+                        <div class={styles.TagWrap}>
+                          {' '}
+                          <p class={[styles.name, 'van-ellipsis']}>
+                            {item.studentName ? item.studentName : '--'}
+                          </p>
+                          <div class={[styles.studentTag]}>{unitTestStatus[item.status]}</div>
+                        </div>
+
+                        <p class={styles.phone}>{item.submitTime}</p>
+                      </div>
+                    )
+                    // value: () => (
+                    //   <span class={[styles.status, item.status === 'LOCKED' ? styles.frozen : '']}>
+                    //     {manageTeacherType[item.status]}
+                    //   </span>
+                    // )
+                  }}
+                </Cell>
+              ))}
+            </List>
+          </OFullRefresh>
+        ) : (
+          <OEmpty btnStatus={false} tips="暂无学员" />
+        )}
+      </div>
+    )
+  }
+})

+ 81 - 0
src/views/unit-test/unit-list/models/unit-student.module.less

@@ -0,0 +1,81 @@
+.radioSection {
+  position: relative;
+  min-width: 32px;
+  justify-content: center;
+}
+
+.radioItem {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  opacity: 0;
+}
+
+.radioSection + .radioSection {
+  margin-left: 12px;
+}
+
+.unitList {
+  padding-top: 12px;
+  .liveList {
+    margin-top: 12px;
+    border-radius: 10px;
+    overflow: hidden;
+  }
+  .manageCell {
+    padding: 15px 13px;
+    .TagWrap {
+      display: flex;
+      flex-direction: row;
+      align-items: center;
+      .studentTag {
+        background-color: #ff8057;
+        border-radius: 4px;
+        line-height: 19px;
+        padding: 0 8px;
+        color: #fff;
+        margin-left: 6px;
+      }
+      .A_PASS {
+        background-color: #ff8057;
+      }
+      .D_NO_SUBMIT {
+        background-color: #8f80ff;
+      }
+      .B_NO_PASS {
+        background-color: #ff5c5f;
+      }
+    }
+    .img {
+      width: 48px;
+      height: 48px;
+      overflow: hidden;
+      border-radius: 50%;
+      margin-right: 10px;
+    }
+
+    .name {
+      font-size: 16px;
+      font-weight: 500;
+      color: #333333;
+      line-height: 22px;
+      max-width: 200px;
+    }
+    .phone {
+      font-size: 14px;
+      color: #777777;
+      line-height: 20px;
+    }
+
+    .status {
+      font-size: 16px;
+      font-weight: 500;
+      color: #333333;
+    }
+    .frozen {
+      color: #f44541;
+    }
+  }
+}

+ 16 - 5
src/views/unit-test/unit-list/unitDetail.tsx

@@ -18,8 +18,9 @@ import {
 import { defineComponent, onMounted, reactive, ref } from 'vue'
 import questIcon from '@/school/images/quest-icon.png'
 import styles from './index.module.less'
-import { useRouter } from 'vue-router'
+import { useRoute, useRouter } from 'vue-router'
 import UnitListItem from './models/unit-list-item'
+import UnitStudentList from './models/unit-student-list'
 import OFullRefresh from '@/components/o-full-refresh'
 //
 import request from '@/helpers/request'
@@ -27,12 +28,22 @@ export default defineComponent({
   name: 'unitDetail',
   setup() {
     const router = useRouter()
+    const route = useRoute()
     const form = reactive({})
     const refreshing = ref(false)
     const loading = ref(false)
     const activeName = ref('one')
     const showTip = ref(false)
-    onMounted(() => {})
+    const getDetail = async () => {
+      try {
+        const res = await request.get('/api-teacher/unitExamination/detail', {
+          params: { unitExaminationId: route.query.id }
+        })
+      } catch (e) {}
+    }
+    onMounted(() => {
+      getDetail()
+    })
 
     return () => (
       <div class={styles.unitDetail}>
@@ -57,13 +68,13 @@ export default defineComponent({
             shrink
           >
             <Tab name="one" title="I类学生">
-              11111
+              <UnitStudentList></UnitStudentList>
             </Tab>
             <Tab name="two" title="II类学生">
-              22222
+              <UnitStudentList></UnitStudentList>
             </Tab>
             <Tab name="three" title="III类学生">
-              3333333
+              <UnitStudentList></UnitStudentList>
             </Tab>
           </Tabs>
         </div>