Prechádzať zdrojové kódy

Merge branch 'master' of http://git.dayaedu.com/lex/orchestra-app

mo 2 rokov pred
rodič
commit
3b8159d50d
45 zmenil súbory, kde vykonal 2024 pridanie a 991 odobranie
  1. 6 0
      src/components/o-upload/index.tsx
  2. 3 1
      src/helpers/request.ts
  3. 101 83
      src/school/approval-manage/agency/index.tsx
  4. 9 0
      src/school/approval-manage/index.module.less
  5. 1 0
      src/school/approval-manage/index.tsx
  6. 1 3
      src/school/approval-manage/subsidy/exercise-detail.tsx
  7. 26 4
      src/school/approval-manage/subsidy/index.tsx
  8. 150 59
      src/school/companion-teacher/companion-teacher-register.tsx
  9. 17 18
      src/school/manage-teacher/manage-teacher-register.tsx
  10. 1 1
      src/school/mass-message/component/student-list/index.tsx
  11. 1 1
      src/school/mass-message/index.tsx
  12. 7 9
      src/school/orchestra/compontent/information.tsx
  13. 1 1
      src/school/orchestra/compontent/plan.tsx
  14. 26 2
      src/school/train-planning/component/course-preview/index.tsx
  15. 1 1
      src/school/train-planning/modal/timer/index.tsx
  16. 4 1
      src/student/my-orchestra/apply-withdrawal.tsx
  17. 0 1
      src/teacher/main.ts
  18. 3 2
      src/views/coursewarePlay/component/musicScore.module.less
  19. 32 0
      src/views/coursewarePlay/component/video-play.tsx
  20. 0 0
      src/views/coursewarePlay/datas/data.json
  21. 47 18
      src/views/coursewarePlay/index.module.less
  22. 301 261
      src/views/coursewarePlay/index.tsx
  23. 66 36
      src/views/unit-test/examination-mode/index.tsx
  24. BIN
      src/views/unit-test/images/icon-analysis.png
  25. BIN
      src/views/unit-test/images/icon-success.png
  26. 13 1
      src/views/unit-test/index.tsx
  27. 26 0
      src/views/unit-test/model/anser-title/index.module.less
  28. 67 0
      src/views/unit-test/model/anser-title/index.tsx
  29. 32 0
      src/views/unit-test/model/answer-analysis/index.module.less
  30. 62 0
      src/views/unit-test/model/answer-analysis/index.tsx
  31. 46 10
      src/views/unit-test/model/answer-list/index.tsx
  32. 4 26
      src/views/unit-test/model/choice-question/index.module.less
  33. 58 32
      src/views/unit-test/model/choice-question/index.tsx
  34. 8 28
      src/views/unit-test/model/drag-question/index.module.less
  35. 159 91
      src/views/unit-test/model/drag-question/index.tsx
  36. 7 1
      src/views/unit-test/model/error-mode/index.tsx
  37. 3 24
      src/views/unit-test/model/keep-look-question/index.module.less
  38. 95 61
      src/views/unit-test/model/keep-look-question/index.tsx
  39. 2 27
      src/views/unit-test/model/play-question/index.module.less
  40. 145 50
      src/views/unit-test/model/play-question/index.tsx
  41. 20 10
      src/views/unit-test/practice-mode/index.module.less
  42. 194 48
      src/views/unit-test/practice-mode/index.tsx
  43. 31 6
      src/views/unit-test/test-exercise/index.tsx
  44. 226 74
      src/views/unit-test/unit-detail/index.tsx
  45. 22 0
      src/views/unit-test/unit.ts

+ 6 - 0
src/components/o-upload/index.tsx

@@ -56,10 +56,15 @@ export default defineComponent({
     bucket: {
       type: String,
       default: 'daya'
+    },
+    disabled: {
+      type: Boolean,
+      default: false
     }
   },
   methods: {
     nativeUpload() {
+      if (this.disabled) return
       postMessage(
         {
           api: 'chooseFile',
@@ -218,6 +223,7 @@ export default defineComponent({
             afterRead={this.afterRead}
             beforeRead={this.beforeRead}
             beforeDelete={this.beforeDelete}
+            disabled={this.disabled}
             v-slots={{
               default: () =>
                 this.modelValue ? (

+ 3 - 1
src/helpers/request.ts

@@ -93,6 +93,7 @@ request.interceptors.response.use(
       closeToast()
     }, 100)
 
+
     if (res.status > 299 || res.status < 200) {
       clearTimeout(toast)
       const msg = '服务器错误,状态码' + res.status
@@ -100,7 +101,8 @@ request.interceptors.response.use(
       throw new Error(msg)
     }
     const data = await res.clone().json()
-    if (data.code !== 200 && data.errCode !== 0) {
+    // 999 为特殊code码
+    if (data.code !== 200 && data.errCode !== 0 && data.code !== 999) {
       let msg = data.msg || data.message || '处理失败,请重试'
       if (initRequest) {
         if (data.code === 403 || data.code === 5000) {

+ 101 - 83
src/school/approval-manage/agency/index.tsx

@@ -7,6 +7,7 @@ import iconPhoto from '../images/icon-photo.png'
 import request from '@/helpers/request'
 import { state } from '@/state'
 import OFullRefresh from '@/components/o-full-refresh'
+import OEmpty from '@/components/o-empty'
 
 interface ISalaryRecord {
   /**学校补助确认记录ID */
@@ -34,8 +35,9 @@ interface ISalaryRecord {
 export default defineComponent({
   name: 'approval-manage-agency',
   setup() {
+    const loading = ref(true)
     const data = reactive({
-      salaryRecordList: [{}] as ISalaryRecord[],
+      salaryRecordList: [] as ISalaryRecord[],
       /**训练照片 */
       schoolWeekPhoto: {
         /**预计的训练照片数 */
@@ -62,9 +64,11 @@ export default defineComponent({
     })
     const refreshing = ref(false)
     const getData = async () => {
+      loading.value = true
       try {
         const res: any = await request.post(`${state.platformApi}/schoolWeekSalaryRecord/manage`)
       } catch (error) {}
+      loading.value = false
       refreshing.value = false
     }
     onMounted(() => {
@@ -77,90 +81,104 @@ export default defineComponent({
         style="min-height: calc(100vh - var(--van-nav-bar-height) - var(--header-height))"
       >
         <div class={styles.wrap}>
-          {data.salaryRecordList.map((item: ISalaryRecord) => {
-            return (
-              <div class={styles.item} style={{ marginTop: 0 }}>
-                <Cell
-                  center
-                  label={`${item.startDate} 至 ${item.endDate}`}
-                  isLink
-                  to={'/approval-manage-subsidy?salaryId=' + (item.id || '')}
-                >
-                  {{
-                    title: () => (
-                      <div class={styles.itemTitle}>
-                        <img class={styles.titleIcon} src={iconSubsidy} />
-                        <span>补助确认</span>
-                      </div>
-                    )
-                  }}
-                </Cell>
-                <Grid class={styles.grid} columnNum={3} border={false}>
-                  <GridItem>
-                    <div class={styles.gridItem}>
-                      <div class={styles.gridItemTop}>
-                        <span class={styles.topNum}>{item.manageSalary}</span>元
-                      </div>
-                      <div>管理补助</div>
-                    </div>
-                  </GridItem>
-                  <GridItem>
-                    <div class={styles.gridItem}>
-                      <div class={styles.gridItemTop}>
-                        <span class={styles.topNum}>{item.courseSalary}</span>元
-                      </div>
-                      <div>训练补助</div>
-                    </div>
-                  </GridItem>
-                  <GridItem>
-                    <div class={styles.gridItem}>
-                      <div class={styles.gridItemTop}>
-                        <span class={styles.topNum}>{item.trainingSalary}</span>元
-                      </div>
-                      <div>练习奖励</div>
-                    </div>
-                  </GridItem>
-                </Grid>
-              </div>
-            )
-          })}
-
-          <div class={styles.item}>
-            <Cell
-              style={{ '--van-cell-value-color': '#333' }}
-              center
-              label={`${data.schoolWeekPhoto.startDate} 至 ${data.schoolWeekPhoto.endDate}`}
-              value={data.schoolWeekPhoto.photoNum + '/' + data.schoolWeekPhoto.expectPhotoNum}
-              isLink
-            >
-              {{
-                title: () => (
-                  <div class={styles.itemTitle}>
-                    <img class={styles.titleIcon} src={iconNews} />
-                    <span>训练照片</span>
+          {!data.salaryRecordList.length ? null : (
+            <>
+              {data.salaryRecordList.map((item: ISalaryRecord) => {
+                return (
+                  <div class={styles.item} style={{ marginTop: 0 }}>
+                    <Cell
+                      center
+                      label={`${item.startDate} 至 ${item.endDate}`}
+                      isLink
+                      to={'/approval-manage-subsidy?salaryId=' + (item.id || '')}
+                    >
+                      {{
+                        title: () => (
+                          <div class={styles.itemTitle}>
+                            <img class={styles.titleIcon} src={iconSubsidy} />
+                            <span>补助确认</span>
+                          </div>
+                        )
+                      }}
+                    </Cell>
+                    <Grid class={styles.grid} columnNum={3} border={false}>
+                      <GridItem>
+                        <div class={styles.gridItem}>
+                          <div class={styles.gridItemTop}>
+                            <span class={styles.topNum}>{item.manageSalary}</span>元
+                          </div>
+                          <div>管理补助</div>
+                        </div>
+                      </GridItem>
+                      <GridItem>
+                        <div class={styles.gridItem}>
+                          <div class={styles.gridItemTop}>
+                            <span class={styles.topNum}>{item.courseSalary}</span>元
+                          </div>
+                          <div>训练补助</div>
+                        </div>
+                      </GridItem>
+                      <GridItem>
+                        <div class={styles.gridItem}>
+                          <div class={styles.gridItemTop}>
+                            <span class={styles.topNum}>{item.trainingSalary}</span>元
+                          </div>
+                          <div>练习奖励</div>
+                        </div>
+                      </GridItem>
+                    </Grid>
                   </div>
                 )
-              }}
-            </Cell>
-          </div>
-          <div class={styles.item}>
-            <Cell
-              style={{ '--van-cell-value-color': '#333' }}
-              center
-              label={`${data.schoolWeekNews.startDate} 至 ${data.schoolWeekNews.endDate}`}
-              value={data.schoolWeekNews.newsNum + '/' + data.schoolWeekNews.expectNewsNum}
-              isLink
-            >
-              {{
-                title: () => (
-                  <div class={styles.itemTitle}>
-                    <img class={styles.titleIcon} src={iconPhoto} />
-                    <span>乐团资讯</span>
-                  </div>
-                )
-              }}
-            </Cell>
-          </div>
+              })}
+            </>
+          )}
+
+          {!data.schoolWeekPhoto.expectPhotoNum ? null : (
+            <div class={styles.item}>
+              <Cell
+                style={{ '--van-cell-value-color': '#333' }}
+                center
+                label={`${data.schoolWeekPhoto.startDate} 至 ${data.schoolWeekPhoto.endDate}`}
+                value={data.schoolWeekPhoto.photoNum + '/' + data.schoolWeekPhoto.expectPhotoNum}
+                isLink
+              >
+                {{
+                  title: () => (
+                    <div class={styles.itemTitle}>
+                      <img class={styles.titleIcon} src={iconNews} />
+                      <span>训练照片</span>
+                    </div>
+                  )
+                }}
+              </Cell>
+            </div>
+          )}
+          {!data.schoolWeekNews.expectNewsNum ? null : (
+            <div class={styles.item}>
+              <Cell
+                style={{ '--van-cell-value-color': '#333' }}
+                center
+                label={`${data.schoolWeekNews.startDate} 至 ${data.schoolWeekNews.endDate}`}
+                value={data.schoolWeekNews.newsNum + '/' + data.schoolWeekNews.expectNewsNum}
+                isLink
+              >
+                {{
+                  title: () => (
+                    <div class={styles.itemTitle}>
+                      <img class={styles.titleIcon} src={iconPhoto} />
+                      <span>乐团资讯</span>
+                    </div>
+                  )
+                }}
+              </Cell>
+            </div>
+          )}
+          {!loading.value &&
+          !data.salaryRecordList.length &&
+          !data.schoolWeekPhoto.expectPhotoNum &&
+          !data.schoolWeekNews.expectNewsNum ? (
+            <OEmpty />
+          ) : null}
         </div>
       </OFullRefresh>
     )

+ 9 - 0
src/school/approval-manage/index.module.less

@@ -226,3 +226,12 @@
     margin-left: 5px;
   }
 }
+.confirmBtn{
+  position: fixed;
+  left: 0;
+  bottom: 0;
+  right: 0;
+  padding: 15px 25px;
+  background: #fff;
+  z-index: 10;
+}

+ 1 - 0
src/school/approval-manage/index.tsx

@@ -29,6 +29,7 @@ export default defineComponent({
           animated
           sticky
           offsetTop={headerHeight.value}
+          swipeable
         >
           <Tab name="wait" title="处理事项">
             <Agency />

+ 1 - 3
src/school/approval-manage/subsidy/exercise-detail.tsx

@@ -22,9 +22,7 @@ export default defineComponent({
     const data = reactive({
       /**补助明细 */
       record: {
-        courseSalaryRecordDetailList: [
-          { courseScheduleTeacherAttendanceList: [{}] }
-        ] as ICourseSalaryRecordDetailItem[]
+        courseSalaryRecordDetailList: [] as ICourseSalaryRecordDetailItem[]
       } as ICourseSalaryRecordDetail
     })
     const getData = () => {

+ 26 - 4
src/school/approval-manage/subsidy/index.tsx

@@ -1,10 +1,10 @@
-import { Cell, Dialog, Grid, GridItem, Sticky } from 'vant'
+import { Button, Cell, Dialog, Grid, GridItem, Sticky } from 'vant'
 import { defineComponent, onMounted, reactive, ref } from 'vue'
 import styles from '../index.module.less'
 import iconA from '../images/icon-photo.png'
 import request from '@/helpers/request'
 import { state } from '@/state'
-import { useRoute } from 'vue-router'
+import { useRoute, useRouter } from 'vue-router'
 import OFullRefresh from '@/components/o-full-refresh'
 import OHeader from '@/components/o-header'
 import OSticky from '@/components/o-sticky'
@@ -13,6 +13,7 @@ export default defineComponent({
   name: 'approval-manage-subsidy',
   setup() {
     const route = useRoute()
+    const router = useRouter()
     const dialog = ref(false)
     const refreshing = ref(false)
     const data = reactive({
@@ -39,6 +40,16 @@ export default defineComponent({
     onMounted(() => {
       getData()
     })
+    /**确认补助 */
+    const sendConfirm = () => {
+      request
+        .post(`${state.platformApi}/choolWeekSalaryRecord/manageConfirmed/${route.query.salaryId}`)
+        .then((res: any) => {
+          if (res?.code == 200) {
+            router.back()
+          }
+        })
+    }
     return () => (
       <div>
         <OSticky
@@ -194,7 +205,7 @@ export default defineComponent({
             </div>
 
             <div class={styles.item}>
-              <Cell center to={'/subsidy-exercise-detail'}>
+              <Cell center>
                 {{
                   title: () => (
                     <div class={styles.itemTitle}>
@@ -246,7 +257,7 @@ export default defineComponent({
                       center
                       title={teacher.userName}
                       isLink
-                      to={'/subsidy-exercise-detail?id='}
+                      to={'/subsidy-exercise-detail?id=' + teacher.id}
                     >
                       {{
                         icon: () => <img class={styles.itemPicture} src={teacher.avatar} />,
@@ -263,8 +274,19 @@ export default defineComponent({
                   )
                 })}
             </div>
+
+            <div class="van-safe-area-bottom">
+              <div style={{ height: 'calc(var(--van-button-default-height) + 0.8rem)' }}></div>
+            </div>
           </div>
         </OFullRefresh>
+        <div class={styles.confirmBtn}>
+          <div class="van-safe-area-bottom">
+            <Button block type="primary" round onClick={sendConfirm}>
+              确认无误
+            </Button>
+          </div>
+        </div>
         <Dialog v-model:show={dialog.value} confirmButtonText="我知道了">
           <div class={[styles.item, styles.tipItem]}>
             <Cell center border={false}>

+ 150 - 59
src/school/companion-teacher/companion-teacher-register.tsx

@@ -18,7 +18,7 @@ import {
   Popup,
   CountDown
 } from 'vant'
-import { defineComponent, onMounted, reactive } from 'vue'
+import { defineComponent, onMounted, reactive, ref } from 'vue'
 import { useRoute } from 'vue-router'
 import styles from './companion-teacher-register.module.less'
 import ImgCode from '@/components/o-img-code'
@@ -41,10 +41,12 @@ export default defineComponent({
       showSubject: false,
       submitStatus: false,
       showEducation: false,
+      checkPhone: true,
       id: route.query.id,
       name: route.query.name,
       t: route.query.t as any, // 过期时间
-      // qrCodeStatus: false, // 二维码是否失效
+      qrCodeStatus: false, // 二维码是否失效
+      qrCodeMessage: '',
       pattern: /^1(3|4|5|6|7|8|9)\d{9}$/,
       columns: [] as any,
       pickerType: null, // 下拉类型
@@ -66,7 +68,6 @@ export default defineComponent({
         idcardBackImg: '' // 身份证反面照
       },
       btnLoading: false,
-      checkPhone: false,
       checked: true,
       columnSubject: [] as any,
       countDownStatus: true, // 是否发送验证码
@@ -77,19 +78,21 @@ export default defineComponent({
     })
 
     const onSubmit = async () => {
-      // if (state.qrCodeStatus) {
-      //   showDialog({
-      //     title: '提示',
-      //     message: '二维码已失效',
-      //     theme: 'round-button',
-      //     confirmButtonColor: '#ff8057'
-      //   })
-      //   return
-      // }
+      if (state.qrCodeStatus) {
+        showDialog({
+          title: '提示',
+          message: state.qrCodeMessage,
+          theme: 'round-button',
+          confirmButtonColor: '#ff8057'
+        })
+        return
+      }
       if (!state.checked) {
         showToast('请阅读并同意协议')
         return
       }
+      console.log(state.forms, '///222')
+      return
       state.btnLoading = true
       try {
         const forms = state.forms
@@ -198,6 +201,28 @@ export default defineComponent({
       //   return
       // }
 
+      // t: route.query.t, // 过期时间
+      try {
+        const res = 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 (res.code === 999) {
+          showDialog({
+            title: '提示',
+            message: res.message,
+            theme: 'round-button',
+            confirmButtonColor: '#ff8057'
+          })
+          state.qrCodeStatus = true
+        }
+      } catch (e: any) {
+        //
+        console.log(e)
+      }
+
       try {
         const tempareas: any = []
         areas.forEach((item) => {
@@ -241,38 +266,6 @@ export default defineComponent({
           confirmButtonColor: '#ff8057'
         })
       }
-
-      // 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',
-        //     params: {
-        //       paramName: 'qr_code_expire_hours'
-        //     }
-        //   })
-        //   if (dayjs(Number(state.t)).add(data.paramValue, 'hour').isBefore(dayjs())) {
-        // showDialog({
-        //   title: '提示',
-        //   message: '二维码已失效',
-        //   theme: 'round-button',
-        //   confirmButtonColor: '#ff8057'
-        // })
-        //     state.qrCodeStatus = true
-        //   } else {
-        //     state.qrCodeStatus = false
-        //   }
-        // }
-      } catch (e: any) {
-        //
-        console.log(e)
-      }
     })
 
     const onPreview = () => {
@@ -282,6 +275,76 @@ export default defineComponent({
       )
     }
 
+    const formRef = ref()
+    const checkchangePhone = async () => {
+      try {
+        await formRef.value?.validate('phone')
+        state.checkPhone = false
+
+        getTeacherDetails()
+      } catch {
+        //
+      }
+    }
+
+    const getTeacherDetails = async () => {
+      try {
+        if (!state.forms.phone) return
+        const { data } = await request.post(
+          '/api-school/open/teacher/detail/' + state.forms.phone,
+          {
+            requestType: 'form',
+            data: {
+              phone: state.forms.phone
+            }
+          }
+        )
+        // 判断是否有数据
+        if (!data) return
+        const subjects = data.subjectId
+          ? data.subjectId.split(',').map((subject: any) => Number(subject))
+          : []
+        // 显示声部
+        subjects.forEach((subject: any) => {
+          const item = state.columnSubject.find((item: any) => item.value === subject)
+
+          if (item) {
+            state.selectSubjects.push(item)
+          }
+        })
+
+        let cityCodeName = ''
+        state.columns.forEach((p: any) => {
+          if (p.areas && p.areas.length > 0) {
+            p.areas.forEach((c: any) => {
+              if (c.code === Number(data.cityCode)) {
+                cityCodeName = c.name
+              }
+            })
+          }
+        })
+
+        state.forms = {
+          realName: data.realName,
+          phone: data.phone,
+          gender: data.gender,
+          idCardNo: data.idCardNo,
+          cityCode: data.cityCode,
+          cityCodeName: cityCodeName,
+          provinceCode: data.provinceCode,
+          showSubjectIds: data.subjectId,
+          subjectIds: subjects as any,
+          smsValidCode: '',
+          educationBackground: data.educationBackground || '', // 学历
+          graduateSchool: data.graduateSchool, // 毕业学校
+          idcardFrontImg: data.idcardFrontImg || '',
+          idcardBackImg: data.idcardBackImg || '' // 身份证反面照
+        }
+      } catch {
+        //
+      }
+    }
+
     return () => (
       <div class={styles.register}>
         <div class={styles.title}>
@@ -290,20 +353,10 @@ export default defineComponent({
             <span>{state.name}</span>
           </p>
         </div>
-        <Form validateFirst scrollToError onSubmit={onSubmit} ref="form" class={styles.form}>
+        <Form validateFirst scrollToError onSubmit={onSubmit} ref={formRef} class={styles.form}>
           <CellGroup inset class={styles['cell-group']}>
             <Field
               required
-              label="真实姓名"
-              v-model={state.forms.realName}
-              rules={[{ required: true, message: '请填写真实姓名' }]}
-              name="realName"
-              placeholder="请填写真实姓名"
-              maxlength="50"
-            ></Field>
-
-            <Field
-              required
               label="手机号码"
               v-model={state.forms.phone}
               rules={[
@@ -313,8 +366,19 @@ export default defineComponent({
               name="phone"
               placeholder="请输入手机号码"
               maxlength={11}
+              onBlur={checkchangePhone}
               type="tel"
             ></Field>
+            <Field
+              required
+              label="真实姓名"
+              v-model={state.forms.realName}
+              rules={[{ required: true, message: '请填写真实姓名' }]}
+              name="realName"
+              placeholder="请填写真实姓名"
+              maxlength="50"
+              disabled={state.checkPhone}
+            ></Field>
 
             <Field
               required
@@ -331,6 +395,7 @@ export default defineComponent({
               name="idCardNo"
               maxlength={18}
               placeholder="请输入身份证号码"
+              disabled={state.checkPhone}
             ></Field>
 
             <Field
@@ -345,6 +410,7 @@ export default defineComponent({
                     checked-color="#FF8057"
                     v-model={state.forms.gender}
                     direction="horizontal"
+                    disabled={state.checkPhone}
                   >
                     <Tag
                       size="large"
@@ -385,6 +451,7 @@ export default defineComponent({
                   <OUpload
                     style={{ width: '100%' }}
                     tips="上传身份证正面"
+                    disabled={state.checkPhone}
                     v-model:modelValue={state.forms.idcardFrontImg}
                   />
                 )
@@ -404,6 +471,7 @@ export default defineComponent({
                   <OUpload
                     style={{ width: '100%' }}
                     tips="上传身份证反面"
+                    disabled={state.checkPhone}
                     v-model:modelValue={state.forms.idcardBackImg}
                   />
                 )
@@ -415,8 +483,12 @@ export default defineComponent({
               label="学历"
               v-model={state.forms.educationBackground}
               readonly
+              disabled={state.checkPhone}
               name="educationBackground"
-              onClick={() => (state.showEducation = true)}
+              onClick={() => {
+                if (state.checkPhone) return
+                state.showEducation = true
+              }}
               rules={[{ required: true, message: '请选择学历', trigger: 'onChange' }]}
               placeholder="请选择学历"
             >
@@ -430,6 +502,7 @@ export default defineComponent({
             <Field
               required
               label="毕业学校"
+              disabled={state.checkPhone}
               v-model={state.forms.graduateSchool}
               rules={[{ required: true, message: '请输入毕业学校' }]}
               name="graduateSchool"
@@ -441,8 +514,12 @@ export default defineComponent({
               label="所在城市"
               v-model={state.forms.cityCodeName}
               readonly
+              disabled={state.checkPhone}
               name="cityCodeName"
-              onClick={() => (state.showPicker = true)}
+              onClick={() => {
+                if (state.checkPhone) return
+                state.showPicker = true
+              }}
               rules={[{ required: true, message: '请选择所在城市', trigger: 'onChange' }]}
               placeholder="请选择所在城市"
             >
@@ -459,7 +536,11 @@ export default defineComponent({
               v-model={state.forms.showSubjectIds}
               readonly
               name="showSubjectIds"
-              onClick={() => (state.showSubject = true)}
+              disabled={state.checkPhone}
+              onClick={() => {
+                if (state.checkPhone) return
+                state.showSubject = true
+              }}
               rules={[{ required: true, message: '请选择声部', trigger: 'onChange' }]}
               placeholder="请选择声部"
             >
@@ -504,12 +585,20 @@ export default defineComponent({
               rules={[{ required: true, message: '请输入验证码', trigger: 'onChange' }]}
               placeholder="请输入验证码"
               maxlength={6}
+              disabled={state.checkPhone}
               type="tel"
             >
               {{
                 button: () =>
                   state.countDownStatus ? (
-                    <Button type="primary" round size="small" color="#ff8057" onClick={onSendCode}>
+                    <Button
+                      type="primary"
+                      round
+                      size="small"
+                      disabled={state.checkPhone}
+                      color="#ff8057"
+                      onClick={onSendCode}
+                    >
                       发送验证码
                     </Button>
                   ) : (
@@ -559,10 +648,12 @@ export default defineComponent({
             color="#FF8057"
             loading={state.btnLoading}
             native-type="submit"
+            disabled={state.checkPhone || state.btnLoading}
           >
             完成
           </Button>
         </Form>
+        {/* 城市 */}
         <Popup v-model:show={state.showPicker} position="bottom" round>
           <Picker
             showToolbar

+ 17 - 18
src/school/manage-teacher/manage-teacher-register.tsx

@@ -127,29 +127,28 @@ export default defineComponent({
       //   return
       // }
 
+      // t: route.query.t, // 过期时间
       try {
-        if (state.t) {
-          const { data } = await request.get('/api-school/open/paramConfig/queryByParamName', {
-            requestType: 'form',
-            params: {
-              paramName: 'qr_code_expire_hours'
-            }
-          })
-          if (dayjs(Number(state.t)).add(data.paramValue, 'hour').isBefore(dayjs())) {
-            showDialog({
-              title: '提示',
-              message: '二维码已失效',
-              theme: 'round-button',
-              confirmButtonColor: '#64A9FF'
-            })
-            state.qrCodeStatus = true
-          } else {
-            state.qrCodeStatus = false
+        const res = 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 (res.code === 999) {
+          state.qrCodeStatus = true
+          showDialog({
+            title: '提示',
+            message: res.message,
+            theme: 'round-button',
+            confirmButtonColor: '#64A9FF'
+          })
         }
-      } catch {
+      } catch (e: any) {
         //
+        console.log(e)
       }
+
       try {
         const tempareas: any = []
         areas.forEach((item) => {

+ 1 - 1
src/school/mass-message/component/student-list/index.tsx

@@ -137,7 +137,7 @@ export default defineComponent({
           <OSearch
             inputBackground="white"
             background="#F8F8F8"
-            placeholder="老师名称/手机号"
+            placeholder="学员名称/手机号"
             onSearch={(val: any) => {
               forms.params.keyword = val
               onSearch()

+ 1 - 1
src/school/mass-message/index.tsx

@@ -122,7 +122,7 @@ export default defineComponent({
           <OSearch
             background="#f6f8f9"
             inputBackground="white"
-            placeholder="请输入群聊/学员名称"
+            placeholder="请输入群聊/学员名称/老师名称"
             onSearch={(val: string) => {
               console.log('val', val)
               state.params.keyword = val

+ 7 - 9
src/school/orchestra/compontent/information.tsx

@@ -66,7 +66,7 @@ export default defineComponent({
       params: {
         startTime: dayjs(dayjs().year() + startTime.value).format('YYYY-MM-DD HH:mm:ss'),
         endTime: dayjs(dayjs().year() + endTime.value)
-          .subtract(1, 'day')
+          .subtract(1, 'year')
           .format('YYYY-MM-DD HH:mm:ss'),
         page: 1,
         rows: 20
@@ -134,11 +134,9 @@ export default defineComponent({
 
     const getStatistics = async () => {
       try {
-        const { data } = await request.post('/api-school/classGroup/statistics', {
+        const { data } = await request.post('/api-school/school/schoolSummaryStat', {
           data: {
-            orchestraId: route.query.id,
-            startTime: state.params.startTime,
-            endTime: state.params.endTime
+            orchestraId: route.query.id
           }
         })
         state.statistics = data || {}
@@ -192,10 +190,10 @@ export default defineComponent({
       nextTick(() => {
         // 在读学生
         const statistics = state.statistics
-        new CountUp('currentStudentNum', statistics.studentNum || 0).start()
-        new CountUp('time1', statistics.attendanceRate || 0).start()
-        new CountUp('time2', statistics.homeworkRate || 0).start()
-        new CountUp('time3', statistics.homeworkQualifiedRate || 0).start()
+        new CountUp('currentStudentNum', statistics.currentStudent || 0).start()
+        new CountUp('time1', statistics.attendanceRate * 100 || 0).start()
+        new CountUp('time2', statistics.homeworkSubmissionRate * 100 || 0).start()
+        new CountUp('time3', statistics.practicePassRate * 100 || 0).start()
       })
     }
 

+ 1 - 1
src/school/orchestra/compontent/plan.tsx

@@ -44,7 +44,7 @@ export default defineComponent({
       params: {
         startTime: dayjs(dayjs().year() + startTime.value).format('YYYY-MM-DD HH:mm:ss'),
         endTime: dayjs(dayjs().year() + endTime.value)
-          .subtract(1, 'day')
+          .subtract(1, 'year')
           .format('YYYY-MM-DD HH:mm:ss'),
         page: 1,
         rows: 20

+ 26 - 2
src/school/train-planning/component/course-preview/index.tsx

@@ -1,6 +1,18 @@
 import OHeader from '@/components/o-header'
 import OSticky from '@/components/o-sticky'
-import { Button, Cell, CellGroup, Dialog, Icon, Image, showToast, Tab, Tabs, Tag } from 'vant'
+import {
+  Button,
+  Cell,
+  CellGroup,
+  Dialog,
+  Icon,
+  Image,
+  showDialog,
+  showToast,
+  Tab,
+  Tabs,
+  Tag
+} from 'vant'
 import { defineComponent, onMounted, reactive, ref, nextTick } from 'vue'
 import styles from './index.module.less'
 import iconTimer from '../../images/icon-timer.png'
@@ -157,7 +169,19 @@ export default defineComponent({
     const onSubmit = async () => {
       try {
         state.isClick = true
-        await request.post('/api-school/orchestra/trainingPlan/' + route.query.cacheId)
+        const res = await request.post('/api-school/orchestra/trainingPlan/' + route.query.cacheId)
+
+        // 判断数据是否已经过期
+        if (res.code === 999) {
+          showDialog({
+            title: '提示',
+            message: res.message,
+            confirmButtonColor: '#ff8057'
+          }).then(() => {
+            router.back()
+          })
+          return
+        }
         setTimeout(() => {
           showToast(state.type === 'change' ? '调整成功' : '排课成功')
         }, 100)

+ 1 - 1
src/school/train-planning/modal/timer/index.tsx

@@ -197,7 +197,7 @@ export default defineComponent({
               value={`${dayjs(item.startTime).format('HH:mm')}~${dayjs(item.endTime).format(
                 'HH:mm'
               )}`}
-              title="可选时间"
+              title="可排课时间段"
             ></Cell>
           ))}
 

+ 4 - 1
src/student/my-orchestra/apply-withdrawal.tsx

@@ -1,5 +1,5 @@
 import OHeader from '@/components/o-header'
-import { defineComponent, onMounted, reactive, ref } from 'vue'
+import { defineComponent, onMounted, onUnmounted, reactive, ref } from 'vue'
 import iconStudent from '@common/images/icon_student.png'
 import styles from './apply-withdrawal.module.less'
 import { Button, Field, Image, showToast } from 'vant'
@@ -72,6 +72,9 @@ export default defineComponent({
       postMessage({ api: 'setStatusBarTextColor', content: { statusBarTextColor: true } })
       getDetails()
     })
+    onUnmounted(() => {
+      postMessage({ api: 'setStatusBarTextColor', content: { statusBarTextColor: false } })
+    })
     return () => (
       <div class={styles.applyWithdrawal}>
         <div class={styles.headers}>

+ 0 - 1
src/teacher/main.ts

@@ -26,7 +26,6 @@ const app = createApp(App).use(Vue3Lottie)
 
 // 获取token
 promisefiyPostMessage({ api: 'getToken' }).then((res: any) => {
-  console.log(res, 'res')
   const content = res.content
   if (content?.accessToken) {
     setAuth(content.tokenType + ' ' + content.accessToken)

+ 3 - 2
src/views/coursewarePlay/component/musicScore.module.less

@@ -1,13 +1,14 @@
 .musicScore {
   position: relative;
+  width: 100%;
   height: 100%;
   -webkit-overflow-scrolling: touch;
   overflow: scroll;
   .container {
     display: block;
     border: none;
-    width: 100vw;
-    height: 100vh;
+    width: 100%;
+    height: 100%;
   }
   .musicModel {
     position: absolute;

+ 32 - 0
src/views/coursewarePlay/component/video-play.tsx

@@ -0,0 +1,32 @@
+import { defineComponent, nextTick, onMounted } from "vue";
+import 'plyr/dist/plyr.css'
+import Plyr from "plyr";
+import { ref } from "vue";
+
+export default defineComponent({
+    name: 'video-play',
+    props:{
+        item: {
+            type: Object,
+            default: () => {return {}}
+        }
+    },
+    setup(props){
+        const videoRef = ref()
+        const item = props.item
+        onMounted(() => {
+            const v = new Plyr(videoRef.value, {
+                muted: true
+            })
+            v.play()
+            nextTick(() => {
+                v.muted = false
+            })
+        })
+        return () => (
+            <div style={{width: '100%', height: '100%'}}>
+                <video style={{width: '100%', height: '100%'}} src={item.content} ref={videoRef}></video>
+            </div>
+        )
+    }
+})

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 0 - 0
src/views/coursewarePlay/datas/data.json


+ 47 - 18
src/views/coursewarePlay/index.module.less

@@ -1,7 +1,13 @@
+.playContent {
+  width: 100vw;
+  height: 100vh;
+  background-color: #000;
+}
 .coursewarePlay {
   position: relative;
   height: 100vh;
-  background-color: rgba(89, 98, 126, 0.2);
+  margin: 0 auto;
+  overflow: hidden;
 }
 .playModel {
   position: absolute;
@@ -13,24 +19,28 @@
   pointer-events: none;
 }
 .headerContainer {
-  position: fixed;
+  position: absolute;
   top: 0;
   left: 0;
   right: 0;
-  z-index: 1;
+  z-index: 10;
   display: flex;
   align-items: center;
-  background: linear-gradient(180deg, rgba(0, 0, 0, .6), transparent);
+  background: linear-gradient(180deg, rgba(0, 0, 0, 0.6), transparent);
 }
 .backBtn {
   color: #fff;
-  width: 40px;
   height: 26px;
   display: flex;
   justify-content: space-between;
   align-items: center;
   z-index: 10;
-  padding: 4px 10px 4px 20px;
+  padding: 4px 10px 4px 15px;
+  :global{
+    .van-icon{
+      margin-right: 8px;
+    }
+  }
 }
 .menu {
   position: absolute;
@@ -55,7 +65,7 @@
     }
   }
 }
-.swipeItem{
+.swipeItem {
   overflow: hidden;
 }
 .itemDiv {
@@ -74,10 +84,11 @@
   }
 }
 .rightFixedBtns {
-  position: fixed;
+  position: absolute;
   top: 50%;
   transform: translateY(-50%);
-  right: 20px;
+  right: 12px;
+  z-index: 10;
   .point {
     margin-top: 10px;
     border-bottom-left-radius: 0;
@@ -89,10 +100,11 @@
   }
 }
 .leftFixedBtns {
-  position: fixed;
+  position: absolute;
   top: 50%;
   transform: translateY(-50%);
-  left: 26px;
+  left: 12px;
+  z-index: 10;
   .prePoint {
     margin-bottom: 8px;
   }
@@ -107,6 +119,8 @@
   align-items: center;
   color: #fff;
   justify-content: space-evenly;
+  overflow: hidden;
+  white-space: nowrap;
   &:active {
     opacity: 0.8;
   }
@@ -118,6 +132,7 @@
   bottom: 0;
   z-index: 10;
   background: linear-gradient(0deg, rgba(0, 0, 0, 0.5), transparent);
+  transition: transform 0.5s;
   .time {
     display: flex;
     justify-content: space-between;
@@ -127,8 +142,8 @@
   }
   .slider {
     padding: 8px 20px;
-    :global{
-      .van-slider__button{
+    :global {
+      .van-slider__button {
         background: var(--van-primary);
       }
     }
@@ -138,16 +153,16 @@
     justify-content: space-between;
     color: #fff;
     font-size: 12px;
-    padding: 0 10px 0 20px;
+    padding: 0 10px 4px 20px;
     align-items: center;
-    .actionBtn{
+    .actionBtn {
       display: flex;
     }
     .actionBtn > img {
-      width: 26px;
-      height: 26px;
+      width: 30px;
+      height: 30px;
       display: block;
-      padding: 4px 10px 14px 4px;
+      padding: 4px 10px 4px 4px;
     }
   }
 }
@@ -174,6 +189,7 @@
   .left-enter-from,
   .left-leave-to {
     left: -60px;
+    opacity: 0;
   }
 
   .right-enter-active,
@@ -184,6 +200,7 @@
   .right-enter-from,
   .right-leave-to {
     right: -60px;
+    opacity: 0;
   }
 
   .bottom-enter-active,
@@ -196,3 +213,15 @@
     transform: translateY(100%);
   }
 }
+
+.loadWrap{
+  position: absolute;
+  left: 0;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  background: linear-gradient(45deg, #21232A, #111218);
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}

+ 301 - 261
src/views/coursewarePlay/index.tsx

@@ -1,5 +1,4 @@
 import {
-  Button,
   closeToast,
   Icon,
   Loading,
@@ -7,9 +6,8 @@ import {
   showToast,
   Slider,
   Swipe,
-  SwipeItem,
-  Tab,
-  Tabs
+  SwipeInstance,
+  SwipeItem
 } from 'vant'
 import {
   defineComponent,
@@ -19,8 +17,7 @@ import {
   onUnmounted,
   ref,
   watch,
-  Transition,
-  computed
+  Transition
 } from 'vue'
 import iconBack from './image/back.svg'
 import styles from './index.module.less'
@@ -39,15 +36,47 @@ import iconplay from './image/icon-play.svg'
 import iconpause from './image/icon-pause.svg'
 import iconUp from './image/icon-up.svg'
 import iconDown from './image/icon-down.svg'
-import iconVideobg from './image/icon-videobg.png'
 import Points from './component/points'
 import { browser, getSecondRPM } from '@/helpers/utils'
-import { useRect } from '@vant/use'
+import { Vue3Lottie } from 'vue3-lottie'
+import playLoadData from './datas/data.json'
+import { usePageVisibility } from '@vant/use'
 
 export default defineComponent({
   name: 'CoursewarePlay',
   setup() {
+    const pageVisibility = usePageVisibility()
+    const isPlay = ref(false)
+    /** 页面显示和隐藏 */
+    watch(pageVisibility, (value) => {
+      const activeItem = data.itemList[popupData.activeIndex]
+      if (activeItem.type != 'VIDEO') return
+      if (value == 'hidden') {
+        isPlay.value = !activeItem.paused
+        handlePaused(activeItem)
+      } else {
+        // 页面显示,并且
+        if (isPlay.value) handlePlay(activeItem)
+      }
+    })
+    /** 设置播放容器 16:9 */
+    const parentContainer = reactive({
+      width: '100vw'
+    })
+    const setContainer = () => {
+      let min = Math.min(screen.width, screen.height)
+      let max = Math.max(screen.width, screen.height)
+      let width = min * (16 / 9)
+      if (width > max) {
+        parentContainer.width = '100vw'
+        return
+      } else {
+        parentContainer.width = width + 'px'
+      }
+    }
     const handleInit = (type = 0) => {
+      //设置容器16:9
+      setContainer()
       // 横屏
       postMessage({
         api: 'setRequestedOrientation',
@@ -106,7 +135,7 @@ export default defineComponent({
           type: material.type // SONG VIDEO IMAGE
         }
       })
-      console.log('缓存路径返回', res)
+      // console.log('缓存路径返回', res)
       return res
     }
     // 获取当前课程是否签退
@@ -154,7 +183,7 @@ export default defineComponent({
               playModel: false,
               isprepare: false,
               isDrage: false,
-              muted: false // 是否静音
+              muted: true // 是否静音
             }
           }
           list.push({
@@ -184,7 +213,7 @@ export default defineComponent({
         item.autoPlay = true
         item.muted = true
       }
-      console.log('🚀 ~ list', list)
+      // console.log('🚀 ~ list', list)
       data.itemList = list
     }
     const getDetail = async () => {
@@ -218,10 +247,10 @@ export default defineComponent({
     const iframeHandle = (ev: MessageEvent) => {
       // console.log(ev.data)
       if (ev.data?.api === 'headerTogge') {
-        // activeData.model = ev.data.show
         activeData.model = !activeData.model
       }
     }
+
     onMounted(() => {
       getDetail()
       getCourseSchedule()
@@ -232,11 +261,11 @@ export default defineComponent({
       postMessage({ api: 'goBack' })
     }
 
-    const swipeRef = ref()
+    const swipeRef = ref<SwipeInstance>()
     const popupData = reactive({
       firstIndex: 0,
       open: false,
-      activeIndex: -1,
+      activeIndex: 0,
       tabActive: '',
       tabName: '',
       itemActive: '',
@@ -244,33 +273,41 @@ export default defineComponent({
     })
     // 设置当前的激活状态
     const setActiveData = (val: any, oldVal: any) => {
-      handleStopVideo()
-      handleStopMusicScore()
+      handleStop()
     }
     watch(() => popupData.activeIndex, setActiveData)
 
-    // 停止所有的播放
-    const handleStopVideo = () => {
-      data.itemList.forEach((m: any) => {
-        const item = data.itemList[popupData.activeIndex]
-        if (item?.id != m.id) {
-          m.autoPlay = false
-          m.videoEle?.pause()
+    /**停止所有的播放 */
+    const handleStop = () => {
+      const activeItem = data.itemList[popupData.activeIndex]
+      for (let i = 0; i < data.itemList.length; i++) {
+        const item = data.itemList[i]
+        // 停止视频播放
+        if (item.type === 'VIDEO') {
+          // console.log("🚀 ~ item", item)
+          if (item?.id != activeItem.id) {
+            item.currentTime = 0
+            item.progress = 0
+            if (item.videoEle) {
+              item.videoEle.currentTime = 0
+              item.videoEle.pause()
+            }
+          }
         }
-      })
-    }
-    // 停止曲谱的播放
-    const handleStopMusicScore = () => {
-      data.itemList.forEach((m: any) => {
-        m.iframeRef?.contentWindow?.postMessage({ api: 'setPlayState' }, '*')
-      })
+        // 停止曲谱的播放
+        if (item.type === 'SONG') {
+          item.iframeRef?.contentWindow?.postMessage({ api: 'setPlayState' }, '*')
+        }
+      }
     }
     // 切换素材
     const toggleMaterial = () => {
       const index = data.itemList.findIndex((n: any) => n.id == popupData.itemActive)
       if (index > -1) {
         popupData.activeIndex = index
-        swipeRef.value?.swipeTo(index)
+        swipeRef.value?.swipeTo(index, {
+          immediate: true
+        })
       }
     }
     /** 延迟收起模态框 */
@@ -348,14 +385,12 @@ export default defineComponent({
     }
 
     // 暂停播放
-    const handlePaused = (e: Event, m: any) => {
-      e.stopPropagation()
+    const handlePaused = (m: any) => {
       m.videoEle?.pause()
       m.paused = true
     }
     // 开始播放
-    const handlePlay = (e: Event, m: any) => {
-      e.stopPropagation()
+    const handlePlay = (m: any) => {
       closeToast()
       m.videoEle?.play()
     }
@@ -378,97 +413,109 @@ export default defineComponent({
     }
 
     return () => (
-      <div class={styles.coursewarePlay}>
-        <Swipe
-          style={{ height: '100vh' }}
-          ref={swipeRef}
-          showIndicators={false}
-          loop={false}
-          vertical
-          lazyRender={true}
-          touchable={false}
-          initialSwipe={popupData.firstIndex}
-          onChange={handleSwipeChange}
-        >
-          {data.itemList.map((m: any, mIndex: number) => {
-            return (
-              <SwipeItem class={styles.swipeItem}>
-                <>
-                  <div
-                    class={styles.itemDiv}
-                    onClick={() => {
-                      clearTimeout(activeData.timer)
-                      if (Date.now() - activeData.nowTime < 300) {
-                        handleDbClick(m)
-                        return
-                      }
-                      activeData.nowTime = Date.now()
-                      activeData.timer = setTimeout(() => {
-                        activeData.model = !activeData.model
-                        setModelOpen()
-                      }, 300)
-                    }}
-                  >
-                    {m.type === 'VIDEO' ? (
-                      <>
-                        <video
-                          playsinline="false"
-                          muted={m.muted}
-                          preload="auto"
-                          class="player"
-                          poster={iconVideobg}
-                          data-vid={m.id}
-                          src={m.content}
-                          loop={m.loop}
-                          autoplay={m.autoPlay}
-                          onLoadedmetadata={(e: Event) => {
-                            const videoEle = e.target as unknown as HTMLVideoElement
-                            m.currentTime = videoEle.currentTime
-                            m.duration = videoEle.duration
-                            m.videoEle = videoEle
-                            m.isprepare = true
-                          }}
-                          onTimeupdate={(e: Event) => {
-                            if (!m.isprepare) return
-                            const videoEle = e.target as unknown as HTMLVideoElement
-                            m.currentTime = videoEle.currentTime
-                            m.progress = Number(
-                              ((videoEle.currentTime / m.duration) * 100).toFixed(1)
-                            )
-                          }}
-                          onPlay={() => {
-                            // 播放
-                            m.paused = false
-                            console.log('播放')
-                            setModelOpen()
-                            m.muted = false
-                          }}
-                          onPause={() => {
-                            //暂停
-                            clearTimeout(activeData.timer)
-                            m.paused = true
-                          }}
-                          onEnded={() => handleEnded(m)}
-                        >
-                          <source src={m.content} type="video/mp4" />
-                        </video>
-                        <Transition name="bottom">
-                          {activeData.model && (
-                            <div class={[styles.bottomFixedContainer]}>
+      <div class={styles.playContent}>
+        <div class={styles.coursewarePlay} style={{ width: parentContainer.width }}>
+          <Swipe
+            style={{ height: '100%' }}
+            ref={swipeRef}
+            showIndicators={false}
+            loop={false}
+            duration={0}
+            vertical
+            lazyRender={true}
+            touchable={false}
+            initialSwipe={popupData.firstIndex}
+            onChange={handleSwipeChange}
+          >
+            {data.itemList.map((m: any, mIndex: number) => {
+              return (
+                <SwipeItem class={styles.swipeItem}>
+                  <>
+                    <div
+                      class={styles.itemDiv}
+                      onClick={() => {
+                        clearTimeout(activeData.timer)
+                        if (Date.now() - activeData.nowTime < 300) {
+                          handleDbClick(m)
+                          return
+                        }
+                        activeData.nowTime = Date.now()
+                        activeData.timer = setTimeout(() => {
+                          activeData.model = !activeData.model
+                          setModelOpen()
+                        }, 300)
+                      }}
+                    >
+                      {m.type === 'VIDEO' ? (
+                        <>
+                          <video
+                            playsinline="false"
+                            muted={m.muted}
+                            preload="auto"
+                            class="player"
+                            data-vid={m.id}
+                            src={m.content}
+                            loop={m.loop}
+                            autoplay={m.autoPlay}
+                            onLoadedmetadata={(e: Event) => {
+                              const videoEle = e.target as unknown as HTMLVideoElement
+                              m.currentTime = videoEle.currentTime
+                              m.duration = videoEle.duration
+                              m.videoEle = videoEle
+                              m.isprepare = true
+                            }}
+                            onTimeupdate={(e: Event) => {
+                              if (!m.isprepare) return
+                              const videoEle = e.target as unknown as HTMLVideoElement
+                              m.currentTime = videoEle.currentTime
+                              m.progress = Number((videoEle.currentTime / m.duration) * 100)
+                            }}
+                            onPlay={() => {
+                              // 播放
+                              m.paused = false
+                              console.log('播放')
+                              setModelOpen()
+                              // 第一次播放
+                              if (m.muted) {
+                                m.muted = false
+                                m.autoPlay = false
+                              }
+                            }}
+                            onPause={() => {
+                              //暂停
+                              clearTimeout(activeData.timer)
+                              m.paused = true
+                            }}
+                            onEnded={() => handleEnded(m)}
+                          >
+                            <source src={m.content} type="video/mp4" />
+                          </video>
+                          {m.muted && (
+                            <div class={styles.loadWrap}>
+                              <Vue3Lottie animationData={playLoadData}></Vue3Lottie>
+                            </div>
+                          )}
+                          <div
+                            style={{ transform: activeData.model ? '' : 'translateY(100%)' }}
+                            class={styles.bottomFixedContainer}
+                            onClick={(e: Event) => {
+                              e.stopPropagation()
+                              setModelOpen()
+                            }}
+                          >
+                            <div style={{ opacity: m.isprepare ? '1' : '0' }}>
                               <div class={styles.time}>
                                 <span>{getSecondRPM(m.currentTime)}</span>
                                 <span>{getSecondRPM(m.duration)}</span>
                               </div>
                               <div class={styles.slider}>
                                 <Slider
-                                  onClick={() => {
-                                    setModelOpen()
-                                  }}
-                                  style={{ display: m.isprepare ? 'block' : 'none' }}
+                                  onClick={() => setModelOpen()}
                                   buttonSize={16}
-                                  step={0.1}
+                                  step={1}
                                   modelValue={m.progress}
                                   onUpdate:modelValue={(val: any) => {
+                                    console.log('val', val)
                                     m.progress = val
                                     handleChangeSlider(m)
                                   }}
@@ -479,175 +526,168 @@ export default defineComponent({
                                     if (!m.paused) {
                                       m.isDrage = true
                                     }
-                                    handlePaused(e, m)
+                                    handlePaused(m)
                                   }}
                                   onDragEnd={(e: Event) => {
                                     console.log('结束拖动')
                                     if (m.isDrage) {
                                       m.isDrage = false
-                                      handlePlay(e, m)
+                                      handlePlay(m)
                                     }
                                   }}
                                   min={0}
                                   max={100}
                                 />
                               </div>
+                            </div>
 
-                              <div class={styles.actions}>
-                                <div class={styles.actionBtn}>
-                                  {m.isprepare ? (
-                                    <>
-                                      {m.paused ? (
-                                        <img
-                                          src={iconplay}
-                                          onClick={(e: Event) => handlePlay(e, m)}
-                                        />
-                                      ) : (
-                                        <img
-                                          src={iconpause}
-                                          onClick={(e: Event) => handlePaused(e, m)}
-                                        />
-                                      )}
-                                    </>
-                                  ) : (
-                                    <Loading color="#fff" />
-                                  )}
+                            <div class={styles.actions}>
+                              <div class={styles.actionBtn}>
+                                {m.isprepare ? (
+                                  <>
+                                    {m.paused ? (
+                                      <img src={iconplay} onClick={(e: Event) => handlePlay(m)} />
+                                    ) : (
+                                      <img
+                                        src={iconpause}
+                                        onClick={(e: Event) => handlePaused(m)}
+                                      />
+                                    )}
+                                  </>
+                                ) : (
+                                  <Loading color="#fff" />
+                                )}
 
-                                  {m.loop ? (
-                                    <img
-                                      src={iconLoopActive}
-                                      onClick={(e: Event) => {
-                                        e.stopPropagation()
-                                        m.loop = false
-                                      }}
-                                    />
-                                  ) : (
-                                    <img
-                                      src={iconLoop}
-                                      onClick={(e: Event) => {
-                                        e.stopPropagation()
-                                        m.loop = true
-                                      }}
-                                    />
-                                  )}
-                                </div>
-                                <div>{m.name}</div>
+                                {m.loop ? (
+                                  <img
+                                    src={iconLoopActive}
+                                    onClick={(e: Event) => (m.loop = false)}
+                                  />
+                                ) : (
+                                  <img src={iconLoop} onClick={(e: Event) => (m.loop = true)} />
+                                )}
                               </div>
+                              <div>{m.name}</div>
                             </div>
-                          )}
-                        </Transition>
-                      </>
-                    ) : m.type === 'IMG' ? (
-                      <img src={m.content} />
-                    ) : (
-                      <MusicScore
-                        data-vid={m.id}
-                        music={m}
-                        onSetIframe={(el: any) => {
-                          m.iframeRef = el
-                        }}
-                      />
-                    )}
-                  </div>
-                </>
-              </SwipeItem>
-            )
-          })}
-        </Swipe>
+                          </div>
+                          {/* <Transition name="bottom">
+                            {activeData.model && (
+                              
+                            )}
+                          </Transition> */}
+                        </>
+                      ) : m.type === 'IMG' ? (
+                        <img src={m.content} />
+                      ) : (
+                        <MusicScore
+                          data-vid={m.id}
+                          music={m}
+                          onSetIframe={(el: any) => {
+                            m.iframeRef = el
+                          }}
+                        />
+                      )}
+                    </div>
+                  </>
+                </SwipeItem>
+              )
+            })}
+          </Swipe>
 
-        <Transition name="top">
-          {activeData.model && (
-            <div class={styles.headerContainer} ref={headeRef}>
-              <div class={styles.backBtn} onClick={() => goback()}>
-                <Icon name={iconBack} />
-                返回
+          <Transition name="top">
+            {activeData.model && (
+              <div class={styles.headerContainer} ref={headeRef}>
+                <div class={styles.backBtn} onClick={() => goback()}>
+                  <Icon name={iconBack} />
+                  返回
+                </div>
+                <div class={styles.menu}>{popupData.tabName}</div>
               </div>
-              <div class={styles.menu}>{popupData.tabName}</div>
-            </div>
-          )}
-        </Transition>
+            )}
+          </Transition>
 
-        <Transition name="right">
-          {activeData.model && (
-            <div class={styles.rightFixedBtns}>
-              <div
-                class={styles.fullBtn}
-                onClick={() => {
-                  clearTimeout(activeData.timer)
-                  popupData.open = true
-                }}
-              >
-                <img src={iconMenu} />
-                <span>知识点</span>
+          <Transition name="right">
+            {activeData.model && (
+              <div class={styles.rightFixedBtns}>
+                <div
+                  class={styles.fullBtn}
+                  onClick={() => {
+                    clearTimeout(activeData.timer)
+                    popupData.open = true
+                  }}
+                >
+                  <img src={iconMenu} />
+                  <span>知识点</span>
+                </div>
+                {data.isCourse && (
+                  <>
+                    <div
+                      class={[styles.fullBtn, styles.point]}
+                      onClick={() => gotoRollCall('student_roll_call')}
+                    >
+                      <img src={iconDian} />
+                      <span>点名</span>
+                    </div>
+                    <div class={styles.fullBtn} onClick={() => gotoRollCall('sign_out')}>
+                      <img src={iconPoint} />
+                      <span>签退</span>
+                    </div>
+                  </>
+                )}
               </div>
-              {data.isCourse && (
-                <>
+            )}
+          </Transition>
+
+          <Transition name="left">
+            {activeData.model && (
+              <div class={styles.leftFixedBtns}>
+                {popupData.activeIndex != 0 && (
                   <div
-                    class={[styles.fullBtn, styles.point]}
-                    onClick={() => gotoRollCall('student_roll_call')}
+                    class={[styles.fullBtn, styles.prePoint]}
+                    onClick={() => handlePreAndNext('up')}
                   >
-                    <img src={iconDian} />
-                    <span>点名</span>
+                    <img src={iconUp} />
+                    <span style={{ textAlign: 'center' }}>上一个</span>
                   </div>
-                  <div class={styles.fullBtn} onClick={() => gotoRollCall('sign_out')}>
-                    <img src={iconPoint} />
-                    <span>签退</span>
+                )}
+                {popupData.activeIndex != data.itemList.length - 1 && (
+                  <div class={styles.fullBtn} onClick={() => handlePreAndNext('down')}>
+                    <span style={{ textAlign: 'center' }}>下一个</span>
+                    <img src={iconDown} />
                   </div>
-                </>
-              )}
-            </div>
-          )}
-        </Transition>
-
-        <Transition name="left">
-          {activeData.model && (
-            <div class={styles.leftFixedBtns}>
-              {popupData.activeIndex != 0 && (
-                <div
-                  class={[styles.fullBtn, styles.prePoint]}
-                  onClick={() => handlePreAndNext('up')}
-                >
-                  <img src={iconUp} />
-                  <span style={{ textAlign: 'center' }}>上一个</span>
-                </div>
-              )}
-              {popupData.activeIndex != data.itemList.length - 1 && (
-                <div class={styles.fullBtn} onClick={() => handlePreAndNext('down')}>
-                  <span style={{ textAlign: 'center' }}>下一个</span>
-                  <img src={iconDown} />
-                </div>
-              )}
-            </div>
-          )}
-        </Transition>
+                )}
+              </div>
+            )}
+          </Transition>
 
-        <Popup
-          class={styles.popup}
-          overlayClass={styles.overlayClass}
-          position="right"
-          round
-          v-model:show={popupData.open}
-          onClose={() => {
-            const item = data.itemList[popupData.activeIndex]
-            if (item?.type == 'VIDEO') {
-              setModelOpen()
-            }
-          }}
-        >
-          <Points
-            data={data.knowledgePointList}
-            tabActive={popupData.tabActive}
-            itemActive={popupData.itemActive}
-            onHandleSelect={(res: any) => {
-              // console.log(res)
-              popupData.tabActive = res.tabActive
-              popupData.itemActive = res.itemActive
-              popupData.tabName = res.tabName
-              popupData.open = false
-              toggleMaterial()
+          <Popup
+            class={styles.popup}
+            overlayClass={styles.overlayClass}
+            position="right"
+            round
+            v-model:show={popupData.open}
+            onClose={() => {
+              const item = data.itemList[popupData.activeIndex]
+              if (item?.type == 'VIDEO') {
+                setModelOpen()
+              }
             }}
-          />
-        </Popup>
+          >
+            <Points
+              data={data.knowledgePointList}
+              tabActive={popupData.tabActive}
+              itemActive={popupData.itemActive}
+              onHandleSelect={(res: any) => {
+                // console.log(res)
+                popupData.tabActive = res.tabActive
+                popupData.itemActive = res.itemActive
+                popupData.tabName = res.tabName
+                popupData.open = false
+                toggleMaterial()
+              }}
+            />
+          </Popup>
+        </div>
       </div>
     )
   }

+ 66 - 36
src/views/unit-test/examination-mode/index.tsx

@@ -10,7 +10,7 @@ import {
   SwipeItem,
   Tag
 } from 'vant'
-import { defineComponent, onMounted, reactive, ref } from 'vue'
+import { defineComponent, nextTick, onMounted, reactive, ref } from 'vue'
 import { useRoute, useRouter } from 'vue-router'
 import styles from './index.module.less'
 import iconQuestionNums from '../images/icon-question-nums.png'
@@ -26,14 +26,8 @@ import PlayQuestion from '../model/play-question'
 import request from '@/helpers/request'
 import dayjs from 'dayjs'
 import ResultFinish from '../model/result-finish'
-
-export const QuestionType = {
-  RADIO: 'RADIO',
-  CHECKBOX: 'CHECKBOX',
-  LINK: 'LINK',
-  SORT: 'SORT',
-  PLAY: 'PLAY'
-}
+import { QuestionType } from '../unit'
+import { useRect } from '@vant/use'
 
 export default defineComponent({
   name: 'unit-detail',
@@ -54,7 +48,8 @@ export default defineComponent({
       resultInfo: {} as any,
       resultStatusType: 'SUCCESS', // 'SUCCESS' | 'FAIL'
       visiableExam: false, // 考试已结束
-      nextStatus: false
+      nextStatus: false,
+      swipeHeight: 'auto' as any
     })
 
     const getExamDetails = async () => {
@@ -73,7 +68,6 @@ export default defineComponent({
         temp.forEach((item: any) => {
           item.userAnswer = formatUserAnswers(item, studentAnswerJson)
         })
-        console.log(temp)
         state.questionList = temp
 
         state.examDetail = { ...res } || {}
@@ -117,6 +111,22 @@ export default defineComponent({
     }
 
     /**
+     * @description 重置当前的题目高度
+     */
+    const resizeSwipeItemHeight = () => {
+      nextTick(() => {
+        window.scrollTo(0, 0)
+        setTimeout(() => {
+          const currentItemDom: Element =
+            document.querySelectorAll('.swipe-item-question')[state.currentIndex]
+          console.log(currentItemDom, state.currentIndex)
+          const rect = useRect(currentItemDom)
+          state.swipeHeight = rect.height
+        }, 100)
+      })
+    }
+
+    /**
      * @description 下一题 | 测试完成
      */
     const onNextQuestion = async () => {
@@ -139,7 +149,7 @@ export default defineComponent({
         })
 
         // 判断是否是最后一题
-        console.log(state.questionList.length, state.currentIndex)
+        // console.log(state.questionList.length, state.currentIndex, userAnswerList, '-----')
         if (state.questionList.length === state.currentIndex + 1) {
           state.visiableSure = true
           return
@@ -216,6 +226,9 @@ export default defineComponent({
 
     onMounted(() => {
       getExamDetails()
+
+      // 初始化高度
+      resizeSwipeItemHeight()
     })
 
     return () => (
@@ -254,40 +267,57 @@ export default defineComponent({
           ref={swipeRef}
           duration={300}
           touchable={false}
+          height={state.swipeHeight}
           lazyRender
-          // initialSwipe={state.currentIndex}
           onChange={(index: number) => {
             state.currentIndex = index
+            resizeSwipeItemHeight()
           }}
         >
           {state.questionList.map((item: any, index: number) => (
-            // item.questionTypeCode === QuestionType.LINK && (
+            // item.questionTypeCode === QuestionType.PLAY && (
             //   <SwipeItem>
-            //     <KeepLookQuestion v-model:value={item.userAnswer} data={item} index={index + 1} />
+            //     <PlayQuestion
+            //       v-model:value={item.userAnswer}
+            //       data={item}
+            //       index={index + 1}
+            //       unitId={state.id as any}
+            //     />
             //   </SwipeItem>
             // )
             <SwipeItem>
-              {item.questionTypeCode === QuestionType.RADIO && (
-                <ChoiceQuestion
-                  v-model:value={item.userAnswer}
-                  index={index + 1}
-                  data={item}
-                  type="radio"
-                />
-              )}
-              {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 />}
+              <div class="swipe-item-question">
+                {item.questionTypeCode === QuestionType.RADIO && (
+                  <ChoiceQuestion
+                    v-model:value={item.userAnswer}
+                    index={index + 1}
+                    data={item}
+                    type="radio"
+                  />
+                )}
+                {item.questionTypeCode === QuestionType.CHECKBOX && (
+                  <ChoiceQuestion
+                    v-model:value={item.userAnswer}
+                    index={index + 1}
+                    data={item}
+                    type="checkbox"
+                  />
+                )}
+                {item.questionTypeCode === QuestionType.SORT && (
+                  <DragQuestion v-model:value={item.userAnswer} data={item} index={index + 1} />
+                )}
+                {item.questionTypeCode === QuestionType.LINK && (
+                  <KeepLookQuestion v-model:value={item.userAnswer} data={item} index={index + 1} />
+                )}
+                {item.questionTypeCode === QuestionType.PLAY && (
+                  <PlayQuestion
+                    v-model:value={item.userAnswer}
+                    data={item}
+                    index={index + 1}
+                    unitId={state.id as any}
+                  />
+                )}
+              </div>
             </SwipeItem>
           ))}
         </Swipe>

BIN
src/views/unit-test/images/icon-analysis.png


BIN
src/views/unit-test/images/icon-success.png


+ 13 - 1
src/views/unit-test/index.tsx

@@ -105,6 +105,12 @@ export default defineComponent({
     // 查看测验
     const onUnitTestLook = (item: any) => {
       //
+      router.push({
+        path: '/unit-detail',
+        query: {
+          id: item.id
+        }
+      })
     }
 
     const onExamStart = async () => {
@@ -241,7 +247,13 @@ export default defineComponent({
                             block
                             style={{ color: '#F67146' }}
                             onClick={() => {
-                              router.push('/test-exercise')
+                              router.push({
+                                path: '/test-exercise',
+                                query: {
+                                  id: item.unitExaminationId,
+                                  name: item.name
+                                }
+                              })
                             }}
                           >
                             练习模式

+ 26 - 0
src/views/unit-test/model/anser-title/index.module.less

@@ -0,0 +1,26 @@
+.unitSubjectTitle {
+  font-size: 16px;
+  font-weight: 500;
+  color: #333333;
+  line-height: 26px;
+  .unitScore {
+    color: #777777;
+  }
+  :global {
+    .van-tag {
+      vertical-align: middle;
+      margin-top: -3px;
+    }
+  }
+}
+.unitDetail {
+  padding-top: 20px;
+  padding-bottom: 10px;
+  font-size: 14px;
+  img {
+    width: 100%;
+  }
+}
+.unitTitleImg {
+  width: 100%;
+}

+ 67 - 0
src/views/unit-test/model/anser-title/index.tsx

@@ -0,0 +1,67 @@
+import { Tag, Image } from 'vant'
+import { computed, defineComponent } from 'vue'
+import { QuestionType, QuestionTypeName } from '../../unit'
+import styles from './index.module.less'
+
+export default defineComponent({
+  name: 'answer-title',
+  props: {
+    index: {
+      type: Number,
+      default: 1
+    },
+    name: {
+      // 题目名称
+      type: String,
+      default: ''
+    },
+    score: {
+      type: Number,
+      default: 0
+    },
+    answerType: {
+      // 类型
+      type: String,
+      default: ''
+    },
+    extra: {
+      type: Object,
+      default: () => ({})
+    }
+  },
+  setup(props) {
+    const answerTypeName = computed(() => {
+      if (props.answerType === QuestionType.CHECKBOX) {
+        return QuestionTypeName.CHECKBOX
+      } else if (props.answerType === QuestionType.LINK) {
+        return QuestionTypeName.LINK
+      } else if (props.answerType === QuestionType.SORT) {
+        return QuestionTypeName.SORT
+      } else if (props.answerType === QuestionType.PLAY) {
+        return QuestionTypeName.PLAY
+      }
+      return QuestionTypeName.RADIO
+    })
+
+    const mediaUrls = computed(() =>
+      props.extra.mediaUrls ? props.extra.mediaUrls.split(',') : ''
+    )
+    return () => (
+      <>
+        <div class={styles.unitSubjectTitle}>
+          {props.index}、{props.name} <span class={styles.unitScore}>({props.score || 0}分)</span>
+          <Tag type="primary">{answerTypeName.value}</Tag>
+        </div>
+        {props.extra.questionDetail || mediaUrls.value ? (
+          <div class={styles.unitDetail}>
+            <div v-html={props.extra.questionDetail}></div>
+            {mediaUrls.value &&
+              mediaUrls.value.map((url: any) => <Image class={styles.unitTitleImg} src={url} />)}
+          </div>
+        ) : (
+          ''
+        )}
+      </>
+    )
+  }
+})

+ 32 - 0
src/views/unit-test/model/answer-analysis/index.module.less

@@ -0,0 +1,32 @@
+.answerAnalysis {
+  .analysisResult {
+    display: flex;
+    align-items: center;
+    // line-height: 22px;
+    font-size: 16px;
+    font-weight: 600;
+    color: #333333;
+
+    .aImg {
+      font-size: 18px;
+      margin-right: 6px;
+    }
+  }
+  .success {
+    color: #3b8df3;
+  }
+  .error {
+    color: #f44541;
+  }
+  .analysisTitle {
+    padding-top: 20px;
+    padding-bottom: 10px;
+  }
+  .analysisMessage {
+    font-size: 15px;
+    color: #333333;
+    line-height: 21px;
+    text-align: justify;
+    padding-bottom: 5px;
+  }
+}

+ 62 - 0
src/views/unit-test/model/answer-analysis/index.tsx

@@ -0,0 +1,62 @@
+import { defineComponent } from 'vue'
+import styles from './index.module.less'
+import iconError from '../../images/icon-error.png'
+import iconSuccess from '../../images/icon-success.png'
+import iconAnalysis from '../../images/icon-analysis.png'
+import { Icon } from 'vant'
+
+// 答案解析
+export default defineComponent({
+  name: 'answer-analysis',
+  props: {
+    topic: {
+      // 状态
+      type: Boolean,
+      default: true
+    },
+    userResult: {
+      tyep: Boolean,
+      default: false
+    },
+    answerAnalysis: {
+      type: String,
+      default: ''
+    }
+  },
+  setup(props) {
+    return () => (
+      <div class={styles.answerAnalysis}>
+        {props.topic ? (
+          <>
+            {props.userResult ? (
+              <div class={[styles.analysisResult, styles.success]}>
+                <Icon name={iconSuccess} class={styles.aImg} />
+                回答正确
+              </div>
+            ) : (
+              <div class={[styles.analysisResult, styles.error]}>
+                <Icon name={iconError} class={styles.aImg} />
+                回答错误
+              </div>
+            )}
+          </>
+        ) : (
+          ''
+          // <div class={[styles.analysisResult]}>
+          //   正确答案: <span class={styles.success}>A</span>
+          // </div>
+        )}
+
+        {props.answerAnalysis && (
+          <>
+            <div class={[styles.analysisResult, styles.analysisTitle]}>
+              <Icon name={iconAnalysis} class={styles.aImg} />
+              答案解析
+            </div>
+            <div class={styles.analysisMessage}>{props.answerAnalysis}</div>
+          </>
+        )}
+      </div>
+    )
+  }
+})

+ 46 - 10
src/views/unit-test/model/answer-list/index.tsx

@@ -9,6 +9,10 @@ export default defineComponent({
       type: Array,
       default: () => []
     },
+    look: {
+      type: Boolean,
+      default: false
+    },
     statusList: {
       type: Array,
       default: () => [
@@ -21,11 +25,29 @@ export default defineComponent({
           color: '#EAEAEA'
         }
       ]
+    },
+    // 结果
+    answerResult: {
+      type: Array,
+      default: () => []
     }
   },
   emits: ['select'],
   setup(props, { emit }) {
-    console.log(props.value)
+    /**
+     * @description 检查用户是否答对
+     * @returns Boolean
+     */
+    const formatUserResult = (id: string) => {
+      let result = false
+      props.answerResult.forEach((item: any) => {
+        if (item.questionId === id) {
+          result = item.rightFlag
+        }
+      })
+      return result
+    }
+
     return () => (
       <div class={styles.anserList}>
         <div class={styles.status}>
@@ -37,15 +59,29 @@ export default defineComponent({
           ))}
         </div>
 
-        <Grid class={styles.aList} columnNum={6} border={false}>
-          {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>
+        <div
+          style={{
+            maxHeight: '40vh',
+            minHeight: '20vh',
+            overflowX: 'auto'
+          }}
+        >
+          <Grid class={styles.aList} columnNum={6} border={false}>
+            {props.value.map((item: any, index: number) => (
+              <GridItem onClick={() => emit('select', index)}>
+                {/* 判断是否答题了 */}
+                <span
+                  class={[
+                    item.userAnswer && item.userAnswer.length > 0 && styles.answered,
+                    props.look && (formatUserResult(item.id) ? styles.yes : styles.no)
+                  ]}
+                >
+                  {index + 1}
+                </span>
+              </GridItem>
+            ))}
+          </Grid>
+        </div>
       </div>
     )
   }

+ 4 - 26
src/views/unit-test/model/choice-question/index.module.less

@@ -4,36 +4,13 @@
   background-color: #fff;
   overflow: hidden;
   border-radius: 10px;
-}
-.unitSubjectTitle {
-  // display: flex;
-  // align-items: center;
-  // flex-wrap: wrap;
-  font-size: 16px;
-  font-weight: 500;
-  color: #333333;
-  line-height: 26px;
-  .unitScore {
-    color: #777777;
-  }
-  :global {
-    .van-tag {
-      vertical-align: middle;
-      margin-top: -3px;
-    }
+  & + .unitSubject {
+    margin-top: 12px;
   }
 }
-.unitDetail {
-  padding-top: 20px;
-  padding-bottom: 10px;
-  font-size: 14px;
-}
-.unitTitleImg {
-  width: 100%;
-}
 
 .unitAnswers {
-  padding-bottom: 30px;
+  // padding-bottom: 30px;
   .unitAnswer {
     margin-top: 15px;
     background: #f6f6f6;
@@ -48,6 +25,7 @@
       margin-right: 10px;
     }
     .value {
+      word-break: break-all;
       :global {
         .van-image {
           height: 38px;

+ 58 - 32
src/views/unit-test/model/choice-question/index.tsx

@@ -1,6 +1,8 @@
 import { Tag, Image } from 'vant'
 import { computed, defineComponent, PropType, reactive } from 'vue'
-import { labelOptions } from '../../unit'
+import { AnswerType, labelOptions, QuestionType } from '../../unit'
+import AnserTitle from '../anser-title'
+import AnswerAnalysis from '../answer-analysis'
 import styles from './index.module.less'
 
 // 单选题 - 多选题
@@ -27,6 +29,19 @@ export default defineComponent({
     readOnly: {
       type: Boolean,
       default: false
+    },
+    showAnalysis: {
+      // 是否显示解析
+      type: Boolean,
+      default: false
+    },
+    analysis: {
+      type: Object,
+      default: () => ({
+        message: '',
+        topic: false, // 是否显示结果
+        userResult: true // 用户答题对错
+      })
     }
   },
   emits: ['update:value'],
@@ -65,41 +80,52 @@ export default defineComponent({
       })
       return list
     })
-    const mediaUrls = computed(() => (props.data.mediaUrls ? props.data.mediaUrls.split(',') : ''))
 
     return () => (
-      <div class={styles.unitSubject}>
-        <div class={styles.unitSubjectTitle}>
-          {props.index}、{props.data.name}{' '}
-          <span class={styles.unitScore}>({props.data.totalScore || 0}分)</span>
-          <Tag type="primary">{props.type === 'radio' ? '单选题' : '多选题'}</Tag>
-        </div>
-        <div class={styles.unitDetail}>
-          <div v-html={props.data.questionDetail}></div>
-          {mediaUrls.value &&
-            mediaUrls.value.map((url: any) => <Image class={styles.unitTitleImg} src={url} />)}
-        </div>
+      <>
+        <div class={styles.unitSubject}>
+          {/* 标题 */}
+          <AnserTitle
+            index={props.index}
+            name={props.data.name}
+            score={props.data.totalScore}
+            answerType={props.type === 'radio' ? QuestionType.RADIO : QuestionType.CHECKBOX}
+            extra={{
+              questionDetail: props.data.questionDetail,
+              mediaUrls: props.data.mediaUrls
+            }}
+          />
 
-        <div class={styles.unitAnswers}>
-          {/* styles.active */}
-          {answers.value.map((item: any, index: number) => (
-            <div
-              class={[styles.unitAnswer, item.checked && styles.active]}
-              onClick={() => onSelect(item)}
-            >
-              <span class={styles.option}>{labelOptions[index + 1]}.</span>
-              {item.questionAnswerTypeCode === 'IMAGE' && (
-                <div class={styles.value}>
-                  <Image src={item.questionAnswer} />
-                </div>
-              )}
-              {item.questionAnswerTypeCode === 'TXT' && (
-                <div class={styles.value}>{item.questionAnswer}</div>
-              )}
-            </div>
-          ))}
+          <div class={styles.unitAnswers}>
+            {answers.value.map((item: any, index: number) => (
+              <div
+                class={[styles.unitAnswer, item.checked && styles.active]}
+                onClick={() => onSelect(item)}
+              >
+                <span class={styles.option}>{labelOptions[index + 1]}.</span>
+                {item.questionAnswerTypeCode === AnswerType.IMAGE && (
+                  <div class={styles.value}>
+                    <Image src={item.questionAnswer} />
+                  </div>
+                )}
+                {item.questionAnswerTypeCode === AnswerType.TXT && (
+                  <div class={styles.value}>{item.questionAnswer}</div>
+                )}
+              </div>
+            ))}
+          </div>
         </div>
-      </div>
+
+        {props.showAnalysis && (
+          <div class={styles.unitSubject}>
+            <AnswerAnalysis
+              answerAnalysis={props.analysis.message}
+              topic={props.analysis.topic}
+              userResult={props.analysis.userResult}
+            />
+          </div>
+        )}
+      </>
     )
   }
 })

+ 8 - 28
src/views/unit-test/model/drag-question/index.module.less

@@ -4,35 +4,10 @@
   background-color: #fff;
   // overflow: hidden;
   border-radius: 10px;
-}
-
-.unitSubjectTitle {
-  // display: flex;
-  // align-items: center;
-  // flex-wrap: wrap;
-  font-size: 16px;
-  font-weight: 500;
-  color: #333333;
-  line-height: 26px;
-  .unitScore {
-    color: #777777;
-  }
-  :global {
-    .van-tag {
-      vertical-align: middle;
-      margin-top: -3px;
-    }
+  & + .unitSubject {
+    margin-top: 12px;
   }
 }
-.unitTitleSection {
-  margin-top: 20px;
-  border-radius: 6px;
-  border: 1px solid #d5d5d5;
-  padding: 25px 5px;
-}
-.unitTitleImg {
-  width: 100%;
-}
 
 .unitAnswers {
   padding-bottom: 30px;
@@ -66,7 +41,8 @@
     }
   }
 
-  .items {
+  .items,
+  .imgs {
     min-width: 56px;
     height: 36px;
     font-size: 16px;
@@ -77,4 +53,8 @@
     color: #f67146;
     justify-content: center;
   }
+
+  .imgs {
+    height: 56px;
+  }
 }

+ 159 - 91
src/views/unit-test/model/drag-question/index.tsx

@@ -1,31 +1,47 @@
 import { Tag, Image, Button } from 'vant'
-import { defineComponent, nextTick, onMounted, PropType, reactive } from 'vue'
-import { labelOptions } from '../../unit'
+import { computed, defineComponent, nextTick, onMounted, PropType, reactive } from 'vue'
+import { AnswerType, labelOptions, QuestionType } from '../../unit'
 import Draggable from 'vuedraggable'
 import styles from './index.module.less'
 import Sortable from 'sortablejs'
 import deepClone from '@/helpers/deep-clone'
+import AnserTitle from '../anser-title'
+import AnswerAnalysis from '../answer-analysis'
 
 // 单选和多选题
 export default defineComponent({
   name: 'choice-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: {
       type: Boolean,
       default: false
+    },
+    showAnalysis: {
+      // 是否显示解析
+      type: Boolean,
+      default: false
+    },
+    analysis: {
+      type: Object,
+      default: () => ({
+        message: '',
+        topic: false, // 是否显示结果
+        userResult: true // 用户答题对错
+      })
     }
   },
   emits: ['update:value'],
@@ -35,46 +51,9 @@ export default defineComponent({
       drag: false,
       sortable: null as any,
       list: [] as any,
-      options: [
-        {
-          index: 1,
-          order: 1,
-          value: 'Sol'
-        },
-        {
-          index: 2,
-          order: 2,
-          value: 'Sal'
-        },
-        {
-          index: 3,
-          order: 3,
-          value: 'La'
-        },
-        {
-          index: 4,
-          order: 4,
-          value: 'Si'
-        }
-      ]
+      options: [] as any
     })
 
-    // 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)
-    //   }
-    // }
-
     onMounted(() => {
       nextTick(() => {
         const el = document.getElementById(state.domId)
@@ -84,64 +63,153 @@ export default defineComponent({
           sort: true,
           fallbackTolerance: 3,
           onUpdate: (evt: any) => {
-            // console.log(evt, 'update')
             const updatePosition = (list: any) =>
               list.splice(evt.newIndex, 0, list.splice(evt.oldIndex, 1)[0])
-
-            // const list = deepClone(state.options)
-            // updatePosition(list)
-
-            // state.options = list
             if (state.list && state.list.length > 0) {
               updatePosition(state.list)
-              return
+            } else {
+              state.list = [...state.options]
+              updatePosition(state.list)
             }
-            state.list = [...state.options]
-            updatePosition(state.list)
-            // console.log(updatePosition(state.options))
+            onSelect()
           }
         })
       })
     })
+
+    // 返回选中的结果
+    const onSelect = () => {
+      const options = state.options || []
+      const list = state.list || []
+      const result: any = []
+
+      list.forEach((item: any, index: number) => {
+        const rightOption = options[index]
+        result.push({
+          answerId: item.index,
+          answer: item.leftValue,
+          extra: rightOption.rightValue
+        })
+      })
+      emit('update:value', result)
+    }
+
+    const initOptions = () => {
+      const answers = props.data.answers || []
+      const userAnswer = props.data.userAnswer || [] // 用户填写的答案
+      if (userAnswer.length > 0) {
+        userAnswer.forEach((answer: any) => {
+          const rightOption = answers.find(
+            (child: any) => answer.answerId === child.examinationQuestionAnswerId
+          )
+          const rightValue = answers.find((child: any) => answer.extra === child.questionExtra)
+          const tmp = {
+            index: answer.answerId, // 左边的值
+            leftValue: answer.answer, // 左边的值
+            rightValue: answer.extra, // 右边的值
+            leftType: rightOption.questionAnswerTypeCode || 'TXT', // 左边类型
+            rightType: rightOption.questionExtraTypeCode || 'TXT', // 右边类型
+            rightIndex: rightValue ? rightValue.examinationQuestionAnswerId : ''
+          }
+
+          state.options.push(tmp)
+        })
+      } else {
+        const resultValue: any = []
+        answers.forEach((answer: any) => {
+          const tmp = {
+            index: answer.examinationQuestionAnswerId, // 左边的值
+            leftValue: answer.questionAnswer, // 左边的值
+            rightValue: answer.questionExtra, // 右边的值
+            leftType: answer.questionAnswerTypeCode || 'TXT', // 左边类型
+            rightType: answer.questionExtraTypeCode || 'TXT' // 右边类型
+          }
+          resultValue.push({
+            answerId: answer.examinationQuestionAnswerId,
+            answer: answer.questionAnswer,
+            extra: answer.questionExtra
+          })
+          state.options.push(tmp)
+        })
+        // 进页面就默认初始化答案
+        // emit('update:value', resultValue)
+      }
+    }
+
+    onMounted(() => {
+      initOptions()
+    })
+
     return () => (
-      <div class={styles.unitSubject}>
-        <div class={styles.unitSubjectTitle}>
-          1、选出与方框内音符时值相同的节奏阶段 <span class={styles.unitScore}>(5分)</span>
-          <Tag type="primary">排序题</Tag>
-        </div>
-        <div class={styles.unitTitleSection}>
-          <Image
-            class={styles.unitTitleImg}
-            src="https://lanhu-dds-backend.oss-cn-beijing.aliyuncs.com/merge_image/imgs/dbb27307d428424c8efb9f26032cfa1a_mergeImage.png"
+      <>
+        <div class={styles.unitSubject}>
+          {/* 标题 */}
+          <AnserTitle
+            index={props.index}
+            name={props.data.name}
+            score={props.data.totalScore}
+            answerType={QuestionType.SORT}
+            extra={{
+              questionDetail: props.data.questionDetail,
+              mediaUrls: props.data.mediaUrls
+            }}
           />
-        </div>
 
-        <div class={[styles.unitAnswers, 'van-hairline--top']}>
-          <div class={styles.sortReset}>
-            <span class={styles.tips}>请长按拖拽答案进行排序</span>
-            <Button
-              type="primary"
-              round
-              onClick={() => {
-                const order = state.sortable.toArray()
-                state.sortable.sort(
-                  order.sort((a: any, b: any) => a - b),
-                  true
-                )
-              }}
-            >
-              重置
-            </Button>
-          </div>
-          <div id={state.domId}>
-            {state.options.map((item: any) => (
-              <Tag class={[styles.items]} data-id={item.index}>
-                {item.value + item.index}
-              </Tag>
-            ))}
+          <div class={[styles.unitAnswers, 'van-hairline--top']}>
+            <div class={styles.sortReset}>
+              <span class={styles.tips}>请长按拖拽答案进行排序</span>
+              <Button
+                type="primary"
+                round
+                disabled={props.readOnly}
+                onClick={() => {
+                  const ids: any = []
+                  const answers = props.data.answers || []
+                  answers.forEach((item: any) => {
+                    ids.push(item.examinationQuestionAnswerId)
+                  })
+                  state.sortable.sort(
+                    ids.sort((a: any, b: any) => a - b),
+                    true
+                  )
+
+                  onSelect()
+                }}
+              >
+                重置
+              </Button>
+            </div>
+            <div id={state.domId}>
+              {state.options.map((item: any) => (
+                <>
+                  {item.leftType === AnswerType.TXT && (
+                    <Tag class={[styles.items]} data-id={item.index}>
+                      {item.leftValue}
+                    </Tag>
+                  )}
+                  {item.leftType === AnswerType.IMAGE && (
+                    <Image
+                      src={item.leftValue}
+                      data-id={item.index}
+                      class={styles.imgs}
+                      fit="cover"
+                    />
+                  )}
+                </>
+              ))}
+            </div>
           </div>
         </div>
-      </div>
+        {props.showAnalysis && (
+          <div class={styles.unitSubject}>
+            <AnswerAnalysis
+              answerAnalysis={props.analysis.message}
+              topic={props.analysis.topic}
+              userResult={props.analysis.userResult}
+            />
+          </div>
+        )}
+      </>
     )
   }
 })

+ 7 - 1
src/views/unit-test/model/error-mode/index.tsx

@@ -5,6 +5,12 @@ import styles from './index.module.less'
 
 export default defineComponent({
   name: 'result-mode',
+  props: {
+    confirmButtonText: {
+      type: String,
+      default: '我知道了'
+    }
+  },
   emits: ['close', 'conform'],
   setup(props, { emit }) {
     return () => (
@@ -34,7 +40,7 @@ export default defineComponent({
             emit('close')
           }}
         >
-          我知道啦
+          {props.confirmButtonText}
         </Button>
       </div>
     )

+ 3 - 24
src/views/unit-test/model/keep-look-question/index.module.less

@@ -4,35 +4,14 @@
   background-color: #fff;
   // overflow: hidden;
   border-radius: 10px;
-}
-
-.unitSubjectTitle {
-  // display: flex;
-  // align-items: center;
-  // flex-wrap: wrap;
-  font-size: 16px;
-  font-weight: 500;
-  color: #333333;
-  line-height: 26px;
-  .unitScore {
-    color: #777777;
-  }
-  :global {
-    .van-tag {
-      vertical-align: middle;
-      margin-top: -3px;
-    }
+  & + .unitSubject {
+    margin-top: 12px;
   }
 }
 
-.unitTitleImg {
-  width: 100%;
-  padding-top: 20px;
-  padding-bottom: 10px;
-}
-
 .unitAnswers {
   position: relative;
+  padding-top: 20px;
   padding-bottom: 20px;
 
   .answerItem {

+ 95 - 61
src/views/unit-test/model/keep-look-question/index.tsx

@@ -1,7 +1,10 @@
 import { Tag, Image, Button } from 'vant'
-import { defineComponent, nextTick, onMounted, PropType, reactive, ref } from 'vue'
+import { computed, defineComponent, nextTick, onMounted, PropType, reactive, ref } from 'vue'
 import styles from './index.module.less'
 import { useRect } from '@vant/use'
+import { AnswerType, QuestionType } from '../../unit'
+import AnserTitle from '../anser-title'
+import AnswerAnalysis from '../answer-analysis'
 
 // 单选和多选题
 export default defineComponent({
@@ -24,6 +27,19 @@ export default defineComponent({
     readOnly: {
       type: Boolean,
       default: false
+    },
+    showAnalysis: {
+      // 是否显示解析
+      type: Boolean,
+      default: false
+    },
+    analysis: {
+      type: Object,
+      default: () => ({
+        message: '',
+        topic: false, // 是否显示结果
+        userResult: true // 用户答题对错
+      })
     }
   },
   emits: ['update:value'],
@@ -325,7 +341,7 @@ export default defineComponent({
         state.drawLineList.push(temps)
       })
 
-      console.log(state.drawLineList, state.options)
+      // console.log(state.drawLineList, state.options)
       // 反显答案-连线
       nextTick(() => {
         state.drawLineList.forEach((draw: any) => {
@@ -360,69 +376,87 @@ export default defineComponent({
     })
 
     return () => (
-      <div class={styles.unitSubject}>
-        <div class={styles.unitSubjectTitle}>
-          1、选出与方框内音符时值相同的节奏阶段 <span class={styles.unitScore}>(5分)</span>
-          <Tag type="primary">连连看</Tag>
-        </div>
-
-        <Image
-          class={styles.unitTitleImg}
-          src="https://lanhu-dds-backend.oss-cn-beijing.aliyuncs.com/merge_image/imgs/dbb27307d428424c8efb9f26032cfa1a_mergeImage.png"
-        />
+      <>
+        <div class={styles.unitSubject}>
+          {/* 标题 */}
+          <AnserTitle
+            index={props.index}
+            name={props.data.name}
+            score={props.data.totalScore}
+            answerType={QuestionType.LINK}
+            extra={{
+              questionDetail: props.data.questionDetail,
+              mediaUrls: props.data.mediaUrls
+            }}
+          />
 
-        <div class={[styles.unitAnswers]} id={state.answerDomId}>
-          <canvas
-            ref={canvasRef}
-            class={styles.canvasSection}
-            width={state.answerRect.width || 0}
-            height={state.answerRect.height || 0}
-          ></canvas>
-          {state.options.map((item: any) => (
-            <div class={styles.answerItem}>
-              <div
-                class={[styles.unitItem, item.left && styles.active]}
-                id={item.index + '-left'}
-                onClick={(e: any) => onLeftClick(e, item)}
-              >
-                {item.leftType === 'TXT' && item.leftValue}
-                {item.leftType === 'IMAGE' && <Image src={item.leftValue} class={styles.img} />}
-              </div>
-              <div
-                class={[styles.unitItem, item.right && styles.active]}
-                id={item.index + '-right'}
-                onClick={(e: any) => onRightClick(e, item)}
-              >
-                {item.rightType === 'TXT' && item.rightValue}
-                {item.rightType === 'IMAGE' && <Image src={item.rightValue} class={styles.img} />}
+          <div class={[styles.unitAnswers]} id={state.answerDomId}>
+            <canvas
+              ref={canvasRef}
+              class={styles.canvasSection}
+              width={state.answerRect.width || 0}
+              height={state.answerRect.height || 0}
+            ></canvas>
+            {state.options.map((item: any) => (
+              <div class={styles.answerItem}>
+                <div
+                  class={[styles.unitItem, item.left && styles.active]}
+                  id={item.index + '-left'}
+                  onClick={(e: any) => onLeftClick(e, item)}
+                >
+                  {item.leftType === AnswerType.TXT && item.leftValue}
+                  {item.leftType === AnswerType.IMAGE && (
+                    <Image src={item.leftValue} class={styles.img} />
+                  )}
+                </div>
+                <div
+                  class={[styles.unitItem, item.right && styles.active]}
+                  id={item.index + '-right'}
+                  onClick={(e: any) => onRightClick(e, item)}
+                >
+                  {item.rightType === AnswerType.TXT && item.rightValue}
+                  {item.rightType === AnswerType.IMAGE && (
+                    <Image src={item.rightValue} class={styles.img} />
+                  )}
+                </div>
               </div>
-            </div>
-          ))}
-        </div>
-        <div class={styles.resetBtnGroup}>
-          <Button
-            round
-            type="primary"
-            disabled={props.readOnly}
-            onClick={() => {
-              state.drawLineList = []
-              renderDrawLine(canvasRef.value)
+            ))}
+          </div>
+          <div class={styles.resetBtnGroup}>
+            <Button
+              round
+              type="primary"
+              disabled={props.readOnly}
+              onClick={() => {
+                state.drawLineList = []
+                renderDrawLine(canvasRef.value)
 
-              // 清除所有的选中状态
-              state.options.forEach((item: any) => {
-                item.left = false
-                item.right = false
-                item.leftLocked = false
-                item.rightLocked = false
-              })
-              // 清除所有的选择的内容
-              state.selectItem = []
-            }}
-          >
-            重置
-          </Button>
+                // 清除所有的选中状态
+                state.options.forEach((item: any) => {
+                  item.left = false
+                  item.right = false
+                  item.leftLocked = false
+                  item.rightLocked = false
+                })
+                // 清除所有的选择的内容
+                state.selectItem = []
+              }}
+            >
+              重置
+            </Button>
+          </div>
         </div>
-      </div>
+
+        {props.showAnalysis && (
+          <div class={styles.unitSubject}>
+            <AnswerAnalysis
+              answerAnalysis={props.analysis.message}
+              topic={props.analysis.topic}
+              userResult={props.analysis.userResult}
+            />
+          </div>
+        )}
+      </>
     )
   }
 })

+ 2 - 27
src/views/unit-test/model/play-question/index.module.less

@@ -4,35 +4,10 @@
   background-color: #fff;
   // overflow: hidden;
   border-radius: 10px;
-}
-
-.unitSubjectTitle {
-  // display: flex;
-  // align-items: center;
-  // flex-wrap: wrap;
-  font-size: 16px;
-  font-weight: 500;
-  color: #333333;
-  line-height: 26px;
-  .unitScore {
-    color: #777777;
-  }
-  :global {
-    .van-tag {
-      vertical-align: middle;
-      margin-top: -3px;
-    }
+  & + .unitSubject {
+    margin-top: 12px;
   }
 }
-.unitTitleSection {
-  margin-top: 20px;
-  border-radius: 6px;
-  border: 1px solid #d5d5d5;
-  padding: 25px 5px;
-}
-.unitTitleImg {
-  width: 100%;
-}
 
 .unitAnswers {
   padding-top: 20px;

+ 145 - 50
src/views/unit-test/model/play-question/index.tsx

@@ -1,82 +1,177 @@
 import { Tag, Image, Button, Cell, Icon } from 'vant'
-import { defineComponent, nextTick, onMounted, PropType, reactive } from 'vue'
+import { computed, defineComponent, nextTick, onMounted, PropType, reactive } from 'vue'
 import styles from './index.module.less'
+import {
+  listenerMessage,
+  postMessage,
+  promisefiyPostMessage,
+  removeListenerMessage
+} from '@/helpers/native-message'
 import deepClone from '@/helpers/deep-clone'
 import iconSong from '../../images/icon-song.png'
+import AnserTitle from '../anser-title'
+import { QuestionType } from '../../unit'
+import AnswerAnalysis from '../answer-analysis'
 
 // 单选和多选题
 export default defineComponent({
   name: 'choice-question',
   props: {
     value: {
-      type: [String, Number, Array],
-      default: ''
+      type: Array,
+      default: () => []
+    },
+    index: {
+      // 题目是第几道
+      type: Number,
+      default: 1
     },
-    answers: {
+    data: {
       type: Object,
-      default: {}
+      default: () => ({})
+    },
+    // 测试编号
+    unitId: {
+      type: String,
+      default: ''
     },
     /* 只读 */
     readOnly: {
       type: Boolean,
       default: false
+    },
+    showAnalysis: {
+      // 是否显示解析
+      type: Boolean,
+      default: false
+    },
+    analysis: {
+      type: Object,
+      default: () => ({
+        message: '',
+        topic: false, // 是否显示结果
+        userResult: true // 用户答题对错
+      })
     }
   },
   emits: ['update:value'],
   setup(props, { emit }) {
+    console.log(props)
     const state = reactive({
-      list: [] as any
+      list: [] as any,
+      score: 0
     })
 
-    // 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 mediaUrls = computed(() => (props.data.mediaUrls ? props.data.mediaUrls.split(',') : ''))
+
+    const questionExtendsInfo = computed(() =>
+      props.data.questionExtendsInfo ? JSON.parse(props.data.questionExtendsInfo) : ''
+    )
+
+    /**
+     * @description 进行评测
+     */
+    const onEvaluation = () => {
+      const info = questionExtendsInfo.value
+      if (!info) return
+      console.log(props.unitId)
+      postMessage({
+        api: 'openAccompanyWebView',
+        content: {
+          url: `https://ponline.colexiu.com/orchestra-music-score/?id=${info.musicSheetId}&modelType=evaluation&unitId=${props.unitId}`,
+          // url: `${location.origin}/orchestra-music-score/?id=${info.musicSheetId}&modelType=evaluation&unitId=${props.unitId}`,
+          orientation: 0,
+          isHideTitle: true,
+          statusBarTextColor: false,
+          isOpenLight: true
+        }
+      })
+
+      // 打开页面监听
+      listenerMessage('webViewOnResume', () => {
+        promisefiyPostMessage({ api: 'getCache', content: { key: 'h5-orchestra-unit' } }).then(
+          (res: any) => {
+            const content = res.content
+            if (content.value) {
+              const result = content.value ? JSON.parse(content.value) : {}
+              if (result.unitId === props.unitId) {
+                const tempScore = result.score || 0
+                // 跟上一次分数对比
+                if (state.score < tempScore) {
+                  state.score = result.score || 0
+                }
+              }
+              const answer = props.data.answer || []
+              emit('update:value', [
+                {
+                  answerId: answer[0].examinationQuestionId,
+                  answer: state.score,
+                  extra: ''
+                }
+              ])
+            }
+
+            // 关闭页面监听
+            removeListenerMessage('webViewOnResume', () => {
+              //
+            })
+          }
+        )
+      })
+    }
+
     return () => (
-      <div class={styles.unitSubject}>
-        <div class={styles.unitSubjectTitle}>
-          4、请点击以下曲目进行评测,评测分数达到80分合格
-          <span class={styles.unitScore}>(5分)</span>
-          <Tag type="primary">演奏题</Tag>
-        </div>
-        <div class={styles.unitTitleSection}>
-          <Image
-            class={styles.unitTitleImg}
-            src="https://lanhu-dds-backend.oss-cn-beijing.aliyuncs.com/merge_image/imgs/dbb27307d428424c8efb9f26032cfa1a_mergeImage.png"
+      <>
+        <div class={styles.unitSubject}>
+          {/* 标题 */}
+          <AnserTitle
+            index={props.index}
+            name={props.data.name}
+            score={props.data.totalScore}
+            answerType={QuestionType.PLAY}
+            extra={{
+              questionDetail: props.data.questionDetail,
+              mediaUrls: ''
+            }}
           />
-        </div>
 
-        <div class={[styles.unitAnswers]}>
-          <Cell class={styles.playSection} center titleClass={['van-ellipsis', styles.playTitle]}>
-            {{
-              icon: () => <Image class={styles.img} src={iconSong} />,
-              title: () => <>没开机三江源没开机三江源</>,
-              value: () => (
-                <Button round class={styles.playBtn} type="primary">
-                  点击评测
-                  <Icon name="play" />
-                </Button>
-              )
-            }}
-          </Cell>
-          <div class={['van-hairline--top', styles.unitScoreNum]}>
-            <div class={styles.score}>89</div>
-            <div class={styles.scoreTitle}>评测分数</div>
-            <div class={styles.scoreTips}>多次评测取完整评测的最高分数</div>
+          <div class={[styles.unitAnswers]}>
+            {questionExtendsInfo.value && (
+              <Cell
+                class={styles.playSection}
+                center
+                titleClass={['van-ellipsis', styles.playTitle]}
+              >
+                {{
+                  icon: () => <Image class={styles.img} src={iconSong} />,
+                  title: () => <>{questionExtendsInfo.value.musicName}</>,
+                  value: () => (
+                    <Button round class={styles.playBtn} type="primary" onClick={onEvaluation}>
+                      点击评测
+                      <Icon name="play" />
+                    </Button>
+                  )
+                }}
+              </Cell>
+            )}
+
+            <div class={['van-hairline--top', styles.unitScoreNum]}>
+              <div class={styles.score}>{state.score}</div>
+              <div class={styles.scoreTitle}>评测分数</div>
+              <div class={styles.scoreTips}>多次评测取完整评测的最高分数</div>
+            </div>
           </div>
         </div>
-      </div>
+        {props.showAnalysis && (
+          <div class={styles.unitSubject}>
+            <AnswerAnalysis
+              answerAnalysis={props.analysis.message}
+              topic={props.analysis.topic}
+              userResult={props.analysis.userResult}
+            />
+          </div>
+        )}
+      </>
     )
   }
 })

+ 20 - 10
src/views/unit-test/practice-mode/index.module.less

@@ -15,26 +15,36 @@
     color: #333333;
   }
 
+  :global {
+    .van-cell__value {
+      flex: 0 auto;
+    }
+  }
+
   .unitCount {
     padding-top: 10px;
+    padding-left: 8px;
     display: flex;
     align-items: center;
     justify-content: space-between;
   }
-  .qNums {
+  .countSection {
     display: flex;
     align-items: center;
-    font-size: 14px;
+    font-size: 12px;
+    line-height: 17px;
     color: #333333;
-    line-height: 20px;
-    .num {
-      color: #f67146;
+    flex-direction: column;
+    .nums {
+      font-family: 'DIAN';
+      font-weight: bold;
+      color: #333333;
+      font-size: 20px;
+      padding-bottom: 3px;
+    }
+    & + .countSection {
+      margin-left: 25px;
     }
-  }
-  .icon {
-    width: 14px;
-    height: 14px;
-    margin-right: 4px;
   }
 }
 

+ 194 - 48
src/views/unit-test/practice-mode/index.tsx

@@ -10,7 +10,7 @@ import {
   SwipeItem,
   Tag
 } from 'vant'
-import { defineComponent, onMounted, reactive, ref } from 'vue'
+import { defineComponent, nextTick, onMounted, reactive, ref } from 'vue'
 import { useRoute, useRouter } from 'vue-router'
 import styles from './index.module.less'
 import iconQuestionNums from '../images/icon-question-nums.png'
@@ -26,6 +26,10 @@ import KeepLookQuestion from '../model/keep-look-question'
 import PlayQuestion from '../model/play-question'
 import ErrorMode from '../model/error-mode'
 import ResultFinish from '../model/result-finish'
+import { QuestionType } from '../unit'
+import request from '@/helpers/request'
+import { useRect } from '@vant/use'
+import OHeader from '@/components/o-header'
 
 export default defineComponent({
   name: 'unit-detail',
@@ -38,38 +42,133 @@ export default defineComponent({
       visiableError: false,
       visiableAnswer: false,
       visiableResult: false,
+      id: route.query.id,
+      examDetail: {} as any,
       currentIndex: 0,
-      questionList: [1, 2, 3, 4, 5],
-      answerList: {},
-      time: 30 * 60 * 1000,
+      questionList: [],
+      time: 0,
       visiableSure: false,
-      childs: [
-        { name: 'John', id: 0 },
-        { name: 'Joao', id: 1 },
-        { name: 'Jean', id: 2 }
-      ]
+      resultInfo: {} as any,
+      resultStatusType: 'SUCCESS', // 'SUCCESS' | 'FAIL'
+      visiableExam: false, // 考试已结束
+      nextStatus: false,
+      swipeHeight: 'auto' as any
     })
 
+    const getExamDetails = async () => {
+      try {
+        const { data } = await request.post('/api-student/examinationQuestion/page', {
+          data: {
+            page: 1,
+            row: 50,
+            categoryId: state.id
+          }
+        })
+        const { questionJson, studentAnswerJson, ...res } = data
+        const temp = data.rows || []
+        temp.forEach((item: any) => {
+          item.showAnalysis = false // 默认不显示解析
+          item.analysis = {
+            message: item.answerAnalysis,
+            topic: true, // 是否显示结果
+            userResult: false // 用户答题对错
+          }
+          item.userAnswer = [] // 用户答题
+        })
+        state.questionList = temp
+
+        state.examDetail = { ...res } || {}
+      } catch {
+        //
+      }
+    }
+
+    /**
+     * @description 下一题 | 测试完成
+     */
+    const onNextQuestion = async () => {
+      try {
+        const questionList = state.questionList || []
+        const userAnswerList: any = [] // 所有题目的答案
+        // let currentResult = false // 当前题目是否已经答题
+        questionList.forEach((question: any) => {
+          // 格式化所有题目的答案
+          if (question.userAnswer && question.userAnswer.length > 0) {
+            userAnswerList.push({
+              questionId: question.id,
+              details: question.userAnswer
+            })
+          }
+        })
+
+        // 判断是否是最后一题
+        if (state.questionList.length === state.currentIndex + 1) {
+          state.visiableSure = true
+          return
+        }
+
+        swipeRef.value?.next()
+      } catch {
+        //
+      }
+    }
+
+    /**
+     * @description 重置当前的题目高度
+     */
+    const resizeSwipeItemHeight = () => {
+      nextTick(() => {
+        window.scrollTo(0, 0)
+        setTimeout(() => {
+          const currentItemDom: Element =
+            document.querySelectorAll('.swipe-item-question')[state.currentIndex]
+          const rect = useRect(currentItemDom)
+          state.swipeHeight = rect.height
+        }, 100)
+      })
+    }
+
+    onMounted(() => {
+      getExamDetails()
+
+      resizeSwipeItemHeight()
+    })
     return () => (
       <div class={styles.unitDetail}>
+        <OSticky position="top">
+          <OHeader
+            v-slots={{
+              right: () => (
+                <span
+                  style="color: var(--van-primary-color)"
+                  onClick={() => {
+                    // router.push('/create-message')
+                  }}
+                >
+                  结束练习
+                </span>
+              )
+            }}
+          />
+        </OSticky>
         <Cell center class={styles.unitSection}>
           {{
-            title: () => <div class={styles.unitTitle}>长笛level1上册测验一</div>,
-            label: () => (
+            title: () => (
+              <div class={[styles.unitTitle]}>
+                {state.examDetail.unitExaminationName}i门口看门口看看门口看看门口看看门口看看看
+              </div>
+            ),
+            value: () => (
               <div class={styles.unitCount}>
-                <div class={styles.qNums}>
-                  <Icon class={styles.icon} name={iconQuestionNums} />
-                  题目数量 <span class={styles.num}>1</span>/4
+                <div class={styles.countSection}>
+                  <span class={styles.nums}>12</span>
+                  <span>答对</span>
                 </div>
-                <div class={styles.qNums}>
-                  <Icon class={styles.icon} name={iconCountDown} />
-                  剩余时长:
-                  <CountDown
-                    ref={countDownRef}
-                    time={state.time}
-                    format={'mm:ss'}
-                    autoStart={false}
-                  />
+                <div class={styles.countSection}>
+                  <span class={styles.nums} style={{ color: '#F44541' }}>
+                    12
+                  </span>
+                  <span>答错</span>
                 </div>
               </div>
             )
@@ -83,26 +182,77 @@ export default defineComponent({
           duration={300}
           touchable={false}
           lazyRender
-          // initialSwipe={state.currentIndex}
+          height={state.swipeHeight}
           onChange={(index: number) => {
             state.currentIndex = index
+            resizeSwipeItemHeight()
           }}
         >
-          <SwipeItem>
-            <ChoiceQuestion v-model:value={state.answerList[0]} type="checkbox" />
-          </SwipeItem>
-          <SwipeItem>
-            <ChoiceQuestion v-model:value={state.answerList[1]} type="radio" />
-          </SwipeItem>
-          <SwipeItem>
-            <DragQuestion />
-          </SwipeItem>
-          <SwipeItem>
-            <KeepLookQuestion />
-          </SwipeItem>
-          <SwipeItem>
-            <PlayQuestion />
-          </SwipeItem>
+          {state.questionList.map((item: any, index: number) => (
+            // item.questionTypeCode === QuestionType.PLAY && (
+            //   <SwipeItem>
+            //     <KeepLookQuestion v-model:value={item.userAnswer} data={item} index={index + 1} />
+            //     <PlayQuestion
+            //       v-model:value={item.userAnswer}
+            //       data={item}
+            //       index={index + 1}
+            //       unitId={state.id as any}
+            //     />
+            //   </SwipeItem>
+            // )
+            <SwipeItem>
+              <div class="swipe-item-question">
+                {item.questionTypeCode === QuestionType.RADIO && (
+                  <ChoiceQuestion
+                    v-model:value={item.userAnswer}
+                    index={index + 1}
+                    data={item}
+                    type="radio"
+                    showAnalysis={item.showAnalysis}
+                    analysis={item.analysis}
+                  />
+                )}
+                {item.questionTypeCode === QuestionType.CHECKBOX && (
+                  <ChoiceQuestion
+                    v-model:value={item.userAnswer}
+                    index={index + 1}
+                    data={item}
+                    type="checkbox"
+                    showAnalysis={item.showAnalysis}
+                    analysis={item.analysis}
+                  />
+                )}
+                {item.questionTypeCode === QuestionType.SORT && (
+                  <DragQuestion
+                    v-model:value={item.userAnswer}
+                    data={item}
+                    index={index + 1}
+                    showAnalysis={item.showAnalysis}
+                    analysis={item.analysis}
+                  />
+                )}
+                {item.questionTypeCode === QuestionType.LINK && (
+                  <KeepLookQuestion
+                    v-model:value={item.userAnswer}
+                    data={item}
+                    index={index + 1}
+                    showAnalysis={item.showAnalysis}
+                    analysis={item.analysis}
+                  />
+                )}
+                {item.questionTypeCode === QuestionType.PLAY && (
+                  <PlayQuestion
+                    v-model:value={item.userAnswer}
+                    data={item}
+                    index={index + 1}
+                    unitId={state.id as any}
+                    showAnalysis={item.showAnalysis}
+                    analysis={item.analysis}
+                  />
+                )}
+              </div>
+            </SwipeItem>
+          ))}
         </Swipe>
 
         <OSticky position="bottom" background="white">
@@ -124,16 +274,12 @@ export default defineComponent({
               block
               round
               type="primary"
-              onClick={() => {
-                if (state.questionList.length - 1 === state.currentIndex) {
-                  state.visiableSure = true
-                } else {
-                  // swipeRef.value?.next()
-                  state.visiableError = true
-                }
-              }}
+              onClick={onNextQuestion}
+              loading={state.nextStatus}
+              disabled={state.nextStatus}
             >
-              {state.questionList.length - 1 === state.currentIndex ? '测验完成' : '下一题'}
+              提交
+              {/* {state.questionList.length === state.currentIndex + 1 ? '提交' : '提交'} */}
             </Button>
             <Image
               src={iconButtonList}

+ 31 - 6
src/views/unit-test/test-exercise/index.tsx

@@ -3,6 +3,7 @@ import { defineComponent, onMounted, reactive, ref } from 'vue'
 import { useRoute, useRouter } from 'vue-router'
 import styles from './index.module.less'
 import iconTag from '../images/icon-tag.png'
+import request from '@/helpers/request'
 
 export default defineComponent({
   name: 'unit-detail',
@@ -10,23 +11,47 @@ export default defineComponent({
     const route = useRoute()
     const router = useRouter()
     const state = reactive({
-      visiableNotice: false
+      id: route.query.id,
+      name: route.query.name,
+      visiableNotice: false,
+      list: [] as any
     })
 
+    const getDetails = async () => {
+      try {
+        const { data } = await request.post('/api-student/unitExamination/queryKnowledgePoint', {
+          requestType: 'form',
+          data: {
+            unitExaminationId: state.id
+          }
+        })
+        state.list = data || []
+        console.log(data)
+      } catch {
+        //
+      }
+    }
+
     const onDetail = (item: any) => {
       router.push({
-        path: '/practice-mode'
+        path: '/practice-mode',
+        query: {
+          id: item.id
+        }
       })
     }
 
+    onMounted(() => {
+      getDetails()
+    })
     return () => (
       <div class={styles.unitDetail}>
         <Cell center class={styles.unitSection}>
           {{
             title: () => (
               <div class={styles.unitTitle}>
-                长笛level1上册测验一长笛level1上册测验一册测验一
-                <Tag type="primary">长笛单技课</Tag>
+                {state.name}
+                {/* <Tag type="primary">长笛单技课</Tag> */}
               </div>
             ),
             label: () => (
@@ -42,11 +67,11 @@ export default defineComponent({
           <div class={styles.cellTitle}>
             <i></i>考点
           </div>
-          {[1, 2, 3, 44, 5, 6, 7, 8].map((item: any) => (
+          {state.list.map((item: any) => (
             <Cell center isLink titleClass={['van-ellipsis']} onClick={() => onDetail(item)}>
               {{
                 icon: () => <Image src={iconTag} class={styles.img} />,
-                title: () => <>乐理知识乐理知识乐理知识乐理知识乐理知识乐理知识-音符</>,
+                title: () => <>{item.name}</>,
                 value: () => <span>去练习</span>
               }}
             </Cell>

+ 226 - 74
src/views/unit-test/unit-detail/index.tsx

@@ -1,71 +1,143 @@
-import {
-  ActionSheet,
-  Button,
-  Cell,
-  CountDown,
-  Icon,
-  Image,
-  Popup,
-  Swipe,
-  SwipeItem,
-  Tag
-} from 'vant'
-import { defineComponent, onMounted, reactive, ref } from 'vue'
+import { ActionSheet, Button, Cell, Icon, Image, Swipe, SwipeItem } from 'vant'
+import { defineComponent, onMounted, reactive, ref, nextTick } from 'vue'
 import { useRoute, useRouter } from 'vue-router'
-import NoticeStart from '../model/notice-start'
 import styles from './index.module.less'
 import iconQuestionNums from '../images/icon-question-nums.png'
-import iconCountDown from '../images/icon-count-down.png'
 import iconButtonList from '../images/icon-button-list.png'
 import OSticky from '@/components/o-sticky'
 import ChoiceQuestion from '../model/choice-question'
 import AnswerList from '../model/answer-list'
-import ODialog from '@/components/o-dialog'
 import DragQuestion from '../model/drag-question'
 import KeepLookQuestion from '../model/keep-look-question'
 import PlayQuestion from '../model/play-question'
+import request from '@/helpers/request'
+import { QuestionType } from '../unit'
+import { useRect } from '@vant/use'
 
 export default defineComponent({
   name: 'unit-detail',
   setup() {
     const route = useRoute()
     const router = useRouter()
-    const countDownRef = ref()
     const swipeRef = ref()
     const state = reactive({
+      id: route.query.id,
+      examDetail: {} as any,
       visiableAnswer: false,
       currentIndex: 0,
-      questionList: [5],
-      answerList: {},
-      time: 30 * 60 * 1000,
-      visiableSure: false,
-      childs: [
-        { name: 'John', id: 0 },
-        { name: 'Joao', id: 1 },
-        { name: 'Jean', id: 2 }
-      ]
+      questionList: [],
+      time: 0,
+      resultInfo: {} as any,
+      answerResult: [] as any,
+      nextStatus: false,
+      swipeHeight: 'auto' as any
+    })
+
+    const getExamDetails = async () => {
+      try {
+        const { data } = await request.post('/api-school/studentUnitExamination/detail', {
+          requestType: 'form',
+          data: {
+            studentUnitExaminationId: state.id
+          }
+        })
+        const { questionJson, studentAnswerJson, answerResult, ...res } = data
+        const temp = questionJson || []
+        temp.forEach((item: any) => {
+          item.userAnswer = formatUserAnswers(item, studentAnswerJson)
+        })
+        // 问题列表
+        state.questionList = temp
+        // 正确答案
+        state.answerResult = answerResult ? JSON.parse(answerResult) : []
+
+        // 详情
+        state.examDetail = { ...res } || {}
+      } catch {
+        //
+      }
+    }
+
+    /**
+     * @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 检查用户是否答对
+     * @returns Boolean
+     */
+    const formatUserResult = (id: string) => {
+      let result = false
+      state.answerResult.forEach((item: any) => {
+        if (item.questionId === id) {
+          result = item.rightFlag
+        }
+      })
+      return result
+    }
+
+    /**
+     * @description 重置当前的题目高度
+     */
+    const resizeSwipeItemHeight = () => {
+      nextTick(() => {
+        window.scrollTo(0, 0)
+        setTimeout(() => {
+          const currentItemDom: Element =
+            document.querySelectorAll('.swipe-item-question')[state.currentIndex]
+          console.log(currentItemDom, state.currentIndex)
+          const rect = useRect(currentItemDom)
+          state.swipeHeight = rect.height
+        }, 100)
+      })
+    }
+
+    /**
+     * @description 下一题 | 测试完成
+     */
+    const onNextQuestion = async () => {
+      try {
+        state.nextStatus = true
+        if (state.questionList.length === state.currentIndex + 1) {
+          router.back()
+        }
+        swipeRef.value?.next()
+        state.nextStatus = false
+      } catch {
+        //
+        state.nextStatus = false
+      }
+    }
+
+    onMounted(async () => {
+      await getExamDetails()
+
+      // 初始化高度
+      resizeSwipeItemHeight()
     })
 
     return () => (
       <div class={styles.unitDetail}>
         <Cell center class={styles.unitSection}>
           {{
-            title: () => <div class={styles.unitTitle}>长笛level1上册测验一</div>,
+            title: () => <div class={styles.unitTitle}>{state.examDetail.unitExaminationName}</div>,
             label: () => (
               <div class={styles.unitCount}>
                 <div class={styles.qNums}>
                   <Icon class={styles.icon} name={iconQuestionNums} />
-                  题目数量 <span class={styles.num}>1</span>/4
-                </div>
-                <div class={styles.qNums}>
-                  <Icon class={styles.icon} name={iconCountDown} />
-                  剩余时长:
-                  <CountDown
-                    ref={countDownRef}
-                    time={state.time}
-                    format={'mm:ss'}
-                    autoStart={false}
-                  />
+                  题目数量{' '}
+                  <span class={styles.num} style={{ paddingLeft: '6px' }}>
+                    {state.currentIndex + 1}
+                  </span>
+                  /{state.examDetail.questionNum || 0}
                 </div>
               </div>
             )
@@ -79,26 +151,109 @@ export default defineComponent({
           duration={300}
           touchable={false}
           lazyRender
-          // initialSwipe={state.currentIndex}
+          style={{ paddingBottom: '12px' }}
+          height={state.swipeHeight}
           onChange={(index: number) => {
             state.currentIndex = index
+            resizeSwipeItemHeight()
           }}
         >
-          <SwipeItem>
-            <ChoiceQuestion v-model:value={state.answerList[0]} type="checkbox" />
-          </SwipeItem>
-          <SwipeItem>
-            <ChoiceQuestion v-model:value={state.answerList[1]} type="radio" />
-          </SwipeItem>
-          <SwipeItem>
-            <DragQuestion />
-          </SwipeItem>
-          <SwipeItem>
-            <KeepLookQuestion />
-          </SwipeItem>
-          <SwipeItem>
-            <PlayQuestion />
-          </SwipeItem>
+          {state.questionList.map((item: any, index: number) => (
+            // item.questionTypeCode === QuestionType.CHECKBOX && (
+            //   <SwipeItem>
+            //     <ChoiceQuestion
+            //       v-model:value={item.userAnswer}
+            //       index={index + 1}
+            //       data={item}
+            //       readOnly
+            //       type="radio"
+            //       showAnalysis
+            //       analysis={{
+            //         message: item.answerAnalysis,
+            //         topic: true, // 是否显示结果
+            //         userResult: formatUserResult(item.id) // 用户答题对错
+            //       }}
+            //     />
+            //   </SwipeItem>
+            // )
+            <SwipeItem>
+              <div class="swipe-item-question">
+                {item.questionTypeCode === QuestionType.RADIO && (
+                  <ChoiceQuestion
+                    v-model:value={item.userAnswer}
+                    index={index + 1}
+                    data={item}
+                    readOnly
+                    type="radio"
+                    showAnalysis
+                    analysis={{
+                      message: item.answerAnalysis,
+                      topic: true, // 是否显示结果
+                      userResult: formatUserResult(item.id) // 用户答题对错
+                    }}
+                  />
+                )}
+                {item.questionTypeCode === QuestionType.CHECKBOX && (
+                  <ChoiceQuestion
+                    v-model:value={item.userAnswer}
+                    index={index + 1}
+                    data={item}
+                    readOnly
+                    type="checkbox"
+                    showAnalysis
+                    analysis={{
+                      message: item.answerAnalysis,
+                      topic: true, // 是否显示结果
+                      userResult: formatUserResult(item.id) // 用户答题对错
+                    }}
+                  />
+                )}
+                {item.questionTypeCode === QuestionType.SORT && (
+                  <DragQuestion
+                    readOnly
+                    v-model:value={item.userAnswer}
+                    data={item}
+                    index={index + 1}
+                    showAnalysis
+                    analysis={{
+                      message: item.answerAnalysis,
+                      topic: true, // 是否显示结果
+                      userResult: formatUserResult(item.id) // 用户答题对错
+                    }}
+                  />
+                )}
+                {item.questionTypeCode === QuestionType.LINK && (
+                  <KeepLookQuestion
+                    readOnly
+                    v-model:value={item.userAnswer}
+                    data={item}
+                    index={index + 1}
+                    showAnalysis
+                    analysis={{
+                      message: item.answerAnalysis,
+                      topic: true, // 是否显示结果
+                      userResult: formatUserResult(item.id) // 用户答题对错
+                    }}
+                  />
+                )}
+                {item.questionTypeCode === QuestionType.PLAY && (
+                  <PlayQuestion
+                    readOnly
+                    v-model:value={item.userAnswer}
+                    data={item}
+                    index={index + 1}
+                    unitId={state.id as any}
+                    showAnalysis
+                    analysis={{
+                      message: item.answerAnalysis,
+                      topic: true, // 是否显示结果
+                      userResult: formatUserResult(item.id) // 用户答题对错
+                    }}
+                  />
+                )}
+              </div>
+            </SwipeItem>
+          ))}
         </Swipe>
 
         <OSticky position="bottom" background="white">
@@ -120,16 +275,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}
@@ -142,7 +292,19 @@ export default defineComponent({
         {/* 题目集合 */}
         <ActionSheet v-model:show={state.visiableAnswer} title="题目列表" safeAreaInsetBottom>
           <AnswerList
-            value={[1, 3, 4]}
+            value={state.questionList}
+            answerResult={state.answerResult}
+            look
+            statusList={[
+              {
+                text: '答对',
+                color: '#71B0FF'
+              },
+              {
+                text: '答错',
+                color: '#FF8486'
+              }
+            ]}
             onSelect={(item: any) => {
               // 跳转,并且跳过动画
               swipeRef.value?.swipeTo(item, {
@@ -152,16 +314,6 @@ export default defineComponent({
             }}
           />
         </ActionSheet>
-
-        <ODialog
-          v-model:show={state.visiableSure}
-          title="测验完成"
-          message="确认本次测验的题目都完成了吗?\n提交后不可修改哦"
-          messageAlign="left"
-          showCancelButton
-          cancelButtonText="再等等"
-          confirmButtonText="确认完成"
-        />
       </div>
     )
   }

+ 22 - 0
src/views/unit-test/unit.ts

@@ -9,4 +9,26 @@ export const labelOptions = {
   8: 'H',
   9: 'I',
   10: 'J'
+}
+
+
+export const QuestionType = {
+  RADIO: 'RADIO',
+  CHECKBOX: 'CHECKBOX',
+  LINK: 'LINK',
+  SORT: 'SORT',
+  PLAY: 'PLAY'
+}
+export const QuestionTypeName = {
+  RADIO: '单选题',
+  CHECKBOX: '多选题',
+  LINK: '连线题',
+  SORT: '排序题',
+  PLAY: '演奏题'
+}
+
+export const AnswerType = {
+  IMAGE: 'IMAGE',
+  TXT: 'TXT',
+  RADIO: 'RADIO',
 }

Niektoré súbory nie sú zobrazené, pretože je v týchto rozdielových dátach zmenené mnoho súborov